A new version of reversi

This commit is contained in:
JialianLee 2017-12-24 00:42:59 +08:00
parent dcf293d637
commit 162aa313b6

View File

@ -1,303 +1,202 @@
from __future__ import print_function import numpy as np
import numpy as np '''
Settings of the Reversi game.
'''
Settings of the Go game. (1, 1) is considered as the upper left corner of the board,
(size, 1) is the lower left
(1, 1) is considered as the upper left corner of the board, '''
(size, 1) is the lower left
'''
class Reversi:
def __init__(self, black=None, white=None):
def find_correct_moves(own, enemy): self.board = None # 8 * 8 board with 1 for black, -1 for white and 0 for blank
"""return legal moves""" self.color = None # 1 for black and -1 for white
left_right_mask = 0x7e7e7e7e7e7e7e7e # Both most left-right edge are 0, else 1 self.action = None # number in 0~63
top_bottom_mask = 0x00ffffffffffff00 # Both most top-bottom edge are 0, else 1 self.winner = None
mask = left_right_mask & top_bottom_mask self.black_win = None
mobility = 0 self.size = 8
mobility |= search_offset_left(own, enemy, left_right_mask, 1) # Left
mobility |= search_offset_left(own, enemy, mask, 9) # Left Top def _deflatten(self, idx):
mobility |= search_offset_left(own, enemy, top_bottom_mask, 8) # Top x = idx // self.size + 1
mobility |= search_offset_left(own, enemy, mask, 7) # Top Right y = idx % self.size + 1
mobility |= search_offset_right(own, enemy, left_right_mask, 1) # Right return (x, y)
mobility |= search_offset_right(own, enemy, mask, 9) # Bottom Right
mobility |= search_offset_right(own, enemy, top_bottom_mask, 8) # Bottom def _flatten(self, vertex):
mobility |= search_offset_right(own, enemy, mask, 7) # Left bottom x, y = vertex
return mobility if (x == 0) and (y == 0):
return 64
return (x - 1) * self.size + (y - 1)
def calc_flip(pos, own, enemy):
"""return flip stones of enemy by bitboard when I place stone at pos. def get_board(self, board=None):
self.board = board or np.zeros([8,8])
:param pos: 0~63 self.board[3, 3] = -1
:param own: bitboard (0=top left, 63=bottom right) self.board[4, 4] = -1
:param enemy: bitboard self.board[3, 4] = 1
:return: flip stones of enemy when I place stone at pos. self.board[4, 3] = 1
""" return self.board
f1 = _calc_flip_half(pos, own, enemy)
f2 = _calc_flip_half(63 - pos, rotate180(own), rotate180(enemy)) def _find_correct_moves(self, is_next=False):
return f1 | rotate180(f2) moves = []
if is_next:
color = 0 - self.color
def _calc_flip_half(pos, own, enemy): else:
el = [enemy, enemy & 0x7e7e7e7e7e7e7e7e, enemy & 0x7e7e7e7e7e7e7e7e, enemy & 0x7e7e7e7e7e7e7e7e] color = self.color
masks = [0x0101010101010100, 0x00000000000000fe, 0x0002040810204080, 0x8040201008040200] for i in range(64):
masks = [b64(m << pos) for m in masks] x, y = self._deflatten(i)
flipped = 0 valid = self._is_valid(x - 1, y - 1, color)
for e, mask in zip(el, masks): if valid:
outflank = mask & ((e | ~mask) + 1) & own moves.append(i)
flipped |= (outflank - (outflank != 0)) & mask return moves
return flipped
def _one_direction_valid(self, x, y, color):
if (x >= 0) and (x < self.size):
def search_offset_left(own, enemy, mask, offset): if (y >= 0) and (y < self.size):
e = enemy & mask if self.board[x, y] == color:
blank = ~(own | enemy) return True
t = e & (own >> offset) return False
t |= e & (t >> offset)
t |= e & (t >> offset) def _is_valid(self, x, y, color):
t |= e & (t >> offset) if self.board[x, y]:
t |= e & (t >> offset) return False
t |= e & (t >> offset) # Up to six stones can be turned at once for x_direction in [-1, 0, 1]:
return blank & (t >> offset) # Only the blank squares can be started for y_direction in [-1, 0, 1]:
new_x = x
new_y = y
def search_offset_right(own, enemy, mask, offset): flag = 0
e = enemy & mask while True:
blank = ~(own | enemy) new_x += x_direction
t = e & (own << offset) new_y += y_direction
t |= e & (t << offset) if self._one_direction_valid(new_x, new_y, 0 - color):
t |= e & (t << offset) flag = 1
t |= e & (t << offset) else:
t |= e & (t << offset) break
t |= e & (t << offset) # Up to six stones can be turned at once if self._one_direction_valid(new_x, new_y, color) and flag:
return blank & (t << offset) # Only the blank squares can be started return True
return False
def flip_vertical(x): def simulate_get_mask(self, state, action_set):
k1 = 0x00FF00FF00FF00FF history_boards, color = state
k2 = 0x0000FFFF0000FFFF self.board = np.reshape(history_boards[-1], (self.size, self.size))
x = ((x >> 8) & k1) | ((x & k1) << 8) self.color = color
x = ((x >> 16) & k2) | ((x & k2) << 16) valid_moves = self._find_correct_moves()
x = (x >> 32) | b64(x << 32) print(valid_moves)
return x if not len(valid_moves):
invalid_action_mask = action_set[0:-1]
else:
def b64(x): invalid_action_mask = []
return x & 0xFFFFFFFFFFFFFFFF for action in action_set:
if action not in valid_moves:
invalid_action_mask.append(action)
def bit_count(x): return invalid_action_mask
return bin(x).count('1')
def simulate_step_forward(self, state, action):
self.board = state[0].copy()
def bit_to_array(x, size): self.board = np.reshape(self.board, (self.size, self.size))
"""bit_to_array(0b0010, 4) -> array([0, 1, 0, 0])""" self.color = state[1]
return np.array(list(reversed((("0" * size) + bin(x)[2:])[-size:])), dtype=np.uint8) self.action = action
if self.action == 64:
valid_moves = self._find_correct_moves(is_next=True)
def flip_diag_a1h8(x): if not len(valid_moves):
k1 = 0x5500550055005500 self._game_over()
k2 = 0x3333000033330000 return None, self.winner * self.color
k4 = 0x0f0f0f0f00000000 else:
t = k4 & (x ^ b64(x << 28)) return [self.board, 0 - self.color], 0
x ^= t ^ (t >> 28) self._step()
t = k2 & (x ^ b64(x << 14)) return [self.board, 0 - self.color], 0
x ^= t ^ (t >> 14)
t = k1 & (x ^ b64(x << 7)) def _game_over(self):
x ^= t ^ (t >> 7) black_num, white_num = self._number_of_black_and_white()
return x self.black_win = black_num - white_num
if self.black_win > 0:
self.winner = 1
def rotate90(x): elif self.black_win < 0:
return flip_diag_a1h8(flip_vertical(x)) self.winner = -1
else:
self.winner = 0
def rotate180(x):
return rotate90(rotate90(x)) def _number_of_black_and_white(self):
black_num = 0
white_num = 0
class Reversi: board_list = np.reshape(self.board, self.size ** 2)
def __init__(self, black=None, white=None): for i in range(len(board_list)):
self.black = black or (0b00001000 << 24 | 0b00010000 << 32) if board_list[i] == 1:
self.white = white or (0b00010000 << 24 | 0b00001000 << 32) black_num += 1
self.board = None # 8 * 8 board with 1 for black, -1 for white and 0 for blank elif board_list[i] == -1:
self.color = None # 1 for black and -1 for white white_num += 1
self.action = None # number in 0~63 return black_num, white_num
self.winner = None
self.black_win = None def _step(self):
self.size = 8 if self.action < 0 or self.action > 63:
raise ValueError("Action not in the range of [0,63]!")
def get_board(self, black=None, white=None): if self.action is None:
self.black = black or (0b00001000 << 24 | 0b00010000 << 32) raise ValueError("Action is None!")
self.white = white or (0b00010000 << 24 | 0b00001000 << 32) x, y = self._deflatten(self.action)
self.board = self.bitboard2board() valid = self._flip(x -1, y - 1)
return self.board if not valid:
raise ValueError("Illegal action!")
def is_valid(self, is_next=False):
self.board2bitboard() def _flip(self, x, y):
own, enemy = self.get_own_and_enemy(is_next) valid = 0
mobility = find_correct_moves(own, enemy) self.board[x, y] = self.color
valid_moves = bit_to_array(mobility, 64) for x_direction in [-1, 0, 1]:
valid_moves = np.argwhere(valid_moves) for y_direction in [-1, 0, 1]:
valid_moves = list(np.reshape(valid_moves, len(valid_moves))) new_x = x
return valid_moves new_y = y
flag = 0
def simulate_get_mask(self, state, action_set): while True:
history_boards, color = state new_x += x_direction
board = history_boards[-1] new_y += y_direction
self.board = board if self._one_direction_valid(new_x, new_y, 0 - self.color):
self.color = color flag = 1
valid_moves = self.is_valid() else:
# TODO it seems that the pass move is not considered break
if not len(valid_moves): if self._one_direction_valid(new_x, new_y, self.color) and flag:
invalid_action_mask = action_set[0:-1] valid = 1
else: flip_x = x
invalid_action_mask = [] flip_y = y
for action in action_set: while True:
if action not in valid_moves: flip_x += x_direction
invalid_action_mask.append(action) flip_y += y_direction
return invalid_action_mask if self._one_direction_valid(flip_x, flip_y, 0 - self.color):
self.board[flip_x, flip_y] = self.color
def simulate_step_forward(self, state, action): else:
self.board = state[0] break
self.color = state[1] if valid:
self.board2bitboard() return True
self.action = action else:
if self.action == 64: return False
valid_moves = self.is_valid(is_next=True)
if not len(valid_moves): def executor_do_move(self, history, latest_boards, board, color, vertex):
self._game_over() self.board = np.reshape(board, (self.size, self.size))
return None, self.winner * self.color self.color = color
else: self.action = self._flatten(vertex)
return [self.board, 0 - self.color], 0 if self.action == 64:
self.step() valid_moves = self._find_correct_moves(is_next=True)
new_board = self.bitboard2board() if not len(valid_moves):
return [new_board, 0 - self.color], 0 return False
else:
def executor_do_move(self, board, color, vertex): return True
self.board = board else:
self.color = color self._step()
self.board2bitboard() return True
self.action = self._flatten(vertex)
if self.action == 64: def executor_get_score(self, board):
valid_moves = self.is_valid(is_next=True) self.board = board
if not len(valid_moves): self._game_over()
return False if self.black_win is not None:
else: return self.black_win
return True else:
else: raise ValueError("Game not finished!")
self.step()
new_board = self.bitboard2board()
for i in range(64): if __name__ == "__main__":
board[i] = new_board[i] reversi = Reversi()
return True # board = reversi.get_board()
# print(board)
def executor_get_score(self, board): # state, value = reversi.simulate_step_forward([board, -1], 20)
self.board = board # print(state[0])
self._game_over() # print("board")
if self.black_win is not None: # print(board)
return self.black_win # r = reversi.executor_get_score(board)
else: # print(r)
raise ValueError("Game not finished!")
def board2bitboard(self):
count = 1
if self.board is None:
raise 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:
raise ValueError("Action not in the range of [0,63]!")
if self.action is None:
raise ValueError("Action is None!")
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)
raise ValueError("Illegal action!")
own ^= flipped
own |= 1 << self.action
enemy ^= flipped
self.set_own_and_enemy(own, enemy)
def _game_over(self):
# self.done = True
if self.winner is None:
black_num, white_num = self.number_of_black_and_white
self.black_win = black_num - white_num
if self.black_win > 0:
self.winner = 1
elif self.black_win < 0:
self.winner = -1
else:
self.winner = 0
def illegal_move_to_lose(self, action):
self._game_over()
def get_own_and_enemy(self, is_next=False):
if is_next:
color = 0 - self.color
else:
color = self.color
if color == 1:
own, enemy = self.black, self.white
elif 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
def _deflatten(self, idx):
x = idx // self.size + 1
y = idx % self.size + 1
return (x, y)
def _flatten(self, vertex):
x, y = vertex
if (x == 0) and (y == 0):
return 64
return (x - 1) * self.size + (y - 1)
@property
def number_of_black_and_white(self):
return bit_count(self.black), bit_count(self.white)