Skip to content

Commit

Permalink
Merge pull request kobanium#86 from kobanium/support#83
Browse files Browse the repository at this point in the history
support fixed_handicap command
  • Loading branch information
kobanium authored Dec 30, 2023
2 parents fa80979 + 0fa54c1 commit 30d8158
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 5 deletions.
63 changes: 60 additions & 3 deletions board/go_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,56 @@ def put_stone(self, pos: int, color: Stone) -> NoReturn:
self.record.save(self.moves, color, pos, self.positional_hash)
self.moves += 1

def put_handicap_stone(self, pos: int, color: Stone) -> NoReturn:
"""指定された座標に指定された色の置き石を置く。
Args:
pos (int): 石を置く座標。
color (Stone): 置く石の色。
"""
opponent_color = Stone.get_opponent_color(color)

self.board[pos] = color
self.pattern.put_stone(pos, color)
self.positional_hash = affect_stone_hash(self.positional_hash, pos, color)

neighbor4 = self.get_neighbor4(pos)

connection = []
prisoner = 0

for neighbor in neighbor4:
if self.board[neighbor] == color:
self.strings.remove_liberty(neighbor, pos)
connection.append(self.strings.get_id(neighbor))
elif self.board[neighbor] == opponent_color:
self.strings.remove_liberty(neighbor, pos)
if self.strings.get_num_liberties(neighbor) == 0:
removed_stones = self.strings.remove_string(self.board, neighbor)
prisoner += len(removed_stones)
for removed_pos in removed_stones:
self.pattern.remove_stone(removed_pos)
self.positional_hash = affect_string_hash(self.positional_hash, \
removed_stones, opponent_color)

if color == Stone.BLACK:
self.prisoner[0] += prisoner
elif color == Stone.WHITE:
self.prisoner[1] += prisoner

if len(connection) == 0:
self.strings.make_string(self.board, pos, color)
if prisoner == 1 and self.strings.get_num_liberties(pos) == 1:
self.ko_move = self.moves
self.ko_pos = self.strings.string[self.strings.get_id(pos)].lib[0]
elif len(connection) == 1:
self.strings.add_stone(self.board, pos, color, connection[0])
else:
self.strings.connect_string(self.board, pos, color, connection)

# 着手した時に記録
self.record.save_handicap(pos)

def _is_suicide(self, pos: int, color: Stone) -> bool:
"""自殺手か否かを判定する。
自殺手ならTrue、そうでなければFalseを返す。
Expand Down Expand Up @@ -477,9 +527,8 @@ def get_to_move(self) -> Stone:
"""
if self.moves == 1:
return Stone.BLACK
else:
last_move_color, _, _ = self.record.get(self.moves - 1)
return Stone.get_opponent_color(last_move_color)
last_move_color, _, _ = self.record.get(self.moves - 1)
return Stone.get_opponent_color(last_move_color)

def get_move_history(self) -> List[Tuple[Stone, int, np.array]]:
"""着手の履歴を取得する。
Expand All @@ -489,6 +538,14 @@ def get_move_history(self) -> List[Tuple[Stone, int, np.array]]:
"""
return [self.record.get(m) for m in range(1, self.moves)]

def get_handicap_history(self) -> List[int]:
"""置き石の座標を取得する。
Returns:
List[int]: 置き石の座標のリスト。
"""
return self.record.handicap_pos[:]

def count_score(self) -> int: # pylint: disable=R0912
"""領地を簡易的にカウントする。
Expand Down
83 changes: 83 additions & 0 deletions board/handicap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""置き石の座標。
"""
from typing import List


handicap_coordinate_map = {
9 : {
2 : ["G7", "C3",],
3 : ["C7", "G7", "C3"],
4 : ["C7", "G7", "C3", "G3"],
5 : ["C7", "G7", "E5", "C3", "G3"],
6 : ["C7", "G7", "C5", "G5", "C3", "G3"],
7 : ["C7", "G7", "C5", "E5", "G5", "C3", "G3"],
8 : ["C7", "E7", "G7", "C5", "G5", "C3", "E3", "G3"],
9 : ["C7", "E7", "G7", "C5", "E5", "G5", "C3", "E3", "G3"],
},
11 : {
2 : ["J9", "C3"],
3 : ["C9", "J9", "C3"],
4 : ["C9", "J9", "C3", "J3"],
5 : ["C9", "J9", "F6", "C3", "J3"],
6 : ["C9", "J9", "C6", "J6", "C3", "J3"],
7 : ["C9", "J9", "C6", "F6", "J6", "C3", "J3"],
8 : ["C9", "F9", "J9", "C6", "J6", "C3", "F3", "J3"],
9 : ["C9", "F9", "J9", "C6", "F6", "J6", "C3", "F3", "J3"],
},
13 : {
2 : ["K10", "D4"],
3 : ["D10", "K10", "D4"],
4 : ["D10", "K10", "D4", "K4"],
5 : ["D10", "K10", "G7", "D4", "K4"],
6 : ["D10", "K10", "D7", "K7", "D4", "K4"],
7 : ["D10", "K10", "D7", "G7", "K7", "D4", "K4"],
8 : ["D10", "G10", "K10", "D7", "K7", "D4", "G4", "K4"],
9 : ["D10", "G10", "K10", "D7", "G7", "K7", "D4", "G4", "K4"],
},
15 : {
2 : ["M12", "D4"],
3 : ["D12", "M12", "D4"],
4 : ["D12", "M12", "D4", "M4"],
5 : ["D12", "M12", "H8", "D4", "M4"],
6 : ["D12", "M12", "D8", "M8", "D4", "M4"],
7 : ["D12", "M12", "D8", "H8", "M8", "D4", "M4"],
8 : ["D12", "H12", "M12", "D8", "M8", "D4", "H4", "M4"],
9 : ["D12", "H12", "M12", "D8", "H8", "M8", "D4", "H4", "M4"],
},
17 : {
2 : ["O14", "D4"],
3 : ["D14", "O14", "D4"],
4 : ["D14", "O14", "D4", "O4"],
5 : ["D14", "O14", "J9", "D4", "O4"],
6 : ["D14", "O14", "D9", "O9", "D4", "O4"],
7 : ["D14", "O14", "D9", "J9", "O9", "D4", "O4"],
8 : ["D14", "J14", "O14", "D9", "O9", "D4", "J4", "O4"],
9 : ["D14", "J14", "O14", "D9", "J9", "O9", "D4", "J4", "O4"],
},
19 : {
2 : ["Q16", "D4"],
3 : ["D16", "Q16", "D4"],
4 : ["D16", "Q16", "D4", "Q4"],
5 : ["D16", "Q16", "K10", "D4", "Q4"],
6 : ["D16", "Q16", "D10", "Q10", "D4", "Q4"],
7 : ["D16", "Q16", "D10", "K10", "Q10", "D4", "Q4"],
8 : ["D16", "K16", "Q16", "D10", "Q10", "D4", "K4", "Q4"],
9 : ["D16", "K16", "Q16", "D10", "K10", "Q10", "D4", "K4", "Q4"],
},
}


def get_handicap_coordinates(size: int, handicaps: int) -> List[int]:
"""置き石の座標リストを取得する。
Args:
size (int): 碁盤のサイズ。
handicaps (int): 置き石の数。
Returns:
List[int]: 置き石の座標リスト。
"""
if size in handicap_coordinate_map and \
handicaps in handicap_coordinate_map[size]:
return handicap_coordinate_map[size][handicaps]
return None
10 changes: 10 additions & 0 deletions board/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ def __init__(self):
self.color = [Stone.EMPTY] * MAX_RECORDS
self.pos = [PASS] * MAX_RECORDS
self.hash_value = np.zeros(shape=MAX_RECORDS, dtype=np.uint64)
self.handicap_pos = []

def clear(self) -> NoReturn:
"""データを初期化する。
"""
self.color = [Stone.EMPTY] * MAX_RECORDS
self.pos = [PASS] * MAX_RECORDS
self.hash_value.fill(0)
self.handicap_pos = []

def save(self, moves: int, color: Stone, pos: int, hash_value: np.array) -> NoReturn:
"""着手の履歴の記録する。
Expand All @@ -41,6 +43,14 @@ def save(self, moves: int, color: Stone, pos: int, hash_value: np.array) -> NoRe
else:
print_err("Cannot save move record.")

def save_handicap(self, pos: int) -> NoReturn:
"""置き石の座標を記録する。
Args:
pos (int): 置き石の座標。
"""
self.handicap_pos.append(pos)

def has_same_hash(self, hash_value: np.array) -> bool:
"""同じハッシュ値があるかを確認する。
Expand Down
41 changes: 39 additions & 2 deletions gtp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from board.constant import PASS, RESIGN
from board.coordinate import Coordinate
from board.go_board import GoBoard
from board.handicap import get_handicap_coordinates
from board.stone import Stone
from common.print_console import print_err, print_out
from gtp.gogui import GoguiAnalyzeCommand, display_policy_distribution, \
Expand Down Expand Up @@ -65,6 +66,7 @@ def __init__(self, board_size: int, superko: bool, model_file_path: str, \
"komi",
"showboard",
"load_sgf",
"fixed_handicap",
"gogui-analyze_commands",
"lz-analyze",
"lz-genmove_analyze",
Expand Down Expand Up @@ -167,14 +169,21 @@ def _play(self, color: str, pos: str) -> NoReturn:
def _undo(self) -> NoReturn:
"""undoコマンドを処理する。
"""
# 一旦クリアして初手から直前手まで打ち直す非効率実装
history = self.board.get_move_history()
if not history:
respond_failure("cannot undo")
return
self._clear_board()

handicap_history = self.board.get_handicap_history()

self.board.clear()

for handicap in handicap_history:
self.board.put_handicap_stone(handicap, Stone.BLACK)

for (color, pos, _) in history[:-1]:
self.board.put_stone(pos, color)

respond_success("")

def _genmove(self, color: str) -> NoReturn:
Expand Down Expand Up @@ -309,6 +318,32 @@ def _load_sgf(self, arg_list: List[str]) -> NoReturn:

respond_success("")

def _fixed_handicap(self, handicaps: str) -> NoReturn:
"""fixed_handicapコマンドを処理する。
指定した数の置き石を置く。
Args:
handicaps (str): 置き石の個数
"""
if self.board.moves > 1 or len(self.board.get_handicap_history()) > 1 :
respond_failure("board not empty")
return

num_handicaps = int(handicaps)
board_size = self.board.get_board_size()

handicap_list = get_handicap_coordinates(board_size, num_handicaps)

if handicap_list is None:
respond_failure(f"size {board_size}, handicaps {handicaps} is not supported")
return

for handicap in handicap_list:
pos = self.board.coordinate.convert_from_gtp_format(handicap)
self.board.put_handicap_stone(pos, Stone.BLACK)

respond_success(" ".join(handicap_list))

def _decode_analyze_arg(self, arg_list: List[str]) -> (Stone, float):
"""analyzeコマンド(lz-analyze, cgos-analyze)の引数を解釈する。
不正な引数の場合は更新間隔として負値を返す。
Expand Down Expand Up @@ -463,6 +498,8 @@ def run(self) -> NoReturn: # pylint: disable=R0912,R0915
self._showboard()
elif input_gtp_command == "load_sgf":
self._load_sgf(command_list[1:])
elif input_gtp_command == "fixed_handicap":
self._fixed_handicap(command_list[1])
elif input_gtp_command == "final_score":
respond_success("?")
elif input_gtp_command == "showstring":
Expand Down

0 comments on commit 30d8158

Please sign in to comment.