I am new to programming and wrote a small Pong game for an assignment using multiple threads, one thread for each ball. This programs works really well on Ubuntu but when I run in on Red Hat all sorts of crazy characters appear on the screen randomly. It seem to happening quicker the more balls I use.
Here is the code. It is run by using "pong a b c d" for example which will produce balls on the screen using the character symbols as the ball. I am pretty sure the issue is inside the animate function as that is where all the refreshing and moving is done. But don't know what the issue is.
#include <stdio.h>
#include <curses.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include "Pong.h"
#define MAXBALL 10 /* limit of balls */
int game_over = 0; /* game over flag */
int num_msg; /* number of balls */
int select_ball; /* select ball */
struct ppball balls[MAXBALL]; /* array of balls */
struct paddle the_paddle ; /* paddle */
struct window the_window ; /* window */
pthread_t thrds[MAXBALL]; /* the threads */
void set_ticker(); /* the animation */
void paddle_move(int move); /* the animation */
void wrap_up(); /* the animation */
void *animate(); /* the animation */
pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER;
int main(int ac, char *av[]){
int i; /* for loops */
select_ball = 0;
if ( ac == 1 ){
printf("usage: Pong char ..\n");
exit(1);
}
num_msg = setup(ac-1,av+1,balls);
/* draw the window to the screen */
the_window.win = newwin(20, 60, 1, 1);
wborder(the_window.win, 0, ' ', 0, 0, 0, 0, 0, 0);
refresh();
wrefresh(the_window.win);
/* create all the threads */
for( i=0 ; i<num_msg; i++ ){
if ( pthread_create(&thrds[i], NULL, animate, &balls[i]) ){
fprintf(stderr,"error creating thread");
endwin();
exit(0);
}
}
while( game_over != 1 ){ /* the main loop */
int c = getch(); /* grab char */
if( c == 'Q' ) {
game_over = 1;
}
else if( c == 'S' ){
for(i=0 ; i<num_msg; i++){
if( balls[i].stimer > INCREMENT ){
balls[i].stimer = balls[i].stimer + INCREMENT;
}
}
}
else if( c == 'F' ){
for(i=0 ; i<num_msg; i++){
if( balls[i].stimer > INCREMENT ){
balls[i].stimer = balls[i].stimer - INCREMENT;
}
}
}
else if( c == 'a' ){
if( the_paddle.mid_x_pos >= TOP_ROW + 2){
paddle_move(0);
}
}
else if( c == 'z' ){
if( the_paddle.mid_x_pos <= BOT_ROW-2){
paddle_move(1);
}
}
}
for ( i=0; i<num_msg; i++ ){
pthread_cancel(thrds[i]);
}
wrap_up();
return 0;
}
void wrap_up(){
curs_set(1);
clear();
endwin();
}
int setup(int nstrings, char *strings[], struct ppball ball[]){
int num_msg = ( nstrings > MAXBALL ? MAXBALL : nstrings );
int i;
/* assign positions and direction to each ball */
srand(getpid());
for(i=0 ; i<num_msg; i++){
ball[i].stimer = START_TIMER;
ball[i].y_pos = rand() % 10 + 2;
ball[i].x_pos = rand() % 10 + 5;
ball[i].y_dir = ((rand()%2)?1:-1); ;
ball[i].x_dir = ((rand()%2)?1:-1); ;
ball[i].symbol = *strings[i];
}
/* set up paddle */
the_paddle.y_pos = PADDLE_Y_INIT;
the_paddle.top_x_pos = PADDLE_X_INIT-1;
the_paddle.mid_x_pos = PADDLE_X_INIT;
the_paddle.bot_x_pos = PADDLE_X_INIT+1;
the_paddle.symbol = PADDLE_SYMBOL ;
/* set up curses */
initscr();
crmode();
noecho();
clear();
curs_set(0);
return num_msg;
}
void *animate(void *arg)
{
struct ppball *info = arg; /* point to info block */
int i;
while( game_over != 1 ){
usleep(info->stimer);
if ( info->y_pos == TOP_ROW ){
info->y_dir = 1 ;
} else if ( info->y_pos == BOT_ROW ){
info->y_dir = -1 ;
}
/* check if ball hits paddle */
if ( (info->y_pos == the_paddle.top_x_pos || info->y_pos == the_paddle.mid_x_pos || info->y_pos == the_paddle.bot_x_pos ) && info->x_pos == the_paddle.y_pos ){
info->x_dir = -1;
}
if ( info->x_pos == LEFT_EDGE ){
info->x_dir = 1 ;
} else if ( info->x_pos == RIGHT_EDGE ){
/* if ball hits right edge program ends */
game_over = 1;
}
/* erase ball */
mvaddch( info->y_pos, info->x_pos, BLANK );
info->y_pos += info->y_dir ; /* move */
info->x_pos += info->x_dir ; /* move */
pthread_mutex_lock(&mx);
mvaddch( info->y_pos, info->x_pos, info->symbol );
mvaddch( the_paddle.top_x_pos, the_paddle.y_pos, the_paddle.symbol );
mvaddch( the_paddle.mid_x_pos, the_paddle.y_pos, the_paddle.symbol );
mvaddch( the_paddle.bot_x_pos, the_paddle.y_pos, the_paddle.symbol );
pthread_mutex_unlock(&mx);
mvprintw(LINES-3,3,"'a' and 'z' to move paddle");
mvprintw(LINES-2,3,"'S' and 'F' dec/inc all ball speed.");
mvprintw(LINES-1,3,"'s' and 'f' dec/inc %c speed, 'w' or 'r' to change ball", balls[select_ball].symbol );
refresh();
}
mvprintw(10,10,"Game Over");
}
void paddle_move(int move)
{
pthread_mutex_lock(&mx);
mvaddch( the_paddle.top_x_pos, the_paddle.y_pos, BLANK );
mvaddch( the_paddle.mid_x_pos, the_paddle.y_pos, BLANK );
mvaddch( the_paddle.bot_x_pos, the_paddle.y_pos, BLANK );
if( move == 0 && the_paddle.mid_x_pos >= TOP_ROW + 1){
the_paddle.top_x_pos--;
the_paddle.mid_x_pos--;
the_paddle.bot_x_pos--;
refresh();
}
else if( move == 1 && the_paddle.mid_x_pos <= BOT_ROW-3){
the_paddle.top_x_pos++;
the_paddle.mid_x_pos++;
the_paddle.bot_x_pos++;
}
mvaddch( the_paddle.top_x_pos, the_paddle.y_pos, the_paddle.symbol );
mvaddch( the_paddle.mid_x_pos, the_paddle.y_pos, the_paddle.symbol );
mvaddch( the_paddle.bot_x_pos, the_paddle.y_pos, the_paddle.symbol );
refresh();
pthread_mutex_unlock(&mx);
}
I know it is a lot of code to read through. I am happy to cut it down.
here is what it looks like on Red Hat
This is what it looks like on Ubuntu.
Your code implicitly operates on the "current screen" in ncurses, a shared object, which is not thread safe. There is a mutex, but you are not consistently holding it across all calls to ncurses. This might not be the issue you're seeing, but it's a gaping problem.