/*------------------------------------------------------------------------- * Copyright (c) 2000-2002 Kenneth W. Sodemann (stufflehead@bigfoot.com) *------------------------------------------------------------------------- * game_engine * * Synopsis: * The main engine of the MathWar game * * $Id: game_engine.c,v 1.8 2002/11/18 16:42:55 stuffle Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * *------------------------------------------------------------------------- */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <gnome.h> #include <stdlib.h> #include <time.h> #include "draw.h" #include "game_engine.h" #include "ini_defs.h" #include "mainwin.h" #include "operators.h" #include "player.h" #include "support.h" /* will not be needed once mainwin is fixed to have pointer to drawing area */ #define TICKS_PER_SEC 10 #define NO_GUESS -1 #define TEN_HZ_PERIOD 100 #define MIN_SCORE 10 #define BONUS 5 #define ANSWER_STR "Answer: %d %c %d = %d" #ifdef DEBUG_GAME_ENGINE /* * temp debugging function. */ static void dump_game_engine (game_engine *game) { int i; int tmp[21]; printf ("Dump of game engine:\n"); printf ("Player 1:\n\tName: %s\n\tType: %s\n\tScore: %d\n", get_player_name (game->player1), (get_type_of_player (game->player1) == HUMAN) ? "Human" : "Puter", get_player_score (game->player1)); printf ("Player 2:\n\tName: %s\n\tType: %s\n\tScore: %d\n", get_player_name (game->player2), (get_type_of_player (game->player2) == HUMAN) ? "Human" : "Puter", get_player_score (game->player2)); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { tmp[game->operator_pool[i]]++; } printf ("Operators:\n\tAdd: %d\n\tSub: %d\n\tMult: %d\n", tmp[ADDITION], tmp[SUBTRACTION], tmp[MULTIPLICATION]); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { tmp[game->add_cards[i]]++; } printf ("Addition Cards:\n"); printf ("\t 0: %d\n", tmp[0]); printf ("\t 1: %d\n", tmp[1]); printf ("\t 2: %d\n", tmp[2]); printf ("\t 3: %d\n", tmp[3]); printf ("\t 4: %d\n", tmp[4]); printf ("\t 5: %d\n", tmp[5]); printf ("\t 6: %d\n", tmp[6]); printf ("\t 7: %d\n", tmp[7]); printf ("\t 8: %d\n", tmp[8]); printf ("\t 9: %d\n", tmp[9]); printf ("\t10: %d\n", tmp[10]); printf ("\t11: %d\n", tmp[11]); printf ("\t12: %d\n", tmp[12]); printf ("\t13: %d\n", tmp[13]); printf ("\t14: %d\n", tmp[14]); printf ("\t15: %d\n", tmp[15]); printf ("\t16: %d\n", tmp[16]); printf ("\t17: %d\n", tmp[17]); printf ("\t18: %d\n", tmp[18]); printf ("\t19: %d\n", tmp[19]); printf ("\t20: %d\n", tmp[20]); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { tmp[game->sub_cards[i]]++; } printf ("Subtraction Cards:\n"); printf ("\t 0: %d\n", tmp[0]); printf ("\t 1: %d\n", tmp[1]); printf ("\t 2: %d\n", tmp[2]); printf ("\t 3: %d\n", tmp[3]); printf ("\t 4: %d\n", tmp[4]); printf ("\t 5: %d\n", tmp[5]); printf ("\t 6: %d\n", tmp[6]); printf ("\t 7: %d\n", tmp[7]); printf ("\t 8: %d\n", tmp[8]); printf ("\t 9: %d\n", tmp[9]); printf ("\t10: %d\n", tmp[10]); printf ("\t11: %d\n", tmp[11]); printf ("\t12: %d\n", tmp[12]); printf ("\t13: %d\n", tmp[13]); printf ("\t14: %d\n", tmp[14]); printf ("\t15: %d\n", tmp[15]); printf ("\t16: %d\n", tmp[16]); printf ("\t17: %d\n", tmp[17]); printf ("\t18: %d\n", tmp[18]); printf ("\t19: %d\n", tmp[19]); printf ("\t20: %d\n", tmp[20]); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { tmp[game->mult_cards[i]]++; } printf ("Multiplication Cards:\n"); printf ("\t 0: %d\n", tmp[0]); printf ("\t 1: %d\n", tmp[1]); printf ("\t 2: %d\n", tmp[2]); printf ("\t 3: %d\n", tmp[3]); printf ("\t 4: %d\n", tmp[4]); printf ("\t 5: %d\n", tmp[5]); printf ("\t 6: %d\n", tmp[6]); printf ("\t 7: %d\n", tmp[7]); printf ("\t 8: %d\n", tmp[8]); printf ("\t 9: %d\n", tmp[9]); printf ("\t10: %d\n", tmp[10]); printf ("\t11: %d\n", tmp[11]); printf ("\t12: %d\n", tmp[12]); printf ("\t13: %d\n", tmp[13]); printf ("\t14: %d\n", tmp[14]); printf ("\t15: %d\n", tmp[15]); printf ("\t16: %d\n", tmp[16]); printf ("\t17: %d\n", tmp[17]); printf ("\t18: %d\n", tmp[18]); printf ("\t19: %d\n", tmp[19]); printf ("\t20: %d\n", tmp[20]); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { if (game->computer_guess[i]) tmp[1]++; else tmp[0]++; } printf ("Computer Guesses:\n\tYes: %d\n\tNo: %d\n", tmp[1], tmp[0]); for (i = 0; i < 21; i++) tmp[i] = 0; for (i = 0; i < NUM_MARBLES; i++) { if (game->guess_diff[i] < 21) { tmp[game->guess_diff[i]]++; } else { tmp[20]++; } } printf ("Guess Deviation:\n"); printf ("\t 0: %d\n", tmp[0]); printf ("\t 1: %d\n", tmp[1]); printf ("\t 2: %d\n", tmp[2]); printf ("\t 3: %d\n", tmp[3]); printf ("\t 4: %d\n", tmp[4]); printf ("\t 5: %d\n", tmp[5]); printf ("\t 6: %d\n", tmp[6]); printf ("\t 7: %d\n", tmp[7]); printf ("\t 8: %d\n", tmp[8]); printf ("\t 9: %d\n", tmp[9]); printf ("\t10: %d\n", tmp[10]); printf ("\t11: %d\n", tmp[11]); printf ("\t12: %d\n", tmp[12]); printf ("\t13: %d\n", tmp[13]); printf ("\t14: %d\n", tmp[14]); printf ("\t15: %d\n", tmp[15]); printf ("\t16: %d\n", tmp[16]); printf ("\t17: %d\n", tmp[17]); printf ("\t18: %d\n", tmp[18]); printf ("\t19: %d\n", tmp[19]); printf ("\t20 or more: %d\n", tmp[20]); printf ("Number of rounds: %d\n", game->num_rounds); printf ("Round Ticks: %d\n", game->round_tick_len); printf ("Comp Guess Ticks: %d\n", game->comp_avg_guess_tick); printf ("Comp Guess Ticks Mav Dev: %d\n", game->comp_guess_tick_max_dev); } #endif /* * get a random integer between min_val and max_val, inclusively. */ static gint get_rand_int (gint min_val, gint max_val) { double rand_pct; gint rtn; g_assert (min_val <= max_val); rand_pct = ((double)rand () / (RAND_MAX + 1.0)); rtn = min_val + (gint)((double)(max_val - min_val + 1) * rand_pct); g_assert (rtn >= min_val && rtn <= max_val); return rtn; } /* * fill the given array with integers between min_val and max_val * inclusively. */ static void fill_int_array (gint *arr, gint arr_els, gint min_val, gint max_val) { gint i; g_assert (min_val <= max_val); for (i = 0; i < arr_els; i++) { arr[i] = get_rand_int (min_val, max_val); } return; } static gint delayed_round_begin (gpointer data) { game_engine *game = (game_engine *)data; clear_message_area(); if (game->num_rounds) { game->state = BETWEEN_ROUNDS; begin_round (game); } else { disable_all_input_widgets (game->parent); draw_game_over (game); game->state = GAME_OVER; } return 0; } void handle_yes_answer (game_engine *game) { GString *str; gint delay_ticks; if (game->comp_guess == game->answer) { draw_message (_("Good Job!!"), _("You get 5 bonus points!!"), TRUE); update_player_stats (game->player1, BONUS, TRUE); } else { str = g_string_new (""); g_string_sprintf (str, _("The correct answer is: %d"), game->answer); draw_message (_("Sorry!!"), str->str, TRUE); g_string_free (str, TRUE); } delay_ticks = ini_get_int (PACKAGE, BASIC_PARMS, RESP_DELAY, DEF_RESP_DELAY) * 1000; game->state = AFTER_COMP_GUESS; game->cur_timer = gtk_timeout_add (delay_ticks, delayed_round_begin, game); game->timer_is_active = TRUE; } void handle_no_answer (game_engine *game) { GString *str; gint delay_ticks; if (game->comp_guess == game->answer) { draw_message (_("Sorry!!"), _("The computer is correct."), TRUE); } else { str = g_string_new (""); g_string_sprintf (str, _("The correct answer is: %d"), game->answer); draw_message (_("Good Job!!"), str->str, TRUE); update_player_stats (game->player1, BONUS, TRUE); g_string_free (str, TRUE); } delay_ticks = ini_get_int (PACKAGE, BASIC_PARMS, RESP_DELAY, DEF_RESP_DELAY) * 1000; game->state = AFTER_COMP_GUESS; game->cur_timer = gtk_timeout_add (delay_ticks, delayed_round_begin, game); game->timer_is_active = TRUE; } /* * The computer is guessing.... */ static void handle_computer_guess (game_engine *game) { GString *msg; gint idx; gint score = 0; /* * first things first, stop the timer... */ gtk_timeout_remove (game->cur_timer); game->timer_is_active = FALSE; game->state = COMP_GUESSING; /* * Determine if the computer will be correct or not. Apply and variance * based on even or odd ticks. */ idx = get_rand_int (0, NUM_MARBLES - 1); if (game->tick % 2) { game->comp_guess = game->answer + game->guess_diff[idx]; } else { game->comp_guess = game->answer - game->guess_diff[idx]; } /* * Display the computer's answer, and ask the player if the computer * is correct. */ msg = g_string_new (""); g_string_sprintf (msg, _("The computer guesses %d %c %d = %d"), game->card1, operator_symbol (game->cur_operator), game->card2, game->comp_guess); draw_message (msg->str, _("Is the computer player correct?"), FALSE); if (game->answer == game->comp_guess) { score = (game->answer > MIN_SCORE) ? game->answer : MIN_SCORE; } update_player_stats (game->player2, score, FALSE); yesno_activate (game->parent); } /* * The round is over. Handle it. */ static void handle_round_over (game_engine *game) { GString *msg; gint delay_ticks; /* * first things first, stop the timer... */ gtk_timeout_remove (game->cur_timer); game->timer_is_active = FALSE; game->state = BETWEEN_ROUNDS; msg = g_string_new (""); g_string_sprintf (msg, ANSWER_STR, game->card1, operator_symbol (game->cur_operator), game->card2, game->answer); draw_message (_("Time's Up!!"), msg->str, TRUE); delay_ticks = ini_get_int (PACKAGE, BASIC_PARMS, RESP_DELAY, DEF_RESP_DELAY) * 1000; game->cur_timer = gtk_timeout_add (delay_ticks, delayed_round_begin, game); game->timer_is_active = TRUE; g_string_free (msg, TRUE); } /* * The human player has made a guess. Handle it. */ void 00420 handle_player_answer (game_engine *game, const gchar *answer) { GString *msg; gint actual; gint guess; gint score; gint delay_ticks; /* * The only time the player should be pressing "Submit" is if we * are in a round. If we are not in a round (or if we are paused), * just ignore the user... */ if (game->state != IN_ROUND || game->is_paused) { return; } /* * first things first, stop the timer... */ gtk_timeout_remove (game->cur_timer); game->timer_is_active = FALSE; game->state = BETWEEN_ROUNDS; /* * Get the actual answer, compare with the player's answer. */ guess = atoi (answer); actual = game->answer; if (guess == actual) { draw_message (_("Good Job!!"), _("You got it right!!"), TRUE); score = (actual < MIN_SCORE) ? MIN_SCORE : actual; } else { score = 0; msg = g_string_new (""); g_string_sprintf (msg, "The correct answer is: %d", actual); draw_message (_("Sorry!!"), msg->str, TRUE); g_string_free (msg, TRUE); } update_player_stats (game->player1, score, FALSE); delay_ticks = ini_get_int (PACKAGE, BASIC_PARMS, RESP_DELAY, DEF_RESP_DELAY) * 1000; game->cur_timer = gtk_timeout_add (delay_ticks, delayed_round_begin, game); game->timer_is_active = TRUE; } /* * Processing done at 10 hz during execution of the round. */ static gint ten_hz_processing (gpointer user_data) { game_engine *game = (game_engine *)user_data; gint rtn = 1; double pct; /* * Determine what tick this is, and redraw the timer dial. */ game->tick++; pct = (double)(game->round_tick_len - game->tick) / (double)game->round_tick_len; set_timer_pct (game->parent, pct); /* * Determine if anything needs to be done. */ if (game->tick == game->comp_guess_tick) { handle_computer_guess (game); rtn = 0; } else if (game->tick == game->round_tick_len) { handle_round_over (game); rtn = 0; } return rtn; } /* * The "between round" timeout has tripped. We should be setup for * another round. Launch it... */ static gint launch_round (gpointer user_data) { game_engine *game = (game_engine *)user_data; draw_cards_up (game->card1, game->card2, game->cur_operator); reset_input_widgets (game->parent); game->num_rounds--; display_rounds_left (game->parent, game->num_rounds); game->cur_timer = gtk_timeout_add (TEN_HZ_PERIOD, ten_hz_processing, game); game->timer_is_active = TRUE; game->state = IN_ROUND; refresh_board(game->player1, game->player2); return 0; } void 00537 begin_game (game_engine *game) { gint i; gint tmp_int; gint cur_end; reset_player (game->player1); reset_player (game->player2); game->num_rounds = ini_get_int (PACKAGE, BASIC_PARMS, NUM_ROUNDS, DEF_NUM_ROUNDS); game->cur_timer = -1; game->timer_is_active = FALSE; game->tick = 0; game->round_tick_len = ini_get_int (PACKAGE, BASIC_PARMS, SECONDS, DEF_SECONDS) * TICKS_PER_SEC; game->comp_guess_tick = 0; tmp_int = ini_get_int (PACKAGE, COMP_PLAYER, GUESS_TIME, DEF_TIME); game->comp_avg_guess_tick = (gint) (game->round_tick_len * tmp_int / 100); tmp_int = ini_get_int (PACKAGE, COMP_PLAYER, MAX_DEVIATION, DEF_MAX_DEV); game->comp_guess_tick_max_dev = (gint) (game->round_tick_len * tmp_int / 100); /* * The game engine uses "bags of marbles" to determine events that * depend upon user defined probabilities. Setup those bags of * marbles. * * card arrays, random between 0 and user defined max */ tmp_int = ini_get_int (PACKAGE, OPERATORS, ADD_MAX, DEF_ADD_MAX); fill_int_array (game->add_cards, NUM_MARBLES, 0, tmp_int); tmp_int = ini_get_int (PACKAGE, OPERATORS, SUB_MAX, DEF_SUB_MAX); fill_int_array (game->sub_cards, NUM_MARBLES, 0, tmp_int); tmp_int = ini_get_int (PACKAGE, OPERATORS, MULT_MAX, DEF_MULT_MAX); fill_int_array (game->mult_cards, NUM_MARBLES, 0, tmp_int); /* percent of rounds that are add, sub and mult */ tmp_int = ini_get_int (PACKAGE, OPERATORS, SUB_PCT, DEF_SUB_PCT); for (i = 0; i < tmp_int; i++) { game->operator_pool[i] = (gint)SUBTRACTION; } cur_end = tmp_int; tmp_int = ini_get_int (PACKAGE, OPERATORS, MULT_PCT, DEF_MULT_PCT); for (i = cur_end; i < cur_end + tmp_int; i++) { game->operator_pool[i] = (gint)MULTIPLICATION; } cur_end += tmp_int; for (i = cur_end; i < NUM_MARBLES; i++) { game->operator_pool[i] = (gint)ADDITION; } /* Computer guess deviation (zero deviations are correct guesses). */ tmp_int = ini_get_int (PACKAGE, COMP_PLAYER, RIGHT_PCT, DEF_RIGHT_PCT); cur_end = NUM_MARBLES - tmp_int; tmp_int = ini_get_int (PACKAGE, COMP_PLAYER, GUESS_DEV, DEF_GUESS_DEV); fill_int_array (game->guess_diff, cur_end, 1, tmp_int); for (i = cur_end; i < NUM_MARBLES; i++) { game->guess_diff[i] = 0; } /* Computer guess pct */ tmp_int = ini_get_int (PACKAGE, COMP_PLAYER, GUESS_PCT, DEF_GUESS_PCT); for (i = 0; i < tmp_int; i++) { game->computer_guess[i] = TRUE; } for (i = tmp_int; i < NUM_MARBLES; i++) { game->computer_guess[i] = FALSE; } } game_engine * 00625 create_game_engine (GtkWidget *parent) { game_engine *game; GString *name; srand ((unsigned int)time (NULL)); game = g_malloc (sizeof (game_engine)); /* * Player 1 is always human, and is the current OS user. * Player 2 is always the computer. */ if (getenv ("LOGNAME")) { name = g_string_new (getenv ("LOGNAME")); } else { name = g_string_new (_("Human Player")); } game->player1 = create_player (name->str, HUMAN); game->player2 = create_player (_("Computer Player"), COMPUTER); game->parent = parent; game->state = NOT_RUNNING; game->is_paused = FALSE; /* * Call begin_game to do all of the pre-game initialization. */ begin_game (game); /* * Clean up... */ g_string_free (name, TRUE); #ifdef DEBUG_GAME_ENGINE dump_game_engine (game); #endif return game; } void 00672 destroy_game_engine (game_engine *game) { destroy_player (game->player1); destroy_player (game->player2); if (game->timer_is_active) { gtk_timeout_remove (game->cur_timer); } g_free (game); } void 00687 begin_round (game_engine *game) { gint idx; gint delay_ticks; /* * Show the screen in "pause" mode (cards back side up, * unkown operator) */ draw_cards_down (); /* * Select the operator. Based on the operator, we need to select * two cards from the appropriate card pools. */ idx = get_rand_int (0, NUM_MARBLES - 1); game->cur_operator = game->operator_pool[idx]; game->tick = 0; switch (game->cur_operator) { case ADDITION: idx = get_rand_int (0, NUM_MARBLES - 1); game->card1 = game->add_cards[idx]; idx = get_rand_int (0, NUM_MARBLES - 1); game->card2 = game->add_cards[idx]; break; case SUBTRACTION: idx = get_rand_int (0, NUM_MARBLES - 1); game->card1 = game->sub_cards[idx]; idx = get_rand_int (0, NUM_MARBLES - 1); game->card2 = game->sub_cards[idx]; break; case MULTIPLICATION: idx = get_rand_int (0, NUM_MARBLES - 1); game->card1 = game->mult_cards[idx]; idx = get_rand_int (0, NUM_MARBLES - 1); game->card2 = game->mult_cards[idx]; break; default: g_assert_not_reached (); break; } /* * Determine if the computer will guess. If so, we also need to * determine when. */ idx = get_rand_int (0, NUM_MARBLES - 1); if (game->computer_guess[idx]) { game->comp_guess_tick = game->comp_avg_guess_tick + get_rand_int (-game->comp_guess_tick_max_dev, game->comp_guess_tick_max_dev); } else { game->comp_guess_tick = NO_GUESS; } game->comp_guess = 0; game->answer = operator_result (game->card1, game->card2, game->cur_operator); /* * Start an X second timeout before the start of this round. * Handle the actual starting of the round in the callback. */ delay_ticks = ini_get_int (PACKAGE, BASIC_PARMS, SECONDS_BTWN, DEF_SECS_BTWN) * 1000; game->state = BETWEEN_ROUNDS; game->cur_timer = gtk_timeout_add (delay_ticks, launch_round, game); game->timer_is_active = TRUE; refresh_board(game->player1, game->player2); } const player_object * get_player1 (const game_engine *game) { g_assert (game != NULL); return game->player1; } const player_object * get_player2 (const game_engine *game) { g_assert (game != NULL); return game->player2; } void pause_game (game_engine *game) { if (game->timer_is_active) { gtk_timeout_remove (game->cur_timer); game->timer_is_active = FALSE; } game->is_paused = TRUE; draw_pause(); } void restore_game (game_engine *game) { game->is_paused = FALSE; switch (game->state) { case BETWEEN_ROUNDS: delayed_round_begin (game); break; case IN_ROUND: game->cur_timer = gtk_timeout_add (TEN_HZ_PERIOD, ten_hz_processing, game); game->timer_is_active = TRUE; draw_cards_up (game->card1, game->card2, game->cur_operator); refresh_board (game->player1, game->player2); break; case COMP_GUESSING: refresh_board (game->player1, game->player2); break; case AFTER_COMP_GUESS: refresh_board (game->player1, game->player2); break; default: g_print ("game->state: %d\n", game->state); g_assert_not_reached(); } }