This commit is contained in:
OusmBlueNinja 2025-04-03 17:22:17 -05:00
parent bf07969ee9
commit e4117b60ef
24 changed files with 749 additions and 542 deletions

View File

@ -4,8 +4,7 @@
"name": "Win32",
"includePath": [
"${default}",
"${workspaceFolder}/**",
"C:/msys64/mingw64/include"
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",

72
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,72 @@
{
"files.associations": {
"*.pyx": "python",
"*.js": "javascript",
"*.c": "c",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"netfwd": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp"
}
}

View File

@ -1,39 +1,29 @@
# Compiler
CC = gcc
CXX := g++
CXXFLAGS := -std=c++20 -Wall -Wextra -g -Iinclude -I/c/msys64/mingw64/include
# Paths
SRC_DIR = src
BUILD_DIR = build
OUT = main.exe
LDFLAGS := -lws2_32
# Source files
SRC = \
$(SRC_DIR)\main.c \
$(SRC_DIR)\net.c \
$(SRC_DIR)\event.c \
$(SRC_DIR)\player.c
SRC_DIRS := src
BUILD_DIR := build
# Object files
OBJ = $(patsubst $(SRC_DIR)\%.c, $(BUILD_DIR)\%.o, $(SRC))
# Find all source files
SRC := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp))
# Include and linker flags
INCLUDE_FLAGS = -IC:/msys64/mingw64/include
LIBS = -LC:/msys64/mingw64/lib -static -lws2_32 -lSDL2main -lSDL2 -lwinmm -limm32 -lole32 -loleaut32 -lversion -lsetupapi -lgdi32 -lshell32 -luser32 -mconsole
CFLAGS = -Wall -Wextra -g $(INCLUDE_FLAGS)
LDFLAGS = $(LIBS)
OBJ := $(patsubst %.cpp, $(BUILD_DIR)/%.o, $(SRC))
# Default target
all: $(OUT)
TARGET := main.exe
$(OUT): $(OBJ)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
all: $(TARGET)
# Compile each .c into .o
$(BUILD_DIR)\%.o: $(SRC_DIR)\%.c
@if not exist $(BUILD_DIR) mkdir $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJ)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
$(BUILD_DIR)/%.o: %.cpp
@mkdir "$(dir $@)" || exit 0
$(CXX) $(CXXFLAGS) -c $< -o $@
# Clean
clean:
del /Q $(BUILD_DIR)\*.o 2>nul || exit 0
del /Q $(OUT) 2>nul || exit 0
del /Q /S $(subst /,\,$(BUILD_DIR)\*.o) $(TARGET) 2>nul || exit 0
rmdir /S /Q $(BUILD_DIR) 2>nul || exit 0
.PHONY: all clean

BIN
build/src/Client.o Normal file

Binary file not shown.

BIN
build/src/Packet.o Normal file

Binary file not shown.

BIN
build/src/Server.o Normal file

Binary file not shown.

BIN
build/src/main.o Normal file

Binary file not shown.

9
include/Client.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef CLIENT_H
#define CLIENT_H
#include <string>
// Runs the client (with OpenGL rendering and network handling).
void runClient(const std::string& host, const std::string& portStr);
#endif // CLIENT_H

40
include/Packet.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef PACKET_H
#define PACKET_H
#include <string>
#include <cstdint>
#include <vector>
#include <winsock2.h>
#include <ws2tcpip.h>
enum PacketType : uint8_t {
HEARTBEAT = 1,
JOIN,
DISCONNECT,
MOVE,
CHAT,
PLAYER_UPDATE,
SCORE_UPDATE,
GAME_START,
GAME_END,
PING,
PONG,
AUTH_REQUEST,
AUTH_RESPONSE,
PACKET_MAX
};
struct Packet
{
PacketType type;
std::string data;
};
uint32_t to_network_uint32(uint32_t host);
uint32_t from_network_uint32(uint32_t net);
bool send_all(SOCKET sock, const char *data, size_t len);
bool recv_all(SOCKET sock, char *buffer, size_t len);
bool sendPacket(SOCKET sock, const Packet &packet);
bool receivePacket(SOCKET sock, Packet &packet);
#endif // PACKET_H

23
include/Server.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef SERVER_H
#define SERVER_H
#include <winsock2.h>
#include <ws2tcpip.h>
#include <vector>
#include <mutex>
#include "Packet.h"
class Server {
public:
Server(unsigned short port);
~Server();
void start();
void broadcast(const Packet &packet);
private:
void handleClient(SOCKET clientSock);
SOCKET listenSock;
std::vector<SOCKET> clients;
std::mutex mutex_;
};
#endif // SERVER_H

18
include/Utils.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef UTILS_H
#define UTILS_H
#include <chrono>
#include <string>
#include <sstream>
// Returns the current timestamp in milliseconds since the epoch.
inline std::string getCurrentTimestamp() {
using namespace std::chrono;
auto now = system_clock::now();
auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
std::ostringstream oss;
oss << ms;
return oss.str();
}
#endif // UTILS_H

BIN
main.exe

Binary file not shown.

98
src/Client.cpp Normal file
View File

@ -0,0 +1,98 @@
// Client.cpp
#include "Client.h"
#include "Packet.h"
#include "Utils.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <string>
void sendAuthPacket(SOCKET sock, const std::string &username, const std::string &password) {
Packet authPacket;
authPacket.type = AUTH_REQUEST; // Ensure AUTH_REQUEST is defined in your PacketType enum.
authPacket.data = username + ":" + password;
if(!sendPacket(sock, authPacket)) {
std::cerr << "Failed to send auth packet." << std::endl;
}
}
void runClient(const std::string& host, const std::string& portStr) {
int port = std::stoi(portStr);
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock == INVALID_SOCKET) {
std::cerr << "Failed to create socket." << std::endl;
return;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr);
serverAddr.sin_port = htons(port);
if(connect(sock, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Connect failed." << std::endl;
closesocket(sock);
return;
}
std::cout << "Connected to server." << std::endl;
// Prompt for authentication details.
std::string username, password;
std::cout << "Enter username: ";
std::getline(std::cin, username);
std::cout << "Enter password: ";
std::getline(std::cin, password);
// Send authentication packet.
sendAuthPacket(sock, username, password);
Packet joinPacket;
joinPacket.type = JOIN;
joinPacket.data = "I have joined.";
if(!sendPacket(sock, joinPacket)) {
std::cerr << "Failed to send join packet." << std::endl;
closesocket(sock);
return;
}
// Main loop to receive packets from the server.
Packet packet;
while(receivePacket(sock, packet)) {
switch(packet.type) {
case HEARTBEAT: {
try {
long long serverTimestamp = std::stoll(packet.data);
using namespace std::chrono;
auto now = system_clock::now();
auto msNow = duration_cast<milliseconds>(now.time_since_epoch()).count();
long long ping = msNow - serverTimestamp;
std::cout << "Heartbeat: " << ping << " ms" << std::endl;
} catch(...) {
}
break;
}
case JOIN:
std::cout << "Connect: " << packet.data << std::endl;
break;
case DISCONNECT:
std::cout << "Disconnect: " << packet.data << std::endl;
break;
case AUTH_RESPONSE:
std::cout << "Auth Response: " << packet.data << std::endl;
break;
default:
std::cout << "Unknown packet type: " << static_cast<int>(packet.type)
<< " | Data: " << packet.data << std::endl;
break;
}
}
std::cout << "Disconnected from server." << std::endl;
closesocket(sock);
}

72
src/Packet.cpp Normal file
View File

@ -0,0 +1,72 @@
#include "Packet.h"
#include <cstdlib>
#include <vector>
#include <cstring>
uint32_t to_network_uint32(uint32_t host) {
return htonl(host);
}
uint32_t from_network_uint32(uint32_t net) {
return ntohl(net);
}
bool send_all(SOCKET sock, const char* data, size_t len) {
size_t totalSent = 0;
while(totalSent < len) {
int sent = send(sock, data + totalSent, static_cast<int>(len - totalSent), 0);
if(sent <= 0)
return false;
totalSent += sent;
}
return true;
}
bool recv_all(SOCKET sock, char* buffer, size_t len) {
size_t totalRecv = 0;
while(totalRecv < len) {
int recvd = recv(sock, buffer + totalRecv, static_cast<int>(len - totalRecv), 0);
if(recvd <= 0)
return false;
totalRecv += recvd;
}
return true;
}
bool sendPacket(SOCKET sock, const Packet& packet) {
// Payload length = 1 byte for type + data length.
uint32_t payloadLen = 1 + static_cast<uint32_t>(packet.data.size());
uint32_t netLen = to_network_uint32(payloadLen);
if(!send_all(sock, reinterpret_cast<const char*>(&netLen), sizeof(netLen)))
return false;
if(!send_all(sock, reinterpret_cast<const char*>(&packet.type), sizeof(packet.type)))
return false;
if(!packet.data.empty()) {
if(!send_all(sock, packet.data.data(), packet.data.size()))
return false;
}
return true;
}
bool receivePacket(SOCKET sock, Packet &packet) {
uint32_t netLen = 0;
if(!recv_all(sock, reinterpret_cast<char*>(&netLen), sizeof(netLen)))
return false;
uint32_t len = from_network_uint32(netLen);
if(len < 1)
return false;
char typeChar;
if(!recv_all(sock, &typeChar, 1))
return false;
packet.type = static_cast<PacketType>(typeChar);
size_t dataLen = len - 1;
if(dataLen > 0) {
std::vector<char> buf(dataLen);
if(!recv_all(sock, buf.data(), dataLen))
return false;
packet.data = std::string(buf.begin(), buf.end());
} else {
packet.data.clear();
}
return true;
}

151
src/Server.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "Server.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <algorithm>
#include <cstdlib>
Server::Server(unsigned short port) {
listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listenSock == INVALID_SOCKET) {
std::cerr << "Failed to create socket." << std::endl;
std::exit(EXIT_FAILURE);
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(port);
if(bind(listenSock, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Bind failed." << std::endl;
closesocket(listenSock);
std::exit(EXIT_FAILURE);
}
if(listen(listenSock, SOMAXCONN) == SOCKET_ERROR) {
std::cerr << "Listen failed." << std::endl;
closesocket(listenSock);
std::exit(EXIT_FAILURE);
}
}
Server::~Server() {
closesocket(listenSock);
}
void Server::start() {
std::thread([this](){
while(true) {
SOCKET clientSock = accept(listenSock, nullptr, nullptr);
if(clientSock == INVALID_SOCKET) {
std::cerr << "Accept failed." << std::endl;
continue;
}
std::cout << "Client connected." << std::endl;
{
std::lock_guard<std::mutex> lock(mutex_);
clients.push_back(clientSock);
}
// Broadcast join packet.
Packet joinPacket;
joinPacket.type = JOIN;
joinPacket.data = "Client Connected";
broadcast(joinPacket);
// Spawn a thread to handle the new client.
std::thread(&Server::handleClient, this, clientSock).detach();
}
}).detach();
}
void Server::broadcast(const Packet &packet) {
std::lock_guard<std::mutex> lock(mutex_);
for(auto it = clients.begin(); it != clients.end();) {
if(!sendPacket(*it, packet)) {
// Remove client if send fails.
closesocket(*it);
it = clients.erase(it);
} else {
++it;
}
}
}
void Server::handleClient(SOCKET clientSock) {
bool authenticated = false;
std::string sessionToken;
Packet packet;
while(receivePacket(clientSock, packet)) {
if(packet.type == AUTH_REQUEST) {
std::string creds = packet.data;
size_t delim = creds.find(":");
Packet authResp;
authResp.type = AUTH_RESPONSE;
if(delim != std::string::npos) {
std::string username = creds.substr(0, delim);
std::string password = creds.substr(delim + 1);
if(username == "testuser" && password == "pass123") {
authenticated = true;
sessionToken = "SESSION_TOKEN_ABC123";
authResp.data = sessionToken;
std::cout << "Client authenticated: " << username << std::endl;
} else {
authResp.data = "AUTH_FAILED";
std::cout << "Client failed to authenticate: " << username << std::endl;
}
} else {
authResp.data = "AUTH_FAILED";
std::cerr << "Invalid authentication packet format." << std::endl;
}
sendPacket(clientSock, authResp);
}
else if(!authenticated) {
Packet notAuth;
notAuth.type = AUTH_RESPONSE;
notAuth.data = "NOT_AUTHENTICATED";
sendPacket(clientSock, notAuth);
}
else {
switch(packet.type) {
case MOVE:
broadcast(packet);
break;
case HEARTBEAT:
break;
case JOIN:
std::cout << "Join packet received: " << packet.data << std::endl;
break;
case DISCONNECT:
std::cout << "Disconnect packet received: " << packet.data << std::endl;
break;
default:
std::cout << "Unknown packet type: " << static_cast<int>(packet.type)
<< " | Data: " << packet.data << std::endl;
break;
}
}
}
std::cout << "Client disconnected." << std::endl;
Packet disconnectPacket;
disconnectPacket.type = DISCONNECT;
disconnectPacket.data = "A client disconnected.";
broadcast(disconnectPacket);
std::lock_guard<std::mutex> lock(mutex_);
auto it = std::find(clients.begin(), clients.end(), clientSock);
if(it != clients.end()) {
closesocket(*it);
clients.erase(it);
}
}

View File

@ -1,14 +0,0 @@
#include "event.h"
#define MAX_HANDLERS 256
static PacketHandler g_handlers[MAX_HANDLERS] = {0};
void register_handler(uint8_t type, PacketHandler handler) {
g_handlers[type] = handler;
}
void handle_packet(Packet* pkt, SOCKET client) {
if (g_handlers[pkt->type]) {
g_handlers[pkt->type](pkt, client);
}
}

View File

@ -1,15 +0,0 @@
#ifndef EVENT_H
#define EVENT_H
#include "net.h"
// Each handler receives Packet* and the client socket
typedef void (*PacketHandler)(Packet*, SOCKET client);
// Register a handler for a certain PacketType
void register_handler(uint8_t type, PacketHandler handler);
// Dispatch a packet
void handle_packet(Packet* pkt, SOCKET client);
#endif

View File

@ -1,336 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#define SDL_MAIN_HANDLED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h> // Sleep()
#include "net.h"
#include "event.h"
#include "player.h"
#include <SDL2/SDL.h>
CRITICAL_SECTION g_playerLock;
static Player g_players[MAX_PLAYERS];
static int g_numPlayers = 0;
static SOCKET g_clients[MAX_CLIENTS];
static int g_clientCount = 0;
static uint32_t g_nextID = 1; // to assign unique IDs
// Forward declarations
DWORD WINAPI client_thread(LPVOID param);
void broadcast_packet(Packet* pkt, SOCKET except);
void on_player_move(Packet* pkt, SOCKET client) {
PlayerMove pm;
memcpy(&pm, pkt->data, sizeof(PlayerMove));
EnterCriticalSection(&g_playerLock);
for(int i = 0; i < g_numPlayers; i++) {
if(g_players[i].id == pm.id) {
g_players[i].x += pm.dx;
g_players[i].y += pm.dy;
break;
}
}
// broadcast updated positions
int numStates = g_numPlayers;
int dataSize = sizeof(PlayerState) * numStates;
PlayerState* states = malloc(dataSize);
for(int i = 0; i < g_numPlayers; i++) {
states[i].id = g_players[i].id;
states[i].x = g_players[i].x;
states[i].y = g_players[i].y;
}
Packet* statePkt = create_packet(PACKET_PLAYER_STATE, states, dataSize);
free(states);
broadcast_packet(statePkt, INVALID_SOCKET);
destroy_packet(statePkt);
LeaveCriticalSection(&g_playerLock);
}
void broadcast_packet(Packet* pkt, SOCKET except) {
// serialize
char buffer[BUFFER_SIZE];
int len = serialize_packet(pkt, buffer);
EnterCriticalSection(&g_playerLock);
for(int i = 0; i < g_clientCount; i++) {
if(g_clients[i] == except) continue;
send(g_clients[i], buffer, len, 0);
}
LeaveCriticalSection(&g_playerLock);
}
DWORD WINAPI client_thread(LPVOID param) {
SOCKET client = *(SOCKET*)param;
free(param);
// Non-blocking for easier single-thread dispatch or we do blocking in a thread
set_socket_nonblocking(client, 0); // blocking in this thread
// Assign a new player
Player newPlayer;
newPlayer.id = g_nextID++;
newPlayer.x = 100; // start at 100,100
newPlayer.y = 100;
EnterCriticalSection(&g_playerLock);
g_players[g_numPlayers++] = newPlayer;
g_clients[g_clientCount++] = client;
LeaveCriticalSection(&g_playerLock);
printf("[SERVER] Client %u connected.\n", newPlayer.id);
// Now run receive loop
char buffer[BUFFER_SIZE];
while(1) {
int received = recv(client, buffer, BUFFER_SIZE, 0);
if (received <= 0) {
break; // client disconnected
}
Packet* pkt = deserialize_packet(buffer, received);
if (pkt) {
// Dispatch
handle_packet(pkt, client);
destroy_packet(pkt);
}
}
// Remove from global state
EnterCriticalSection(&g_playerLock);
// remove from g_clients
for(int i = 0; i < g_clientCount; i++) {
if(g_clients[i] == client) {
g_clients[i] = g_clients[--g_clientCount];
break;
}
}
// remove from g_players
for(int i = 0; i < g_numPlayers; i++) {
if(g_players[i].id == newPlayer.id) {
g_players[i] = g_players[--g_numPlayers];
break;
}
}
LeaveCriticalSection(&g_playerLock);
closesocket(client);
printf("[SERVER] Client %u disconnected.\n", newPlayer.id);
return 0;
}
void run_server(int port) {
net_init();
InitializeCriticalSection(&g_playerLock);
// Register server handlers
register_handler(PACKET_PLAYER_MOVE, on_player_move);
SOCKET server = net_bind_and_listen(port);
if (server == INVALID_SOCKET) {
printf("Failed to bind on port %d\n", port);
return;
}
printf("[SERVER] Listening on port %d...\n", port);
while(1) {
SOCKET s = net_accept(server);
if (s == INVALID_SOCKET) {
Sleep(10);
continue;
}
// Start a client thread
SOCKET* ptr = malloc(sizeof(SOCKET));
*ptr = s;
CreateThread(NULL, 0, client_thread, ptr, 0, NULL);
}
DeleteCriticalSection(&g_playerLock);
closesocket(server);
net_cleanup();
}
static Player g_localPlayer = {0};
static PlayerState g_otherPlayers[MAX_PLAYERS];
static int g_otherCount = 0;
// For now, store the socket globally
static SOCKET g_sock = INVALID_SOCKET;
// Handler: PACKET_PLAYER_STATE
void on_player_state(Packet* pkt, SOCKET _) {
// We'll parse an array of PlayerStates
int num = pkt->size / sizeof(PlayerState);
if(num > MAX_PLAYERS) num = MAX_PLAYERS;
memcpy(g_otherPlayers, pkt->data, pkt->size);
g_otherCount = num;
}
void run_client(const char* ip, int port) {
net_init();
// Register client handlers
register_handler(PACKET_PLAYER_STATE, on_player_state);
// Connect
g_sock = net_connect(ip, port);
if (g_sock == INVALID_SOCKET) {
printf("[CLIENT] Could not connect to server.\n");
return;
}
printf("[CLIENT] Connected to server %s:%d\n", ip, port);
// We'll assign an ID after reading from server in a real scenario,
// but for simplicity, let's guess the server will do so.
// We'll just store local ID = 999, or we wait for some handshake.
// For now, let's store a random number:
g_localPlayer.id = 9999;
g_localPlayer.x = 200;
g_localPlayer.y = 200;
// Setup SDL
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("SDL_Init failed: %s\n", SDL_GetError());
return;
}
SDL_Window* window = SDL_CreateWindow(
"MPGC Client",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
800, 600,
0
);
if(!window) {
printf("SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
if(!renderer) {
printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
// Non-blocking so we can poll in main loop
set_socket_nonblocking(g_sock, 1);
int running = 1;
while (running) {
// ---- Handle SDL events
SDL_Event e;
while(SDL_PollEvent(&e)) {
if(e.type == SDL_QUIT) {
running = 0;
}
}
// ---- Keyboard input (WASD)
const Uint8* state = SDL_GetKeyboardState(NULL);
float dx = 0.f;
float dy = 0.f;
if(state[SDL_SCANCODE_W]) { dy -= 2.f; }
if(state[SDL_SCANCODE_S]) { dy += 2.f; }
if(state[SDL_SCANCODE_A]) { dx -= 2.f; }
if(state[SDL_SCANCODE_D]) { dx += 2.f; }
if(dx != 0 || dy != 0) {
// Move local player
g_localPlayer.x += dx;
g_localPlayer.y += dy;
// Send PACKET_PLAYER_MOVE
PlayerMove pm;
pm.id = g_localPlayer.id;
pm.dx = dx;
pm.dy = dy;
Packet* movePkt = create_packet(PACKET_PLAYER_MOVE, &pm, sizeof(pm));
char buffer[BUFFER_SIZE];
int len = serialize_packet(movePkt, buffer);
send(g_sock, buffer, len, 0);
destroy_packet(movePkt);
}
// ---- Check for incoming data
while (1) {
char netbuf[BUFFER_SIZE];
int r = recv(g_sock, netbuf, BUFFER_SIZE, 0);
if (r <= 0) break; // no data or error
Packet* pkt = deserialize_packet(netbuf, r);
if(pkt) {
handle_packet(pkt, g_sock);
destroy_packet(pkt);
}
}
// ---- Render scene
SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255);
SDL_RenderClear(renderer);
// Draw local player
SDL_Rect rLoc = {
(int)g_localPlayer.x,
(int)g_localPlayer.y,
30, 30
};
SDL_SetRenderDrawColor(renderer, 200, 200, 50, 255);
SDL_RenderFillRect(renderer, &rLoc);
// Draw other players
SDL_SetRenderDrawColor(renderer, 100, 200, 200, 255);
for(int i = 0; i < g_otherCount; i++) {
SDL_Rect rOth = {
(int)g_otherPlayers[i].x,
(int)g_otherPlayers[i].y,
30, 30
};
SDL_RenderFillRect(renderer, &rOth);
}
SDL_RenderPresent(renderer);
}
// Cleanup
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
closesocket(g_sock);
net_cleanup();
}
int main(int argc, char* argv[]) {
printf("[INFO] Starting...\n");
SDL_SetMainReady();
if (argc < 3) {
printf("Usage:\n %s server <port>\n %s client <ip> <port>\n", argv[0], argv[0]);
return 1;
}
if(strcmp(argv[1], "server") == 0) {
int port = atoi(argv[2]);
run_server(port);
printf("[SERVER] Listening on port %d\n", port);
} else if(strcmp(argv[1], "client") == 0 && argc >= 4) {
const char* ip = argv[2];
int port = atoi(argv[3]);
run_client(ip, port);
} else {
printf("Invalid arguments.\n");
}
return 0;
}

72
src/main.cpp Normal file
View File

@ -0,0 +1,72 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include "Server.h"
#include "Client.h"
#include "Utils.h"
int main(int argc, char* argv[]) {
std::cout << "[Server] Starting Server on port:" << argv[2] << "" << std::endl;
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
std::cerr << "WSAStartup failed." << std::endl;
return EXIT_FAILURE;
}
if(argc < 3) {
std::cout << "Usage:\n Server: " << argv[0] << " server <port>\n Client: " << argv[0] << " client <IP>:<port>\n";
WSACleanup();
return EXIT_FAILURE;
}
std::string mode = argv[1];
if(mode == "server") {
unsigned short port = static_cast<unsigned short>(std::stoi(argv[2]));
Server server(port);
server.start();
// Heartbeat thread: send a heartbeat with current timestamp every second.
std::thread heartbeat([&server](){
while(true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
Packet heartbeat;
heartbeat.type = HEARTBEAT;
heartbeat.data = getCurrentTimestamp();
server.broadcast(heartbeat);
}
});
while(true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
heartbeat.join();
} else if(mode == "client") {
std::string ip_port = argv[2];
size_t colonPos = ip_port.find(':');
if(colonPos == std::string::npos) {
std::cerr << "Invalid client format. Use IP:port" << std::endl;
WSACleanup();
return EXIT_FAILURE;
}
std::string ip = ip_port.substr(0, colonPos);
std::string port = ip_port.substr(colonPos + 1);
runClient(ip, port);
} else {
std::cerr << "Unknown mode: " << mode << std::endl;
WSACleanup();
return EXIT_FAILURE;
}
WSACleanup();
return EXIT_SUCCESS;
}

View File

@ -1,78 +0,0 @@
#include "net.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
Packet* create_packet(PacketType type, const void* data, uint16_t size) {
Packet* pkt = malloc(sizeof(Packet) + size);
pkt->type = type;
pkt->size = size;
memcpy(pkt->data, data, size);
return pkt;
}
int serialize_packet(Packet* pkt, char* buffer) {
buffer[0] = pkt->type;
buffer[1] = (pkt->size >> 8) & 0xFF;
buffer[2] = pkt->size & 0xFF;
memcpy(buffer + 3, pkt->data, pkt->size);
return 3 + pkt->size;
}
Packet* deserialize_packet(const char* buffer, int len) {
if (len < 3) return NULL;
uint8_t type = buffer[0];
uint16_t size = ((uint8_t)buffer[1] << 8) | (uint8_t)buffer[2];
if (size > len - 3) return NULL;
return create_packet(type, buffer + 3, size);
}
void destroy_packet(Packet* pkt) {
free(pkt);
}
void net_init() {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
}
void net_cleanup() {
WSACleanup();
}
SOCKET net_connect(const char* ip, int port) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port)
};
inet_pton(AF_INET, ip, &addr.sin_addr);
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
return sock;
}
SOCKET net_bind_and_listen(int port) {
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = INADDR_ANY
};
bind(server, (struct sockaddr*)&addr, sizeof(addr));
listen(server, SOMAXCONN);
return server;
}
SOCKET net_accept(SOCKET server) {
return accept(server, NULL, NULL);
}
void set_socket_nonblocking(SOCKET sock, int enable)
{
u_long mode = (enable) ? 1 : 0;
ioctlsocket(sock, FIONBIO, &mode);
}

View File

@ -1,40 +0,0 @@
#ifndef NET_H
#define NET_H
#include <winsock2.h>
#include <stdint.h>
#define MAX_CLIENTS 64
#define BUFFER_SIZE 1024
typedef enum {
PACKET_PING = 1,
PACKET_TEXT,
PACKET_CLIENT_JOINED,
PACKET_CLIENT_LEFT,
PACKET_PLAYER_MOVE,
PACKET_PLAYER_STATE,
PACKET_MAX
} PacketType;
typedef struct {
uint8_t type;
uint16_t size;
char data[];
} Packet;
Packet* create_packet(PacketType type, const void* data, uint16_t size);
int serialize_packet(Packet* pkt, char* buffer);
Packet* deserialize_packet(const char* buffer, int len);
void destroy_packet(Packet* pkt);
// Networking setup
void net_init();
void net_cleanup();
SOCKET net_connect(const char* ip, int port);
SOCKET net_bind_and_listen(int port);
SOCKET net_accept(SOCKET server);
void set_socket_nonblocking(SOCKET sock, int enable);
#endif

View File

@ -1,2 +0,0 @@
#include "player.h"

View File

@ -1,25 +0,0 @@
#ifndef PLAYER_H
#define PLAYER_H
#include <stdint.h>
typedef struct {
uint32_t id;
float x, y;
} Player;
typedef struct {
uint32_t id;
float dx;
float dy;
} PlayerMove;
typedef struct {
uint32_t id;
float x;
float y;
} PlayerState;
#define MAX_PLAYERS 64
#endif

173
test.py Normal file
View File

@ -0,0 +1,173 @@
#!/usr/bin/env python3
import socket
import struct
import threading
import tkinter as tk
from tkinter import scrolledtext, messagebox
import queue
import sys
PACKET_NAMES = {
1: "HEARTBEAT",
2: "JOIN",
3: "DISCONNECT",
4: "MOVE",
5: "CHAT",
6: "PLAYER_UPDATE",
7: "SCORE_UPDATE",
8: "GAME_START",
9: "GAME_END",
10: "PING",
11: "PONG",
12: "AUTH_REQUEST",
13: "AUTH_RESPONSE",
14: "PACKET_MAX"
}
class PacketClient:
def __init__(self, server_ip, server_port):
self.server_ip = server_ip
self.server_port = server_port
self.sock = None
self.running = False
self.recv_queue = queue.Queue() # For thread-safe packet passing
def connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.sock.connect((self.server_ip, self.server_port))
except Exception as e:
raise RuntimeError(f"Failed to connect to {self.server_ip}:{self.server_port}: {e}")
self.running = True
threading.Thread(target=self.receive_loop, daemon=True).start()
def receive_loop(self):
while self.running:
try:
header = self._recv_all(4)
if header is None:
break
(payload_len,) = struct.unpack("!I", header)
payload = self._recv_all(payload_len)
if payload is None:
break
packet_type = payload[0]
data = payload[1:].decode("utf-8", errors="replace")
# Enqueue the packet for the GUI.
self.recv_queue.put((packet_type, data))
except Exception as e:
self.recv_queue.put(("Error", str(e)))
break
self.running = False
def _recv_all(self, n):
data = b""
while len(data) < n:
try:
chunk = self.sock.recv(n - len(data))
except Exception:
return None
if not chunk:
return None
data += chunk
return data
def send_packet(self, packet_type, data):
data_bytes = data.encode("utf-8")
payload = struct.pack("!B", packet_type) + data_bytes
packet = struct.pack("!I", len(payload)) + payload
try:
self.sock.sendall(packet)
except Exception as e:
raise RuntimeError(f"Failed to send packet: {e}")
def close(self):
self.running = False
if self.sock:
self.sock.close()
# The GUI application.
class PacketClientGUI:
def __init__(self, master, client: PacketClient):
self.master = master
self.client = client
master.title("Packet Client GUI")
# Create a scrolling text widget for received packets.
self.received_text = scrolledtext.ScrolledText(master, width=80, height=20, state="disabled")
self.received_text.grid(row=0, column=0, columnspan=4, padx=10, pady=10)
# Packet type label and entry.
tk.Label(master, text="Packet Type:").grid(row=1, column=0, sticky="e", padx=(10,2))
self.type_entry = tk.Entry(master, width=5)
self.type_entry.grid(row=1, column=1, sticky="w")
self.type_entry.insert(0, "4") # Default to MOVE packet
# Data label and entry.
tk.Label(master, text="Data:").grid(row=1, column=2, sticky="e", padx=(10,2))
self.data_entry = tk.Entry(master, width=40)
self.data_entry.grid(row=1, column=3, sticky="w", padx=(0,10))
# Send button.
self.send_button = tk.Button(master, text="Send Packet", command=self.send_packet)
self.send_button.grid(row=2, column=0, columnspan=4, pady=(5,10))
# Start periodic GUI update.
self.master.after(100, self.process_recv_queue)
def process_recv_queue(self):
while not self.client.recv_queue.empty():
pkt_type, data = self.client.recv_queue.get()
if isinstance(pkt_type, int):
name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})")
else:
name = pkt_type # For error messages
self.append_text(f"{name} > {data}\n")
self.master.after(100, self.process_recv_queue)
def append_text(self, text):
self.received_text.config(state="normal")
self.received_text.insert(tk.END, text)
self.received_text.see(tk.END)
self.received_text.config(state="disabled")
def send_packet(self):
try:
pkt_type = int(self.type_entry.get())
except ValueError:
tk.messagebox.showerror("Error", "Packet type must be an integer")
return
data = self.data_entry.get()
try:
self.client.send_packet(pkt_type, data)
# Show the packet sent, using the name if available.
pkt_name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})")
self.append_text(f"Sent Packet | Type: {pkt_name} | Data: {data}\n")
except Exception as e:
tk.messagebox.showerror("Error", str(e))
def main():
if len(sys.argv) < 3:
print("Usage: python packet_client_gui.py <server_ip> <server_port>")
sys.exit(1)
server_ip = sys.argv[1]
server_port = int(sys.argv[2])
client = PacketClient(server_ip, server_port)
try:
client.connect()
except Exception as e:
print(e)
sys.exit(1)
root = tk.Tk()
app = PacketClientGUI(root, client)
try:
root.mainloop()
finally:
client.close()
if __name__ == "__main__":
main()