/* * Othello.java 1.0 96/12/25 */ /* 1. All of the brain power of this program comes from John Thornley's ANSI C version of Othello /******************************************************************************/ /* */ /* Othello Game */ /* ------------ */ /* */ /* Written by: John Thornley, Computer Science Dept., Caltech. */ /* Last modified: Saturday 3rd September 1994. */ /* */ /* Purpose: Program to play a complete game of Othello with the user. */ /* */ /******************************************************************************/ /* 2. /******************************************************************************/ /* Ported to Java: 24th December 1996 by Chris Heath */ /* Last modified: 27th December 1996 */ /* I give you permission to modify this as much as you like... */ /* ... so long as you keep my name in the credits! */ /* ... and that you email me (cheath@umich.edu) if you've done */ /* something worth looking at. */ /******************************************************************************/ /*############################################################################*/ /*############################################################################*/ import java.awt.*; import java.awt.image.*; import java.net.*; import java.applet.*; /******************************************************************************/ /* SMALL (auxilliary) CLASSES */ /******************************************************************************/ // Note about consistency checking: since the java compiler does not // have a preprocessor which would allow us to easily turn checking // off for the final release, we instead use search and replace as follows. // To turn checking on, search for //ch and replace with /*//ch*/ // Vice versa for turning checking off. //ch class check { //ch static void assert(boolean should_be_true) { //ch if (!should_be_true) throw new NullPointerException(); //ch } //ch //ch static boolean valid_score(int score){ //ch boolean result; //ch int stable_pieces = ((score&0x00ff0000)>>16)-65; //ch int mobility = ((score&0x0000ff00)>>8)-65; //ch int total_pieces = (score&0x000000ff)-65; //ch //ch if (stable_pieces < -65 || 65 < stable_pieces) //ch result = false; //ch else if (mobility < -65 || 65 < mobility) //ch result = false; //ch else if (total_pieces < -65 || 65 < total_pieces) //ch result = false; //ch else //ch result = true; //ch return result; //ch } //ch //ch static boolean valid_color(int color) { //ch return color == game.BLACK || color == game.WHITE; //ch } //ch //ch static boolean valid_occupant(int occupant) { //ch return occupant == game.NO_COLOR || //ch occupant == game.BLACK || occupant == game.WHITE; //ch } //ch //ch static boolean valid_game(game the_game) { //ch boolean result = check.valid_color(the_game.to_move); //ch for (int r = 0; result && r <= 7; r++) //ch for (int c = 0; result && c <= 7; c++) //ch result = result && check.valid_occupant(the_game.board[r][c]); //ch return result; //ch } //ch //ch }; /*----------------------------------------------------------------------------*/ class stable_set { int/*square*/ items[/*64*/]; int next, last; boolean member[/*8*/][/*8*/]; int black_pieces, white_pieces; public stable_set() { items = new int/*square*/[64]; member = new boolean[8][8]; } }; /******************************************************************************/ /* CLASS GAME */ /******************************************************************************/ class game { static final int MAX_MOVES=48; static final int NO_MOVE=-1; static final int GAME_NOT_STARTED=-2; static final int GAME_FINISHED=-1; static final int NO_COLOR=0; static final int BLACK=1; static final int WHITE=2; static final int pos_infinity = make_score(65, 65, 65); static final int neg_infinity = make_score(-65, -65, -65); int/*color*/ to_move, robust_to_move; int/*occupant*/ board[/*8*/][/*8*/]; int/*square*/ the_moves[] = new int[game.MAX_MOVES]; int n_moves; int/*square*/ last_move; /* last move by the computer */ int black_count, white_count; volatile int game_count = 0; boolean skipped_move; Othello the_app; public game(Othello the_app) { /* A game created with this constructor is stored in the Othello (applet) object. the_app is used by DoTurn to do various applet-related tasks. */ this.the_app = the_app; board = new int/*occupant*/[8][8]; clear_game(); } public game(game a) { /* A game created with this constructor is a temporary copy of the game position used in the minimax algorithm to store its look-ahead moves. */ the_app = null; board = new int/*occupant*/[8][8]; to_move = a.to_move; robust_to_move = a.robust_to_move; for (int r=0; r<8; r++) for (int c=0; c<8; c++) board[r][c] = a.board[r][c]; black_count = a.black_count; white_count = a.white_count; last_move = a.last_move; skipped_move = a.skipped_move; n_moves = 0; } /*------------------------------------------------------------------class game---*/ static boolean in_range(int i, int low, int high) { return low <= i && i <= high; } static int sign(int i) { if (i < 0) return -1; else if (i > 0) return +1; else return 0; } /*------------------------------------------------------------------class game---*/ boolean valid_move(int/*square*/ the_move) { //ch check.assert(check.valid_game(this)); boolean result; int row = (the_move&0xff00)>>8; int column = (the_move&0x00ff); if (!in_range(row, 0, 7) || !in_range(column, 0, 7) || board[row][column] != game.NO_COLOR) result = false; else { int delta_r, delta_c; result = false; for (delta_r = -1; !result && delta_r <= +1; delta_r++) for (delta_c = -1; !result && delta_c <= +1; delta_c++) if (delta_r != 0 || delta_c != 0) { int r = row + delta_r; int c = column + delta_c; while (in_range(r, 0, 7) && in_range(c, 0, 7) && board[r][c] == other_color(to_move) ) { r = r + delta_r; c = c + delta_c; } result = in_range(r, 0, 7) && in_range(c, 0, 7) && (r != row + delta_r || c != column + delta_c) && board[r][c] == to_move; } } return result; } /*------------------------------------------------------------------class game---*/ void generate_moves() /* also counts the pieces */ { int r,c; // square/*move*/ m = new square(); //ch check.assert(check.valid_game(this)); n_moves = 0; black_count = 0; white_count = 0; for (r = 0; r <= 7; r++) for (c = 0; c <= 7; c++) { switch (board[r][c]) { case NO_COLOR: int m = make_square(r, c); if (valid_move(m)) { //ch check.assert(n_moves < MAX_MOVES); the_moves[n_moves] = m; n_moves = n_moves + 1; } break; case BLACK: black_count = black_count + 1; break; case WHITE: white_count = white_count + 1; break; } } } /*------------------------------------------------------------------class game---*/ //ch boolean terminal_game() { //ch /* only used for checking with check.assert */ //ch boolean result; //ch //ch check.assert(check.valid_game(this)); //ch //ch generate_moves(); //ch if (n_moves > 0) //ch result = false; //ch else { //ch to_move = other_color(to_move); //ch generate_moves(); //ch result = (n_moves == 0); //ch to_move = other_color(to_move); //ch n_moves = 0; //ch } //ch return result; //ch } /*------------------------------------------------------------------class game---*/ void make_stable(int row, int column, stable_set stable) { //ch check.assert(in_range(row, 0, 7) && in_range(column, 0, 7)); //ch check.assert(in_range(stable.last, -1, 62)); //ch check.assert(!stable.member[row][column]); //ch check.assert(board[row][column] == game.BLACK || //ch board[row][column] == game.WHITE ); stable.last = stable.last + 1; stable.items[stable.last] = make_square(row, column); stable.member[row][column] = true; switch (board[row][column]) { case BLACK: stable.black_pieces = stable.black_pieces + 1; break; case WHITE: stable.white_pieces = stable.white_pieces + 1; break; } } /*------------------------------------------------------------------class game---*/ static int make_square(int row, int column) { return (row<<8) + column; } /*------------------------------------------------------------------class game---*/ static int make_score(int stable_pieces, /* (black - white) stable pieces */ int mobility, /* (black - white) mobility */ int total_pieces) /* (black - white) total pieces */ { return ((stable_pieces+65)<<16) + ((mobility+65)<<8) + total_pieces+65; } /*------------------------------------------------------------------class game---*/ static int/*color*/ other_color(int color) { //ch check.assert(check.valid_color(color)); return (BLACK+WHITE) - color; } /*------------------------------------------------------------------class game---*/ int/*score*/ static_evaluation(int prior_mobility) { int r, c, i; int total_black_pieces = 0, total_white_pieces = 0; int current_mobility = 0; int row_count[] = new int[8]; int column_count[] = new int[8]; int pos_diagonal_count[] = new int[15]; int neg_diagonal_count[] = new int[15]; boolean row_filled[] = new boolean[8]; boolean column_filled [] = new boolean[8]; boolean pos_diagonal_filled[] = new boolean[15]; boolean neg_diagonal_filled[] = new boolean[15]; stable_set stable = new stable_set(); //ch check.assert(check.valid_game(this)); //ch check.assert(0 <= prior_mobility); for (r = 0; r <= 7; r++) for (c = 0; c <= 7; c++) { // square/*move*/ m = new square(r, c); switch (board[r][c]) { case NO_COLOR: if (valid_move(make_square(r, c))) current_mobility = current_mobility + 1; break; case BLACK: total_black_pieces = total_black_pieces + 1; break; case WHITE: total_white_pieces = total_white_pieces + 1; break; } if (board[r][c] != NO_COLOR) { row_count[r] = row_count[r] + 1; column_count[c] = column_count[c] + 1; pos_diagonal_count[r + c] = pos_diagonal_count[r + c] + 1; neg_diagonal_count[r - c + 7] = neg_diagonal_count[r - c + 7] + 1; } stable.member[r][c] = false; } if (prior_mobility == 0 && current_mobility == 0) { stable.black_pieces = total_black_pieces; stable.white_pieces = total_white_pieces; } else { for (i = 0; i <= 7; i++) { row_filled[i] = (row_count[i] == 8); column_filled[i] = (column_count[i] == 8); } for (i = 0; i <= 14; i++) { pos_diagonal_filled[i] = (pos_diagonal_count[i] == 8 - Math.abs(i - 7)); neg_diagonal_filled[i] = (neg_diagonal_count[i] == 8 - Math.abs(i - 7)); } stable.next = 0; stable.last = -1; for (r = 0; r <= 7; r++) if (row_filled[r]) for (c = 0; c <= 7; c++) if (column_filled[c]) if (pos_diagonal_filled[r + c] && neg_diagonal_filled[r - c + 7]) make_stable(r, c, stable); for (r = 0; r <= 7; r = r + 7) for (c = 0; c <= 7; c = c + 7) if (board[r][c] != game.NO_COLOR && !stable.member[r][c]) make_stable(r, c, stable); while (stable.next <= stable.last) { int row = (stable.items[stable.next]&0xff00)>>8; int column = stable.items[stable.next]&0x00ff; stable.next = stable.next + 1; for (r = Math.max(0, row - 1); r <= Math.min(7, row + 1); r++) for (c = Math.max(0, column - 1); c <= Math.min(7, column + 1); c++) if (board[r][c] != game.NO_COLOR && !stable.member[r][c] && (c == 0 || c == 7 || row_filled[r] || board[r][c - 1] == board[r][c] && stable.member[r][c - 1] || board[r][c + 1] == board[r][c] && stable.member[r][c + 1]) && (r == 0 || r == 7 || column_filled[c] || board[r - 1][c] == board[r][c] && stable.member[r - 1][c] || board[r + 1][c] == board[r][c] && stable.member[r + 1][c]) && (c == 0 || c == 7 || r == 0 || r == 7 || pos_diagonal_filled[r + c] || board[r + 1][c - 1] == board[r][c] && stable.member[r + 1][c - 1] || board[r - 1][c + 1] == board[r][c] && stable.member[r - 1][c + 1]) && (c == 0 || c == 7 || r == 0 || r == 7 || neg_diagonal_filled[r - c + 7] || board[r + 1][c + 1] == board[r][c] && stable.member[r + 1][c + 1] || board[r - 1][c - 1] == board[r][c] && stable.member[r - 1][c - 1])) make_stable(r, c, stable); } } return make_score(stable.black_pieces - stable.white_pieces, (to_move == BLACK) ? current_mobility - prior_mobility : prior_mobility - current_mobility, total_black_pieces - total_white_pieces); } /*------------------------------------------------------------------class game---*/ int/*score*/ terminal_evaluation() { //ch check.assert(check.valid_game(this)); //ch check.assert(terminal_game()); count_pieces(); return make_score(black_count - white_count, 0, black_count - white_count); } /*------------------------------------------------------------------class game---*/ public synchronized void set_occupant(int color, int r, int c, int delta_r, int delta_c) /* This is synchronized with clear_game() so that this routine cannot muck up what clear_game is doing. (clear_game sets to_move to GAME_NOT_STARTED) Also DoTurn is synchronized with clear_game. */ { if (to_move < BLACK) return; board[r][c] = color; if (the_app != null) the_app.the_game_board.paint_piece(null, null, r, c, delta_r, delta_c); } /*------------------------------------------------------------------class game---*/ void apply_move(int/*square*/ the_move) /* Note this does not call set_occupant directly, because moves are displayed asynchronously in a ShowMoveThread. */ { int row = (the_move&0xff00)>>8; int column = (the_move&0x00ff); //ch check.assert(check.valid_game(this)); //ch check.assert(valid_move(the_move)); board[row][column] = to_move; int delta_r = -1, delta_c = 1; do { int r, c; boolean found; r = row + delta_r; c = column + delta_c; while (in_range(r, 0, 7) && in_range(c, 0, 7) && board[r][c] == other_color(to_move) ) { r = r + delta_r; c = c + delta_c; } found = in_range(r, 0, 7) && in_range(c, 0, 7) && (r != row + delta_r || c != column + delta_c) && board[r][c] == to_move; if (found) { r = row + delta_r; c = column + delta_c; while (board[r][c] == other_color(to_move) ) { board[r][c] = to_move; r = r + delta_r; c = c + delta_c; } } int old_delta_r = delta_r; delta_r = sign(delta_r+delta_c); /*proceed around in a circle*/ delta_c = sign(delta_c-old_delta_r); /*(add tangent vector, and project onto "unit square")*/ } while (delta_r != -1 || delta_c != 1); to_move = other_color(to_move); } /*------------------------------------------------------------------class game---*/ void count_pieces() { int r, c; //ch check.assert(check.valid_game(this)); black_count = 0; white_count = 0; for (r = 0; r <= 7; r++) for (c = 0; c <= 7; c++) switch (board[r][c]) { case NO_COLOR: break; case BLACK: black_count = black_count + 1; break; case WHITE: white_count = white_count + 1; break; } } /*------------------------------------------------------------------class game---*/ public synchronized void clear_game() { game_count++; to_move = GAME_NOT_STARTED; robust_to_move = GAME_NOT_STARTED; for (int r=0; r<8; r++) for (int c=0; c<8; c++) board[r][c] = game.NO_COLOR; black_count = 0; white_count = 0; last_move = NO_MOVE; } /*------------------------------------------------------------------class game---*/ void start_game() { for (int r=0; r<8; r++) for (int c=0; c<8; c++) board[r][c] = NO_COLOR; to_move = BLACK; board[3][3] = WHITE; board[3][4] = BLACK; board[4][3] = BLACK; board[4][4] = WHITE; the_app.the_game_board.paint(null); /* clear board and draw counters */ /* (nec when computer=BLACK, probably good to do it all the time) */ the_app.start_turn_internal(); the_app.start_turn_display(); } /*------------------------------------------------------------------class game---*/ public synchronized void DoTurn(int/*color*/ who, int/*square*/ chosen_move, int game_count) { if (game_count != this.game_count) return; /* game was cleared since the event happened */ if (who != robust_to_move || who != the_app.display_position.robust_to_move) { /* not your turn! */ the_app.play(the_app.getCodeBase(), "audio/beep.au"); return; } if (valid_move(chosen_move)) { /* undo all the extra screen formatting */ int old_last_move = last_move; last_move = NO_MOVE; if (old_last_move != NO_MOVE) { int row = (old_last_move&0xff00)>>8; int column = old_last_move&0x00ff; /* repaint that square */ set_occupant(board[row][column], row, column, 0, 0); } game old_position = new game(this); old_position.the_app = the_app; the_app.display_position = old_position; if (who == the_app.players_color) /* get rid of the crosses */ the_app.the_game_board.paint(null); /* do that move - quickly */ if (who == other_color(the_app.players_color)) last_move = chosen_move; apply_move(chosen_move); /* now work out whose turn it is next and start their turn */ the_app.start_turn_internal(); /* Start the thread to show the move on-screen */ /* When it's finished, it will change the_app.display_position back to what it should be and display it. */ ShowMoveThread thr = new ShowMoveThread("ShowMove", chosen_move, the_app); thr.start(); } else the_app.play(the_app.getCodeBase(), "audio/beep.au"); } }; /******************************************************************************/ /* CLASS SHOWMOVETHREAD */ /* */ /* I use a separate Thread for displaying the move piece by piece. */ /* This is so that the repaint and clicking events are dealt with quickly */ /******************************************************************************/ class ShowMoveThread extends Thread { Othello the_app; int chosen_move; int game_count; public ShowMoveThread(String str, int chosen_move, Othello the_app) { super(str); this.the_app = the_app; this.chosen_move = chosen_move; /* used to verify that the user didn't start a new game during the show_move*/ game_count = the_app.position.game_count; } /*--------------------------------------------------------class ShowMoveThread*/ void sleep(int millis) throws InterruptedException { Thread.sleep(millis); if (game_count != the_app.position.game_count) throw new InterruptedException(); } /*--------------------------------------------------------class ShowMoveThread*/ void show_move(int/*square*/ the_move) throws InterruptedException { int row = (the_move&0xff00)>>8; int column = (the_move&0x00ff); game g = the_app.display_position; if (game_count != the_app.position.game_count) throw new InterruptedException(); //ch check.assert(check.valid_game(g)); //ch check.assert(g.valid_move(the_move)); int delta_r = -1, delta_c = 1; do { int r, c; boolean found; /* This is inside the loop because it draws the arrow */ g.set_occupant(g.to_move, row, column, delta_r, delta_c); sleep(60); r = row + delta_r; c = column + delta_c; while (game.in_range(r, 0, 7) && game.in_range(c, 0, 7) && g.board[r][c] == game.other_color(g.to_move) ) { r = r + delta_r; c = c + delta_c; } found = game.in_range(r, 0, 7) && game.in_range(c, 0, 7) && (r != row + delta_r || c != column + delta_c) && g.board[r][c] == g.to_move; if (found) { r = row + delta_r; c = column + delta_c; while (g.board[r][c] == game.other_color(g.to_move) ) { g.set_occupant(g.to_move, r, c, 0, 0); sleep(15); r = r + delta_r; c = c + delta_c; } sleep(150); } int old_delta_r = delta_r; delta_r = game.sign(delta_r+delta_c); /*proceed around in a circle*/ delta_c = game.sign(delta_c-old_delta_r); /*(add tangent vector, and project onto "unit square")*/ } while (delta_r != -1 || delta_c != 1); g.set_occupant(g.to_move, row, column, 0, 0); sleep(150); } /*--------------------------------------------------------class ShowMoveThread*/ public void run() { try { show_move(chosen_move); } catch (InterruptedException e) {return;} the_app.display_position = the_app.position; the_app.start_turn_display(); } }; /******************************************************************************/ /* CLASS BRAINTHREAD */ /* */ /* I use a separate Thread for the intense computational work when */ /* the computer does its minimax thing. This is so that the repaint */ /* works after the user's turn. Repaint seems to be asyncronous, so */ /* the repaint doesn't actually happen until we give the original */ /* Thread some spare time! */ /******************************************************************************/ class BrainThread extends Thread { Othello the_app; int game_count; public BrainThread(String str, Othello the_app) { super(str); this.the_app = the_app; /* used to verify we are still playing the same game after emerging from deep thought! */ game_count = the_app.position.game_count; } /*-----------------------------------------------------------class BrainThread*/ void sort(int n, int out_of_order, int scores[], int order[]) /* Precondition: 0 <= n and out_of_order in {-1, +1} and */ /* for all i in 0 .. n-1 : valid_score(scores[i]) and */ /* for all i in 0 .. n-1 : order[i] = i. */ /* Postcondition: equal(scores, scores'in) and permutation(order, order'in) and */ /* for all i in 1 .. n-1 : */ /* game.sign(scores[order[i-1]] - scores[order[i]]) != out_of_order. */ { int i, j; //ch check.assert(0 <= n); //ch check.assert(out_of_order == -1 || out_of_order == +1); //ch for (i = 0; i < n; i++) check.assert(check.valid_score(scores[i])); //ch for (i = 0; i < n; i++) check.assert(order[i] == i); for (i = 1; i < n; i++) { int x = order[i]; for (j = i - 1; j >= 0; j--) if (game.sign(scores[order[j]] - scores[x]) != out_of_order) break; else order[j + 1] = order[j]; order[j + 1] = x; } } /*-----------------------------------------------------------class BrainThread*/ int minimax(game position, int prior_mobility, int depth, int max_depth, boolean move_ordering, boolean alpha_beta, int/*score*/ black_best, int/*score*/ white_best, int[]/*square*/ chosen_move, int[]/*score*/ chosen_score) /* Note: chosen_move and chosen_score are "var" parameters, ie do not assign to them by chosen_move = blah. On the other hand, black_best and white_best are "value" params so you _must_ assign to them! */ /* returns the node count */ { //ch check.assert(check.valid_game(position)); //ch check.assert(0 <= prior_mobility); //ch check.assert(0 <= depth && depth <= max_depth); //ch check.assert(depth != 0 || !position.terminal_game()); //ch check.assert(check.valid_score(black_best) && check.valid_score(white_best)); //ch check.assert(game.neg_infinity <= black_best && //ch black_best < game.pos_infinity); //ch check.assert(game.neg_infinity < white_best && //ch white_best <= game.pos_infinity); //ch check.assert(black_best < white_best); int node_count = 1; if (depth == max_depth) chosen_score[0] = position.static_evaluation(prior_mobility); else { game new_position; int[]/*square*/ the_move = new int[1]; int[]/*score*/ the_score = new int[1]; int/*square*/ best_move = game.NO_MOVE; int/*score*/ best_score; int subtree_count; position.generate_moves(); if (position.n_moves == 0) { if (prior_mobility == 0) chosen_score[0] = position.terminal_evaluation(); else { new_position = new game(position); new_position.to_move = game.other_color(position.to_move); subtree_count = minimax(new_position, 0, depth + 1, max_depth, move_ordering, alpha_beta, black_best, white_best, the_move, the_score); node_count = node_count + subtree_count; chosen_score[0] = the_score[0]; } } else { game new_positions[]; new_positions = new game[position.n_moves]; int/*score*/ static_scores[]; static_scores = new int[position.n_moves]; int order[]; order = new int[position.n_moves]; boolean cutoff; int m; for (m = 0; m < position.n_moves; m++) order[m] = m; if (move_ordering && depth < max_depth - 1) { for (m = 0; m < position.n_moves; m++) { new_positions[m] = new game(position); new_positions[m].apply_move(position.the_moves[m]); static_scores[m] = new_positions[m].static_evaluation(position.n_moves); } switch (position.to_move) { case game.BLACK: sort(position.n_moves, -1, static_scores, order); break; case game.WHITE: sort(position.n_moves, +1, static_scores, order); break; } } best_score = (position.to_move == game.BLACK) ? game.neg_infinity : game.pos_infinity; cutoff = false; for (m = 0; !cutoff && m < position.n_moves; m++) { if (move_ordering && depth < max_depth - 1) subtree_count = minimax(new_positions[order[m]], position.n_moves, depth + 1, max_depth, move_ordering, alpha_beta, black_best, white_best, the_move, the_score); else { new_position = new game(position); new_position.apply_move(position.the_moves[order[m]]); subtree_count = minimax(new_position, position.n_moves, depth + 1, max_depth, false, alpha_beta, black_best, white_best, the_move, the_score); } node_count = node_count + subtree_count; switch (position.to_move) { case game.BLACK: if (the_score[0] > best_score) { best_move = position.the_moves[order[m]]; best_score = the_score[0]; if (alpha_beta && best_score > black_best) if (best_score >= white_best) cutoff = true; else black_best = best_score; } break; case game.WHITE: if (the_score[0] < best_score) { best_move = position.the_moves[order[m]]; best_score = the_score[0]; if (alpha_beta && best_score < white_best) if (best_score <= black_best) cutoff = true; else white_best = best_score; } break; } } chosen_move[0] = best_move; chosen_score[0] = best_score; } } return node_count; } /*-----------------------------------------------------------class BrainThread*/ public void run() { int[]/*score*/ chosen_score = new int[1]; int[]/*square*/ chosen_move = new int[1]; int node_count; if (the_app.position.n_moves == 1) { chosen_move[0] = the_app.position.the_moves[0]; node_count = 0; } else node_count = minimax(the_app.position, 0, 0, the_app.max_depth, true, true, game.neg_infinity, game.pos_infinity, chosen_move, chosen_score); /* relay the move back to the game (if it's still the same game being played!) */ while (the_app.display_position != the_app.position) ; /* just wait for the display to catch up with the actual situation */ the_app.position.DoTurn(game.other_color(the_app.players_color), chosen_move[0], game_count); } }; /******************************************************************************/ /* USER INTERFACE CLASSES */ /******************************************************************************/ class StatusPanel extends Panel { Othello the_app; int the_color; public StatusPanel(Othello the_app, int the_color) { this.the_app = the_app; this.the_color = the_color; } public void paint(Graphics g) { if (g == null) g = getGraphics(); Dimension d = size(); int count; g.clearRect(0, 0, d.width, d.height); if (the_color == game.BLACK) { g.setColor(Color.black); count = the_app.display_position.black_count; } else { g.setColor(Color.white); count = the_app.display_position.white_count; } g.fillOval(4, 4, 12, 12); g.setColor(Color.black); g.drawOval(4, 4, 12, 12); g.drawString(count + " pieces", 20, 14); if (the_app.display_position.robust_to_move == game.GAME_FINISHED) { int my_pieces, opp_pieces; if (the_color == game.BLACK) { my_pieces = the_app.display_position.black_count; opp_pieces = the_app.display_position.white_count; } else { my_pieces = the_app.display_position.white_count; opp_pieces = the_app.display_position.black_count; } if (the_color == the_app.players_color) { if (my_pieces > opp_pieces) g.drawString("You won!", 4, 36); else if (my_pieces < opp_pieces) g.drawString("You lost.", 4, 36); else g.drawString("Tied game.", 4, 36); } else { if (my_pieces > opp_pieces) g.drawString("I won!", 4, 36); else if (my_pieces < opp_pieces) g.drawString("I lost.", 4, 36); else g.drawString("Tied game.", 4, 36); } } else if (the_app.display_position.robust_to_move == the_color) { if (the_color == the_app.players_color) g.drawString("Your move", 4, 36); else { g.drawString("My move", 4, 36); g.drawString("(please wait)", 8, 50); } } else if (the_app.display_position.robust_to_move == game.other_color(the_color)) { if (the_app.display_position.skipped_move) { g.drawString("(no move", 4, 36); g.drawString("is possible)", 10, 50); } } g.drawRect(0, 0, d.width-1, d.height-1); } }; /******************************************************************************/ class GameBoard extends Panel { Othello the_app; Color background; public GameBoard(Othello the_app) { this.the_app = the_app; background = new Color(105,189,140/*157*/); } /*-------------------------------------------------------------class GameBoard---*/ private Rectangle get_board_rect() { Rectangle result = new Rectangle(); Dimension d = size(); result.width = result.height = Math.min(d.width-2, d.height-3); result.x = (d.width - result.width) / 2; result.y = (d.height - result.height) / 2; return result; } /*-------------------------------------------------------------class GameBoard---*/ public synchronized void paint_piece(Graphics g, Rectangle br, int r, int c, int delta_r, int delta_c) { /* This is synchronized with paint() so that if user presses clear, we finish drawing here before starting to clear. */ if (g == null) g = getGraphics(); if (br == null) br = get_board_rect(); int color = the_app.display_position.board[r][c]; if (color == game.NO_COLOR) return; /* should almost never happen... but this is called asynchronously! */ int piece_x = br.x+br.width*c/8 + br.width/48; int piece_y = br.y+br.height*r/8 + br.height/48; int piece_width = br.width/12; int piece_height = br.height/12; if (color == game.WHITE) g.setColor(Color.white); g.fillOval(piece_x, piece_y, piece_width, piece_height); g.setColor(Color.black); g.drawOval(piece_x, piece_y, piece_width, piece_height); if (color == game.BLACK) g.setXORMode(Color.white); int diag_offs = (int)(piece_width*(0.5-0.3535534)); switch (delta_r*3+delta_c) { case 0: if (the_app.display_position.last_move == game.make_square(r, c)) { g.drawLine(piece_x+piece_width/2, piece_y, piece_x+piece_width/2, piece_y+3); g.drawLine(piece_x+piece_width/2, piece_y+piece_height, piece_x+piece_width/2, piece_y+piece_height-3); g.drawLine(piece_x, piece_y+piece_height/2, piece_x+3, piece_y+piece_height/2); g.drawLine(piece_x+piece_width, piece_y+piece_height/2, piece_x+piece_width-3, piece_y+piece_height/2); } break; case (-1*3 +1): g.drawLine(piece_x+diag_offs, piece_y+piece_height-diag_offs, piece_x+piece_width-diag_offs, piece_y+diag_offs); g.drawLine(piece_x+piece_width-diag_offs, piece_y+diag_offs, piece_x+piece_width-diag_offs-piece_width/6, piece_y+diag_offs+piece_width/18); g.drawLine(piece_x+piece_width-diag_offs, piece_y+diag_offs, piece_x+piece_width-diag_offs-piece_width/18, piece_y+diag_offs+piece_width/6); break; case (0*3 +1): g.drawLine(piece_x, piece_y+piece_height/2, piece_x+piece_width, piece_y+piece_height/2); g.drawLine(piece_x+piece_width, piece_y+piece_height/2, piece_x+piece_width-piece_width/6, piece_y+piece_height/2+piece_height/12); g.drawLine(piece_x+piece_width, piece_y+piece_height/2, piece_x+piece_width-piece_width/6, piece_y+piece_height/2-piece_height/12); break; case (+1*3 +1): g.drawLine(piece_x+diag_offs, piece_y+diag_offs, piece_x+piece_width-diag_offs, piece_y+piece_height-diag_offs); g.drawLine(piece_x+piece_width-diag_offs, piece_y+piece_height-diag_offs, piece_x+piece_width-diag_offs-piece_width/6, piece_y+piece_height-diag_offs-piece_width/18); g.drawLine(piece_x+piece_width-diag_offs, piece_y+piece_height-diag_offs, piece_x+piece_width-diag_offs-piece_width/18, piece_y+piece_height-diag_offs-piece_width/6); break; case (+1*3 +0): g.drawLine(piece_x+piece_width/2, piece_y, piece_x+piece_width/2, piece_y+piece_height); g.drawLine(piece_x+piece_width/2, piece_y+piece_height, piece_x+piece_width/2+piece_width/12, piece_y+piece_height-piece_height/6); g.drawLine(piece_x+piece_width/2, piece_y+piece_height, piece_x+piece_width/2-piece_width/12, piece_y+piece_height-piece_height/6); break; case (+1*3 -1): g.drawLine(piece_x+diag_offs, piece_y+piece_height-diag_offs, piece_x+piece_width-diag_offs, piece_y+diag_offs); g.drawLine(piece_x+diag_offs, piece_y+piece_height-diag_offs, piece_x+diag_offs+piece_width/6, piece_y+piece_height-diag_offs-piece_width/18); g.drawLine(piece_x+diag_offs, piece_y+piece_height-diag_offs, piece_x+diag_offs+piece_width/18, piece_y+piece_height-diag_offs-piece_width/6); break; case (0*3 -1): g.drawLine(piece_x, piece_y+piece_height/2, piece_x+piece_width, piece_y+piece_height/2); g.drawLine(piece_x, piece_y+piece_height/2, piece_x+piece_width/6, piece_y+piece_height/2+piece_height/12); g.drawLine(piece_x, piece_y+piece_height/2, piece_x+piece_width/6, piece_y+piece_height/2-piece_height/12); break; case (-1*3 -1): g.drawLine(piece_x+diag_offs, piece_y+diag_offs, piece_x+piece_width-diag_offs, piece_y+piece_height-diag_offs); g.drawLine(piece_x+diag_offs, piece_y+diag_offs, piece_x+diag_offs+piece_width/6, piece_y+diag_offs+piece_width/18); g.drawLine(piece_x+diag_offs, piece_y+diag_offs, piece_x+diag_offs+piece_width/18, piece_y+diag_offs+piece_width/6); break; case (-1*3 +0): g.drawLine(piece_x+piece_width/2, piece_y, piece_x+piece_width/2, piece_y+piece_height); g.drawLine(piece_x+piece_width/2, piece_y, piece_x+piece_width/2+piece_width/12, piece_y+piece_height/6); g.drawLine(piece_x+piece_width/2, piece_y, piece_x+piece_width/2-piece_width/12, piece_y+piece_height/6); break; } g.setPaintMode(); } /*-------------------------------------------------------------class GameBoard---*/ /** * Paint the board. */ public synchronized void paint(Graphics g) { if (g == null) g = getGraphics(); Rectangle br = get_board_rect(); Rectangle piece = new Rectangle(); g.setColor(background); g.fillRect(br.x, br.y, br.width, br.height); // play(getCodeBase(), "audio/beep.au"); g.setColor(Color.black); for (int i = 0; i <= 8; i++) g.drawLine(br.x+br.width*i/8, br.y, br.x+br.width*i/8, br.y+br.height); for (int i = 0; i <= 8; i++) g.drawLine(br.x, br.y+br.height*i/8, br.x+br.width, br.y+br.height*i/8); int m = 0; for (int r = 0 ; r < 8 ; r++) { for (int c = 0 ; c < 8 ; c++) { int color = the_app.display_position.board[r][c]; if (color == game.NO_COLOR) { /* maybe draw a cross if a valid_move */ if (the_app.display_position.to_move == the_app.players_color && m < the_app.display_position.n_moves && the_app.display_position.the_moves[m] == game.make_square(r, c)) { g.drawLine(br.x+br.width*c/8 + br.width/16-2, br.y+br.height*r/8 + br.height/16-2, br.x+br.width*c/8 + br.width/16+2, br.y+br.height*r/8 + br.height/16+2); g.drawLine(br.x+br.width*c/8 + br.width/16-2, br.y+br.height*r/8 + br.height/16+2, br.x+br.width*c/8 + br.width/16+2, br.y+br.height*r/8 + br.height/16-2); m++; } } else paint_piece(g, br, r, c, 0, 0); } } } /*-------------------------------------------------------------class GameBoard---*/ /** * The user has clicked in the game board. Figure out where * and call DoTurn, which does the move if it's legal. */ public boolean mouseUp(Event evt, int x, int y) { int/*square*/ chosen_move; if (the_app.display_position.to_move < game.BLACK) return true; /* game not in limbo */ //ch check.assert(check.valid_color(the_app.display_position.to_move)); // Figure out the row/column Rectangle br = get_board_rect(); chosen_move = game.make_square((y - br.y) * 8 / br.height, (x - br.x) * 8 / br.width); the_app.position.DoTurn(the_app.players_color, chosen_move, the_app.position.game_count); return true; } }; /******************************************************************************/ /* CLASS OTHELLO */ /* */ /* This is the main applet class - it sets up the board, creates a game */ /* object, handles the button I/O, and that's about it! */ /******************************************************************************/ public class Othello extends Applet { game position = new game(this); volatile game display_position = position; /* usually, but not always == position */ GameBoard the_game_board; StatusPanel black_status_panel, white_status_panel; Button start_button; int/*color*/ players_color = game.BLACK; /*default value*/ int max_depth = 3; /*default value*/ Choice BW_choice, level_choice; /*---------------------------------------------------------------class Othello---*/ /** * Initialize the applet. (load images & sounds?) */ public void init() { setLayout(new BorderLayout(10,0)); add("Center", the_game_board = new GameBoard(this)); Panel p = new Panel(); GridBagLayout layout = new GridBagLayout(); p.setLayout(layout); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.fill = GridBagConstraints.NONE; constraints.insets.right = 1; Panel q = new Panel(); q.setLayout(new BorderLayout()); q.add("North", new Label("Your color:")); BW_choice = new Choice(); BW_choice.addItem("Black"); BW_choice.addItem("White"); q.add("Center", BW_choice); q.add("South", new Label("(black starts)")); constraints.gridheight = 3; constraints.weighty = 0.0; layout.setConstraints(q, constraints); p.add(q); q = new Panel(); q.setLayout(new BorderLayout()); q.add("North", new Label("Level:", Label.LEFT)); level_choice = new Choice(); level_choice.addItem("Beginner"); level_choice.addItem("Getting better"); level_choice.addItem("Pretty good"); level_choice.addItem("Challenging"); level_choice.addItem("A perfectionist"); level_choice.select(max_depth-1); q.add("Center", level_choice); constraints.gridheight = 2; constraints.weighty = 0.0; layout.setConstraints(q, constraints); p.add(q); start_button = new Button("Start game"); constraints.gridheight = 1; constraints.weighty = 1.0; layout.setConstraints(start_button, constraints); p.add(start_button); black_status_panel = new StatusPanel(this, game.BLACK); constraints.gridheight = 2; constraints.weighty = 2.0; constraints.fill = GridBagConstraints.BOTH; constraints.insets.bottom=3; layout.setConstraints(black_status_panel, constraints); p.add(black_status_panel); white_status_panel = new StatusPanel(this, game.WHITE); layout.setConstraints(white_status_panel, constraints); p.add(white_status_panel); add("East", p); start_turn_display(); /* repaints */ } /*---------------------------------------------------------------class Othello---*/ public synchronized void start_turn_internal() /* Internally, the player's turn starts immediately after the last move has been _calculated_. */ /* This is synchronized with start_turn_display so that if the computer comes back with the next move very quickly (ie before the previous start_turn_display has terminated), it doesn't garble the status information. */ { if (position.to_move >= game.BLACK) { /* work out whose turn it is */ position.generate_moves(); if (position.n_moves == 0) { position.to_move = game.other_color(position.to_move); position.generate_moves(); if (position.n_moves > 0) { position.skipped_move = true; } else position.to_move = game.GAME_FINISHED; } else position.skipped_move = false; position.robust_to_move = position.to_move; /* start the computer's turn if appropriate */ if (position.to_move == game.other_color(players_color)) { /* Note: one thread is for one turn only. If the computer gets to have two turns in a row, then the first BrainThread will create another one for the second turn. */ BrainThread thr = new BrainThread("Brain", this); thr.start(); } } } /*---------------------------------------------------------------class Othello---*/ public synchronized void start_turn_display() /* After the last move has finished displaying, we update the screen to reflect start of new turn */ { if (position.to_move < game.BLACK) the_game_board.paint(null); black_status_panel.paint(null); white_status_panel.paint(null); if (position.to_move == game.GAME_NOT_STARTED) start_button.setLabel("Start Game"); else if (position.to_move == game.GAME_FINISHED) start_button.setLabel("New Game"); else start_button.setLabel("Clear"); if (position.to_move == players_color) { /* draw the crosses */ the_game_board.paint(null); } } /*---------------------------------------------------------------class Othello---*/ public boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (display_position.to_move >= game.BLACK) { /* clear board */ position.clear_game(); display_position = position; start_turn_display(); } else position.start_game(); } else if (e.target == BW_choice) { if (display_position.to_move < game.BLACK) players_color = BW_choice.getSelectedIndex()+game.BLACK; else BW_choice.select(players_color-game.BLACK); } else if (e.target == level_choice) { if (display_position.to_move < game.BLACK) max_depth = level_choice.getSelectedIndex()+1; else level_choice.select(max_depth-1); } return true; } /*---------------------------------------------------------------class Othello---*/ public String getAppletInfo() { return "Othello by Chris Heath and John Thornley"; } }