/* * Gnome Nine Mens Morris * Written by Dirk Farin * * 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 the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "board.hh" #include "util.hh" #include "tree.hh" #include #include #include #include #include #include #include #include using namespace std; #define APPNAME "gnmm" #define APPNAME_LONG "gnome nine mens morris" #define VERSION "0.1.2" #define CORNER 0 #define TILE_SIZE 64 #define SIZE 7 GtkWidget* window; GtkWidget* area; GtkWidget* statusbar; GdkPixbuf* boardpic; GdkPixbuf* piecespic; GdkPixmap *buffer; SearchAlgo algo; CountEval eval_cnt; Board board; int history_size; int history_length; int history_pos; Board* history; bool white_user = true; bool black_user = false; int white_level = 3; int black_level = 3; bool animated_move=true; bool quit = false; const int nLevels = 5; int level2nodes[nLevels+1] = { 0, 500, 15000, 100000, 300000, 1000000 }; // player move Move next_valid[200]; // all next available moves int n_next_valid; int prohibit_draw_pos = -1; // piece to exclude from display (or -1 for none) int moving_piece = 0; // piece which is moved around (or 0 for none) int moving_x,moving_y; // moving piece position enum { Set, // player makes his move Start, // player makes his move Moving, // player makes his move Take, // player takes an opponent piece Wait, // computer thinks End, // game over /*Edit*/ ClickToContinue // move has been taken back. click to restart computer thinking } gui_state; Move current_move; int move_offset_x, move_offset_y; // offset from top-left of tile to mouse pointer during drag enum CursorShape { Cursor_Move, Cursor_Take, Cursor_Wait, Cursor_Click }; void computer_move(); void prepare_next_move(); void check_for_end_of_move(); void do_move(); void message_gui_state(); void redraw_area_complete(); void display_area_complete(); void redraw_area(int posx0,int posy0,int posx1,int posy1); void display_area(int x0,int y0,int w,int h); void message(gchar *message); /* ------------------------- MENU ------------------------ */ void new_game_cb(GtkWidget *, gpointer); void quit_game_cb(GtkWidget *, gpointer); void about_cb(GtkWidget *, gpointer); void hint_cb(GtkWidget *, gpointer); void properties_cb(GtkWidget *, gpointer); void animated_moves_cb(GtkWidget*, gpointer); void undo_move_callback(GtkWidget*, gpointer); void redo_move_callback(GtkWidget*, gpointer); GnomeUIInfo game_menu[] = { GNOMEUIINFO_MENU_NEW_GAME_ITEM(new_game_cb, NULL), //GNOMEUIINFO_TOGGLEITEM(N_("edit board"),N_("animate computer moves"), //animated_moves_cb,NULL), GNOMEUIINFO_SEPARATOR, GNOMEUIINFO_MENU_UNDO_MOVE_ITEM(undo_move_callback, NULL), GNOMEUIINFO_MENU_REDO_MOVE_ITEM(redo_move_callback, NULL), GNOMEUIINFO_SEPARATOR, //GNOMEUIINFO_MENU_HINT_ITEM(hint_cb, NULL), GNOMEUIINFO_MENU_EXIT_ITEM(quit_game_cb, NULL), GNOMEUIINFO_END }; GnomeUIInfo help_menu[] = { //GNOMEUIINFO_HELP((void*)"gnmm"), GNOMEUIINFO_MENU_ABOUT_ITEM(about_cb, NULL), GNOMEUIINFO_END }; GnomeUIInfo settings_menu[] = { GNOMEUIINFO_TOGGLEITEM(N_("animated moves"),N_("animate computer moves"), animated_moves_cb,NULL), GNOMEUIINFO_MENU_PREFERENCES_ITEM(properties_cb, NULL), GNOMEUIINFO_END }; GnomeUIInfo main_menu[] = { GNOMEUIINFO_MENU_GAME_TREE(game_menu), GNOMEUIINFO_MENU_SETTINGS_TREE(settings_menu), GNOMEUIINFO_MENU_HELP_TREE(help_menu), GNOMEUIINFO_END }; /* ------------------------------------------------------- */ int pref_white_level; int pref_black_level; void radio_clicked_cb(GtkWidget* w,gpointer data) { if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) return; intptr_t id = (intptr_t)data; if (id>=10) pref_black_level = id-10; else pref_white_level = id; } GtkWidget* make_frame(const char* title,int id_base,int id) { GtkWidget* frame = gtk_frame_new(title); GtkWidget* vbox = gtk_vbox_new(FALSE, GNOME_PAD); gtk_container_add(GTK_CONTAINER(frame),vbox); GtkWidget* w0 = gtk_radio_button_new_with_label(NULL, _("human")); if (id == id_base) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w0),TRUE); gtk_signal_connect(GTK_OBJECT(w0), "toggled", GTK_SIGNAL_FUNC(radio_clicked_cb), (gpointer)id_base); gtk_box_pack_start_defaults(GTK_BOX(vbox), w0); for (int i=1;i<=nLevels;i++) { char buf[100]; sprintf(buf,_("level %d"),i); GtkWidget* w = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(w0), buf); if (id == id_base+i) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),TRUE); gtk_signal_connect(GTK_OBJECT(w), "toggled", GTK_SIGNAL_FUNC(radio_clicked_cb), (gpointer)(id_base+i)); gtk_box_pack_start_defaults(GTK_BOX(vbox), w); } return frame; } void animated_moves_cb(GtkWidget* w, gpointer) { if ((GTK_CHECK_MENU_ITEM(w))->active) animated_move = true; else animated_move = false; } void properties_cb(GtkWidget *, gpointer) { pref_white_level = white_level; if (white_user) pref_white_level = 0; pref_black_level = black_level; if (black_user) pref_black_level = 0; GtkWidget* pref_dialog = gnome_dialog_new(_("Preferences"), GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL, NULL); GtkWidget* hbox = gtk_hbox_new(FALSE, GNOME_PAD); GtkWidget* frame_w = make_frame(_("white player"), 0, pref_white_level); GtkWidget* frame_b = make_frame(_("black player"),10, pref_black_level+10); gtk_box_pack_start_defaults(GTK_BOX(hbox), frame_w); gtk_box_pack_start_defaults(GTK_BOX(hbox), frame_b); gtk_widget_show_all(hbox); gtk_box_pack_start(GTK_BOX(GNOME_DIALOG(pref_dialog)->vbox), hbox, TRUE, TRUE, GNOME_PAD); int button = gnome_dialog_run_and_close(GNOME_DIALOG(pref_dialog)); if (button == 0) // ok { bool old_white_user = white_user; bool old_black_user = black_user; white_user = (pref_white_level == 0); black_user = (pref_black_level == 0); white_level = pref_white_level; black_level = pref_black_level; // if player was changed to human, stop computer if (white_user && board.next==WHITE) { algo.StopComputer(); prepare_next_move(); } if (black_user && board.next==BLACK) { algo.StopComputer(); prepare_next_move(); } // if player was changed from human to computer, start computer move if ((!white_user && old_white_user && board.next==WHITE) || (!black_user && old_black_user && board.next==BLACK)) { prepare_next_move(); } } } /* ------------------------------------------------------- */ void gen_valid_moves() { n_next_valid = board.GenMoves(next_valid,200); #if 0 char buf[100]; for (int i=0;iwindow, cursor); gdk_cursor_destroy(cursor); } struct timeval anim_start; bool anim_end; int anim_x0,anim_y0, anim_x1,anim_y1; gint animate_move(gpointer data) { struct timeval current; gettimeofday(¤t,NULL); long usecs = (current.tv_sec - anim_start.tv_sec)*1000000 + (current.tv_usec - anim_start.tv_usec); long duration = 750000; if (usecs >= duration) { usecs=duration; anim_end = true; } int x = (anim_x0 * (duration - usecs) + anim_x1 * usecs)/duration; int y = (anim_y0 * (duration - usecs) + anim_y1 * usecs)/duration; moving_x = x; moving_y = y; move_offset_x = 0; move_offset_y = 0; int p0x = min(anim_x0,anim_x1)/TILE_SIZE; int p1x = max(anim_x0,anim_x1)/TILE_SIZE; int p0y = min(anim_y0,anim_y1)/TILE_SIZE; int p1y = max(anim_y0,anim_y1)/TILE_SIZE; redraw_area(p0x,p0y,p1x,p1y); display_area(p0x*TILE_SIZE,p0y*TILE_SIZE,p1x*TILE_SIZE+TILE_SIZE-1,p1y*TILE_SIZE+TILE_SIZE-1); if (anim_end) return FALSE; else return TRUE; } void computer_move() { int nNodes; if (board.next == WHITE) nNodes = level2nodes[white_level]; else nNodes = level2nodes[black_level]; current_move = algo.ComputerMove(nNodes); if (!algo.ComputerStopped()) // !current_move.IsEmpty()) { //char buf[10]; board.MoveToString(current_move,buf); //cout << _("computer move: ") << buf << endl; if (animated_move && board.ply >= 18) { gettimeofday(&anim_start,NULL); anim_end = false; prohibit_draw_pos = current_move.start; moving_piece = board.board[current_move.start]; anim_x0 = move2coord[current_move.start][1]*TILE_SIZE; anim_y0 = move2coord[current_move.start][0]*TILE_SIZE; anim_x1 = move2coord[current_move.end][1]*TILE_SIZE; anim_y1 = move2coord[current_move.end][0]*TILE_SIZE; gtk_timeout_add(40,animate_move,NULL); while (!anim_end) { gtk_main_iteration(); } prohibit_draw_pos = -1; } if (!algo.ComputerStopped()) do_move(); } else { // cout << _("ignoring move...\n"); } } void prepare_next_move() { // reset GUI and prepare checking of valid moves prohibit_draw_pos = -1; moving_piece = 0; current_move.Reset(); gen_valid_moves(); // set GUI state bool computer_is_next = false; if (board.next == WHITE && !white_user) computer_is_next = true; if (board.next == BLACK && !black_user) computer_is_next = true; bool set_gui_state = true; if (gui_state == ClickToContinue && computer_is_next) set_gui_state = false; if (set_gui_state) { gui_state = (board.ply >= 18 ? Start : Set); if (board.next == WHITE && !white_user) gui_state = Wait; if (board.next == BLACK && !black_user) gui_state = Wait; if (algo.CurrentHasLost() || n_next_valid == 0) gui_state = End; } switch (gui_state) { case Wait: set_cursor(Cursor_Wait); break; case ClickToContinue: set_cursor(Cursor_Click); break; default: set_cursor(Cursor_Move); break; } message_gui_state(); } void do_move() { board.DoMove(current_move); algo.DoMove(current_move); push(board); redraw_area_complete(); display_area_complete(); prepare_next_move(); } void undo_move_callback(GtkWidget*, gpointer) { if (history_pos<=1) return; algo.StopComputer(); history_pos--; board = history[history_pos-1]; algo.SetBoard(board); redraw_area_complete(); display_area_complete(); gui_state = ClickToContinue; prepare_next_move(); } void redo_move_callback(GtkWidget*, gpointer) { if (history_pos >= history_length) return; algo.StopComputer(); board = history[history_pos]; algo.SetBoard(board); history_pos++; redraw_area_complete(); display_area_complete(); gui_state = ClickToContinue; prepare_next_move(); } void check_for_end_of_move() { if (is_valid_move(current_move)) { do_move(); } else { gui_state = Take; set_cursor(Cursor_Take); message_gui_state(); } } gint button_press_space(GtkWidget *widget, GdkEventButton *event) { if(event->button == 1) { int y = (int)event->y-CORNER; int x = (int)event->x-CORNER; x /= TILE_SIZE; y /= TILE_SIZE; int pos = coord2move[y][x]; if (gui_state == ClickToContinue) { gui_state = Wait; prepare_next_move(); } else if (gui_state == Set && validplace[y][x] && board.board[pos] == EMPTY) { current_move.end = pos; move_offset_x = 0; move_offset_y = 0; moving_x = x*64; moving_y = y*64; moving_piece = board.next; redraw_area(x,y,x,y); display_area(x*64,y*64,x*64+63,y*64+63); check_for_end_of_move(); } else if (gui_state == Start && validplace[y][x] && board.board[pos]==board.next) { current_move.start = pos; move_offset_x = ((int)event->x) - x*64; move_offset_y = ((int)event->y) - y*64; prohibit_draw_pos = pos; moving_piece = board.board[pos]; gui_state = Moving; } else if (gui_state == Take && validplace[y][x] && board.board[pos]==-board.next) { current_move.take = pos; do_move(); } } return FALSE; } gint button_release_area(GtkWidget *widget, GdkEventButton *event) { if (event->button == 1) { if (gui_state == Moving) { int y = (int)event->y-CORNER; int x = (int)event->x-CORNER; x /= TILE_SIZE; y /= TILE_SIZE; int pos = coord2move[y][x]; current_move.end = pos; if (validplace[y][x] && valid_moving(current_move)) { moving_x = x*64 + move_offset_x; moving_y = y*64 + move_offset_y; check_for_end_of_move(); } else { prohibit_draw_pos = -1; moving_piece = 0; gui_state = Start; } redraw_area_complete(); display_area_complete(); } } return FALSE; } gint button_motion_area(GtkWidget *widget, GdkEventButton *event) { if (gui_state == Moving) { int oldxpos = (moving_x - move_offset_x)/64; int oldypos = (moving_y - move_offset_y)/64; gint mx,my; gdk_window_get_pointer(area->window,&mx,&my,NULL); moving_x = mx; //(int)event->x; moving_y = my; //(int)event->y; int newxpos = (moving_x - move_offset_x)/64; int newypos = (moving_y - move_offset_y)/64; int x0pos = min(newxpos,oldxpos); int y0pos = min(newypos,oldypos); int x1pos = max(newxpos,oldxpos)+1; int y1pos = max(newypos,oldypos)+1; redraw_area(x0pos,y0pos,x1pos,y1pos); display_area(x0pos*64,y0pos*64,x1pos*64+63,y1pos*64+63); } return FALSE; } void redraw_area(int posx0,int posy0,int posx1,int posy1) { // draw board for (int y=posy0;y<=posy1;y++) for (int x=posx0;x<=posx1;x++) { int tile = BoardMapData[y][x]; if (!tile) continue; tile--; int px = tile%4; int py = tile/4; px *= 64; py *= 64; gdk_pixbuf_render_to_drawable_alpha(boardpic, buffer, px,py, CORNER+x*64,CORNER+y*64, 64,64, GDK_PIXBUF_ALPHA_BILEVEL, 128, GDK_RGB_DITHER_NORMAL,0,0); } // draw pieces for (int p=0;p<3*8;p++) if (board.board[p] != EMPTY && p != prohibit_draw_pos) { int y = move2coord[p][0]; int x = move2coord[p][1]; if (xposx1 || yposy1) continue; int px = (board.board[p]+1)/2; int py = 0; px *= 64; gdk_pixbuf_render_to_drawable_alpha(piecespic, buffer, px,py, CORNER+x*64,CORNER+y*64, 64,64, GDK_PIXBUF_ALPHA_BILEVEL, 128, GDK_RGB_DITHER_NORMAL,0,0); } // draw moving piece if (moving_piece) { int px; if (moving_piece < 0) px = 0; else px = 64; gdk_pixbuf_render_to_drawable_alpha(piecespic, buffer, px,0, moving_x - move_offset_x, moving_y - move_offset_y, 64,64, GDK_PIXBUF_ALPHA_BILEVEL, 128, GDK_RGB_DITHER_NORMAL,0,0); } } void redraw_area_complete() { redraw_area(0,0,6,6); } void display_area(int x0,int y0,int w,int h) { gdk_draw_pixmap(area->window, area->style->fg_gc[GTK_WIDGET_STATE(area)], buffer, x0,y0, x0,y0, w,h); } void display_area_complete() { display_area(0,0, area->allocation.width, area->allocation.height); } gint expose_area(GtkWidget *widget, GdkEventExpose *event) { display_area(event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } void message_gui_state() { char buf[100]; char buf2[100]; if (board.next == WHITE) strcpy(buf,_("White: ")); else strcpy(buf,_("Black: ")); switch (gui_state) { case Start: case Moving: strcat(buf,_("move a piece")); break; case Set: sprintf(buf2,_("set your %d. piece"),(board.ply/2)+1); strcat(buf,buf2); break; case Take: strcat(buf,_("remove an opponent piece")); break; case Wait: strcat(buf,_("computer is thinking...")); break; case ClickToContinue: strcpy(buf,_("press button to start computer thinking")); break; case End: if (board.next == WHITE) strcpy(buf,_("Black has won!")); else strcpy(buf,_("White has won!")); break; } message(buf); } void message(gchar *message) { gnome_appbar_pop(GNOME_APPBAR (statusbar)); gnome_appbar_push(GNOME_APPBAR (statusbar), message); } void quit_game_cb(GtkWidget *widget, gpointer data) { algo.StopComputer(); if (buffer) { gdk_pixmap_unref(buffer); buffer=NULL; } if(boardpic) { gdk_pixbuf_unref(boardpic); boardpic=NULL; } if(piecespic) { gdk_pixbuf_unref(piecespic); piecespic=NULL; } //gtk_main_quit(); quit=true; } gint configure_area(GtkWidget *widget, GdkEventConfigure *event) { if (buffer) gdk_pixmap_unref(buffer); buffer = gdk_pixmap_new(area->window, area->allocation.width, area->allocation.height, -1); return TRUE; } void create_window() { window = gnome_app_new(APPNAME, N_(APPNAME_LONG)); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, TRUE); gtk_widget_realize(window); gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(quit_game_cb), NULL); gnome_app_create_menus(GNOME_APP(window), main_menu); gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(settings_menu[0].widget), animated_move); statusbar = gnome_appbar_new(TRUE, TRUE, GNOME_PREFERENCES_USER); gnome_app_set_statusbar(GNOME_APP(window), statusbar); gnome_app_install_menu_hints(GNOME_APP (window), main_menu); } void create_board() { area = gtk_drawing_area_new(); gnome_app_set_contents(GNOME_APP(window),area); gtk_drawing_area_size(GTK_DRAWING_AREA(area), CORNER*2 + SIZE*TILE_SIZE , SIZE*TILE_SIZE + CORNER*2); gtk_widget_set_events(area, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK); gtk_signal_connect(GTK_OBJECT(area), "expose_event", GTK_SIGNAL_FUNC(expose_area), NULL); gtk_signal_connect(GTK_OBJECT(area), "button_press_event", GTK_SIGNAL_FUNC(button_press_space), NULL); gtk_signal_connect(GTK_OBJECT(area), "configure_event", GTK_SIGNAL_FUNC(configure_area), NULL); gtk_signal_connect (GTK_OBJECT(area),"button_release_event", GTK_SIGNAL_FUNC(button_release_area), NULL); gtk_signal_connect (GTK_OBJECT(area), "motion_notify_event", GTK_SIGNAL_FUNC(button_motion_area), NULL); boardpic = gdk_pixbuf_new_from_file(GNOME_ICONDIR"/gnmm/board.png"); piecespic = gdk_pixbuf_new_from_file(GNOME_ICONDIR"/gnmm/pieces.png"); //gdk_pixbuf_add_alpha(boardpic,TRUE, 255,0,255); //int w = gdk_pixbuf_get_width (boardpic); //int h = gdk_pixbuf_get_height(boardpic); } void new_game_cb(GtkWidget *, gpointer) { game_init(); } void about_cb(GtkWidget *, gpointer) { GtkWidget *about; const gchar *authors[] = { "Dirk Farin", NULL }; about = gnome_about_new(_(APPNAME_LONG), VERSION, "(C) 2001 Dirk Farin", (const char **)authors, _("gnmm\n(Comments to: dirk.farin@gmx.de)"), NULL); gtk_widget_show(about); } void hint_cb(GtkWidget *, gpointer) { } int main(int argc, char **argv) { srand(time(NULL)); bindtextdomain(PACKAGE, GNOMELOCALEDIR); textdomain(PACKAGE); gnome_init(_(APPNAME), VERSION, argc, argv); gnome_window_icon_set_default_from_file (GNOME_ICONDIR"/gnmm-logo.png"); create_window(); create_board(); gtk_widget_show(window); InitMillTab(); game_init(); redraw_area_complete(); while (!quit) { // execute computer move if (gui_state==Wait) computer_move(); gtk_main_iteration(); } return 0; }