Compare commits
16 Commits
4cdda5c53a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dbe11ccf9 | |||
| 6fbbdedebe | |||
| 5541aeb799 | |||
| 96b1b6b848 | |||
| 5c2bb282ce | |||
| ab35c9216e | |||
| d79e73df8b | |||
| 97021d0d4b | |||
| 21324f72e6 | |||
| 049c2df51a | |||
| ce6d47845e | |||
| 745bce5ff4 | |||
| 44dbf7813a | |||
| 8a4812973c | |||
| 3607c4bf36 | |||
| 81488417d9 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
*.o
|
||||
netpong
|
||||
netpong.dSYM/
|
||||
netpong.c
|
||||
build/*
|
||||
*.DS_Store
|
||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -14,7 +14,12 @@
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"environment": [
|
||||
{
|
||||
"name": "TERM",
|
||||
"value": "xterm-256color"
|
||||
}
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "lldb",
|
||||
"setupCommands": [
|
||||
@@ -30,6 +35,5 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@@ -9,8 +9,8 @@
|
||||
"-Wall",
|
||||
"-g",
|
||||
"main.c",
|
||||
"court.c",
|
||||
"network/*.c",
|
||||
"src/*.c",
|
||||
"src/network/*.c",
|
||||
"-o",
|
||||
"netpong",
|
||||
"-lncurses"
|
||||
|
||||
31
Makefile
31
Makefile
@@ -1,32 +1,33 @@
|
||||
# Compiler and flags
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -g
|
||||
LIBS = -lncurses -lm
|
||||
|
||||
NETPONG_SRC = main.c court.c $(wildcard network/*.c)
|
||||
# Source files
|
||||
NETPONG_SRC = main.c $(wildcard src/*.c) $(wildcard src/network/*.c)
|
||||
|
||||
LIBS = -lncurses
|
||||
|
||||
|
||||
# Object files
|
||||
NETPONG_OBJ = $(NETPONG_SRC:.c=.o)
|
||||
# Object files in build/
|
||||
NETPONG_OBJ = $(patsubst %.c,build/%.o,$(NETPONG_SRC))
|
||||
|
||||
# Default target
|
||||
all: netpong
|
||||
|
||||
# Build NetPong
|
||||
# Link
|
||||
netpong: $(NETPONG_OBJ)
|
||||
$(CC) $(CFLAGS) -o netpong $(NETPONG_OBJ) $(LIBS)
|
||||
$(CC) $(CFLAGS) -o $@ $(NETPONG_OBJ) $(LIBS)
|
||||
|
||||
# Generic compile rule
|
||||
%.o: %.c smsh.h varlib.h
|
||||
$(CC) $(CFLAGS) -c $<
|
||||
# Compile rule (FULL PATH match)
|
||||
build/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Clean up
|
||||
# Clean
|
||||
clean:
|
||||
rm -f *.o netpong network/*.o
|
||||
rm -rf build netpong
|
||||
|
||||
# Run targets
|
||||
Server:
|
||||
./netpong
|
||||
./netpong 2001
|
||||
|
||||
Client:
|
||||
./netpong -c 127.0.0.1
|
||||
./netpong 127.0.0.1 2001
|
||||
29
README.md
Normal file
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Netpong
|
||||
## About
|
||||
Netpong is a simple, terminal-based Pong game designed for two players. Each player runs the program on their own machine. One player starts the game in server mode, while the other connects to the host. Once connected, both players can enjoy a classic Pong match together in semi-real time.
|
||||
|
||||
This project is written in C and uses standard file descriptor–based sockets for network communication. It was developed to meet the requirements of a Systems Programming course at Kent State University. Netpong is intended to run in a Unix-like environment and supports gameplay over a local network or across the internet with proper port forwarding.
|
||||
|
||||
## The Game
|
||||

|
||||
When the game starts, the connecting client is assigned the initial serve. After serving, that client controls the ball’s movement and uses their paddle to send it toward the opponent. Once the ball crosses the “net” boundary, control is transferred to the other player, and the ball is no longer displayed on the original client’s screen.
|
||||
|
||||

|
||||
This process repeats as players return the ball back and forth, with control switching each time it crosses sides. The rally continues until one player fails to return the ball.
|
||||
|
||||
|
||||

|
||||
When a player exhausts all opportunities to return the ball, the host determines that the game has ended and calculates the final score to declare a winner. Both applications then terminate, and a new session can be started by relaunching the program.
|
||||
|
||||
## Compilation
|
||||
The program requires a Unix Environment as it relies on ncurses to "draw" to the terminal.
|
||||
|
||||
To make the program, navigate to the root of this project and just type `make`
|
||||
|
||||
You should now be able to run `./netpong`. Send the project or the executable to another computer to play between two machines
|
||||
|
||||
### Usage
|
||||
```
|
||||
Server: ./netpong <port>
|
||||
Client: ./netpong <ip_address or domain_name> <port>
|
||||
```
|
||||
22
game.h
22
game.h
@@ -1,22 +0,0 @@
|
||||
#ifndef GAME_H
|
||||
#define GAME_H 1
|
||||
|
||||
// Setup game board
|
||||
#define TOP_ROW 4
|
||||
#define BOT_ROW 21
|
||||
#define LEFT_COL 9
|
||||
#define RIGHT_COL 70
|
||||
|
||||
struct GameData
|
||||
{
|
||||
int playerNumber;
|
||||
char* domain;
|
||||
int port;
|
||||
|
||||
};
|
||||
|
||||
|
||||
void DrawWalls(struct GameData);
|
||||
void DrawHeader(struct GameData);
|
||||
|
||||
#endif
|
||||
BIN
images/CMD.png
Normal file
BIN
images/CMD.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
images/Compile.png
Normal file
BIN
images/Compile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
BIN
images/End.png
Normal file
BIN
images/End.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
images/Play.png
Normal file
BIN
images/Play.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
images/Serve.png
Normal file
BIN
images/Serve.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
63
include/game.h
Normal file
63
include/game.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef GAME_H
|
||||
#define GAME_H 1
|
||||
|
||||
#include "network/networking.h"
|
||||
|
||||
#define NUM_OF_BALLS 6
|
||||
|
||||
|
||||
// Setup game board
|
||||
#define TOP_ROW 4
|
||||
#define BOT_ROW 21
|
||||
#define LEFT_COL 9
|
||||
#define RIGHT_COL 70
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int host;
|
||||
int playerNumber;
|
||||
char* domain;
|
||||
int port;
|
||||
|
||||
} GameData;
|
||||
|
||||
long GetCurrentMS();
|
||||
|
||||
/**
|
||||
* Setups the curses terminal environment
|
||||
*/
|
||||
void SetupTermWin();
|
||||
/**
|
||||
* Cleans up the curses terminal environment
|
||||
*/
|
||||
void CloseTermWin();
|
||||
|
||||
/**
|
||||
* Creates the Game Server that is responsible for the Host's input and processing
|
||||
*/
|
||||
void CreateGameServer(Server* serv,GameData*gd);
|
||||
|
||||
/**
|
||||
* Creates the Client game loop
|
||||
*/
|
||||
void RunGameClient(Client* c,GameData*gd);
|
||||
|
||||
/**
|
||||
* Draws the walls based on player 1 or 2
|
||||
*/
|
||||
void DrawWalls(GameData);
|
||||
/**
|
||||
* Draws the scoreboard and header
|
||||
*/
|
||||
void DrawHeader(GameData* data, int* scores, int numOfBalls);
|
||||
|
||||
/**
|
||||
* Creates text at a location on screen
|
||||
*/
|
||||
void MGameAlert(int y, int x, char* str);
|
||||
/**
|
||||
* Creates text at the top left of the screen for alerts
|
||||
*/
|
||||
void GameAlert(char* str);
|
||||
|
||||
#endif
|
||||
38
include/network/networking.h
Normal file
38
include/network/networking.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef GAME_NETWORKING_H
|
||||
#define GAME_NETWORKING_H 1
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#define MAX_SERVER_CLIENTS 1
|
||||
|
||||
typedef struct {
|
||||
int server_fd;
|
||||
struct sockaddr_in address;
|
||||
int clients[2];
|
||||
int clientID;
|
||||
int client_count;
|
||||
} Server;
|
||||
|
||||
/**
|
||||
* Setup the actual socket server
|
||||
*/
|
||||
Server StartServer(int port);
|
||||
|
||||
/**
|
||||
* Removes client and closes the connection
|
||||
*/
|
||||
void RemoveClient(Server* s, int index);
|
||||
|
||||
void KillServer();
|
||||
|
||||
typedef struct {
|
||||
int client_fd;
|
||||
struct sockaddr_in address;
|
||||
} Client;
|
||||
|
||||
/**
|
||||
* Creates the Client object
|
||||
*/
|
||||
Client StartClient(char* host, int port);
|
||||
|
||||
#endif
|
||||
16
include/network/packet.h
Normal file
16
include/network/packet.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef GAME_PACKET_H
|
||||
#define GAME_PACKET_H 1
|
||||
|
||||
#include "../game.h"
|
||||
#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
|
||||
37
include/object.h
Normal file
37
include/object.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef GAME_PADDLE_H
|
||||
#define GAME_PADDLE_H 1
|
||||
|
||||
#include "game.h"
|
||||
|
||||
typedef struct {
|
||||
int column;
|
||||
int pos;
|
||||
int height;
|
||||
char c;
|
||||
} Paddle;
|
||||
|
||||
Paddle CreatePaddle(GameData* gd);
|
||||
void DrawPaddle(Paddle*);
|
||||
void MovePaddleUP(Paddle*);
|
||||
void MovePaddleDown(Paddle*);
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
|
||||
int lastCharX;
|
||||
int lastCharY;
|
||||
char c;
|
||||
int visible;
|
||||
float vx;
|
||||
float vy;
|
||||
} Ball;
|
||||
|
||||
Ball CreateBall(GameData* gd);
|
||||
void DrawBall(Ball*);
|
||||
void UpdateBall(Ball* ball, Paddle* paddle);
|
||||
int CheckBallLose(GameData* gd,Ball* ball);
|
||||
int CheckBallCourtChange(GameData* gd,Ball* ball);
|
||||
void HideBall(Ball* ball);
|
||||
void ServeBall(Ball* ball, GameData* gd);
|
||||
#endif
|
||||
54
main.c
54
main.c
@@ -1,22 +1,23 @@
|
||||
#include "game.h"
|
||||
#include <curses.h>
|
||||
#include <signal.h>
|
||||
#include "include/game.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string.h>
|
||||
/**
|
||||
* Main Fucntion
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// Create GameData Object
|
||||
struct GameData data;
|
||||
GameData data = { false, 0, NULL, 0 };
|
||||
|
||||
// Detect Server or Client
|
||||
switch (argc) {
|
||||
case 2:
|
||||
// Start Server
|
||||
data.host = true;
|
||||
data.playerNumber = 0;
|
||||
data.port = atoi(argv[1]);
|
||||
break;
|
||||
@@ -34,27 +35,26 @@ int main(int argc, char* argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
srand(getpid());
|
||||
|
||||
// Setup Terminal
|
||||
if (initscr() == NULL) {
|
||||
perror("initscr failed");
|
||||
exit(1);
|
||||
|
||||
|
||||
if (data.host) {
|
||||
// Create Server
|
||||
Server s = StartServer(data.port);
|
||||
printf("Server Started\n");
|
||||
|
||||
CreateGameServer(&s,&data);
|
||||
|
||||
close(s.server_fd);
|
||||
|
||||
} else {
|
||||
|
||||
printf("Connecting to %s:%i\n", data.domain, data.port);
|
||||
Client c = StartClient(data.domain, data.port);
|
||||
|
||||
RunGameClient(&c,&data);
|
||||
|
||||
close(c.client_fd);
|
||||
return 0;
|
||||
}
|
||||
noecho();
|
||||
cbreak();
|
||||
nodelay(stdscr, TRUE);
|
||||
|
||||
DrawWalls(data);
|
||||
DrawHeader(data);
|
||||
|
||||
int socket_fd = 0;
|
||||
|
||||
while (1) {
|
||||
int ch = getch();
|
||||
if (ch == 'q')
|
||||
break;
|
||||
}
|
||||
|
||||
endwin();
|
||||
}
|
||||
112
src/ball.c
Normal file
112
src/ball.c
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "../include/object.h"
|
||||
|
||||
#include <curses.h>
|
||||
#include <math.h>
|
||||
|
||||
Ball CreateBall(GameData* gd)
|
||||
{
|
||||
Ball b;
|
||||
b.visible = false;
|
||||
b.c = 'O';
|
||||
b.y = TOP_ROW + 3;
|
||||
b.x = RIGHT_COL - 3;
|
||||
b.lastCharX = 0;
|
||||
b.lastCharY = 0;
|
||||
|
||||
b.vx = 0;
|
||||
b.vy = 0;
|
||||
|
||||
return b;
|
||||
}
|
||||
void UpdateBall(Ball* ball, Paddle* p)
|
||||
{
|
||||
int x = ball->x;
|
||||
int y = ball->y;
|
||||
|
||||
float nx = ball->x + ball->vx;
|
||||
float ny = ball->y + ball->vy;
|
||||
|
||||
if (floor(ny) == TOP_ROW || floor(ny) == BOT_ROW) {
|
||||
ball->vy = -ball->vy;
|
||||
ny = ball->y + ball->vy;
|
||||
}
|
||||
|
||||
if (floor(nx) == p->column) {
|
||||
if (ny >= p->pos && ny < p->pos + p->height) {
|
||||
ball->vx = -ball->vx;
|
||||
nx = ball->x + ball->vx;
|
||||
}
|
||||
}
|
||||
|
||||
ball->x = nx;
|
||||
ball->y = ny;
|
||||
|
||||
int ncx = ball->x;
|
||||
int ncy = ball->y;
|
||||
|
||||
// if x y changes then re-draw
|
||||
if (ncx != x || ncy != y)
|
||||
DrawBall(ball);
|
||||
}
|
||||
|
||||
int CheckBallLose(GameData* gd, Ball* ball)
|
||||
{
|
||||
if (gd->host) {
|
||||
if (ball->x <= LEFT_COL) {
|
||||
return true;
|
||||
}
|
||||
} else if (ball->x >= RIGHT_COL)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int CheckBallCourtChange(GameData* gd, Ball* ball)
|
||||
{
|
||||
if (gd->host) {
|
||||
if (ball->x >= RIGHT_COL) {
|
||||
return true;
|
||||
}
|
||||
} else if (ball->x <= LEFT_COL)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void DrawBall(Ball* ball)
|
||||
{
|
||||
int x = ball->x;
|
||||
int y = ball->y;
|
||||
|
||||
char lastChar = mvinch(ball->lastCharY, ball->lastCharX) & A_CHARTEXT;
|
||||
if (lastChar == ball->c)
|
||||
mvaddch(ball->lastCharY, ball->lastCharX, ' ');
|
||||
|
||||
mvaddch(y, x, ball->c);
|
||||
ball->lastCharY = y;
|
||||
ball->lastCharX = x;
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HideBall(Ball* ball)
|
||||
{
|
||||
ball->visible = false;
|
||||
char lastChar = mvinch(ball->lastCharY, ball->lastCharX) & A_CHARTEXT;
|
||||
if (lastChar == ball->c)
|
||||
mvaddch(ball->lastCharY, ball->lastCharX, ' ');
|
||||
}
|
||||
|
||||
void ServeBall(Ball* ball, GameData* gd) {
|
||||
ball->visible = true;
|
||||
|
||||
float middle = TOP_ROW + (BOT_ROW-TOP_ROW)/2;
|
||||
ball->y = middle;
|
||||
|
||||
if (gd->host)
|
||||
ball->x = RIGHT_COL-2;
|
||||
else ball->x = LEFT_COL + 2;
|
||||
|
||||
|
||||
ball->vx = -0.2;
|
||||
ball->vy = 0.09;
|
||||
if (!gd->host) ball->vx *= -1;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "game.h"
|
||||
#include "../include/game.h"
|
||||
|
||||
#include <curses.h>
|
||||
#include <signal.h>
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void DrawWalls(struct GameData data)
|
||||
void DrawWalls(GameData data)
|
||||
{
|
||||
for (int i = LEFT_COL; i <= RIGHT_COL; i++) {
|
||||
move(TOP_ROW, i);
|
||||
@@ -25,9 +25,7 @@ void DrawWalls(struct GameData data)
|
||||
|
||||
refresh();
|
||||
}
|
||||
void DrawHeader(struct 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);
|
||||
}
|
||||
140
src/gameClient.c
Normal file
140
src/gameClient.c
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "../include/game.h"
|
||||
#include "../include/network/packet.h"
|
||||
#include "../include/object.h"
|
||||
#include <curses.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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();
|
||||
}
|
||||
230
src/gameServer.c
Normal file
230
src/gameServer.c
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "../include/game.h"
|
||||
#include "../include/network/packet.h"
|
||||
#include "../include/object.h"
|
||||
#include <curses.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
long GetCurrentMS()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
long ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
return ms;
|
||||
}
|
||||
|
||||
void CreateGameServer(Server* serv, GameData* gd)
|
||||
{
|
||||
|
||||
Server s = *serv;
|
||||
Paddle paddle = CreatePaddle(gd);
|
||||
|
||||
Ball ball = CreateBall(gd);
|
||||
long lastBallUpdate = GetCurrentMS();
|
||||
int scores[2] = { 0, 0 };
|
||||
|
||||
int numOfBalls = 1;
|
||||
bool game_running = true;
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(s.server_fd, &fds);
|
||||
|
||||
SetupTermWin();
|
||||
|
||||
DrawWalls(*gd);
|
||||
DrawHeader(gd, scores, 0);
|
||||
DrawPaddle(&paddle);
|
||||
// DrawBall(&ball);
|
||||
|
||||
GameAlert("Waiting for Client");
|
||||
int allowServe = false;
|
||||
|
||||
while (game_running) {
|
||||
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
|
||||
// Add server socket
|
||||
FD_SET(s.server_fd, &read_fds);
|
||||
int max_fd = s.server_fd;
|
||||
|
||||
// Add client sockets
|
||||
for (int i = 0; i < s.client_count; i++) {
|
||||
int fd = s.clients[i];
|
||||
FD_SET(fd, &read_fds);
|
||||
if (fd > max_fd)
|
||||
max_fd = fd;
|
||||
}
|
||||
|
||||
// Small timeout (prevents 100% CPU usage)
|
||||
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;
|
||||
}
|
||||
|
||||
// New connection
|
||||
if (FD_ISSET(s.server_fd, &read_fds)) {
|
||||
int client_fd = accept(s.server_fd, NULL, NULL);
|
||||
if (client_fd >= 0 && s.client_count < MAX_SERVER_CLIENTS) {
|
||||
s.clients[s.client_count++] = client_fd;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle client data
|
||||
for (int i = 0; i < s.client_count; i++) {
|
||||
int fd = s.clients[i];
|
||||
|
||||
if (FD_ISSET(fd, &read_fds)) {
|
||||
char buffer[1024];
|
||||
int bytes = recv(fd, buffer, sizeof(buffer) - 1, 0);
|
||||
|
||||
if (bytes > 0) {
|
||||
buffer[bytes] = '\0';
|
||||
|
||||
char msg[1024];
|
||||
snprintf(msg, sizeof(msg), "Client %d says: %s\r", fd, buffer);
|
||||
GameAlert(msg);
|
||||
|
||||
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);
|
||||
} else if (bytes == 0) {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Client %d disconnected\n", fd);
|
||||
GameAlert(msg);
|
||||
|
||||
RemoveClient(&s, i);
|
||||
i--; // re-check swapped client
|
||||
} else {
|
||||
perror("recv");
|
||||
RemoveClient(&s, i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char ch = getch();
|
||||
if (ch == 'w')
|
||||
MovePaddleUP(&paddle);
|
||||
if (ch == 's')
|
||||
MovePaddleDown(&paddle);
|
||||
|
||||
// Process Ball only if on court
|
||||
if (ball.visible && s.client_count > 0) {
|
||||
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(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') {
|
||||
GameAlert("Quitting...");
|
||||
sleep(1);
|
||||
game_running = false;
|
||||
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");
|
||||
// GameAlert(msg);
|
||||
// } else {
|
||||
// // Process Game Data
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
CloseTermWin();
|
||||
}
|
||||
106
src/network/networking.c
Normal file
106
src/network/networking.c
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "../../include/network/networking.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
|
||||
|
||||
static Server *g_server = NULL;
|
||||
|
||||
Server StartServer(int port) {
|
||||
Server s;
|
||||
g_server = &s;
|
||||
s.clientID = 0;
|
||||
s.client_count = 0;
|
||||
|
||||
|
||||
// Create Socket
|
||||
if ((s.server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||
perror("socket failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Set Address
|
||||
s.address.sin_family = AF_INET;
|
||||
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");
|
||||
close(s.server_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Listen for incoming connections
|
||||
if (listen(s.server_fd, 3) < 0) {
|
||||
perror("listen");
|
||||
close(s.server_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Server listening on port %d...\n", port);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void RemoveClient(Server* s, int index) {
|
||||
close(s->clients[index]);
|
||||
s->clients[index] = s->clients[s->client_count - 1];
|
||||
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;
|
||||
|
||||
// Create socket
|
||||
if ((c.client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Set address info
|
||||
c.address.sin_family = AF_INET;
|
||||
c.address.sin_port = htons(port);
|
||||
|
||||
// Convert hostname/IP to binary form
|
||||
if (inet_pton(AF_INET, host, &c.address.sin_addr) <= 0) {
|
||||
// If not a direct IP, try resolving hostname
|
||||
struct hostent* he = gethostbyname(host);
|
||||
if (he == NULL) {
|
||||
perror("gethostbyname failed");
|
||||
close(c.client_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
memcpy(&c.address.sin_addr, he->h_addr_list[0], he->h_length);
|
||||
}
|
||||
|
||||
// Connect to server
|
||||
if (connect(c.client_fd, (struct sockaddr*)&c.address, sizeof(c.address)) < 0) {
|
||||
perror("connect failed");
|
||||
close(c.client_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Connected to %s:%d\n", host, port);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
110
src/network/packet.c
Normal file
110
src/network/packet.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "../../include/network/packet.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int ProcessBallPacket(char* msg, Ball* ball, GameData* gd)
|
||||
{
|
||||
if (strncmp(msg, "BTRANS", 6) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int matched = sscanf(msg, "BTRANS %f %f %f",
|
||||
&ball->y, &ball->vx, &ball->vy);
|
||||
if (matched != 3) {
|
||||
printf("Parsing failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (gd->host)
|
||||
ball->x = RIGHT_COL - 1;
|
||||
else
|
||||
ball->x = LEFT_COL + 1;
|
||||
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);
|
||||
}
|
||||
69
src/paddle.c
Normal file
69
src/paddle.c
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "../include/object.h"
|
||||
|
||||
#include <curses.h>
|
||||
|
||||
Paddle CreatePaddle(GameData* gd)
|
||||
{
|
||||
Paddle p;
|
||||
GameData data = *gd;
|
||||
|
||||
// Set Column
|
||||
if (data.playerNumber == 0)
|
||||
p.column = LEFT_COL + 2;
|
||||
else
|
||||
p.column = RIGHT_COL - 2;
|
||||
|
||||
// Paddle height
|
||||
p.height = 5;
|
||||
|
||||
// Find Middle of screen;
|
||||
float middle = (BOT_ROW - TOP_ROW) / 2.0f;
|
||||
middle -= p.height / 2;
|
||||
|
||||
// Set Y value to the top of the screen
|
||||
p.pos = TOP_ROW + middle;
|
||||
|
||||
// Set char
|
||||
p.c = '#';
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void DrawPaddle(Paddle* paddle)
|
||||
{
|
||||
Paddle p = *paddle;
|
||||
for (int i = 0; i < p.height; ++i) {
|
||||
move(p.pos + i, p.column);
|
||||
addch(p.c);
|
||||
}
|
||||
|
||||
char topchar = mvinch(p.pos - 1, p.column) & A_CHARTEXT;
|
||||
if (topchar == p.c)
|
||||
mvaddch(p.pos - 1, p.column, ' ');
|
||||
char bottomchar = mvinch(p.pos + p.height, p.column) & A_CHARTEXT;
|
||||
if (bottomchar == p.c)
|
||||
mvaddch(p.pos + p.height, p.column, ' ');
|
||||
|
||||
move(p.pos, p.column);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MovePaddleUP(Paddle* paddle)
|
||||
{
|
||||
int newPos = paddle->pos - 1;
|
||||
|
||||
if (newPos > TOP_ROW) {
|
||||
paddle->pos = newPos;
|
||||
}
|
||||
DrawPaddle(paddle);
|
||||
}
|
||||
|
||||
void MovePaddleDown(Paddle* paddle)
|
||||
{
|
||||
int newPos = paddle->pos + 1;
|
||||
|
||||
if (newPos + paddle->height <= BOT_ROW) {
|
||||
paddle->pos = newPos;
|
||||
}
|
||||
DrawPaddle(paddle);
|
||||
}
|
||||
38
src/term.c
Normal file
38
src/term.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "../include/game.h"
|
||||
|
||||
#include <curses.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void SetupTermWin()
|
||||
{
|
||||
srand(getpid());
|
||||
|
||||
// Setup Terminal
|
||||
if (initscr() == NULL) {
|
||||
perror("initscr failed");
|
||||
exit(1);
|
||||
}
|
||||
noecho();
|
||||
cbreak();
|
||||
nodelay(stdscr, TRUE);
|
||||
}
|
||||
|
||||
void CloseTermWin()
|
||||
{
|
||||
endwin();
|
||||
}
|
||||
|
||||
void GameAlert(char* str)
|
||||
{
|
||||
MGameAlert(0,0,str);
|
||||
}
|
||||
|
||||
void MGameAlert(int y, int x, char* str)
|
||||
{
|
||||
move(y, x);
|
||||
clrtoeol();
|
||||
mvaddstr(y, x, str);
|
||||
refresh();
|
||||
}
|
||||
Reference in New Issue
Block a user