~brown121407/pong

1b4467739c652e87fc20fac42467e2c761ed75e4 — Alexandru-Sergiu Marton 2 years ago
Ping
5 files changed, 263 insertions(+), 0 deletions(-)

A .gitignore
A Makefile
A README.md
A good.png
A pong.c
A  => .gitignore +1 -0
@@ 1,1 @@
pong

A  => Makefile +7 -0
@@ 1,7 @@
.PHONY : all clean

all : 
	gcc -std=c99 pong.c -lm -lncurses `pkg-config --cflags ncurses` -o pong

clean :
	rm pong

A  => README.md +31 -0
@@ 1,31 @@
# pong

Simple pong replica for the terminal; written in C. 

![Picture of the game while running.](good.png)

## How to run

Requires ncurses.

```bash
make ./pong 
```

## Customization

To edit the colors, see the `#defines` for `*_ATTRS` and look into the `setup`
to `init_pair`.

To edit the characters used for drawing the ball and pads, change `BALL` and
`PAD`.

To alter the size of the game field, adjust `WIDTH` and `HEIGHT`.

To change the pad size, edit `PAD_SIZE`.

To change the time between frames, modify `DELTA`.

To change the ball's velocity, set `velocity`. To change the ball's starting
angle, use `set_ball_angle(<angle>)` at the end of `setup`.


A  => good.png +0 -0
A  => pong.c +224 -0
@@ 1,224 @@
#include <ncurses.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>

#define WIDTH 30
#define HEIGHT 10
#define PAD_SIZE 4
#define BALL "O"
#define PAD "#"
#define BLUE 1
#define RED 2
#define YELLOW 3
#define BALL_ATTRS A_BOLD | COLOR_PAIR(YELLOW)
#define PAD_P1_ATTRS A_BOLD | COLOR_PAIR(BLUE)
#define PAD_P2_ATTRS A_BOLD | COLOR_PAIR(RED)
#define PI 3.1415926535
#define DELTA 50000
#define UP -1
#define DOWN 1

enum game_state {
	GS_OK,
	GS_WIN_P1,
	GS_WIN_P2,
} gs = GS_OK;

struct vec2_d {
	double x, y;
};

struct vec2_i {
	int x, y;
};

struct ball {
	struct vec2_d pos;
	struct vec2_d dir;
	double angle;
} ball;

WINDOW* screen = NULL;
struct vec2_i p1_pad, p2_pad;
double velocity = 0.5;

int between(int low, int high, int x) {
	return x >= low && x <= high;
}

void set_ball_angle(double radians) {
	ball.dir.x = cos(radians);
	ball.dir.y = sin(radians);
	ball.angle = radians;
}

void draw_pad(int p) {
	struct vec2_i *pad = NULL;
	pad = (p == 1 ? &p1_pad : &p2_pad);

	p == 1 ? wattron(screen, PAD_P1_ATTRS) : wattron(screen, PAD_P2_ATTRS);
	for (int i = 0; i < PAD_SIZE; i++) {
		mvwprintw(screen, pad->y + i, pad->x, "%s", PAD);
	}
	p == 1 ? wattroff(screen, PAD_P1_ATTRS) : wattroff(screen, PAD_P2_ATTRS);
}

void move_pad(int p, int dir) {
	struct vec2_i *pad = NULL;
	pad = (p == 1 ? &p1_pad : &p2_pad);

	if (dir == UP)
		mvwprintw(screen, pad->y + PAD_SIZE - 1, pad->x, " ");
	else
		mvwprintw(screen, pad->y, pad->x, " ");

	pad->y += dir;
	if (pad->y <= 0)
		pad->y = 1;
	else if (pad->y >= HEIGHT - PAD_SIZE - 1)
		pad->y = HEIGHT - PAD_SIZE - 1;

	draw_pad(p);
}

void move_ball() {
	int collided_pad = FALSE, collided_wall = FALSE;
	struct vec2_d old_pos = ball.pos;

	ball.pos.x += ball.dir.x * velocity;
	ball.pos.y -= ball.dir.y * velocity;

	if ((int)ball.pos.y <= 0) {
		collided_wall = TRUE;
		ball.pos.y = 1.0;
	} else if ((int)ball.pos.y >= HEIGHT - 1) {
		collided_wall = TRUE;
		ball.pos.y = HEIGHT - 2;
	}
	
	if (collided_wall) {
		set_ball_angle(0 - ball.angle);
		gs = GS_OK;
		goto draw;
	}
	
	if (((int)ball.pos.x == p1_pad.x && between(p1_pad.y, p1_pad.y + PAD_SIZE - 1, ball.pos.y))) {
		collided_pad = TRUE;
		ball.pos.x = p1_pad.x + 1.0;
	} else if ((int)ball.pos.x == p2_pad.x && between(p2_pad.y, p2_pad.y + PAD_SIZE - 1, ball.pos.y)) {
		collided_pad = TRUE;
		ball.pos.x = p2_pad.x - 1.0;
	}

	if (collided_pad) {
		set_ball_angle(PI - ball.angle);
		gs = GS_OK;
		goto draw;
	}

	if ((int)ball.pos.x <= 0) {
		gs = GS_WIN_P2;
		return;
	} else if ((int)ball.pos.x >= WIDTH - 1) {
		gs = GS_WIN_P1;
		return;
	} else {
		gs = GS_OK;
	}

draw:
	mvwprintw(screen, (int)old_pos.y, (int)old_pos.x, " ");
	wattron(screen, BALL_ATTRS);
	mvwprintw(screen, (int)ball.pos.y, (int)ball.pos.x, "%s", BALL);
	wattroff(screen, BALL_ATTRS);
}

void run() {
	int should_run = TRUE, key;

	draw_pad(1);
	draw_pad(2);

	while (should_run) {
		key = wgetch(screen);
		switch (key) {
			case 'w': case 'W':
				move_pad(1, UP);
				break;
			case 's': case 'S':
				move_pad(1, DOWN);
				break;
			case KEY_UP:
				move_pad(2, UP);
				break;
			case KEY_DOWN:
				move_pad(2, DOWN);
				break;
			case 'q': case 'Q':
				should_run = FALSE;
				break;
		}
		move_ball();
		
		box(screen, 0, 0);
		wrefresh(screen);

		if (gs != GS_OK)
			should_run = false;

		usleep(DELTA);
	}
}

void setup() {
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);
	curs_set(FALSE);
	start_color();
	init_pair(BLUE, COLOR_BLUE, COLOR_BLACK);
	init_pair(RED, COLOR_RED, COLOR_BLACK);
	init_pair(YELLOW, COLOR_YELLOW, COLOR_BLACK);
	
	refresh();
	
	int max_x, max_y, screen_x, screen_y;
	getmaxyx(stdscr, max_y, max_x);

	screen_x = (max_x - WIDTH) / 2;
	screen_y = (max_y - HEIGHT) / 2;

	screen = newwin(HEIGHT, WIDTH, screen_y, screen_x);
	box(screen, 0, 0);
	nodelay(screen, TRUE);
	keypad(screen, TRUE);
	wrefresh(screen);

	p1_pad.x = 1.;
	p1_pad.y = 1.;
	p2_pad.x = WIDTH - 2.;
	p2_pad.y = 1.;
	ball.pos.x = (double)(WIDTH / 2);
	ball.pos.y = (double)(HEIGHT / 2);
	set_ball_angle(PI/3);
}

void cleanup() {
	endwin();
}

int main() {
	setup();
	run();
	cleanup();

	if (gs == GS_WIN_P1)
		puts("Player 1 wins!\n");
	else if (gs == GS_WIN_P2)
		puts("Player 2 wins!\n");
	else
		puts("Game ended.\n");
	return 0;
}