Commit unzipped original NetworkedTicTacToeStarter folder

This commit is contained in:
2026-03-13 14:40:56 +00:00
parent 64af2ed168
commit 212119adac
50 changed files with 2359 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
#include <SFML/Network.hpp>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
// TODO: move `GameServer` into its own files (h/cpp).
// Note: This is compiled with SFML 2.6.2 in mind.
// It would work similarly with slightly older versions of SFML.
// A thourough rework is necessary for SFML 3.0.
enum GameMessageType : unsigned char {
JOIN_GAME = 0x01, PLACE_TOKEN = 0x02
};
enum Token : unsigned char {
NOUGHTS = 0x01, CROSSES = 0x02
};
class GameServer {
public:
GameServer(unsigned short tcp_port) :
m_tcp_port(tcp_port) {}
// 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"));
if (status != sf::Socket::Status::Done)
{
std::cerr << "Error binding listener to port" << std::endl;
return;
}
std::cout << "TCP Server is listening to port "
<< m_tcp_port
<< ", waiting for connections..."
<< std::endl;
while (true)
{
// ACCEPTING
if(m_player_count < 2)
{
sf::TcpSocket* client = new sf::TcpSocket;
status = listener.accept(*client);
if (status != sf::Socket::Status::Done)
{
delete client;
} else {
{
std::lock_guard<std::mutex> lock(m_clients_mutex);
m_clients.push_back(client);
}
std::cout << "New client connected: "
<< client->getRemoteAddress()
<< std::endl;
m_player_count++;
std::thread(&GameServer::handle_client, this, client, m_player_count).detach();
if(m_player_count == 2)
{
// --------------------------------------------------------------
// Slight pause to ensure the all threads have started
// --------------------------------------------------------------
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
}
}
}
// No need to call close of the listener.
// 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 };
std::vector<sf::TcpSocket*> m_clients;
std::mutex m_clients_mutex;
// Loop around, receive messages from client and send them to all
// the other connected clients.
void handle_client(sf::TcpSocket* client, unsigned short player_num)
{
sf::Socket::Status status;
if(player_num == 1) {
std::cout << "Player " << player_num << " is NOUGHTS" << std::endl;
char buffer[2] = {
GameMessageType::JOIN_GAME,
Token::NOUGHTS
};
status = client->send(buffer, message_size(GameMessageType::JOIN_GAME));
if (status != sf::Socket::Status::Done)
{
std::cerr << "Error sending JOIN_GAME to player 1" << std::endl;
return;
}
}
else if(player_num == 2) {
std::cout << "Player " << player_num << " is CROSSES" << std::endl;
char buffer[2] = {
GameMessageType::JOIN_GAME,
Token::CROSSES
};
status = client->send(buffer, message_size(GameMessageType::JOIN_GAME));
if (status != sf::Socket::Status::Done)
{
std::cerr << "Error sending JOIN_GAME to player 2" << std::endl;
return;
}
}
else {
return; // No more players please!!!
}
while (true)
{
// RECEIVING
char payload[1024];
std::memset(payload, 0, 1024);
size_t received;
sf::Socket::Status status = client->receive(payload, 1024, received);
if (status != sf::Socket::Status::Done)
{
std::cerr << "Error receiving message from client" << std::endl;
break;
} else {
// Actually, there is no need to print the message if the message is not a string
debug_message(payload);
broadcast_message(payload, client);
}
}
// Everything that follows only makes sense if we have a graceful way to exiting the loop.
// Remove the client from the list when done
{
std::lock_guard<std::mutex> lock(m_clients_mutex);
m_clients.erase(std::remove(m_clients.begin(), m_clients.end(), client),
m_clients.end());
}
delete client;
}
// Sends `message` from `sender` to all the other connected clients
bool broadcast_message(const char *buffer, sf::TcpSocket* sender)
{
size_t msgSize { message_size(buffer[0]) };
// You might want to validate the message before you send it.
// A few reasons for that:
// 1. Make sure the message makes sense in the game.
// 2. Make sure the sender is not cheating.
// 3. First need to synchronise the players inputs (usually done in Lockstep).
// 4. Compensate for latency and perform rollbacks (usually done in Ded Reckoning).
// 5. Delay the sending of messages to make the game fairer wrt high ping players.
// This is where you can write the authoritative part of the server.
std::lock_guard<std::mutex> lock(m_clients_mutex);
for (auto& client : m_clients)
{
if (client != sender)
{
// SENDING
sf::Socket::Status status = client->send(buffer, msgSize) ;
if (status != sf::Socket::Status::Done)
{
std::cerr << "Error sending message to client" << std::endl;
return false;
}
}
}
return true;
}
constexpr size_t message_size(const char messageType)
{
switch(messageType) {
case JOIN_GAME: return 2;
case PLACE_TOKEN: return sizeof(int) * 2 + 2;
default: return 0;
}
}
void debug_message(const char *buf)
{
const unsigned char msgType = buf[0];
switch(msgType) {
case JOIN_GAME: {
std::cout << "Player Joined The Game" << std::endl;
break;
}
case PLACE_TOKEN: {
const unsigned char *row { (unsigned char* )buf + 1 };
const unsigned char *col { (unsigned char* )buf + 1 + sizeof(int) };
unsigned int rowI = be32toh(*((unsigned int *) row));
unsigned int colI = be32toh(*((unsigned int *) col));
std::cout << "Player Placed A Token: (" << rowI << ", " << colI << ")" << std::endl;
break;
}
}
}
};
int main()
{
GameServer server(4300);
server.tcp_start();
return 0;
}

View File

@@ -0,0 +1,16 @@
SFML_PATH=/usr/local/Cellar/sfml/2.6.1/
CXXFLAGS= -std=c++14 -Wall -Wpedantic -I${SFML_PATH}include/
LDFLAGS=-L${SFML_PATH}lib/
CFLAGS=-g -lsfml-graphics -lsfml-window -lsfml-system -lsfml-network -pthread
CPPFLAGS=
LDLIBS=
LIBS=
CPP=g++
all: server
server: GameServer.o
$(CPP) $(CXXFLAGS) $(LDFLAGS) $(LIBS) $^ -o $@ $(CFLAGS)
clean:
\rm -f *.o server