From 5c29dad26367ba76c1fbe4a19213c0bf9ae7391e Mon Sep 17 00:00:00 2001 From: JialianLee Date: Fri, 22 Dec 2017 01:57:48 +0800 Subject: [PATCH] An initial version for Reversi --- AlphaGo/reversi.py | 252 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 AlphaGo/reversi.py diff --git a/AlphaGo/reversi.py b/AlphaGo/reversi.py new file mode 100644 index 0000000..49d0e9a --- /dev/null +++ b/AlphaGo/reversi.py @@ -0,0 +1,252 @@ +from __future__ import print_function +import numpy as np + +''' +Settings of the Go game. + +(1, 1) is considered as the upper left corner of the board, +(size, 1) is the lower left +''' + + +def find_correct_moves(own, enemy): + """return legal moves""" + left_right_mask = 0x7e7e7e7e7e7e7e7e # Both most left-right edge are 0, else 1 + top_bottom_mask = 0x00ffffffffffff00 # Both most top-bottom edge are 0, else 1 + mask = left_right_mask & top_bottom_mask + mobility = 0 + mobility |= search_offset_left(own, enemy, left_right_mask, 1) # Left + mobility |= search_offset_left(own, enemy, mask, 9) # Left Top + mobility |= search_offset_left(own, enemy, top_bottom_mask, 8) # Top + mobility |= search_offset_left(own, enemy, mask, 7) # Top Right + mobility |= search_offset_right(own, enemy, left_right_mask, 1) # Right + mobility |= search_offset_right(own, enemy, mask, 9) # Bottom Right + mobility |= search_offset_right(own, enemy, top_bottom_mask, 8) # Bottom + mobility |= search_offset_right(own, enemy, mask, 7) # Left bottom + return mobility + + +def calc_flip(pos, own, enemy): + """return flip stones of enemy by bitboard when I place stone at pos. + + :param pos: 0~63 + :param own: bitboard (0=top left, 63=bottom right) + :param enemy: bitboard + :return: flip stones of enemy when I place stone at pos. + """ + assert 0 <= pos <= 63, f"pos={pos}" + f1 = _calc_flip_half(pos, own, enemy) + f2 = _calc_flip_half(63 - pos, rotate180(own), rotate180(enemy)) + return f1 | rotate180(f2) + + +def _calc_flip_half(pos, own, enemy): + el = [enemy, enemy & 0x7e7e7e7e7e7e7e7e, enemy & 0x7e7e7e7e7e7e7e7e, enemy & 0x7e7e7e7e7e7e7e7e] + masks = [0x0101010101010100, 0x00000000000000fe, 0x0002040810204080, 0x8040201008040200] + masks = [b64(m << pos) for m in masks] + flipped = 0 + for e, mask in zip(el, masks): + outflank = mask & ((e | ~mask) + 1) & own + flipped |= (outflank - (outflank != 0)) & mask + return flipped + + +def search_offset_left(own, enemy, mask, offset): + e = enemy & mask + blank = ~(own | enemy) + t = e & (own >> offset) + t |= e & (t >> offset) + t |= e & (t >> offset) + t |= e & (t >> offset) + t |= e & (t >> offset) + t |= e & (t >> offset) # Up to six stones can be turned at once + return blank & (t >> offset) # Only the blank squares can be started + + +def search_offset_right(own, enemy, mask, offset): + e = enemy & mask + blank = ~(own | enemy) + t = e & (own << offset) + t |= e & (t << offset) + t |= e & (t << offset) + t |= e & (t << offset) + t |= e & (t << offset) + t |= e & (t << offset) # Up to six stones can be turned at once + return blank & (t << offset) # Only the blank squares can be started + + +def flip_vertical(x): + k1 = 0x00FF00FF00FF00FF + k2 = 0x0000FFFF0000FFFF + x = ((x >> 8) & k1) | ((x & k1) << 8) + x = ((x >> 16) & k2) | ((x & k2) << 16) + x = (x >> 32) | b64(x << 32) + return x + + +def b64(x): + return x & 0xFFFFFFFFFFFFFFFF + + +def bit_count(x): + return bin(x).count('1') + + +def bit_to_array(x, size): + """bit_to_array(0b0010, 4) -> array([0, 1, 0, 0])""" + return np.array(list(reversed((("0" * size) + bin(x)[2:])[-size:])), dtype=np.uint8) + + +def flip_diag_a1h8(x): + k1 = 0x5500550055005500 + k2 = 0x3333000033330000 + k4 = 0x0f0f0f0f00000000 + t = k4 & (x ^ b64(x << 28)) + x ^= t ^ (t >> 28) + t = k2 & (x ^ b64(x << 14)) + x ^= t ^ (t >> 14) + t = k1 & (x ^ b64(x << 7)) + x ^= t ^ (t >> 7) + return x + + +def rotate90(x): + return flip_diag_a1h8(flip_vertical(x)) + + +def rotate180(x): + return rotate90(rotate90(x)) + + +class Reversi: + def __init__(self, black=None, white=None): + self.black = black or (0b00001000 << 24 | 0b00010000 << 32) + self.white = white or (0b00010000 << 24 | 0b00001000 << 32) + self.board = None # 8 * 8 board with 1 for black, -1 for white and 0 for blank + self.color = None # 1 for black and -1 for white + self.action = None # number in 0~63 + self.winner = None + + def simulate_is_valid(self, board, color): + self.board = board + self.color = color + self.board2bitboard() + own, enemy = self.get_own_and_enemy() + mobility = find_correct_moves(own, enemy) + valid_moves = bit_to_array(mobility, 64) + valid_moves = list(np.reshape(valid_moves, len(valid_moves))) + return valid_moves + + def simulate_step_forward(self, board, color, vertex): + self.board = board + self.color = color + self.board2bitboard() + self.vertex2action(vertex) + step_forward = self.step() + if step_forward: + new_board = self.bitboard2board() + return new_board + + def executor_do_move(self, board, color, vertex): + self.board = board + self.color = color + self.board2bitboard() + self.vertex2action(vertex) + step_forward = self.step() + if step_forward: + new_board = self.bitboard2board() + return new_board + + def executor_get_score(self, board): + self.board = board + self._game_over() + if self.winner is not None: + return self.winner, 0 - self.winner + else: + ValueError("Game not finished!") + + def board2bitboard(self): + count = 1 + if self.board is None: + ValueError("None board!") + self.black = 0 + self.white = 0 + for i in range(64): + if self.board[i] == 1: + self.black |= count + elif self.board[i] == -1: + self.white |= count + count *= 2 + + def vertex2action(self, vertex): + x, y = vertex + if x == 0 and y == 0: + self.action = None + else: + self.action = 8 * (x - 1) + y - 1 + + def bitboard2board(self): + board = [] + black = bit_to_array(self.black, 64) + white = bit_to_array(self.white, 64) + for i in range(64): + if black[i]: + board.append(1) + elif white[i]: + board.append(-1) + else: + board.append(0) + return board + + def step(self): + if self.action < 0 or self.action > 63: + ValueError("Wrong action!") + if self.action is None: + return False + + own, enemy = self.get_own_and_enemy() + + flipped = calc_flip(self.action, own, enemy) + if bit_count(flipped) == 0: + self.illegal_move_to_lose(self.action) + return False + own ^= flipped + own |= 1 << self.action + enemy ^= flipped + + self.set_own_and_enemy(own, enemy) + return True + + def _game_over(self): + # self.done = True + if self.winner is None: + black_num, white_num = self.number_of_black_and_white + if black_num > white_num: + self.winner = 1 + elif black_num < white_num: + self.winner = -1 + else: + self.winner = 0 + + def illegal_move_to_lose(self, action): + logger.warning(f"Illegal action={action}, No Flipped!") + self._game_over() + + def get_own_and_enemy(self): + if self.color == 1: + own, enemy = self.black, self.white + elif self.color == -1: + own, enemy = self.white, self.black + else: + own, enemy = None, None + return own, enemy + + def set_own_and_enemy(self, own, enemy): + if self.color == 1: + self.black, self.white = own, enemy + else: + self.white, self.black = own, enemy + + @property + def number_of_black_and_white(self): + return bit_count(self.black), bit_count(self.white)