/* * Alizarin Tetris * Functions relating to AI players. * * Copyright 2000, Westley Weimer & Kiri Wagstaff */ #include #include "atris.h" #include "display.h" #include "identity.h" #include "grid.h" #include "piece.h" #include "sound.h" #include "ai.h" #include "menu.h" #include "event.pro" #include "identity.pro" #include "display.pro" /*********** Wes's globals ***************/ typedef struct wessy_struct { int know_what_to_do; int desired_column; int desired_rot; int cc; int current_rot; int best_weight; Grid tg; } Wessy_State; typedef struct double_struct { int know_what_to_do; int desired_col; int desired_rot; int best_weight; int stage_alpha; int cur_alpha_col; int cur_alpha_rot; Grid ag; int cur_beta_col; int cur_beta_rot; Grid tg; } Double_State; #define WES_MIN_COL -4 static int weight_board(Grid *g); /*************************************************************************** * The code here determines what the board would look like after all the * color-sliding and tetris-clearing that would happen if you pasted the * given piece (pp) onto the given scratch grid (tg) at location (x,y) and * rotation (current_rot). You are welcome to copy it. * * Returns -1 on failure or the number of lines cleared. ***************************************************************************/ int drop_piece_on_grid(Grid *g, play_piece *pp, int col, int row, int rot) { int should_we_loop = 0; int lines_cleared = 0; if (!valid_position(pp, col, row, rot, g)) return -1; while (valid_position(pp, col, row, rot, g)) row++; row--; /* back to last valid position */ /* *-*-*-* */ if (!valid_position(pp, col, row, rot, g)) return -1; if (pp->special != No_Special) { handle_special(pp, row, col, rot, g, NULL); cleanup_grid(g); } else paste_on_board(pp, col, row, rot, g); do { int x,y,l; should_we_loop = 0; if ((l = check_tetris(g))) { cleanup_grid(g); } lines_cleared += l; 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); if (determine_falling(g)) { do { fall_down(g); cleanup_grid(g); run_gravity(g); } while (determine_falling(g)); should_we_loop = 1; } } while (should_we_loop); /* * Simulation code ends here. * *-*-*-* */ return lines_cleared; } /*************************************************************************** * double_ai_reset() **************************************************************************/ static void * double_ai_reset(void *state, Grid *g) { Double_State *retval; /* something has changed (e.g., a new piece) */ if (state == NULL) { /* first time we've been called ... */ Calloc(retval, Double_State *, sizeof(Double_State)); } else retval = state; Assert(retval); retval->know_what_to_do=0; retval->desired_col = g->w / 2; retval->desired_rot = 0; retval->best_weight = 1<<30; retval->stage_alpha = 1; retval->cur_alpha_col = WES_MIN_COL; retval->cur_alpha_rot = 0; retval->cur_beta_col = WES_MIN_COL; retval->cur_beta_rot = 0; if (retval->tg.contents == NULL) retval->tg = generate_board(g->w, g->h, 0); if (retval->ag.contents == NULL) retval->ag = generate_board(g->w, g->h, 0); return retval; } /*************************************************************************** * ***************************************************************************/ static void double_ai_think(void *data, Grid *g, play_piece *pp, play_piece *np, int col, int row, int rot) { Double_State *ds = (Double_State *)data; Uint32 incoming_time = SDL_GetTicks(); Assert(ds); for (;SDL_GetTicks() == incoming_time;) { if (ds->know_what_to_do) return; if (ds->stage_alpha) { memcpy(ds->ag.contents, g->contents, (g->w * g->h * sizeof(ds->ag.contents[0]))); memcpy(ds->ag.fall, g->fall, (g->w * g->h * sizeof(ds->ag.fall[0]))); if ( drop_piece_on_grid(&ds->ag, pp, ds->cur_alpha_col, row, ds->cur_alpha_rot) != -1) { int weight; /* success */ ds->cur_beta_col = WES_MIN_COL; ds->cur_beta_rot = 0; ds->stage_alpha = 0; weight = weight_board(&ds->ag); if (weight <= 0) { ds->best_weight = weight; ds->desired_col = ds->cur_alpha_col; ds->desired_rot = ds->cur_alpha_rot; ds->know_what_to_do = 1; } } else { /* advance alpha */ if (++ds->cur_alpha_col == g->w) { ds->cur_alpha_col = WES_MIN_COL; if (++ds->cur_alpha_rot == 4) { ds->cur_alpha_rot = 0; ds->know_what_to_do = 1; } } } } else { /* stage beta */ int weight; memcpy(ds->tg.contents, ds->ag.contents, (ds->ag.w * ds->ag.h * sizeof(ds->ag.contents[0]))); memcpy(ds->tg.fall, ds->ag.fall, (ds->ag.w * ds->ag.h * sizeof(ds->ag.fall[0]))); if (drop_piece_on_grid(&ds->tg, np, ds->cur_beta_col, row, ds->cur_beta_rot) != -1) { /* success */ weight = 1+weight_board(&ds->tg); if (weight < ds->best_weight) { ds->best_weight = weight; ds->desired_col = ds->cur_alpha_col; ds->desired_rot = ds->cur_alpha_rot; } } if (++ds->cur_beta_col == g->w) { ds->cur_beta_col = WES_MIN_COL; if (++ds->cur_beta_rot == 2) { ds->cur_beta_rot = 0; ds->stage_alpha = 1; if (++ds->cur_alpha_col == g->w) { ds->cur_alpha_col = WES_MIN_COL; if (++ds->cur_alpha_rot == 4) { ds->cur_alpha_rot = 0; ds->know_what_to_do = 1; } } } } } /* endof: stage beta */ } /* for: iterations */ } /*************************************************************************** * double_ai_move() ***************************************************************************/ static Command double_ai_move(void *state, Grid *g, play_piece *pp, play_piece *np, int col, int row, int rot) { /* naively, there are ((g->w - pp->base->dim) * 4) choices */ /* determine how to get there ... */ Double_State *ws = (Double_State *) state; if (rot != ws->desired_rot) return MOVE_ROTATE; else if (col > ws->desired_col) return MOVE_LEFT; else if (col < ws->desired_col) return MOVE_RIGHT; else if (ws->know_what_to_do) { return MOVE_DOWN; } else return MOVE_NONE; } /*************************************************************************** * weight_board() * Determines the value the AI places on the given board configuration. * This is a Wes-specific function that is used to evaluate the result of * a possible AI choice. In the end, the choice with the best weight is * selected. ***************************************************************************/ static int weight_board(Grid *g) { int x,y; int w = 0; int holes = 0; int same_color = 0; int garbage = 0; /* favor the vast extremes ... */ int badness[10] = { 7, 9, 9, 9, 9, 9, 9, 9, 9, 7}; /* * Simple Heuristic: highly placed blocks are bad, as are "holes": * blank areas with blocks above them. */ for (x=0; xw; x++) { int possible_holes = 0; for (y=g->h-1; y>=0; y--) { int what; if ((what = GRID_CONTENT(*g,x,y))) { w += 2 * (g->h - y) * badness[x] / 3; if (possible_holes) { if (what != 1) holes += 3 * ((g->h - y)) * g->w * possible_holes; possible_holes = 0; } if (x > 1 && what) if (GRID_CONTENT(*g,x-1,y) == what) same_color++; if (what == 1) garbage++; } else { possible_holes++; } } } w += holes * 2; w += same_color * 4; if (garbage == 0) w = 0; /* you'll win! */ return w; } /*************************************************************************** * wes_ai_think() * Ruminates for the Wessy AI. * * This function is called every so (about every fall_event_interval) by * event_loop(). The AI is expected to think for < 1 "tick" (as in, * SDL_GetTicks()). * * Input: * Grid *g Your side of the board. The currently piece (the * you are trying to place) is not "stamped" on the * board yet. You may not modify this board, but you * may make copies of it in order to reason about it. * play_piece *pp This describes the piece you are currently trying * to place. You can use it in conjunction with * functions like "valid_position()" to see where * possible placements are. * play_piece *np This is the next piece you will be given. * int col,row The current grid coordinates of your (falling) * piece. * int rot The current rotation (0-3) of your (falling) piece. * * Output: * Nothing Later, "ai_move()" will be called. Return your * value (== your action) there. ***************************************************************************/ static void wes_ai_think(void *data, Grid *g, play_piece *pp, play_piece *np, int col, int row, int rot) { int weight; Wessy_State *ws = (Wessy_State *)data; Uint32 incoming_time = SDL_GetTicks(); Assert(ws); for (;SDL_GetTicks() == incoming_time;) { if (ws->know_what_to_do) return; memcpy(ws->tg.contents, g->contents, (g->w * g->h * sizeof(ws->tg.contents[0]))); memcpy(ws->tg.fall, g->fall, (g->w * g->h * sizeof(ws->tg.fall[0]))); /* what would happen if we dropped ourselves on cc, current_rot now? */ if (drop_piece_on_grid(&ws->tg, pp, ws->cc, row, ws->current_rot) != -1) { weight = weight_board(&ws->tg); if (weight < ws->best_weight) { ws->best_weight = weight; ws->desired_column = ws->cc; ws->desired_rot = ws->current_rot; if (weight == 0) ws->know_what_to_do = 1; } } if (++(ws->cc) == g->w) { ws->cc = 0; if (++(ws->current_rot) == 4) ws->know_what_to_do = 1; } } } /*************************************************************************** ***************************************************************************/ static void beginner_ai_think(void *data, Grid *g, play_piece *pp, play_piece *np, int col, int row, int rot) { int weight; Wessy_State *ws = (Wessy_State *)data; Assert(ws); if (ws->know_what_to_do || (SDL_GetTicks() & 3)) return; memcpy(ws->tg.contents, g->contents, (g->w * g->h * sizeof(ws->tg.contents[0]))); memcpy(ws->tg.fall, g->fall, (g->w * g->h * sizeof(ws->tg.fall[0]))); /* what would happen if we dropped ourselves on cc, current_rot now? */ if (drop_piece_on_grid(&ws->tg, pp, ws->cc, row, ws->current_rot) != -1) { weight = weight_board(&ws->tg); if (weight < ws->best_weight) { ws->best_weight = weight; ws->desired_column = ws->cc; ws->desired_rot = ws->current_rot; if (weight == 0) ws->know_what_to_do = 1; } } if (++(ws->cc) == g->w) { ws->cc = 0; if (++(ws->current_rot) == 4) ws->know_what_to_do = 1; } } /*************************************************************************** * wes_ai_reset() * This is a special function instructing you to that the something has * changed (for example, you dropped your piece and you will have a new one * soon) and that you should clear any state you have lying around. **************************************************************************/ static void * wes_ai_reset(void *state, Grid *g) { Wessy_State *retval; /* something has changed (e.g., a new piece) */ if (state == NULL) { /* first time we've been called ... */ Calloc(retval, Wessy_State *, sizeof(Wessy_State)); } else retval = state; Assert(retval); retval->know_what_to_do = 0; retval->cc = WES_MIN_COL; retval->current_rot = 0; retval->best_weight = 1<<30; retval->desired_column = g->w / 2; retval->desired_rot = 0; if (retval->tg.contents == NULL) retval->tg = generate_board(g->w, g->h, 0); return retval; } /*************************************************************************** * wes_ai_move() * Determines the AI's next move. All of the inputs are as for ai_think(). * This function should be virtually instantaneous (do long calculations in * ai_think()). Possible return values are: * MOVE_NONE Do nothing, fall normally. * MOVE_LEFT [Try to] Move one column to the left. * MOVE_RIGHT [Try to] Move one column to the right. * MOVE_ROTATE [Try to] Rotate the piece. * MOVE_DOWN Fall down really quickly. * * This is a special form instructing you to that the something has changed * (for example, you dropped your piece and you will have a new one soon) * and that you should clear any state you have lying around. **************************************************************************/ static Command wes_ai_move(void *state, Grid *g, play_piece *pp, play_piece *np, int col, int row, int rot) { /* naively, there are ((g->w - pp->base->dim) * 4) choices */ /* determine how to get there ... */ Wessy_State *ws = (Wessy_State *) state; if (rot != ws->desired_rot) return MOVE_ROTATE; else if (col > ws->desired_column) return MOVE_LEFT; else if (col < ws->desired_column) return MOVE_RIGHT; else if (ws->know_what_to_do) { return MOVE_DOWN; } else return MOVE_NONE; } /*****************************************************************/ /*************** Impermeable AI barrier **************************/ /*****************************************************************/ /*********** Kiri's globals ***************/ /*#define DEBUG */ typedef struct Aliz_State_struct { double bestEval; int foundBest; int goalColumn, goalRotation; int checkColumn, checkRotation; int goalSides; int checkSides; /* 0, 1, 2 = middle, left, right */ Grid kg; } Aliz_State; /******************************************************************* * evalBoard() * Evaluates the 'value' of a given board configuration. * Return values range from 0 (in theory) to g->h + . * The lowest value is the best. * Check separately for garbage? *******************************************************************/ static double evalBoard(Grid* g, int nLines, int row) { /* Return the max height plus the number of holes under blocks */ /* Should encourage smaller heights */ int x, y, z; int maxHeight = 0, minHeight = g->h; int nHoles = 0, nGarbage = 0, nCanyons = 0; double avgHeight = 0; int nColumns = g->w; /* Find the minimum, maximum, and average height */ for (x=0; xw; x++) { int height = g->h; char gc; for (y=0; yh; y++) { gc = GRID_CONTENT(*g, x, y); if (gc == 0) height--; else { /* garbage */ if (gc == 1) nGarbage++; /* Penalize for holes under blocks */ for (z=y+1; zh; z++) { char gc2 = GRID_CONTENT(*g, x, z); /* count the holes under here */ if (gc2 == 0) { nHoles += 2; /* these count for double! */ } else if (gc2 == 1) nGarbage++; } break; } } avgHeight += height; if (height > maxHeight) maxHeight = height; if (height < minHeight) minHeight = height; } avgHeight /= nColumns; nHoles *= g->h; /* Find the number of holes lower than the maxHeight */ for (x=0; xw; x++) { for (y=g->h-maxHeight; yh; y++) { /* count the canyons under here */ if (GRID_CONTENT(*g, x, y) == 0&& ((x == 0 || GRID_CONTENT(*g, x-1, y)) && (x == g->w-1 || GRID_CONTENT(*g, x+1, y)))) nCanyons += y; } } #ifdef DEBUG printf("*** %d holes ", nHoles); #endif return (double)(maxHeight*(g->h) + avgHeight + (maxHeight - minHeight) + nHoles + nCanyons + nGarbage + (g->h - row) - nLines*nLines); } /******************************************************************* * cogitate() * Kiri's AI 'thinking' function. Again, called once 'every so' * by event_loop(). *******************************************************************/ static void alizCogitate(void *state, Grid* g, play_piece* pp, play_piece* np, int col, int row, int rot) { Aliz_State *as = (Aliz_State *)state; double eval, evalLeft = -1, evalRight = -1; int nLines; Assert(as); if (as->foundBest) return; memcpy(as->kg.contents, g->contents, (g->w * g->h * sizeof(as->kg.contents[0]))); memcpy(as->kg.fall, g->fall, (g->w * g->h * sizeof(*as->kg.fall))); if (as->bestEval == -1) { /* It's our first think! */ as->checkColumn = col; /* check this column first */ as->checkRotation = 0; /* check no rotation (it's easy!) */ as->checkSides = 0; /* middle */ } else { /* Try the next thing */ as->checkRotation = (as->checkRotation+1)%4; if (as->checkRotation == 0) { /* Try a new column */ /* Go all the way left first */ if (as->checkColumn == col || as->checkColumn < col) { as->checkColumn--; if (!valid_position(pp, as->checkColumn, row, as->checkRotation, &as->kg)) { as->checkColumn = col + 1; if (!valid_position(pp, as->checkColumn, row, as->checkRotation, &as->kg)) { /* skip this one */ return; } } } else { /* Now go all the way to the right */ as->checkColumn++; if (!valid_position(pp, as->checkColumn, row, as->checkRotation, &as->kg)) { /* skip this one */ return; } } } else if (!valid_position(pp, as->checkColumn, row, as->checkRotation, &as->kg)) { /* as->checkRotation != 0 */ if (as->checkColumn >= g->w && as->checkRotation == 3) { /* final one */ as->foundBest = TRUE; #ifdef DEBUG printf("Aliz: Found best! (last checked %d, %d)\n", as->checkColumn, as->checkRotation); #endif return; } /* Trying a specific rotation; skip this one */ else return; } } #ifdef DEBUG printf("Aliz: trying (col %d, rot %d) ", as->checkColumn, as->checkRotation); #endif /* Check for impending doom */ if (GRID_CONTENT(*g, col, row+1)) { #ifdef DEBUG printf("Aliz: panic! about to crash, "); #endif /* something right below us, so move the direction with more space */ if (col > g->w - col) { /* go left, leave rotation as is */ as->goalColumn = col/2; /* wessy commentary: you might want to set goalRotation to the * current rotation so that your next move (the panic flight) will * be a motion, not a rotation) ... */ as->goalRotation = as->checkRotation; #ifdef DEBUG printf("moving left.\n"); #endif } else { /* go right, leave rotation as is */ as->goalColumn = col + (g->w - col)/2; as->goalRotation = as->checkRotation; #ifdef DEBUG printf("moving right.\n"); #endif } #ifdef DEBUG printf(" NEW goal: col %d, rot %d\n", as->goalColumn, as->goalRotation); #endif return; } /************** Test the current choice ****************/ nLines = drop_piece_on_grid(&as->kg, pp, as->checkColumn, row, as->checkRotation); if (nLines != -1) { /* invalid place to drop something */ eval = evalBoard(&as->kg, nLines, row); #ifdef DEBUG printf(": eval = %.3f", eval); #endif if (as->bestEval == -1 || eval < as->bestEval) { as->bestEval = eval; as->goalColumn = as->checkColumn; as->goalRotation = as->checkRotation; #ifdef DEBUG printf(" **"); #endif /* See if we should try to slide left */ if (as->checkColumn > 0 && !GRID_CONTENT(*g, as->checkColumn-1, row)) { int y; for (y=row; y>=0; y--) /* worth trying if there's something above it */ if (GRID_CONTENT(*g, as->checkColumn-1, y)) break; if (y >= 0 && valid_position(pp, as->checkColumn-1, row, as->checkRotation, &as->kg)) { printf("Aliz: Trying to slip left.\n"); /* get a fresh copy */ memcpy(as->kg.contents, g->contents, (g->w * g->h * sizeof(as->kg.contents[0]))); memcpy(as->kg.fall, g->fall, (g->w * g->h * sizeof(*as->kg.fall))); paste_on_board(pp, as->checkColumn-1, row, as->checkRotation, &as->kg); /* nLines is the same */ evalLeft = evalBoard(&as->kg, nLines, row); } } /* See if we should try to slide left */ if (as->checkColumn < g->w-1 && !GRID_CONTENT(*g, as->checkColumn+1, row)) { int y; for (y=row; y>=0; y--) /* worth trying if there's something above it */ if (GRID_CONTENT(*g, as->checkColumn+1, y)) break; if (y >= 0 && valid_position(pp, as->checkColumn+1, row, as->checkRotation, &as->kg)) { printf("Aliz: Trying to slip right.\n"); /* get a fresh copy */ memcpy(as->kg.contents, g->contents, (g->w * g->h * sizeof(as->kg.contents[0]))); memcpy(as->kg.fall, g->fall, (g->w * g->h * sizeof(*as->kg.fall))); paste_on_board(pp, as->checkColumn+1, row, as->checkRotation, &as->kg); /* nLines is the same */ evalRight = evalBoard(&as->kg, nLines, row); } } if (evalLeft >= 0 && evalLeft < as->bestEval && evalLeft <= evalRight) { as->goalColumn = as->checkColumn; as->goalSides = -1; printf(": eval = %.3f left **", eval); } else if (evalRight >= 0 && evalRight < as->bestEval && evalRight < evalLeft) { as->goalColumn = as->checkColumn; as->goalSides = 1; printf(": eval = %.3f right **", eval); } else { #ifdef DEBUG printf(": eval = %.3f", eval); #endif if (as->bestEval == -1 || eval < as->bestEval) { as->bestEval = eval; as->goalColumn = as->checkColumn; as->goalRotation = as->checkRotation; as->goalSides = as->checkSides; } } } #ifdef DEBUG printf("\n"); #endif } } /******************************************************************* * alizReset() * Clear all of Kiri's globals. *********************************************************************/ static void * alizReset(void *state, Grid *g) { Aliz_State *as; if (state == NULL) { /* first time we have been called (this game) */ Calloc(as, Aliz_State *,sizeof(Aliz_State)); } else as = (Aliz_State *)state; Assert(as); #ifdef DEBUG printf("Aliz: Clearing state.\n"); #endif as->bestEval = -1; as->foundBest = FALSE; if (as->kg.contents == NULL) as->kg = generate_board(g->w, g->h, 0); return as; } /******************************************************************* * alizMove() * Kiri's AI 'move' function. Possible retvals: * MOVE_NONE Do nothing, fall normally. * MOVE_LEFT [Try to] Move one column to the left. * MOVE_RIGHT [Try to] Move one column to the right. * MOVE_ROTATE [Try to] Rotate the piece. * MOVE_DOWN Fall down really quickly. *********************************************************************/ static Command alizMove(void *state, Grid* g, play_piece* pp, play_piece* np, int col, int row, int rot) { Aliz_State *as = (Aliz_State *)state; Assert(as); if (rot == as->goalRotation) { if (col == as->goalColumn) { if (as->foundBest) { if (GRID_CONTENT(*g, col, row+1)) { if (as->goalSides == -1) return MOVE_LEFT; else if (as->goalSides == 1) return MOVE_RIGHT; } return MOVE_DOWN; } else return MOVE_NONE; } else if (col < as->goalColumn) return MOVE_RIGHT; else return MOVE_LEFT; } else return MOVE_ROTATE; } /************************************************************************* * AI_Players_Setup() * This function creates a structure describing all of the available AI * players. *******************************************************************PROTO*/ AI_Players * AI_Players_Setup(void) { int i; AI_Players *retval; Calloc(retval, AI_Players *, sizeof(AI_Players)); retval->n = 4; /* change this to add another */ Calloc(retval->player, AI_Player *, sizeof(AI_Player) * retval->n); i = 0; retval->player[i].name = "Simple Robot"; retval->player[i].msg = "An introductory opponent."; retval->player[i].move = wes_ai_move; retval->player[i].think = beginner_ai_think; retval->player[i].reset = wes_ai_reset; i++; retval->player[i].name = "Lightning"; retval->player[i].msg = "Warp-speed heuristics."; retval->player[i].move = wes_ai_move; retval->player[i].think = wes_ai_think; retval->player[i].reset = wes_ai_reset; i++; retval->player[i].name = "Aliz"; retval->player[i].msg = "Kiri's codified wit and wisdom."; retval->player[i].move = alizMove; retval->player[i].think = alizCogitate; retval->player[i].reset = alizReset; i++; retval->player[i].name = "Double-Think"; retval->player[i].msg = "Brilliant but slow."; retval->player[i].move = double_ai_move; retval->player[i].think = double_ai_think; retval->player[i].reset = double_ai_reset; i++; Debug("AI Players Initialized (%d AIs).\n",retval->n); return retval; } /*************************************************************************** * pick_key_repeat() * Ask the player to select a keyboard repeat rate. *********************************************************************PROTO*/ int pick_key_repeat(SDL_Surface * screen) { char *factor; int retval; clear_screen_to_flame(); draw_string("(1 = Slow Repeat, 16 = Default, 32 = Fastest)", color_purple, screen->w/2, screen->h/2, DRAW_UPDATE | DRAW_CENTER | DRAW_ABOVE); draw_string("Keyboard repeat delay factor:", color_purple, screen->w/2, screen->h/2, DRAW_UPDATE | DRAW_LEFT); factor = input_string(screen, screen->w/2, screen->h/2, 0); retval = 0; sscanf(factor,"%d",&retval); free(factor); if (retval < 1) retval = 1; if (retval > 32) retval = 32; clear_screen_to_flame(); return retval; } /*************************************************************************** * pick_ai_factor() * Asks the player to choose an AI delay factor. *********************************************************************PROTO*/ int pick_ai_factor(SDL_Surface * screen) { char *factor; int retval; clear_screen_to_flame(); draw_string("(1 = Impossible, 100 = Easy, 0 = Set Automatically)", color_purple, screen->w/2, screen->h/2, DRAW_UPDATE | DRAW_CENTER | DRAW_ABOVE); draw_string("Pick an AI delay factor:", color_purple, screen->w/2, screen->h/2, DRAW_UPDATE | DRAW_LEFT); factor = input_string(screen, screen->w/2, screen->h/2, 0); retval = 0; sscanf(factor,"%d",&retval); free(factor); if (retval < 0) retval = 0; if (retval > 100) retval = 100; return retval; } /*************************************************************************** * pick_an_ai() * Asks the player to choose an AI. * * Returns -1 on "cancel". *********************************************************************PROTO*/ int pick_an_ai(SDL_Surface *screen, char *msg, AI_Players *AI) { WalkRadioGroup *wrg; int i, text_h; int retval; SDL_Event event; wrg = create_single_wrg( AI->n + 1 ); for (i=0; in ; i++) { char buf[1024]; SPRINTF(buf,"\"%s\" : %s", AI->player[i].name, AI->player[i].msg); wrg->wr[0].label[i] = strdup(buf); } wrg->wr[0].label[AI->n] = "-- Cancel --"; wrg->wr[0].defaultchoice = retval = 0; if (wrg->wr[0].defaultchoice > AI->n) PANIC("not enough choices!"); setup_radio(&wrg->wr[0]); wrg->wr[0].x = (screen->w - wrg->wr[0].area.w) / 2; wrg->wr[0].y = (screen->h - wrg->wr[0].area.h) / 2; clear_screen_to_flame(); text_h = draw_string(msg, color_ai_menu, ( screen->w ) / 2, wrg->wr[0].y - 30, DRAW_UPDATE | DRAW_CENTER); draw_string("Choose a Computer Player", color_ai_menu, (screen->w ) / 2, (wrg->wr[0].y - 30) - text_h, DRAW_UPDATE | DRAW_CENTER); draw_radio(&wrg->wr[0], 1); while (1) { int retval; poll_and_flame(&event); retval = handle_radio_event(wrg,&event); if (retval == -1) continue; if (retval == AI->n) return -1; return retval; } } /* * $Log: ai.c,v $ * Revision 1.27 2001/01/05 21:12:31 weimer * advance to atris 1.0.5, add support for ".atrisrc" and changing the * keyboard repeat rate * * Revision 1.26 2000/11/06 04:06:44 weimer * option menu * * Revision 1.25 2000/11/06 01:22:40 wkiri * Updated menu system. * * Revision 1.24 2000/11/06 00:24:01 weimer * add WalkRadioGroup modifications (inactive field for Kiri) and some support * for special pieces * * Revision 1.23 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.22 2000/11/01 04:39:41 weimer * clear the little scoring spot correctly, updates for making a no-sound * distribution * * Revision 1.21 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.20 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.19 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.18 2000/10/29 17:23:13 weimer * incorporate "xflame" flaming background for added spiffiness ... * * Revision 1.17 2000/10/28 21:30:34 wkiri * Kiri AI rules! * * Revision 1.16 2000/10/21 01:14:42 weimer * massic autoconf/automake restructure ... * * Revision 1.15 2000/10/20 01:32:09 weimer * Minor play issue problems -- time is now truly a hard limit! * * Revision 1.14 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.13 2000/10/18 02:04:02 weimer * playability changes ... * * Revision 1.12 2000/10/14 16:17:41 weimer * level adjustment changes, added some new AIs, etc. * * Revision 1.11 2000/10/14 03:36:22 weimer * wessy AI changes: resource management * * Revision 1.10 2000/10/14 02:56:37 wkiri * Kiri AI gets another leg up. * * Revision 1.9 2000/10/14 01:56:35 weimer * remove dates from identity files * * Revision 1.8 2000/10/14 01:56:10 wkiri * Kiri AI comes of age! * * Revision 1.7 2000/10/14 01:24:34 weimer * fixed error with not advancing levels when fighting AI * * Revision 1.6 2000/10/13 22:34:26 weimer * minor wessy AI changes * * Revision 1.5 2000/10/13 21:16:43 wkiri * Aliz AI evolves! * * Revision 1.4 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.3 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.2 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.1 2000/10/12 19:19:43 weimer * Sigh! * */