/* * Alizarin Tetris * The main match event-loop. Code here should relate to the state machine * that keeps track of what to display, accepts as input, send out as output, * etc., as time progresses. * * Copyright 2000, Kiri Wagstaff & Westley Weimer */ #include /* go autoconf! */ #include #include #if HAVE_SYS_SOCKET_H #include #else #if HAVE_WINSOCK_H #include #endif #endif #include "atris.h" #include "grid.h" #include "piece.h" #include "sound.h" #include "ai.h" #include "options.h" #include "ai.pro" #include "display.pro" #include "xflame.pro" extern int Score[]; struct state_struct { int ai; int falling; int fall_speed; int accept_input; int tetris_handling; int limbo; int other_in_limbo; int limbo_sent; int draw; Uint32 collide_time; /* time when your piece merges with the rest */ Uint32 next_draw; Uint32 draw_timeout; Uint32 tv_next_fall; int fall_event_interval; Uint32 tv_next_tetris; int tetris_event_interval; Uint32 tv_next_ai_think; Uint32 tv_next_ai_move; int ai_interval; int ready_for_fast; int ready_for_rotate; int seed; play_piece cp, np; void * ai_state; /* these two are used by tetris_event() */ int check_result; int num_lines_cleared; } State[2]; /* one position structure per player */ struct pos_struct { int x; int y; int rot; int old_x; int old_y; int old_rot; Command move; } pos[2]; Grid distract_grid[2]; /*************************************************************************** * paste_on_board() * Places the given piece on the board. Uses row-column (== grid) * coordinates. *********************************************************************PROTO*/ void paste_on_board(play_piece *pp, int col, int row, int rot, Grid *g) { int i,j,c; for (j=0;jbase->dim;j++) for (i=0;ibase->dim;i++) if ((c=BITMAP(*pp->base,rot,i,j))) { int t_x = i + col; /* was + (screen_x / cs->w); */ int t_y = j + row; /* was + (screen_y / cs->h); */ if ((t_x<0 || t_y<0 || t_x>=g->w || t_y>=g->h)) { Debug("Serious consistency failure: dropping pieces.\n"); continue; } GRID_SET(*g,t_x,t_y,pp->colormap[c]); FALL_SET(*g,t_x,t_y,NOT_FALLING); if (t_x > 0) GRID_CHANGED(*g,t_x-1,t_y) = 1; if (t_y > 0) GRID_CHANGED(*g,t_x,t_y-1) = 1; if (t_x < g->w-1) GRID_CHANGED(*g,t_x+1,t_y) = 1; if (t_y < g->h-1) GRID_CHANGED(*g,t_x,t_y+1) = 1; } return; } /*************************************************************************** * screen_to_exact_grid_coords() * Converts screen coordinates to exact grid coordinates. Will abort the * program if you pass in unaligned coordinates! ***************************************************************************/ static void screen_to_exact_grid_coords(Grid *g, int blockWidth, int screen_x, int screen_y, int *row, int *col) { screen_x -= g->board.x; screen_y -= g->board.y; Assert(screen_x % blockWidth == 0); Assert(screen_y % blockWidth == 0); *row = screen_y / blockWidth; *col = screen_x / blockWidth; return; } /*************************************************************************** * screen_to_grid_coords() * Converts screen coordinates to grid coordinates. Rounds "down". ***************************************************************************/ void screen_to_grid_coords(Grid *g, int blockWidth, int screen_x, int screen_y, int *row, int *col) { screen_x -= g->board.x; screen_y -= g->board.y; if (screen_x < 0) screen_x -= 19; /* round up to negative #s */ if (screen_y < 0) screen_y -= 19; /* round up to negative #s */ *row = screen_y / blockWidth; *col = screen_x / blockWidth; return; } /*************************************************************************** * valid_position() * Determines if the given position is valid. Uses row-column (== grid) * coordinates. Returns 0 if the piece would fall out of bounds or if * some solid part of the piece would fall over something already on the * the grid. Returns 1 otherwise (it is then safe to call * paste_on_board()). *********************************************************************PROTO*/ int valid_position(play_piece *pp, int col, int row, int rot, Grid *g) { int i,j; /* * We don't want this check because you can have col=-2 or whatnot if * your piece doesn't start on its leftmost border. * * if (col < 0 || col >= g->w || row < 0 || row >= g->h) * return 0; */ for (j=0;jbase->dim;j++) for (i=0;ibase->dim;i++) if (BITMAP(*pp->base,rot,i,j)) { int t_x = i + col; /* was + (screen_x / cs->w); */ int t_y = j + row; /* was + (screen_y / cs->h); */ if (t_x < 0 || t_y < 0 || t_x >= g->w || t_y >= g->h) return 0; if (GRID_CONTENT(*g,t_x,t_y)) return 0; } return 1; } /*************************************************************************** * valid_screen_position() * Determines if the given position is valid. Uses screen coordinates. * Handles pieces that are not perfectly row-aligned. Pieces must still * be perfectly column aligned. * * Returns 0 if the piece would fall out of bounds or if some solid part of * the piece would fall over something already on the the grid. Returns 1 * otherwise (it is then safe to call paste_on_board()). *********************************************************************PROTO*/ int valid_screen_position(play_piece *pp, int blockWidth, Grid *g, int rot, int screen_x, int screen_y) { int row, row2, col; screen_to_grid_coords(g, blockWidth, screen_x, screen_y, &row, &col); if (!valid_position(pp, col, row, rot, g)) return 0; screen_to_grid_coords(g, blockWidth, screen_x, screen_y+19, &row2, &col); if (row == row2) return 1; /* no need to recheck, you were aligned */ else return valid_position(pp, col, row2, rot, g); } /*************************************************************************** * tetris_event() * Do some work associated with a collision. Needs the color style to draw * the grid. *************************************************************************/ int tetris_event(int *delay, int count, SDL_Surface * screen, piece_style *ps, color_style *cs, sound_style *ss, Grid g[], int level, int fall_event_interval, int sock, int draw, int *blank, int *garbage, int P) { if (count == 1) { /* determine if anything happened, sounds */ int i; State[P].check_result = check_tetris(g); State[P].num_lines_cleared += State[P].check_result; if (State[P].check_result >= 3) play_sound(ss,SOUND_CLEAR4,256); else for (i=0;itemp,g->fall,g->w*g->h*sizeof(*(g->temp))); for (y=g->h-1;y>=0;y--) for (x=g->w-1;x>=0;x--) FALL_SET(*g,x,y,UNKNOWN); run_gravity(&g[0]); memset(g->changed,0,g->h*g->w*sizeof(*(g->changed))); for (y=g->h-1;y>=0;y--) for (x=g->w-1;x>=0;x--) if (TEMP_CONTENT(*g,x,y)!=FALL_CONTENT(*g,x,y)){ GRID_CHANGED(*g,x,y) = 1; if (x > 0) GRID_CHANGED(*g,x-1,y) = 1; if (y > 0) GRID_CHANGED(*g,x,y-1) = 1; if (x < g->w-1) GRID_CHANGED(*g,x+1,y) = 1; if (y < g->h-1) GRID_CHANGED(*g,x,y+1) = 1; } /* * check: did FALL_CONTENT change? */ draw_grid(screen,cs,&g[0],draw); if (determine_falling(&g[0])) { *delay = 1; return 3; } else { Score[P] += State[P].num_lines_cleared * State[P].num_lines_cleared * level; if (sock) { char msg = 's'; /* WRW: send update */ send(sock,&msg,1,0); send(sock,(char *)&Score[P],sizeof(Score[P]),0); if (State[P].num_lines_cleared >= 5) { char msg = 'g'; /* WRW: send garbage! */ send(sock,&msg,1,0); State[P].num_lines_cleared -= 4; /* might possibly also blank! */ } if (State[P].num_lines_cleared >= 3) { int i; for (i=3;i<=State[P].num_lines_cleared;i++) { char msg = 'b'; /* WRW: send blanking! */ send(sock,&msg,1,0); } } } else { if (State[P].num_lines_cleared >= 5) { *garbage = 1; State[P].num_lines_cleared -= 4; } if (State[P].num_lines_cleared >= 3) { *blank = (State[P].num_lines_cleared - 2); } } draw_score(screen,P); State[P].num_lines_cleared = 0; return 0; } } else if (count >= 3 && count <= 22) { if (draw) draw_falling(screen, cs->w , &g[0], count - 2); /* *delay = max(fall_event_interval / 5,4); */ *delay = 4; return count + 1; } else if (count == 23) { fall_down(g); draw_grid(screen,cs,g,draw); if (run_gravity(g)) play_sound(ss,SOUND_THUD,0); /* *delay = max(fall_event_interval / 5,4); */ *delay = 4; if (determine_falling(&g[0])) return 3; if (check_tetris(g)) return 1; else /* cannot be 0: we must redraw without falling */ return 2; } return 0; /* not done yet */ } /*************************************************************************** * do_blank() * Blank the visual screen of the given (local) player. ***************************************************************************/ static void do_blank(SDL_Surface *screen, sound_style *ss[2], Grid g[], int P) { play_sound(ss[P],SOUND_GARBAGE1,1); if (State[P].draw) { State[P].next_draw = SDL_GetTicks() + 1000; State[P].draw_timeout = 1000; SDL_FillRect(screen, &g[P].board, SDL_MapRGB(screen->format,32,32,32)); SDL_UpdateSafe(screen, 1, &g[P].board); } else { State[P].next_draw += 1000; State[P].draw_timeout += 1000; } { int i,j; for (j=0;j=g->w || y>=g->h)) return; GRID_SET(*g,x,y,REMOVE_ME); } static int most_common = 1; /*************************************************************************** * colorkill_fun() * Function for the repainting special piece. ***************************************************************************/ static void colorkill_recurse(int x, int y, Grid *g, int target_color) { if ((x<0 || y<0 || x>=g->w || y>=g->h)) return; GRID_CHANGED(*g,x,y) = 1; if (GRID_CONTENT(*g,x,y) != target_color) return; GRID_SET(*g,x,y,REMOVE_ME); colorkill_recurse(x - 1, y, g, target_color); colorkill_recurse(x + 1, y, g, target_color); colorkill_recurse(x, y - 1, g, target_color); colorkill_recurse(x, y + 1, g, target_color); } static void colorkill_fun(int x, int y, Grid *g) { int c; if ((x<0 || y<0 || x>=g->w || y>=g->h)) return; GRID_CHANGED(*g,x,y) = 1; c = GRID_CONTENT(*g,x,y); if (c <= 1 || c == REMOVE_ME) return; GRID_SET(*g,x,y,REMOVE_ME); colorkill_recurse(x - 1, y, g, c); colorkill_recurse(x + 1, y, g, c); colorkill_recurse(x, y - 1, g, c); colorkill_recurse(x, y + 1, g, c); } /*************************************************************************** * repaint_fun() * Function for the repainting special piece. ***************************************************************************/ static void repaint_fun(int x, int y, Grid *g) { int c; if ((x<0 || y<0 || x>=g->w || y>=g->h)) return; GRID_CHANGED(*g,x,y) = 1; c = GRID_CONTENT(*g,x,y); if (c <= 1 || c == most_common) return; GRID_SET(*g,x,y,most_common); repaint_fun(x - 1, y, g); repaint_fun(x + 1, y, g); repaint_fun(x, y - 1, g); repaint_fun(x, y + 1, g); } /*************************************************************************** * push_down() * Teleport all of the pieces just below this special piece as far down as * they can go in their column. ***************************************************************************/ static void push_down(play_piece *pp, int col, int row, int rot, Grid *g, void (*fun)(int, int, Grid *)) { int i,j,c; int place_y, look_y; for (j=0;jbase->dim;j++) for (i=0;ibase->dim;i++) if ((c=BITMAP(*pp->base,rot,i,j))) { int t_x = i + col; int t_y = j + row; if ((t_x<0 || t_y<0 || t_x>=g->w || t_y>=g->h)) { continue; } GRID_SET(*g,t_x,t_y,REMOVE_ME); look_y = t_y + 1; if (look_y >= g->h) continue; if (!GRID_CONTENT(*g,t_x,look_y)) continue; /* OK, try to move look_y down as far as possible */ for (place_y = g->h-1; place_y > look_y && GRID_CONTENT(*g,t_x,place_y) != 0; place_y --) ; if (place_y == look_y) continue; /* otherwise, valid swap! */ if (place_y < 0 || place_y >= g->h) continue; if (look_y < 0 || look_y >= g->h) continue; GRID_SET(*g,t_x,place_y, GRID_CONTENT(*g,t_x,look_y)); GRID_SET(*g,t_x,look_y, REMOVE_ME); } } /*************************************************************************** * find_on_board() * Finds where on the board a piece would go: used by special piece * handling routines. ***************************************************************************/ static void find_on_board(play_piece *pp, int col, int row, int rot, Grid *g, void (*fun)(int, int, Grid *)) { int i,j,c; for (j=0;jbase->dim;j++) for (i=0;ibase->dim;i++) if ((c=BITMAP(*pp->base,rot,i,j))) { int t_x = i + col; int t_y = j + row; if ((t_x<0 || t_y<0 || t_x>=g->w || t_y>=g->h)) { continue; } GRID_SET(*g,t_x,t_y,REMOVE_ME); } for (j=0;jbase->dim;j++) for (i=0;ibase->dim;i++) if ((c=BITMAP(*pp->base,rot,i,j))) { int t_x = i + col; int t_y = j + row; if ((t_x<0 || t_y<0 || t_x>=g->w || t_y>=g->h)) { continue; } fun(t_x - 1, t_y, g); fun(t_x + 1, t_y, g); fun(t_x, t_y - 1, g); fun(t_x, t_y + 1, g); } return; } /*************************************************************************** * most_common_color() * Finds the most common (non-zero, non-garbage) color on the board. If * none, returns the garbage color. ***************************************************************************/ static void most_common_color(Grid *g) { int count[256]; int x,y,c; int max, max_count; memset(count,0, sizeof(count)); for (x=0;xw;x++) for (y=0;yh;y++) { c = GRID_CONTENT(*g,x,y); if (c > 1) count[c]++; } max = 1; max_count = 0; for (x=2;x<256;x++) if (count[x] > max_count) { max = x; max_count = count[x]; } most_common = max; return; } /*************************************************************************** * handle_special() * Change the state of the grid based on the magical special piece ... * Look in pos[P] for the current position. *********************************************************************PROTO*/ void handle_special(play_piece *pp, int row, int col, int rot, Grid *g, sound_style *ss) { switch (pp->special) { case No_Special: break; case Special_Bomb: find_on_board(pp, col, row, rot, g, bomb_fun); if (ss) play_sound(ss,SOUND_CLEAR1,256); break; case Special_Repaint: most_common_color(g); find_on_board(pp, col, row, rot, g, repaint_fun); if (ss) play_sound(ss,SOUND_GARBAGE1,256); break; case Special_Pushdown: push_down(pp, col, row, rot, g, repaint_fun); if (ss) play_sound(ss,SOUND_THUD,256*2); break; case Special_Colorkill: find_on_board(pp, col, row, rot, g, colorkill_fun); if (ss) play_sound(ss,SOUND_CLEAR1,256); break; } } /*************************************************************************** * do_pause() * Change the pause status of the local player. ***************************************************************************/ static void do_pause(int paused, Uint32 tv_now, int *pause_begin_time, int *tv_start) { int i; draw_pause(paused); if (!paused) { /* fixup times */ *tv_start += (tv_now - *pause_begin_time); for (i=0; i<2; i++) { State[i].collide_time += (tv_now - *pause_begin_time); State[i].next_draw += (tv_now - *pause_begin_time); State[i].tv_next_fall += (tv_now - *pause_begin_time); State[i].tv_next_tetris += (tv_now - *pause_begin_time); State[i].tv_next_ai_think += (tv_now - *pause_begin_time); State[i].tv_next_ai_move += (tv_now - *pause_begin_time); } } else { *pause_begin_time = tv_now; } } /*************************************************************************** * place_this_piece() * Given that the player's current piece structure is already chosen, try * to place it near the top of the board. this may involve shifting it up * a bit or rotating it or something. * * Returns 0 on success. ***************************************************************************/ static int place_this_piece(int P, int blockWidth, Grid g[]) { int Y, R; /* we'll try Y adjustments from -2 to 0 and rotations from 0 to 3 */ pos[P].x = pos[P].old_x = g[P].board.x + g[P].board.w / 2; for (Y = 0; Y >= -2 ; Y --) { for (R = 0; R <= 3; R++) { pos[P].y = pos[P].old_y = g[P].board.y + (blockWidth * Y); pos[P].old_rot = pos[P].rot = R; if (valid_screen_position(&State[P].cp, blockWidth, &g[P], pos[P].rot, pos[P].x, pos[P].y)) { return 0; } } } return 1; /* no valid position! */ } /*************************************************************************** * event_loop() * The main event-processing dispatch loop. * * Returns 0 on a successful game completion, -1 on a [single-user] quit. *********************************************************************PROTO*/ #define NO_PLAYER 0 #define HUMAN_PLAYER 1 #define AI_PLAYER 2 #define NETWORK_PLAYER 3 int event_loop(SDL_Surface *screen, piece_style *ps, color_style *cs[2], sound_style *ss[2], Grid g[], int level[2], int sock, int *seconds_remaining, int time_is_hard_limit, int adjust[], int (*handle)(const SDL_Event *), int seed, int p1, int p2, AI_Player *AI[2]) { SDL_Event event; Uint32 tv_now, tv_start; int NUM_PLAYER = 0; int NUM_KEYBOARD = 0; int last_seconds = -1; int minimum_fall_event_interval = 100; int paused = 0; Uint32 pause_begin_time = 0; /* measured in milliseconds, 25 frames per second: * 1 frame = 40 milliseconds */ int blockWidth = cs[0]->w; int i,j,P, Q; SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY/Options.key_repeat_delay, SDL_DEFAULT_REPEAT_INTERVAL/2); if (gametype != DEMO) stop_all_playing(); memset(pos, 0, sizeof(pos[0]) * 2); memset(State, 0, sizeof(State[0]) * 2); switch (p1) { case NO_PLAYER: Assert(!handle); break; case HUMAN_PLAYER: Assert(!handle); NUM_PLAYER++; NUM_KEYBOARD++; break; case AI_PLAYER: State[0].ai = 1; NUM_PLAYER++; break; case NETWORK_PLAYER: PANIC("Cannot have player 1 over the network!"); } switch (p2) { case NO_PLAYER: break; case HUMAN_PLAYER: Assert(!handle); NUM_PLAYER++; NUM_KEYBOARD++; break; case AI_PLAYER: State[1].ai = 1; NUM_PLAYER++; break; case NETWORK_PLAYER: Assert(sock); break; } Assert(NUM_PLAYER >= 1 && NUM_PLAYER <= 2); tv_start = tv_now = SDL_GetTicks(); tv_start += *seconds_remaining * 1000; for (P=0; Pdelay_factor == 0) { State[P].ai_interval = State[P].fall_event_interval; if (State[P].ai_interval > 15) State[P].ai_interval = 15; } else { if (AI[P]->delay_factor < 1) AI[P]->delay_factor = 1; if (AI[P]->delay_factor > 100) AI[P]->delay_factor = 100; State[P].ai_interval = AI[P]->delay_factor; } State[P].ai_state = AI[P]->reset(State[P].ai_state, &g[P]); } } /* generate the fake-out grid: shown when the opponent does something * good! */ distract_grid[0] = generate_board(g[0].w,g[0].h,g[0].h-2); distract_grid[0].board = g[0].board; SeedRandom(seed); for (i=0;inum_color)); } if (NUM_PLAYER == 2) { distract_grid[1] = generate_board(g[1].w,g[1].h,g[1].h-2); distract_grid[1].board = g[1].board; for (i=0;inum_color)); } } if (sock) { char msg = 'c'; /* WRW: send update */ send(sock,&msg,1,0); send(sock,g[0].contents,sizeof(*g[0].contents) * g[0].h * g[0].w,0); } draw_clock(0); draw_grid(screen,cs[0],&g[0],1); draw_score(screen, 0); if (NUM_PLAYER == 2) { draw_grid(screen,cs[1],&g[1],1); draw_score(screen,1); } if (sock) draw_score(screen, 1); /* * Major State-Machine Event Loop */ P = 0; while (1) { if (NUM_PLAYER == 2) P = !P; tv_now = SDL_GetTicks(); /* update the on-screen clock */ if (tv_start >= tv_now) * seconds_remaining = (tv_start - tv_now) / 1000; else * seconds_remaining = - ((tv_now - tv_start) / 1000); if (*seconds_remaining != last_seconds && !paused) { last_seconds = *seconds_remaining; draw_clock(*seconds_remaining); if (last_seconds <= 30 && last_seconds >= 0) { play_sound_unless_already_playing(ss[0],SOUND_CLOCK,0); if (NUM_PLAYER == 2) play_sound_unless_already_playing(ss[1],SOUND_CLOCK,0); } } /* check for time-out */ if (*seconds_remaining < 0 && time_is_hard_limit && !paused) { play_sound(ss[0],SOUND_LEVELDOWN,0); adjust[0] = ADJUST_DOWN; if (NUM_PLAYER == 2) { play_sound(ss[1],SOUND_LEVELDOWN,0); adjust[1] = ADJUST_DOWN; } stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return 0; } /* * Visual Events */ if (!State[P].draw && tv_now > State[P].next_draw && !paused) { int i,j; State[P].draw = 1; for (i=0;i= State[P].tv_next_fall && !paused) { int try; int we_fell = 0; #if DEBUG if (tv_now > State[P].tv_next_fall) Debug("Fall: %d %d\n", tv_now, tv_now - State[P].tv_next_fall); #endif /* ok, we had a falling event */ do { State[P].tv_next_fall += State[P].fall_event_interval; } while (State[P].tv_next_fall <= tv_now); for (try = State[P].fall_speed; try > 0; try--) if (valid_screen_position(&State[P].cp,blockWidth,&g[P],pos[P].rot,pos[P].x,pos[P].y+try)) { pos[P].y += try; State[P].fall_speed = try; try = 0; we_fell = 1; } if (!we_fell) { if (!State[P].collide_time) { State[P].collide_time = tv_now + (Options.long_settle_delay ? 400 : 200); continue; /* don't fall */ } if (tv_now < State[P].collide_time) continue; /* don't fall */ /* do fall! */ } else continue; /* collided! */ State[P].collide_time = 0; play_sound(ss[P],SOUND_THUD,0); /* this would only come into play if you were halfway * between levels and suddenly someone added garbage */ while (!valid_screen_position(&State[P].cp,blockWidth,&g[P],pos[P].rot,pos[P].x,pos[P].y) && pos[P].y > 0) pos[P].y--; if (State[P].draw) draw_play_piece(screen, cs[P], &State[P].cp, pos[P].old_x, pos[P].old_y, pos[P].old_rot, &State[P].cp, pos[P].x, pos[P].y, pos[P].rot); if (State[P].cp.special != No_Special) { /* handle special powers! */ int row, col; screen_to_exact_grid_coords(&g[P], blockWidth, pos[P].x, pos[P].y, &row, &col); handle_special(&State[P].cp, row, col, pos[P].rot, &g[P], ss[P]); } else { /* paste the piece on the board */ int row, col; screen_to_exact_grid_coords(&g[P], blockWidth, pos[P].x, pos[P].y, &row, &col); paste_on_board(&State[P].cp, col, row, pos[P].rot, &g[P]); } if (sock) { char msg = 'c'; /* WRW: send update */ send(sock,&msg,1,0); send(sock,g[P].contents,sizeof(*g[P].contents) * g[P].h * g[P].w,0); } draw_grid(screen,cs[P],&g[P],State[P].draw); /* state change */ State[P].falling = 0; State[P].fall_speed = 0; State[P].tetris_handling = 1; State[P].accept_input = 0; State[P].tv_next_tetris = tv_now; } /* * Tetris Clear Events */ if (State[P].tetris_handling != 0 && tv_now >= State[P].tv_next_tetris && !paused) { int blank = 0, garbage = 0; #if DEBUG if (tv_now >= State[P].tv_next_tetris) Debug("Tetr: %d %d (%d)\n", tv_now, tv_now - State[P].tv_next_tetris, State[P].tetris_handling); #endif State[P].tetris_handling = tetris_event( &State[P].tetris_event_interval, State[P].tetris_handling, screen, ps, cs[P], ss[P], &g[P], level[P], minimum_fall_event_interval, sock, State[P].draw, &blank, &garbage, P); if (NUM_PLAYER == 2) { if (blank) do_blank(screen, ss, g, !P); if (garbage) { add_garbage(&g[!P]); play_sound(ss[!P],SOUND_GARBAGE1,1); draw_grid(screen,cs[!P],&g[!P],State[!P].draw); } } tv_now = SDL_GetTicks(); do { /* just in case we're *way* behind */ State[P].tv_next_tetris += State[P].tetris_event_interval; } while (State[P].tv_next_tetris < tv_now); if (State[P].tetris_handling == 0) { /* state change */ /* Time for your next piece ... * Yujia points out that we should try a little harder to * fit your piece on the board. */ State[P].cp = State[P].np; State[P].np = generate_piece(ps, cs[P], State[P].seed++); draw_next_piece(screen, ps, cs[P], &State[P].cp, &State[P].np, P); if (place_this_piece(P, blockWidth, g)) { /* failed to place piece */ you_lose: play_sound(ss[P],SOUND_LEVELDOWN,0); adjust[P] = ADJUST_DOWN; if (NUM_PLAYER == 2 && !sock) adjust[!P] = ADJUST_SAME; if (sock == 0) { stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return 0; } /* Debug("Entering Limbo: adjust down.\n"); */ State[P].falling = 0; State[P].fall_speed = 0; State[P].tetris_handling = 0; State[P].accept_input = 0; State[P].limbo = 1; } else { int x,y,count = 0; if (State[P].ai) State[P].ai_state = AI[P]->reset(State[P].ai_state, &g[P]); for (y=0;yh;y++) for (x=0;xw;x++) if (GRID_CONTENT(g[P],x,y) == 1) count++; if (count == 0) { you_win: play_sound(ss[P],SOUND_LEVELUP,256); if (*seconds_remaining <= 0) { adjust[P] = ADJUST_SAME; if (NUM_PLAYER == 2 && !sock) adjust[!P] = ADJUST_DOWN; } else { adjust[P] = ADJUST_UP; if (NUM_PLAYER == 2 && !sock) adjust[!P] = ADJUST_SAME; } if (sock == 0) { stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return 0; } /* Debug("Entering Limbo: you win, adjust ?/?.\n"); */ State[P].falling = 0; State[P].fall_speed = 0; State[P].tetris_handling = 0; State[P].accept_input = 0; State[P].limbo = 1; } else { /* keep playing */ State[P].falling = 1; State[P].fall_speed = 1; State[P].tetris_handling = 0; State[P].accept_input = 1; State[P].tv_next_fall = SDL_GetTicks() + State[P].fall_event_interval; } } } } /* end: handle tetris events */ /* * AI Events */ if (State[P].ai && tv_now >= State[P].tv_next_ai_think && !paused) { int row, col; #ifdef AI_THINK_TIME Uint32 tv_before = SDL_GetTicks(); #endif screen_to_grid_coords(&g[P], blockWidth, pos[P].x, pos[P].y, &row, &col); /* simulate blanked screens */ if (State[P].draw) AI[P]->think(State[P].ai_state, &g[P], &State[P].cp, &State[P].np, col, row, pos[P].rot); #ifdef AI_THINK_TIME tv_now = SDL_GetTicks(); if (tv_now > tv_before + 1) Debug("AI[%s] took too long in think() [%d ticks].\n", AI[P]->name, tv_now - tv_before); #endif do { /* just in case we're *way* behind */ State[P].tv_next_ai_think += State[P].ai_interval; } while (State[P].tv_next_ai_think < tv_now); } if (State[P].ai && State[P].accept_input && tv_now >= State[P].tv_next_ai_move && !paused) { int row, col; #ifdef AI_THINK_TIME Uint32 tv_before = SDL_GetTicks(); #endif screen_to_grid_coords(&g[P], blockWidth, pos[P].x, pos[P].y, &row, &col); pos[P].move = AI[P]->move(State[P].ai_state, &g[P], &State[P].cp, &State[P].np, col, row, pos[P].rot); #ifdef AI_THINK_TIME tv_now = SDL_GetTicks(); if (tv_now > tv_before + 1) Debug("AI[%s] took too long in move() [%d ticks].\n", AI[P]->name, tv_now - tv_before); #endif do { /* just in case we're *way* behind */ State[P].tv_next_ai_move += State[P].ai_interval * 5; } while (State[P].tv_next_ai_move < tv_now); } /* * User Interface Events */ if (SDL_PollEvent(&event)) { /* special menu handling! */ if (handle) { if (handle(&event)) { return -1; } } else switch (event.type) { case SDL_KEYUP: /* "down" will not affect you again until you release * the down key and press it again */ if (event.key.keysym.sym == SDLK_DOWN) { State[1].ready_for_fast = 1; if (NUM_KEYBOARD == 1) State[0].ready_for_fast = 1; } else if (event.key.keysym.sym == SDLK_UP) { State[1].ready_for_rotate = 1; if (NUM_KEYBOARD == 1) State[0].ready_for_rotate = 1; } else if (event.key.keysym.sym == SDLK_w) State[0].ready_for_rotate = 1; else if (event.key.keysym.sym == SDLK_s) State[0].ready_for_fast = 1; else if (event.key.keysym.sym == SDLK_1) { P = 0; goto you_win; } else if (event.key.keysym.sym == SDLK_2) { P = 0; goto you_lose; } else if (event.key.keysym.sym == SDLK_3) { P = 1; goto you_win; } else if (event.key.keysym.sym == SDLK_4) { P = 1; goto you_lose; } else if (event.key.keysym.sym == SDLK_p && gametype != DEMO) { /* Pause it! */ tv_now = SDL_GetTicks(); paused = !paused; if (sock) { char msg = 'p'; /* WRW: send pause update */ send(sock,&msg,1,0); } do_pause(paused, tv_now, &pause_begin_time, &tv_start); } break; case SDL_KEYDOWN: { int ks = event.key.keysym.sym; Q = -1; /* keys for P=0 */ if (ks == SDLK_UP || ks == SDLK_DOWN || ks == SDLK_RIGHT || ks == SDLK_LEFT) { Q = 1; } else if (ks == SDLK_w || ks == SDLK_s || ks == SDLK_a || ks == SDLK_d) { Q = 0; } else if (ks == SDLK_q) { if (sock == 0) { adjust[0] = -1; if (NUM_PLAYER == 2) adjust[1] = -1; stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return -1; } else { /* Debug("Entering Limbo: adjust down.\n"); */ State[0].falling = 0; State[0].fall_speed = 0; State[0].tetris_handling = 0; State[0].accept_input = 0; State[0].limbo = 1; adjust[0] = ADJUST_DOWN; } } else if ((ks == SDLK_RETURN) && ((event.key.keysym.mod & KMOD_LCTRL) || (event.key.keysym.mod & KMOD_RCTRL))) { SDL_WM_ToggleFullScreen(screen); break; } else break; if (NUM_KEYBOARD == 1) Q = 0; else if (NUM_KEYBOARD < 1) break; /* humans cannot modify AI moves! */ Assert(Q == 0 || Q == 1); if (event.key.keysym.sym != SDLK_DOWN && event.key.keysym.sym != SDLK_s) State[Q].fall_speed = 1; if (!State[Q].accept_input) { break; } /* only if we are accepting input */ switch (event.key.keysym.sym) { case SDLK_UP: case SDLK_w: if (State[Q].ready_for_rotate) pos[Q].move = MOVE_ROTATE; break; case SDLK_DOWN: case SDLK_s: if (State[Q].ready_for_fast) pos[Q].move = MOVE_DOWN; break; case SDLK_LEFT: case SDLK_a: pos[Q].move = MOVE_LEFT; break; case SDLK_RIGHT: case SDLK_d: pos[Q].move = MOVE_RIGHT; break; default: PANIC("unknown keypress"); } } break; case SDL_QUIT: Debug("Window-manager exit request.\n"); adjust[0] = -1; if (NUM_PLAYER == 2) adjust[1] = -1; stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return -1; case SDL_SYSWMEVENT: break; } /* end: switch (event.type) */ } /* * Handle Movement */ for (Q=0;Q 0) { char msg; if (recv(sock,&msg,1,0) != 1) { Debug("WARNING: Other player has left?\n"); close(sock); sock = 0; retval = 0; } else { switch (msg) { case 'b': do_blank(screen, ss, g, P); break; case 'p': paused = !paused; tv_now = SDL_GetTicks(); do_pause(paused, tv_now, &pause_begin_time, &tv_start); break; case ADJUST_DOWN: /* other play in limbo */ case ADJUST_SAME: case ADJUST_UP: State[P].other_in_limbo = 1; adjust[!P] = msg; break; case 'g': add_garbage(&g[P]); play_sound(ss[P],SOUND_GARBAGE1,1); draw_grid(screen,cs[P],&g[P],State[P].draw); break; case 's': recv(sock,(char *)&Score[1], sizeof(Score[1]),0); draw_score(screen, 1); break; case 'c': { int i,j; memcpy(g[!P].temp,g[!P].contents, sizeof(*g[0].temp) * g[!P].w * g[!P].h); recv(sock,g[!P].contents, sizeof(*g[!P].contents) * g[!P].w * g[!P].h,0); for (i=0;i 0) GRID_CHANGED(g[!P],i-1,j) = 1; if (j > 0) GRID_CHANGED(g[!P],i,j-1) = 1; if (i < g[!P].w-1) GRID_CHANGED(g[!P],i+1,j) = 1; if (i < g[!P].h-1) GRID_CHANGED(g[!P],i,j+1) = 1; if (GRID_CONTENT(g[!P],i,j) == 0) GRID_SET(g[!P],i,j,REMOVE_ME); } draw_grid(screen,cs[!P],&g[!P],1); } break; default: break; } } } } while (retval > 0 && !(State[P].limbo && State[P].other_in_limbo)); /* limbo handling */ if (State[P].limbo && State[P].other_in_limbo) { Assert(adjust[0] != -1 && adjust[1] != -1); stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return 0; } else if (State[P].limbo && !State[P].other_in_limbo && !State[P].limbo_sent) { char msg = adjust[P]; /* WRW: send update */ State[P].limbo_sent = 1; send(sock,&msg,1,0); } else if (!State[P].limbo && State[P].other_in_limbo) { char msg; /* WRW: send update */ /* hmm, other guy is done ... */ if (adjust[!P] == ADJUST_UP || adjust[!P] == ADJUST_SAME) { if (*seconds_remaining > 0) adjust[P] = ADJUST_SAME; else adjust[P] = ADJUST_DOWN; } else if (adjust[!P] == ADJUST_DOWN) { adjust[P] = ADJUST_SAME; } /* Debug("Entering Limbo: adjust same/down.\n"); */ State[P].falling = 0; State[P].fall_speed = 0; State[P].tetris_handling = 0; State[P].accept_input = 0; State[P].limbo = 1; State[P].limbo_sent = 1; msg = adjust[P]; send(sock,&msg,1,0); stop_playing_sound(ss[0],SOUND_CLOCK); if (NUM_PLAYER == 2) stop_playing_sound(ss[1],SOUND_CLOCK); return 0; } } if (paused) { atris_run_flame(); } tv_now = SDL_GetTicks(); { Uint32 least = State[0].tv_next_fall; if (State[0].tetris_handling && State[0].tv_next_tetris < least) least = State[0].tv_next_tetris; if (State[0].ai && State[0].tv_next_ai_think < least) least = State[0].tv_next_ai_think; if (State[0].ai && State[0].tv_next_ai_move < least) least = State[0].tv_next_ai_move; if (NUM_PLAYER == 2) { if (State[1].tv_next_fall < least) least = State[1].tv_next_tetris; if (State[1].tetris_handling && State[1].tv_next_tetris < least) least = State[1].tv_next_tetris; if (State[1].ai && State[1].tv_next_ai_think < least) least = State[1].tv_next_ai_think; if (State[1].ai && State[1].tv_next_ai_move < least) least = State[1].tv_next_ai_move; } if (least >= tv_now + 4 && !SDL_PollEvent(NULL)) { /* hey, we could sleep for two ... */ if (State[0].ai || State[1].ai) { SDL_Delay(1); if (State[0].ai && State[0].draw) { int row, col; screen_to_grid_coords(&g[0], blockWidth, pos[0].x, pos[0].y, &row, &col); AI[0]->think(State[0].ai_state, &g[0], &State[0].cp, &State[0].np, col, row, pos[0].rot); } else SDL_Delay(1); if (State[1].ai && State[1].draw) { int row, col; screen_to_grid_coords(&g[1], blockWidth, pos[1].x, pos[1].y, &row, &col); AI[1]->think(State[1].ai_state, &g[1], &State[1].cp, &State[1].np, col, row, pos[1].rot); } else SDL_Delay(1); } else SDL_Delay(2); } else if (least > tv_now && !SDL_PollEvent(NULL)) SDL_Delay(least - tv_now); } } } /* * $Log: event.c,v $ * Revision 1.61 2001/01/05 21:12:32 weimer * advance to atris 1.0.5, add support for ".atrisrc" and changing the * keyboard repeat rate * * Revision 1.60 2000/11/10 18:16:48 weimer * changes for Atris 1.0.4 - three new special options * * Revision 1.59 2000/11/06 04:39:56 weimer * networking consistency check for power pieces * * Revision 1.58 2000/11/06 04:06:44 weimer * option menu * * Revision 1.57 2000/11/06 01:25:54 weimer * add in the other special piece * * Revision 1.56 2000/11/06 00:51:55 weimer * fixed the paint thing so that it actually paints * * Revision 1.55 2000/11/06 00:24:01 weimer * add WalkRadioGroup modifications (inactive field for Kiri) and some support * for special pieces * * Revision 1.54 2000/11/03 04:25:58 weimer * add some optimizations to run_gravity to make it just a bit faster (down * to 0.01 ms/call from 0.02), sleep a bit more in event-loop: generally * trying to make us more CPU friendly ... * * Revision 1.53 2000/11/02 03:06:20 weimer * better interface for walk-radio menus: we are now ready for Kiri to change * them to add run-time options ... * * Revision 1.52 2000/11/01 17:55:33 weimer * remove ambiguous references to "you win" in debugging output * * Revision 1.51 2000/11/01 04:39:41 weimer * clear the little scoring spot correctly, updates for making a no-sound * distribution * * Revision 1.50 2000/11/01 03:53:06 weimer * modifications for version 1.0.1: you can pick your starting level, you can * pick the AI difficulty factor, the game is better about placing new pieces * when there is garbage, when things fall out of your control they now fall * at a uniform rate ... * * Revision 1.49 2000/10/30 16:57:24 weimer * minor doc fixups * * Revision 1.48 2000/10/30 16:25:25 weimer * display the network player score during network games. Also give the * non-server a little message when waiting for the server to go on. * * Revision 1.47 2000/10/29 22:55:01 weimer * networking consistency checks (you must have the same number of doodads): * special hotkey 'f' in main menu toggles full screen mode * added depth specification on the command line * automatically search for the darkest non-black color ... * * Revision 1.46 2000/10/29 21:23:28 weimer * One last round of header-file changes to reflect my newest and greatest * knowledge of autoconf/automake. Now if you fail to have some bizarro * function, we try to go on anyway unless it is vastly needed. * * Revision 1.45 2000/10/29 19:04:32 weimer * minor highscore handling changes: new filename, use the draw_string() and * draw_bordered_rect() and input_string() interfaces, handle the widget layer * and the flame layer, etc. Also fix a minor bug where you would be prevented * from settling if you pressed a key even if it didn't really move you. :-) * * Revision 1.44 2000/10/29 17:23:13 weimer * incorporate "xflame" flaming background for added spiffiness ... * * Revision 1.43 2000/10/29 00:17:39 weimer * added support for a system independent random number generator * * Revision 1.42 2000/10/28 13:39:14 weimer * added a pausing feature ... * * Revision 1.41 2000/10/27 00:07:36 weimer * some sound fixes ... * * Revision 1.40 2000/10/26 22:23:07 weimer * double the effective number of levels * * Revision 1.39 2000/10/22 22:05:51 weimer * Added a few new sounds ... * * Revision 1.38 2000/10/21 01:14:42 weimer * massic autoconf/automake restructure ... * * Revision 1.37 2000/10/20 01:32:09 weimer * Minor play issue problems -- time is now truly a hard limit! * * Revision 1.36 2000/10/19 22:30:55 weimer * make pieces fall a bit faster * * Revision 1.35 2000/10/19 22:06:51 weimer * minor changes ... * * Revision 1.34 2000/10/19 00:20:27 weimer * sound directory changes, added a ticking clock ... * * Revision 1.33 2000/10/18 23:57:49 weimer * general fixup, color changes, display changes. * Notable: "Safe" Blits and Updates now perform "clipping". No more X errors, * we hope! * * Revision 1.32 2000/10/18 03:29:56 weimer * fixed problem with "machine-gun" sounds caused by "run_gravity()" returning * 1 too often * * Revision 1.31 2000/10/18 02:04:02 weimer * playability changes ... * * Revision 1.30 2000/10/14 16:17:41 weimer * level adjustment changes, added some new AIs, etc. * * Revision 1.29 2000/10/14 01:42:53 weimer * better scoring of thumbs-up, thumbs-down * * Revision 1.28 2000/10/14 01:24:34 weimer * fixed error with not advancing levels when fighting AI * * Revision 1.27 2000/10/14 00:58:32 weimer * fixed an unsigned arithmetic problem that was sleeping too often! * * Revision 1.26 2000/10/14 00:56:41 weimer * whoops, minor boolean typo! * * Revision 1.25 2000/10/14 00:42:27 weimer * fixed minor error where you couldn't move all the way to the left ... * * Revision 1.24 2000/10/13 22:34:26 weimer * minor wessy AI changes * * Revision 1.23 2000/10/13 18:23:28 weimer * fixed a race condition in tetris_event() * * Revision 1.22 2000/10/13 17:55:36 weimer * Added another color "Style" ... * * Revision 1.21 2000/10/13 16:37:39 weimer * Changed the AI so that it now passes state around via void pointers, rather * than using global variables. This allows the same AI to play itself. Also * changed the "AI vs. AI" display so that you can keep track of total wins * and losses. * * Revision 1.20 2000/10/13 15:41:53 weimer * revamped AI support, now you can pick your AI and have AI duels (such fun!) * the mighty Aliz AI still crashes a bit, though ... :-) * * Revision 1.19 2000/10/13 03:02:05 wkiri * Added Aliz (Kiri AI) to ai.c. * Note event.c currently calls Aliz, not Wessy AI. * * Revision 1.18 2000/10/12 22:21:25 weimer * display changes, more multi-local-play threading (e.g., myScore -> * Score[0]), that sort of thing ... * * Revision 1.17 2000/10/12 19:17:08 weimer * Further support for AI players and multiple game types. * * Revision 1.16 2000/10/12 00:49:08 weimer * added "AI" support and walking radio menus for initial game configuration * * Revision 1.15 2000/09/09 17:05:35 wkiri * Hideous log changes (Wes: how dare you include a comment character!) * * Revision 1.14 2000/09/09 16:58:27 weimer * Sweeping Change of Ultimate Mastery. Main loop restructuring to clean up * main(), isolate the behavior of the three game types. Move graphic files * into graphics/-, style files into styles/-, remove some unused files, * add game flow support (breaks between games, remembering your level within * this game), multiplayer support for the event loop, some global variable * cleanup. All that and a bag of chips! * * Revision 1.13 2000/09/05 22:10:46 weimer * fixed initial garbage generation strangeness * * Revision 1.12 2000/09/05 21:47:41 weimer * major timing revamping: it actually seems to work now! * * Revision 1.11 2000/09/05 20:22:12 weimer * native video mode selection, timing investigation * * Revision 1.10 2000/09/04 15:20:21 weimer * faster blitting algorithms for drawing the grid and drawing falling pieces * (when you eliminate a line) * * Revision 1.9 2000/09/04 14:18:09 weimer * flushed out the pattern color so that it has the same number of colors as * the default * * Revision 1.8 2000/09/03 20:58:22 weimer * yada * * Revision 1.7 2000/09/03 20:57:02 weimer * changes the time variable to be passed by reference in the event loop * * Revision 1.6 2000/09/03 20:34:42 weimer * only draw their score in multiplayer game * * Revision 1.5 2000/09/03 20:27:38 weimer * the event loop actually works! * * Revision 1.4 2000/09/03 19:59:56 weimer * wessy event-loop changes * * Revision 1.3 2000/09/03 18:44:36 wkiri * Cleaned up atris.c (see high_score_check()). * Added game type (defaults to MARATHON). * */