def to_cboard(self): cboard = CBoard() for x, y in self.board.history: move_id = x + 5 * y while cboard.cube[move_id] != EMPTY: move_id += 25 cboard.move(move_id) return cboard
def generator(): easy = open(easyname, mode='w') hard = open(hardname, mode='w') counter = 0 seed = -1 while counter < 1000: seed += 1 rand = Random(seed) board = CBoard() gameover = False for i in range(rand.randint(8, 18)): move_id = rand.choice(list(board.get_valid_moves())) if is_win(board, move_id): gameover = True break board.move(move_id) if gameover: continue for _ in range(1): move_id = rand.choice(list(board.get_valid_moves())) if is_win(board, move_id): break board.move(move_id) results = negamax_full(board, 4) file = hard if abs(results[0][1]) < WIN_SCORE else easy counter += 1 line = ",".join([str(x) for x in board.history[0:board.round]]) print("{} @{}".format(line, counter)) file.write(line + "\n") easy.close() hard.close()
def test_check_win(): board = CBoard() board.move(1) board.move(6) board.move(2) board.move(7) board.move(3) board.move(8) assert is_win(board, 0) assert is_win(board, 4)
def test_easy_prevent(): board = CBoard() board.move(7) board.move(24) board.move(8) # must now move to 6 (or 5 or 9) to prevent a direct loss result = negamax_full(board) assert result[0][0] in [5, 6, 9] assert result[1][0] in [5, 6, 9] assert result[2][0] in [5, 6, 9]
def negamax(board: CBoard, opponent_move_id, depth, alpha, beta): board.nodes_counter += 1 if depth == 0: return 0 # not the best estimation :) # ok let's do the actual move board.move(opponent_move_id) possible_moves = board.get_valid_moves() # quick lookahead of 1 for next_move_id in possible_moves: if is_win(board, next_move_id): board.undo_move() return MAX_SCORE - board.round # we win, yay! # quick lookahead of 2 forced_move_id = -1 board.current_player ^= 1 # quick zero-move for next_move_id in possible_moves: if is_win(board, next_move_id): if forced_move_id != -1: # we will loose next round board.current_player ^= 1 # reset to actual player board.undo_move() return -(MAX_SCORE - board.round - 1) forced_move_id = next_move_id board.current_player ^= 1 if forced_move_id != -1: sorted_moves = [forced_move_id] else: # TODO: actual sorting sorted_moves = possible_moves best_score = -MAX_SCORE for next_move_id in sorted_moves: current_score = -negamax(board, next_move_id, depth - 1, -beta, -alpha) if current_score > best_score: best_score = current_score if best_score > alpha: alpha = best_score if alpha >= beta: board.beta_cutoffs += 1 break # cut-off board.undo_move() return best_score
def run_testset(filename): counter, solved, nodes, t, cutoff = 0, 0, 0, 0, 0 with open(filename) as file: for line in file: board = CBoard() for move_id in map(lambda x: int(x), line.split(",")): board.move(move_id) start = datetime.datetime.now() resut = negamax_full(board, depth=4) end = datetime.datetime.now() micro = (end - start).microseconds # stats if abs(resut[0][1]) > WIN_SCORE: solved += 1 counter += 1 nodes += board.nodes_counter cutoff += board.beta_cutoffs t += micro print("{}/{}: nodes={} cutoffs={} ms={}" .format(counter, solved, round(nodes / counter), round(cutoff / counter), round(t / counter / 100)))
def test_easy_win(): board = CBoard() board.move(1) board.move(6) board.move(2) board.move(7) # must now move to 3 for a win assert negamax_full(board)[0][0] == 3
def negamax_full(board: CBoard, depth=3): """ :param board: :param depth: search depth :return: """ sorted_moves = board.get_valid_moves() results = [] for next_move_id in sorted_moves: if is_win(board, next_move_id): current_score = MAX_SCORE - board.round else: current_score = -negamax(board, next_move_id, depth, -MAX_SCORE, MAX_SCORE) results.append((next_move_id, current_score)) return sorted(results, key=lambda x: x[1], reverse=True)
def test_undo(): board = CBoard() board.move(1) before = deepcopy(board.cube) board.move(24) board.move(14) board.move(5) board.undo_move() board.undo_move() board.undo_move() assert before == board.cube
def test_check_no_win(): board = CBoard() board.move(1) assert not is_win(board, 0)
return best_score def negamax_full(board: CBoard, depth=3): """ :param board: :param depth: search depth :return: """ sorted_moves = board.get_valid_moves() results = [] for next_move_id in sorted_moves: if is_win(board, next_move_id): current_score = MAX_SCORE - board.round else: current_score = -negamax(board, next_move_id, depth, -MAX_SCORE, MAX_SCORE) results.append((next_move_id, current_score)) return sorted(results, key=lambda x: x[1], reverse=True) if __name__ == "__main__": test_board = CBoard() test_board.move(1) test_board.move(6) test_board.move(2) test_board.move(7) print(test_board) best_move = negamax_full(test_board) print(best_move)