From 1b07a4e41429dfa636c71b7a6d89b57d9323fd2c2c3e2cf8ed7e469593d3f7e9 Mon Sep 17 00:00:00 2001 From: snap Date: Wed, 18 Mar 2026 13:01:42 +0000 Subject: [PATCH] Complete most of networked tick-tac-toe. Finish the questions to ponder! --- .../GameServer/GameServer.cpp | 36 ++++++++++++++++--- .../u0012604/tictactoe/GameMessageType.kt | 7 +++- .../main/kotlin/u0012604/tictactoe/Main.kt | 4 +-- .../tictactoe/networking/NetworkHandler.kt | 4 +++ .../u0012604/tictactoe/screens/FirstScreen.kt | 23 +++++++++++- .../tictactoe/screens/GameOverScreen.kt | 15 ++++++-- 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/Exercises/NetworkedTicTacToe/GameServer/GameServer.cpp b/Exercises/NetworkedTicTacToe/GameServer/GameServer.cpp index 6f089eb..647caa6 100644 --- a/Exercises/NetworkedTicTacToe/GameServer/GameServer.cpp +++ b/Exercises/NetworkedTicTacToe/GameServer/GameServer.cpp @@ -12,7 +12,7 @@ // A thourough rework is necessary for SFML 3.0. enum GameMessageType : unsigned char { - JOIN_GAME = 0x01, PLACE_TOKEN = 0x02 + JOIN_GAME = 0x01, PLACE_TOKEN = 0x02, START_GAME = 0x03, GAME_OVER = 0x04 }; enum Token : unsigned char { @@ -24,13 +24,26 @@ public: GameServer(unsigned short tcp_port) : m_tcp_port(tcp_port) {} + + bool send_start_game_to_clients() { + char buf[1] = { START_GAME }; + std::cout << "Starting the game..." << std::endl; + return broadcast_message(buf, nullptr); + } + + bool send_game_over_to_clients() { + char buf[1] = { GAME_OVER }; + std::cout << "Game Over!" << std::endl; + return broadcast_message(buf, nullptr); + } + // Binds to a port and then loops around. For every client that connects, // we start a new thread receiving their messages. void tcp_start() { // BINDING sf::TcpListener listener; - sf::Socket::Status status = listener.listen(m_tcp_port, sf::IpAddress("A.B.C.D")); + sf::Socket::Status status = listener.listen(m_tcp_port, sf::IpAddress("152.105.66.120")); // Make sure to change this! if (status != sf::Socket::Status::Done) { std::cerr << "Error binding listener to port" << std::endl; @@ -71,6 +84,10 @@ public: // Slight pause to ensure the all threads have started // -------------------------------------------------------------- std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + if(!send_start_game_to_clients()) { + std::cerr << "Could not start game. One or both players did not recieve START_GAME" << std::endl; + } } } } @@ -79,11 +96,12 @@ public: // The connection is closed automatically when the listener object is out of scope. } - private: unsigned short m_tcp_port; unsigned short m_player_count { 0 }; - + unsigned short m_turns_played { 0 }; + int board[3][3]; + std::vector m_clients; std::mutex m_clients_mutex; @@ -144,6 +162,12 @@ private: debug_message(payload); broadcast_message(payload, client); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + if(++m_turns_played == 9) { + send_game_over_to_clients(); + } } } @@ -194,6 +218,8 @@ private: switch(messageType) { case JOIN_GAME: return 2; case PLACE_TOKEN: return sizeof(int) * 2 + 2; + case START_GAME: return 1; + case GAME_OVER: return 1; default: return 0; } } @@ -222,7 +248,7 @@ private: int main() { - GameServer server(4300); + GameServer server(4331); server.tcp_start(); diff --git a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/GameMessageType.kt b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/GameMessageType.kt index 472df9e..367b749 100644 --- a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/GameMessageType.kt +++ b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/GameMessageType.kt @@ -12,7 +12,9 @@ interface Deserializable { enum class GameMessageType(val id: Byte) { JOIN_GAME(1), - PLACE_TOKEN(2); + PLACE_TOKEN(2), + START_GAME(3), + GAME_OVER(4); companion object { fun fromByte(id: Byte) = entries.first { it.id == id } @@ -23,6 +25,9 @@ sealed class GameMessage(val type: GameMessageType) : Serializable { override fun serialize() = byteArrayOf(type.id) + object StartGameMessage : GameMessage(GameMessageType.START_GAME) + object GameOverMessage : GameMessage(GameMessageType.GAME_OVER) + // ----------------------------------------------------------------------------------------------- data class JoinGameMessage(val token: Token) : GameMessage(GameMessageType.JOIN_GAME) { diff --git a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/Main.kt b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/Main.kt index 464406a..28027ed 100644 --- a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/Main.kt +++ b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/Main.kt @@ -20,11 +20,11 @@ class Main : KtxGame() { override fun create() { KtxAsync.initiate() - networkHandler = NetworkHandler("A.B.C.D", 4300, serverChannel, clientChannel) + networkHandler = NetworkHandler("152.105.66.120", 4331, serverChannel, clientChannel) addScreen(FirstScreen(this, clientChannel, serverChannel)) - addScreen(GameOverScreen()) + addScreen(GameOverScreen(0)) setScreen() diff --git a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/networking/NetworkHandler.kt b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/networking/NetworkHandler.kt index 32556b1..5b7e36c 100644 --- a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/networking/NetworkHandler.kt +++ b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/networking/NetworkHandler.kt @@ -153,6 +153,10 @@ class NetworkHandler( GameMessageType.JOIN_GAME -> GameMessage.JoinGameMessage.deserialize(this) GameMessageType.PLACE_TOKEN -> GameMessage.PlaceTokenMessage.deserialize(this) + + GameMessageType.START_GAME -> GameMessage.StartGameMessage + + GameMessageType.GAME_OVER -> GameMessage.GameOverMessage } } diff --git a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/FirstScreen.kt b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/FirstScreen.kt index 0e9bed3..4ea6b65 100644 --- a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/FirstScreen.kt +++ b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/FirstScreen.kt @@ -27,6 +27,9 @@ class FirstScreen( private val receiveChannel: ReceiveChannel, private val sendChannel: SendChannel ) : KtxScreen, InputProcessor { + private var gameStarted = false + + private var gameOver = false private val batch = SpriteBatch() @@ -56,6 +59,12 @@ class FirstScreen( override fun render(delta: Float) { clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f) + if (gameOver) { + game.setScreen() + disposeSafely() + return + } + camera.update() batch.use { board.draw(it) } @@ -87,6 +96,18 @@ class FirstScreen( localPlayerTurn = true } + is GameMessage.StartGameMessage -> { + Gdx.app.log(TAG, "The Server has started the game") + + gameStarted = true + } + + is GameMessage.GameOverMessage -> { + Gdx.app.log(TAG, "The Server has sent Game Over") + + gameStarted = false + gameOver = true + } } Gdx.app.log(TAG, "THE MESSAGE RECEIVED IS: $gm") @@ -114,7 +135,7 @@ class FirstScreen( button: Int ): Boolean { - if(!localPlayerTurn) + if(!gameStarted || !localPlayerTurn) return true; val col = ((screenX.toFloat() / Gdx.graphics.width) * 3).toInt() diff --git a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/GameOverScreen.kt b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/GameOverScreen.kt index d9af4b4..6120f2e 100644 --- a/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/GameOverScreen.kt +++ b/Exercises/NetworkedTicTacToe/core/src/main/kotlin/u0012604/tictactoe/screens/GameOverScreen.kt @@ -6,16 +6,25 @@ import ktx.app.KtxScreen import ktx.app.clearScreen import ktx.graphics.use -class GameOverScreen : KtxScreen { - +class GameOverScreen(private val winType: Int) : KtxScreen { + private var label = winType.toString() private val font = BitmapFont() private val batch = SpriteBatch() + override fun show() { + when (winType) { + 0 -> label = "You Win!" + 1 -> label = "You Lose." + 2 -> label = "Game Over!" + else -> "Invalid type!" + } + } + override fun render(delta: Float) { clearScreen(red = 0.7f, green = 0.7f, blue = 0.7f) batch.use { - font.draw(it, "Game Over!", 10f, 10f) + font.draw(it, label, 10f, 10f) } } }