|
| 1 | +from util import memoize, run_search_function |
| 2 | + |
| 3 | +def basic_evaluate(board): |
| 4 | + """ |
| 5 | + The original focused-evaluate function from the lab. |
| 6 | + The original is kept because the lab expects the code in the lab to be modified. |
| 7 | + """ |
| 8 | + if board.is_game_over(): |
| 9 | + # If the game has been won, we know that it must have been |
| 10 | + # won or ended by the previous move. |
| 11 | + # The previous move was made by our opponent. |
| 12 | + # Therefore, we can't have won, so return -1000. |
| 13 | + # (note that this causes a tie to be treated like a loss) |
| 14 | + score = -1000 |
| 15 | + else: |
| 16 | + score = board.longest_chain(board.get_current_player_id()) * 10 |
| 17 | + # Prefer having your pieces in the center of the board. |
| 18 | + for row in range(6): |
| 19 | + for col in range(7): |
| 20 | + if board.get_cell(row, col) == board.get_current_player_id(): |
| 21 | + score -= abs(3-col) |
| 22 | + elif board.get_cell(row, col) == board.get_other_player_id(): |
| 23 | + score += abs(3-col) |
| 24 | + |
| 25 | + return score |
| 26 | + |
| 27 | + |
| 28 | +def get_all_next_moves(board): |
| 29 | + """ Return a generator of all moves that the current player could take from this position """ |
| 30 | + from connectfour import InvalidMoveException |
| 31 | + |
| 32 | + for i in xrange(board.board_width): |
| 33 | + try: |
| 34 | + yield (i, board.do_move(i)) |
| 35 | + except InvalidMoveException: |
| 36 | + pass |
| 37 | + |
| 38 | +def is_terminal(depth, board): |
| 39 | + """ |
| 40 | + Generic terminal state check, true when maximum depth is reached or |
| 41 | + the game has ended. |
| 42 | + """ |
| 43 | + return depth <= 0 or board.is_game_over() |
| 44 | + |
| 45 | +def minimax_find_board_value(board, depth, eval_fn, |
| 46 | + get_next_moves_fn=get_all_next_moves, |
| 47 | + is_terminal_fn=is_terminal): |
| 48 | + """ |
| 49 | + Minimax helper function: Return the minimax value of a particular board, |
| 50 | + given a particular depth to estimate to |
| 51 | + """ |
| 52 | + if is_terminal_fn(depth, board): |
| 53 | + return eval_fn(board) |
| 54 | + |
| 55 | + best_val = None |
| 56 | + |
| 57 | + for move, new_board in get_next_moves_fn(board): |
| 58 | + val = -1 * minimax_find_board_value(new_board, depth-1, eval_fn, |
| 59 | + get_next_moves_fn, is_terminal_fn) |
| 60 | + if best_val == None or val > best_val: |
| 61 | + best_val = val |
| 62 | + |
| 63 | + return best_val |
| 64 | + |
| 65 | +def minimax(board, depth, eval_fn = basic_evaluate, |
| 66 | + get_next_moves_fn = get_all_next_moves, |
| 67 | + is_terminal_fn = is_terminal, |
| 68 | + verbose = True): |
| 69 | + """ |
| 70 | + Do a minimax search to the specified depth on the specified board. |
| 71 | +
|
| 72 | + board -- the ConnectFourBoard instance to evaluate |
| 73 | + depth -- the depth of the search tree (measured in maximum distance from a leaf to the root) |
| 74 | + eval_fn -- (optional) the evaluation function to use to give a value to a leaf of the tree; see "focused_evaluate" in the lab for an example |
| 75 | +
|
| 76 | + Returns an integer, the column number of the column that the search determines you should add a token to |
| 77 | + """ |
| 78 | + |
| 79 | + best_val = None |
| 80 | + |
| 81 | + for move, new_board in get_next_moves_fn(board): |
| 82 | + val = -1 * minimax_find_board_value(new_board, depth-1, eval_fn, |
| 83 | + get_next_moves_fn, |
| 84 | + is_terminal_fn) |
| 85 | + if best_val == None or val > best_val[0]: |
| 86 | + best_val = (val, move, new_board) |
| 87 | + |
| 88 | + if verbose: |
| 89 | + print "MINIMAX: Decided on column %d with rating %d" % (best_val[1], best_val[0]) |
| 90 | + |
| 91 | + return best_val[1] |
| 92 | + |
| 93 | + |
| 94 | +basic_player = lambda board: minimax(board, depth=4, eval_fn=basic_evaluate) |
| 95 | +progressive_deepening_player = lambda board: run_search_function(board, search_fn=minimax, eval_fn=basic_evaluate) |
0 commit comments