diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..61f3fa7 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${default}", + "${workspaceFolder}/**", + "C:/msys64/mingw64/include" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.26100.0", + "cStandard": "c23", + "cppStandard": "c++20", + "intelliSenseMode": "windows-gcc-x64", + "compilerPath": "C:/Program Files/mingw64/bin/gcc.exe" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4e52bdd --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +# Compiler +CC = gcc + +# Paths +SRC_DIR = src +BUILD_DIR = build +OUT = main.exe + +# Source files +SRC = \ + $(SRC_DIR)\main.c \ + $(SRC_DIR)\net.c \ + $(SRC_DIR)\event.c \ + $(SRC_DIR)\player.c + +# Object files +OBJ = $(patsubst $(SRC_DIR)\%.c, $(BUILD_DIR)\%.o, $(SRC)) + +# 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) + +# Default target +all: $(OUT) + +$(OUT): $(OBJ) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +# Compile each .c into .o +$(BUILD_DIR)\%.o: $(SRC_DIR)\%.c + @if not exist $(BUILD_DIR) mkdir $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +# Clean +clean: + del /Q $(BUILD_DIR)\*.o 2>nul || exit 0 + del /Q $(OUT) 2>nul || exit 0 diff --git a/main.exe b/main.exe new file mode 100644 index 0000000..c005948 Binary files /dev/null and b/main.exe differ diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..2b480bc --- /dev/null +++ b/src/event.c @@ -0,0 +1,14 @@ +#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); + } +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..9a51bac --- /dev/null +++ b/src/event.h @@ -0,0 +1,15 @@ +#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 diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..c1c50a4 --- /dev/null +++ b/src/main.c @@ -0,0 +1,336 @@ +#define WIN32_LEAN_AND_MEAN +#define SDL_MAIN_HANDLED +#include +#include +#include +#include // Sleep() +#include "net.h" +#include "event.h" +#include "player.h" + +#include + +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 \n %s client \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; +} diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..2521139 --- /dev/null +++ b/src/net.c @@ -0,0 +1,78 @@ +#include "net.h" +#include +#include +#include +#include +#include + + +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); +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..75d51c8 --- /dev/null +++ b/src/net.h @@ -0,0 +1,40 @@ +#ifndef NET_H +#define NET_H + +#include +#include + +#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 diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..e09c574 --- /dev/null +++ b/src/player.c @@ -0,0 +1,2 @@ +#include "player.h" + diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..f2bdd71 --- /dev/null +++ b/src/player.h @@ -0,0 +1,25 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include + +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