Switch to OpenGL instead of SDL
This commit is contained in:
parent
c3e1cbe5b0
commit
bf07969ee9
23
.vscode/c_cpp_properties.json
vendored
Normal file
23
.vscode/c_cpp_properties.json
vendored
Normal file
@ -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
|
||||
}
|
39
Makefile
Normal file
39
Makefile
Normal file
@ -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
|
14
src/event.c
Normal file
14
src/event.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
15
src/event.h
Normal file
15
src/event.h
Normal file
@ -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
|
336
src/main.c
Normal file
336
src/main.c
Normal file
@ -0,0 +1,336 @@
|
||||
#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;
|
||||
}
|
78
src/net.c
Normal file
78
src/net.c
Normal file
@ -0,0 +1,78 @@
|
||||
#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);
|
||||
}
|
40
src/net.h
Normal file
40
src/net.h
Normal file
@ -0,0 +1,40 @@
|
||||
#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
|
2
src/player.c
Normal file
2
src/player.c
Normal file
@ -0,0 +1,2 @@
|
||||
#include "player.h"
|
||||
|
25
src/player.h
Normal file
25
src/player.h
Normal file
@ -0,0 +1,25 @@
|
||||
#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
|
Loading…
Reference in New Issue
Block a user