#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdbool.h> #include <pthread.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <ncurses.h> #define BOARD_WIDTH 43 #define BOARD_HEIGHT 21 #define PADDLE_L_X 1 #define PADDLE_R_X BOARD_WIDTH - 2 #define MAX_LINE_LENGTH 2048 #define MAX_PENDING_CONNECTIONS 5 bool is_host = false; int final_socket; bool write_log = false; pthread_mutex_t board_lock = PTHREAD_MUTEX_INITIALIZER; int ball_x, ball_y; int dx, dy; int paddle_l_y, paddle_r_y; int score_l, score_r; WINDOW *window; int receive_int(int); void *network_interaction(void*); int connect_as_host(int); int connect_as_general(char*,char*); void handle_signal(int); void send_string(int, char*); void send_int(int, int); int receive_with_check(int, char*); void print_log(char*); void draw_board(int,int,int,int,int,int); void reset_board(); void countdown(const char*); void game_tick(int); void *listen_input(void *); void initialize_ncurses(); int main(int argc, char *argv[]) { int refresh_rate; int final_socket; if(argc >= 3) { char *host_flag = argv[1]; if (strcmp(host_flag, "--host") == 0){ is_host = true; if (argc < 4){ printf("Usage: ./pong_game --host PORT DIFFICULTY\n"); exit(1); } int port = atoi(argv[2]); char *difficulty = argv[3]; if(strcmp(difficulty, "easy") == 0) refresh_rate = 80000; else if(strcmp(difficulty, "medium") == 0) refresh_rate = 40000; else if(strcmp(difficulty, "hard") == 0) refresh_rate = 20000; else { printf("ERROR: Difficulty should be one of easy, medium, hard.\n"); exit(1); } final_socket = connect_as_host(port); if (argc == 5) { char *str = argv[4]; if (strcmp(str, "DEBUG") == 0) write_log = true; } } else { final_socket = connect_as_general(argv[1], argv[2]); char difficulty[MAX_LINE_LENGTH]; receive_with_check(final_socket, difficulty); if (strcmp(difficulty, "easy") == 0) refresh_rate = 80000; else if (strcmp(difficulty, "medium") == 0) refresh_rate = 40000; else if (strcmp(difficulty, "hard") == 0) refresh_rate = 20000; else { printf("ERROR: user received weird difficulty setting\n"); exit(1); } if (argc == 4) { char *str = argv[3]; if (strcmp(str, "DEBUG") == 0) write_log = true; } } } else { printf("Usage: ./pong_game HOSTNAME PORT\n"); printf("Usage: ./pong_game --host PORT DIFFICULTY\n"); exit(0); } final_socket = final_socket; print_log("CONNECTIONS WORKED"); initialize_ncurses(); reset_board(); countdown("Starting Game"); pthread_t input_thread; if (pthread_create(&input_thread, NULL, listen_input, &final_socket) < 0){ perror("listen_input socket"); exit(1); } pthread_t network_thread; if (pthread_create(&network_thread, NULL, network_interaction, &final_socket) < 0){ perror("network_interaction socket"); exit(1); } struct timeval tv; while(1) { signal(SIGINT, handle_signal); gettimeofday(&tv,NULL); unsigned long before = 1000000 * tv.tv_sec + tv.tv_usec; pthread_mutex_lock(&board_lock); game_tick(final_socket); pthread_mutex_unlock(&board_lock); gettimeofday(&tv,NULL); unsigned long after = 1000000 * tv.tv_sec + tv.tv_usec; unsigned long to_sleep = refresh_rate - (after - before); if(to_sleep > refresh_rate) to_sleep = refresh_rate; usleep(to_sleep); } pthread_join(input_thread, NULL); endwin(); return 0; } int receive_int(int sock_fd) { int num_bytes_received; int num; int flags = 0; if ((num_bytes_received = recv(sock_fd, &num, sizeof(num) , flags)) == -1){ perror("receive error"); close(sock_fd); exit(1); } if (num_bytes_received == 0){ printf("receive_int: zero bytes received\n"); close(sock_fd); exit(1); } return ntohl(num); } void *network_interaction(void *sock){ int sock_fd = *(int*)sock; int read_size; char buf[MAX_LINE_LENGTH]; char msg_from_other_user[MAX_LINE_LENGTH]; while(1){ receive_with_check(sock_fd, msg_from_other_user); if (strcmp(msg_from_other_user, "ball") == 0){ pthread_mutex_lock(&board_lock); dx = receive_int(sock_fd); dy = receive_int(sock_fd); ball_x = receive_int(sock_fd); ball_y = receive_int(sock_fd); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "ballY") == 0){ pthread_mutex_lock(&board_lock); dy = receive_int(sock_fd); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "paddleR") == 0){ pthread_mutex_lock(&board_lock); paddle_r_y = receive_int(sock_fd); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "paddleL") == 0){ pthread_mutex_lock(&board_lock); paddle_l_y = receive_int(sock_fd); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "scoreL") == 0){ pthread_mutex_lock(&board_lock); score_l = receive_int(sock_fd); reset_board(); countdown("<-- SCORE"); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "scoreR") == 0) { pthread_mutex_lock(&board_lock); score_r = receive_int(sock_fd); reset_board(); countdown("SCORE -->"); pthread_mutex_unlock(&board_lock); } else if (strcmp(msg_from_other_user, "quit") == 0){ print_log("received SIGINT from other player"); close(sock_fd); endwin(); exit(1); } } } int connect_as_host(int port){ print_log("HOST: Attempt Connection"); struct sockaddr_in sin; bzero((char*)&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); int listen_sock_fd; if ((listen_sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("host: socket"); exit(1); } int opt = 1; if ((setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(int))) < 0){ perror("host: setsocket"); close(listen_sock_fd); exit(1); } if ((bind(listen_sock_fd, (struct sockaddr *)&sin, sizeof(sin))) < 0){ perror("host: bind"); close(listen_sock_fd); exit(1); } if ((listen(listen_sock_fd, MAX_PENDING_CONNECTIONS)) < 0){ perror("host: listen"); close(listen_sock_fd); exit(1); } printf("Waiting for challengers on port %d\n", port); int host_sock_fd, addr_len; struct sockaddr_in client_addr; while ((host_sock_fd = accept(listen_sock_fd, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len)) >= 0){ print_log("HOST: Accepted connection"); close(listen_sock_fd); return host_sock_fd; } } int connect_as_general(char *hostname, char * port){ print_log("GENERAL: Attempt Connection"); struct addrinfo hints, *client_info, *ptr; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; int return_val; if ((return_val = getaddrinfo(hostname, port, &hints, &client_info)) != 0){ perror("getaddrinfo"); exit(1); } printf("Connecting to %s on port %s\n", hostname, port); int client_sock_fd; for (ptr = client_info; ptr != NULL; ptr = ptr->ai_next) { if ((client_sock_fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) < 0){ perror("Client: bad socket"); continue; } if (connect(client_sock_fd, ptr->ai_addr, ptr->ai_addrlen) < 0){ perror("client: bad connect"); close(client_sock_fd); continue; } } printf("Connected to %s\n", hostname); return client_sock_fd; } void handle_signal(int signal){ print_log("SIGINT"); send_string(final_socket, "quit"); endwin(); exit(0); } void send_string(int sock, char *to_send){ print_log("sending: "); print_log(to_send); int len = strlen(to_send); int flags = 0; if (send(sock, to_send, len, flags) == -1){ perror("client send error!\n"); exit(1); } } void send_int(int sock, int to_send){ to_send = htonl(to_send); if (send(sock, &to_send, sizeof(to_send), 0) == -1){ perror("sendInt error"); exit(1); } } int receive_with_check(int sock_fd, char *out_msg) { int num_bytes_received; char buf[MAX_LINE_LENGTH]; int len = sizeof(buf); int flags = 0; memset(buf, 0, len); if ((num_bytes_received = recv(sock_fd, buf, len, flags)) == -1){ perror("receive error"); close(sock_fd); exit(1); } if (num_bytes_received == 0){ printf("receive_with_check: zero bytes received\n"); close(sock_fd); exit(1); } strcpy(out_msg, buf); print_log("receiving: "); print_log(out_msg); return num_bytes_received; } void print_log(char *msg){ if (write_log){ FILE *f = fopen("log", "a"); fprintf(f, "%s \n", msg); fclose(f); } } void draw_board(int ball_x, int ball_y, int paddle_l_y, int paddle_r_y, int score_l, int score_r) { int y; for(y = 1; y < BOARD_HEIGHT-1; y++) { mvwaddch(window, y, BOARD_WIDTH / 2, ACS_VLINE); } mvwprintw(window, 1, BOARD_WIDTH / 2 - 3, "%2d", score_l); mvwprintw(window, 1, BOARD_WIDTH / 2 + 2, "%d", score_r); mvwaddch(window, ball_y, ball_x, ACS_BLOCK); for(y = 1; y < BOARD_HEIGHT - 1; y++) { int ch = (y >= paddle_l_y - 2 && y <= paddle_l_y + 2)? ACS_BLOCK : ' '; mvwaddch(window, y, PADDLE_L_X, ch); } for(y = 1; y < BOARD_HEIGHT - 1; y++) { int ch = (y >= paddle_r_y - 2 && y <= paddle_r_y + 2)? ACS_BLOCK : ' '; mvwaddch(window, y, PADDLE_R_X, ch); } wrefresh(window); mvwaddch(window, ball_y, ball_x, ' '); } void reset_board() { ball_x = BOARD_WIDTH / 2; paddle_l_y = paddle_r_y = ball_y = BOARD_HEIGHT / 2; dx = (rand() % 2) * 2 - 1; dy = 0; draw_board(ball_x, ball_y, paddle_l_y, paddle_r_y, score_l, score_r); } void countdown(const char *message) { int h = 4; int w = strlen(message) + 4; WINDOW *popup = newwin(h, w, (LINES - h) / 2, (COLS - w) / 2); box(popup, 0, 0); mvwprintw(popup, 1, 2, message); int countdown; for(countdown = 3; countdown > 0; countdown--) { mvwprintw(popup, 2, w / 2, "%d", countdown); wrefresh(popup); sleep(1); } wclear(popup); wrefresh(popup); delwin(popup); paddle_l_y = paddle_r_y = BOARD_HEIGHT / 2; } void game_tick(int sock_fd) { ball_x += dx; ball_y += dy; int pad_y = (ball_x < BOARD_WIDTH / 2) ? paddle_l_y : paddle_r_y; int col_x = (ball_x < BOARD_WIDTH / 2) ? PADDLE_L_X + 1 : PADDLE_R_X - 1; if(ball_x < 1 || ball_x >= BOARD_WIDTH - 1) { if(ball_y >= pad_y - 2 && ball_y <= pad_y + 2) { dx = -dx; if(ball_y == pad_y - 2) dy = -1; if(ball_y == pad_y + 2) dy = 1; } else { if(ball_x < 1) { score_r++; send_string(sock_fd, "scoreR"); send_int(sock_fd, score_r); } if(ball_x >= BOARD_WIDTH - 1) { score_l++; send_string(sock_fd, "scoreL"); send_int(sock_fd, score_l); } reset_board(); } } if(ball_y < 1 || ball_y >= BOARD_HEIGHT - 1) dy = -dy; if (is_host) { send_string(sock_fd, "ball"); send_int(sock_fd, dx); send_int(sock_fd, dy); send_int(sock_fd, ball_x); send_int(sock_fd, ball_y); } draw_board(ball_x, ball_y, paddle_l_y, paddle_r_y, score_l, score_r); } void *listen_input(void *sock_fd) { int sock = *(int*)sock_fd; while(1) { int ch = getch(); if(ch == KEY_UP) { if(paddle_r_y > 3) paddle_r_y--; if (is_host){ send_string(sock, "paddleR"); send_int(sock, paddle_r_y); } } if(ch == KEY_DOWN) { if(paddle_r_y < BOARD_HEIGHT - 4) paddle_r_y++; if (is_host){ send_string(sock, "paddleR"); send_int(sock, paddle_r_y); } } if(ch == 'w') { if(paddle_l_y > 3) paddle_l_y--; if (is_host){ send_string(sock, "paddleL"); send_int(sock, paddle_l_y); } } if(ch == 's') { if(paddle_l_y < BOARD_HEIGHT - 4) paddle_l_y++; if (is_host){ send_string(sock, "paddleL"); send_int(sock, paddle_l_y); } } } } void initialize_ncurses() { initscr(); cbreak(); noecho(); curs_set(0); timeout(0); keypad(stdscr, TRUE); if(has_colors()) { start_color(); init_pair(1, COLOR_GREEN, COLOR_BLACK); init_pair(2, COLOR_RED, COLOR_BLACK); init_pair(3, COLOR_YELLOW, COLOR_BLACK); init_pair(4, COLOR_CYAN, COLOR_BLACK); } window = newwin(BOARD_HEIGHT, BOARD_WIDTH, (LINES - BOARD_HEIGHT) / 2, (COLS - BOARD_WIDTH) / 2); box(window, 0, 0); wrefresh(window); } This is the source code for netpong.The implementation was a lot easier in C++ so that’s what I did. This is a screenshot of the program working utilizing the localhost and 2 separate terminals. The game is playable by using WASD on the server terminal and arrows on the client terminal. There was a bug I could not figure out where sometimes my paddle would disappear. I would like to solve this, but with the time given on the assignment, this is all I could muster up. Here is the makefile: all: netpong netpong.o: netpong.cpp g++ -std=c++11 -c -o netpong.o netpong.cpp netpong: netpong.o g++ -o netpong -std=c++11 -lncurses -lpthread -lz netpong.o clean: @echo Cleaning... @rm netpong @rm netpong.o