From 5c2bb282ce55e66f713b7248cb034bd6e3c6d6b0 Mon Sep 17 00:00:00 2001 From: Colton Staiduhar Date: Sun, 26 Apr 2026 18:39:43 -0400 Subject: [PATCH] Fully Working Game --- include/game.h | 6 +- include/network/networking.h | 2 + include/network/packet.h | 7 ++ src/ball.c | 6 +- src/court.c | 6 +- src/gameClient.c | 140 +++++++++++++++++++++++++++ src/gameServer.c | 183 ++++++++++++++--------------------- src/network/networking.c | 16 +++ src/network/packet.c | 87 ++++++++++++++++- 9 files changed, 334 insertions(+), 119 deletions(-) create mode 100644 src/gameClient.c diff --git a/include/game.h b/include/game.h index 14a7ea3..2b753f2 100644 --- a/include/game.h +++ b/include/game.h @@ -3,6 +3,8 @@ #include "network/networking.h" +#define NUM_OF_BALLS 2 + // Setup game board #define TOP_ROW 4 @@ -19,6 +21,8 @@ typedef struct } GameData; +long GetCurrentMS(); + /** * Setups the curses terminal environment */ @@ -45,7 +49,7 @@ void DrawWalls(GameData); /** * Draws the scoreboard and header */ -void DrawHeader(GameData); +void DrawHeader(GameData* data, int* scores, int numOfBalls); /** * Creates text at a location on screen diff --git a/include/network/networking.h b/include/network/networking.h index 5b0bbb5..b69d5a8 100644 --- a/include/network/networking.h +++ b/include/network/networking.h @@ -23,6 +23,8 @@ Server StartServer(int port); */ void RemoveClient(Server* s, int index); +void KillServer(); + typedef struct { int client_fd; struct sockaddr_in address; diff --git a/include/network/packet.h b/include/network/packet.h index d1fb5e8..41410bb 100644 --- a/include/network/packet.h +++ b/include/network/packet.h @@ -5,5 +5,12 @@ #include "../object.h" int ProcessBallPacket(char* msg, Ball* ball, GameData* gd); +int ProcessAlert(char* msg); +int ProcessGMSG(char* msg); +int ProcessScore(char* msg, int* scores); +int ProcessBallLost(char* msg, int fd, GameData* gd, int* scores, int* allowSever, int numOfBalls); +int ProcessNumOfBalls(char* msg, int* numOfBalls); + +void SendGameStatus(int fd, char* msg); #endif \ No newline at end of file diff --git a/src/ball.c b/src/ball.c index 5436252..bbaf506 100644 --- a/src/ball.c +++ b/src/ball.c @@ -13,8 +13,8 @@ Ball CreateBall(GameData* gd) b.lastCharX = 0; b.lastCharY = 0; - b.vx = -0.2; - b.vy = 0.2; + b.vx = 0; + b.vy = 0; return b; } @@ -108,6 +108,6 @@ void ServeBall(Ball* ball, GameData* gd) { ball->vx = -0.2; - ball->vy = 0.2; + ball->vy = 0.09; if (!gd->host) ball->vx *= -1; } \ No newline at end of file diff --git a/src/court.c b/src/court.c index 77cc469..091ab0e 100644 --- a/src/court.c +++ b/src/court.c @@ -25,9 +25,7 @@ void DrawWalls(GameData data) refresh(); } -void DrawHeader(GameData data) +void DrawHeader(GameData* data, int* scores, int numOfBalls) { - move(TOP_ROW - 2, LEFT_COL); - addch('P'); - addch('0' + (data.playerNumber + 1)); + mvprintw(TOP_ROW-1, LEFT_COL, "SCORES: P1: %d P2: %d Balls Left: %d", scores[0],scores[1],numOfBalls); } \ No newline at end of file diff --git a/src/gameClient.c b/src/gameClient.c new file mode 100644 index 0000000..987b100 --- /dev/null +++ b/src/gameClient.c @@ -0,0 +1,140 @@ +#include "../include/game.h" +#include "../include/network/packet.h" +#include "../include/object.h" +#include +#include +#include +#include +#include +#include +#include + +void RunGameClient(Client* client, GameData* gd) +{ + Client c = *client; + + Paddle paddle = CreatePaddle(gd); + Ball ball = CreateBall(gd); + int scores[2] = { 0, 0 }; + + long lastBallUpdate = GetCurrentMS(); + + int numOfBalls = 1; + bool game_running = true; + + SetupTermWin(); + + GameAlert("Connected to server"); + MGameAlert(BOT_ROW + 1, LEFT_COL, "Press space to Serve"); + + int allowServe = true; + + DrawWalls(*gd); + DrawPaddle(&paddle); + DrawHeader(gd, scores, numOfBalls); + + SendGameStatus(c.client_fd,"Waiting for serve"); + + while (game_running) { + + fd_set read_fds; + FD_ZERO(&read_fds); + + // Watch server socket + FD_SET(c.client_fd, &read_fds); + + int max_fd = c.client_fd; + + // Small timeout (same idea as server) + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000; // 1ms + + int ret = select(max_fd + 1, &read_fds, NULL, NULL, &tv); + + if (ret < 0) { + perror("select"); + break; + } + + // Handle server messages + if (FD_ISSET(c.client_fd, &read_fds)) { + char buffer[1024]; + int bytes = recv(c.client_fd, buffer, sizeof(buffer) - 1, 0); + + if (bytes > 0) { + buffer[bytes] = '\0'; + + char msg[1024]; + snprintf(msg, sizeof(msg), "Server says: %s\r", buffer); + GameAlert(buffer); + + if (!ProcessBallPacket(buffer, &ball, gd)) {SendGameStatus(c.client_fd,"Ball is in other court"); + MGameAlert(BOT_ROW+1,LEFT_COL,"");} + ProcessAlert(buffer); + ProcessGMSG(buffer); + if (!ProcessNumOfBalls(buffer, &numOfBalls)) { + DrawHeader(gd, scores, numOfBalls); + } + ProcessBallLost(buffer, c.client_fd, gd, scores, &allowServe, numOfBalls); + if (!ProcessScore(buffer, scores)) { + DrawHeader(gd, scores, numOfBalls); + } + + } else if (bytes == 0) { + GameAlert("Server disconnected. Quitting..."); + sleep(2); + break; + } else { + perror("recv"); + break; + } + } + + char ch = getch(); + if (ch == 'w') + MovePaddleUP(&paddle); + if (ch == 's') + MovePaddleDown(&paddle); + + // Process Ball only if on court + if (ball.visible) { + if (GetCurrentMS() > lastBallUpdate + 10) { + lastBallUpdate = GetCurrentMS(); + UpdateBall(&ball, &paddle); + } + if (CheckBallCourtChange(gd, &ball)) { + GameAlert("Ball is in other court"); + HideBall(&ball); + + char msg[1024]; + snprintf(msg, sizeof(msg), "BTRANS %f %f %f", ball.y, ball.vx, ball.vy); + send(c.client_fd, msg, strlen(msg), 0); + } + if (CheckBallLose(gd, &ball)) { + HideBall(&ball); + DrawWalls(*gd); + GameAlert("You lost the ball, other player serves"); + char msg[1024]; + snprintf(msg, sizeof(msg), "BALL_LOST Other player point"); + send(c.client_fd, msg, strlen(msg), 0); + } + } + + if (ch == 'q') { + GameAlert("Quitting..."); + sleep(1); + game_running = false; + break; + } + + if (ch == ' ' && allowServe) { + allowServe = false; + ServeBall(&ball, gd); + MGameAlert(BOT_ROW + 1, LEFT_COL, ""); + SendGameStatus(c.client_fd,"Ball is in other court"); + } + } + + CloseTermWin(); +} \ No newline at end of file diff --git a/src/gameServer.c b/src/gameServer.c index 9a011e2..d188294 100644 --- a/src/gameServer.c +++ b/src/gameServer.c @@ -1,6 +1,6 @@ #include "../include/game.h" -#include "../include/object.h" #include "../include/network/packet.h" +#include "../include/object.h" #include #include #include @@ -26,7 +26,9 @@ void CreateGameServer(Server* serv, GameData* gd) Ball ball = CreateBall(gd); long lastBallUpdate = GetCurrentMS(); + int scores[2] = { 0, 0 }; + int numOfBalls = 1; bool game_running = true; fd_set fds; @@ -38,6 +40,7 @@ void CreateGameServer(Server* serv, GameData* gd) SetupTermWin(); DrawWalls(*gd); + DrawHeader(gd, scores, 0); DrawPaddle(&paddle); // DrawBall(&ball); @@ -82,6 +85,17 @@ void CreateGameServer(Server* serv, GameData* gd) char msg[64]; snprintf(msg, sizeof(msg), "New client connected: %d\n", client_fd); GameAlert(msg); + + numOfBalls = NUM_OF_BALLS; + HideBall(&ball); + for (int i = 0; i < 2; ++i) + scores[i] = 0; + DrawWalls(*gd); + DrawHeader(gd, scores, numOfBalls); + + char bmsg[1024]; + snprintf(bmsg, sizeof(bmsg), "NUM_OF_BALLS %d", numOfBalls); + send(s.clients[0], bmsg, strlen(bmsg), 0); } } @@ -100,10 +114,25 @@ void CreateGameServer(Server* serv, GameData* gd) snprintf(msg, sizeof(msg), "Client %d says: %s\r", fd, buffer); GameAlert(msg); - ProcessBallPacket(buffer,&ball,gd); + if (!ProcessBallPacket(buffer, &ball, gd)) { + SendGameStatus(s.clients[0], "Ball is in other court"); + MGameAlert(BOT_ROW + 1, LEFT_COL, ""); + } + ProcessAlert(buffer); + ProcessGMSG(buffer); + if (!ProcessBallLost(buffer, s.clients[0], gd, scores, &allowServe, numOfBalls)) { + numOfBalls--; + DrawHeader(gd, scores, numOfBalls); + char bmsg[1024]; + snprintf(bmsg, sizeof(bmsg), "NUM_OF_BALLS %d", numOfBalls); + send(s.clients[0], bmsg, strlen(bmsg), 0); + } + if (!ProcessScore(buffer, scores)) { + DrawHeader(gd, scores, numOfBalls); + } // Example: echo back - //send(fd, buffer, bytes, 0); + // send(fd, buffer, bytes, 0); } else if (bytes == 0) { char msg[64]; snprintf(msg, sizeof(msg), "Client %d disconnected\n", fd); @@ -131,14 +160,48 @@ void CreateGameServer(Server* serv, GameData* gd) lastBallUpdate = GetCurrentMS(); UpdateBall(&ball, &paddle); } - if (CheckBallCourtChange(gd,&ball)) { + if (CheckBallCourtChange(gd, &ball)) { GameAlert("Ball is in other court"); HideBall(&ball); char msg[1024]; - snprintf(msg, sizeof(msg), "BTRANS %f %f %f", ball.y,ball.vx,ball.vy); + snprintf(msg, sizeof(msg), "BTRANS %f %f %f", ball.y, ball.vx, ball.vy); send(s.clients[0], msg, strlen(msg), 0); } + if (CheckBallLose(gd, &ball)) { + HideBall(&ball); + DrawWalls(*gd); + numOfBalls--; + DrawHeader(gd, scores, numOfBalls); + GameAlert("You lost the ball, other player serves"); + char bmsg[1024]; + snprintf(bmsg, sizeof(bmsg), "NUM_OF_BALLS %d", numOfBalls); + send(s.clients[0], bmsg, strlen(bmsg), 0); + sleep(1); + char msg[1024]; + snprintf(msg, sizeof(msg), "BALL_LOST Other player point"); + send(s.clients[0], msg, strlen(msg), 0); + } + } + + if (numOfBalls == 0) { + + sleep(1); + if (scores[0] == scores[1]) { + MGameAlert(BOT_ROW + 1, LEFT_COL, "Game over, it's a tie"); + SendGameStatus(s.clients[0], "Game over, it's a tie"); + } + if (scores[0] > scores[1]) { + MGameAlert(BOT_ROW + 1, LEFT_COL, "Game over, you win!"); + SendGameStatus(s.clients[0], "Game over, you lost :("); + } + if (scores[0] < scores[1]) { + MGameAlert(BOT_ROW + 1, LEFT_COL, "Game over, you lost :("); + SendGameStatus(s.clients[0], "Game over, you win!"); + } + + sleep(3); + ch = 'q'; } if (ch == 'q') { @@ -148,6 +211,13 @@ void CreateGameServer(Server* serv, GameData* gd) break; } + if (ch == ' ' && allowServe) { + allowServe = false; + ServeBall(&ball, gd); + MGameAlert(BOT_ROW + 1, LEFT_COL, ""); + SendGameStatus(s.clients[0], "Ball is in other court"); + } + // if (s.client_count < 1) { // char msg[64]; // snprintf(msg, sizeof(msg), "Waiting for other player\n"); @@ -160,106 +230,3 @@ void CreateGameServer(Server* serv, GameData* gd) CloseTermWin(); } - -void RunGameClient(Client* client, GameData* gd) -{ - Client c = *client; - - Paddle paddle = CreatePaddle(gd); - Ball ball = CreateBall(gd); - - - long lastBallUpdate = GetCurrentMS(); - - bool game_running = true; - - SetupTermWin(); - - GameAlert("Connected to server"); - int allowServe = true; - - DrawWalls(*gd); - DrawPaddle(&paddle); - - while (game_running) { - - fd_set read_fds; - FD_ZERO(&read_fds); - - // Watch server socket - FD_SET(c.client_fd, &read_fds); - - int max_fd = c.client_fd; - - // Small timeout (same idea as server) - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 1000; // 1ms - - int ret = select(max_fd + 1, &read_fds, NULL, NULL, &tv); - - if (ret < 0) { - perror("select"); - break; - } - - // Handle server messages - if (FD_ISSET(c.client_fd, &read_fds)) { - char buffer[1024]; - int bytes = recv(c.client_fd, buffer, sizeof(buffer) - 1, 0); - - if (bytes > 0) { - buffer[bytes] = '\0'; - - char msg[1024]; - snprintf(msg, sizeof(msg), "Server says: %s\r", buffer); - GameAlert(msg); - - ProcessBallPacket(buffer,&ball,gd); - - } else if (bytes == 0) { - GameAlert("Server disconnected"); - break; - } else { - perror("recv"); - break; - } - } - - char ch = getch(); - if (ch == 'w') - MovePaddleUP(&paddle); - if (ch == 's') - MovePaddleDown(&paddle); - - // Process Ball only if on court - if (ball.visible) { - if (GetCurrentMS() > lastBallUpdate + 10) { - lastBallUpdate = GetCurrentMS(); - UpdateBall(&ball, &paddle); - } - if (CheckBallCourtChange(gd,&ball)) { - GameAlert("Ball is in other court"); - HideBall(&ball); - - char msg[1024]; - snprintf(msg, sizeof(msg), "BTRANS %f %f %f", ball.y,ball.vx,ball.vy); - send(c.client_fd, msg, strlen(msg), 0); - } - } - - if (ch == 'q') { - GameAlert("Quitting..."); - sleep(1); - game_running = false; - break; - } - - if (ch == ' ') { - allowServe = false; - ServeBall(&ball,gd); - } - } - - CloseTermWin(); -} \ No newline at end of file diff --git a/src/network/networking.c b/src/network/networking.c index bbe8840..6bfacaa 100644 --- a/src/network/networking.c +++ b/src/network/networking.c @@ -6,8 +6,12 @@ #include #include + +static Server *g_server = NULL; + Server StartServer(int port) { Server s; + g_server = &s; s.clientID = 0; s.client_count = 0; @@ -23,6 +27,9 @@ Server StartServer(int port) { s.address.sin_addr.s_addr = INADDR_ANY; // listen on all interfaces s.address.sin_port = htons(port); + signal(SIGINT, KillServer); // ctrl+c + signal(SIGTERM, KillServer); + // Bind Port if (bind(s.server_fd, (struct sockaddr *)&s.address, sizeof(s.address)) < 0) { perror("bind failed"); @@ -48,6 +55,15 @@ void RemoveClient(Server* s, int index) { s->client_count--; } +void KillServer() { + close(g_server->server_fd); + + int c = g_server->client_count; + for (int i = 0; i < c; ++i) { + RemoveClient(g_server,i); + } +} + Client StartClient(char* host, int port) { Client c; diff --git a/src/network/packet.c b/src/network/packet.c index 0419efd..76777ba 100644 --- a/src/network/packet.c +++ b/src/network/packet.c @@ -5,10 +5,7 @@ int ProcessBallPacket(char* msg, Ball* ball, GameData* gd) { - // 1. Verify prefix if (strncmp(msg, "BTRANS", 6) != 0) { - printf("Invalid prefix\n"); - printf("\nMSG: %s\n\n\n", msg); return 1; } @@ -26,4 +23,88 @@ int ProcessBallPacket(char* msg, Ball* ball, GameData* gd) ball->visible = 1; return 0; +} + +int ProcessAlert(char* msg) +{ + if (strncmp(msg, "ALERT:", 6) != 0) { + return 1; + } + char* alertText = msg + 6; + if (*alertText == ' ') { + alertText++; + } + + GameAlert(alertText); + return 0; +} + +int ProcessGMSG(char* msg) +{ + if (strncmp(msg, "GMSG:", 5) != 0) { + return 1; + } + char* alertText = msg + 5; + if (*alertText == ' ') { + alertText++; + } + + MGameAlert(BOT_ROW+1,LEFT_COL,alertText); + return 0; +} + +int ProcessScore(char* msg, int* scores) +{ + if (strncmp(msg, "SCORE", 5) != 0) { + return 1; + } + + int matched = sscanf(msg, "SCORE %d %d", &scores[0], &scores[1]); + if (matched != 2) { + printf("Parsing failed"); + return 1; + } + + return 0; +} + +int ProcessBallLost(char* msg, int fd, GameData* gd, int* scores, int* allowserve, int numOfBalls) +{ + if (strncmp(msg, "BALL_LOST", 9) != 0) { + return 1; + } + + scores[!gd->host]++; + *allowserve = 1; + + char rmsg[1024]; + snprintf(rmsg, sizeof(rmsg), "SCORE %d %d", scores[0], scores[1]); + send(fd, rmsg, strlen(rmsg), 0); + + DrawHeader(gd, scores,numOfBalls); + + GameAlert("You scored a point!"); + MGameAlert(BOT_ROW + 1, LEFT_COL, "Press space to Serve"); + + return 0; +} + +int ProcessNumOfBalls(char* msg, int* numOfBalls) { + if (strncmp(msg, "NUM_OF_BALLS", 12) != 0) { + return 1; + } + + int matched = sscanf(msg, "NUM_OF_BALLS %d", numOfBalls); + if (matched != 1) { + printf("Parsing failed"); + return 1; + } + + return 0; +} + +void SendGameStatus(int fd, char* msg) { + char rmsg[1024]; + snprintf(rmsg, sizeof(rmsg), "GMSG:%s", msg); + send(fd, rmsg, strlen(rmsg), 0); } \ No newline at end of file