Python3 でマルバツゲームを自分ならどう書くだろう?
お題
一番最初の課題:三目並べ(マルバツゲームとも TicTacToe ともいう)の実装。
自分ならどう解くか?
ということで実装してみた。
自分なりに教科書を見ずに課題を解くなら何に気をつけるだろうか?
- ベタ書きを避ける(定数を使う)
- 入出力とロジックは分離する(テストしやすくする+移植しやすくする)
と先ずは考える。
ということで、書いたのがこんな感じ。
バージョンは Python 3.8.3 anaconda
を利用。
class TicTacToe: CELL_EMPTY = '_' CELL_CROSS = 'X' CELL_ZERO = 'O' STATE_DRAW = 'D' PUTABLE_STONE = ['X', 'O'] def __init__(self): self.game_board: list[list[str]] = self.init_board() self.last_position: list = [] def init_board(self): return [[TicTacToe.CELL_EMPTY] * 3 for n in range(3)] def board(self): return self.game_board[:] # return copy def put_stone(self, x: int, y: int, color: str) -> bool: if x > 2 or y > 2: return False # Out of board if color not in [TicTacToe.CELL_CROSS, TicTacToe.CELL_ZERO]: return False # Cant putable stone if self.game_board[y][x] != TicTacToe.CELL_EMPTY: return False # Cannot put if self.is_finished() != TicTacToe.CELL_EMPTY: return False # game is already finished self.last_position = [x, y, color] self.game_board[y][x] = color return True def is_finished(self) -> str: if len(self.last_position) < 3: return TicTacToe.CELL_EMPTY x = self.last_position[0] y = self.last_position[1] put_color = self.last_position[2] # x 軸判定 if self.game_board[0][x] == put_color \ and self.game_board[1][x] == put_color \ and self.game_board[2][x] == put_color: return put_color # y軸判定 if self.game_board[y][0] == put_color \ and self.game_board[y][1] == put_color \ and self.game_board[y][2] == put_color: return put_color # cross \ if self.game_board[0][0] == put_color \ and self.game_board[1][1] == put_color \ and self.game_board[2][2] == put_color: return put_color # cross / if self.game_board[0][2] == put_color \ and self.game_board[1][1] == put_color \ and self.game_board[2][0] == put_color: return put_color # return empty if you can put stone. for cx in range(0, 3): for cy in range(0, 3): if self.game_board[cy][cx] == TicTacToe.CELL_EMPTY: return TicTacToe.CELL_EMPTY # Draw return TicTacToe.STATE_DRAW def display(board): for ls in board: line = ' '.join(ls) print(line) def input_asInt(axis: str) -> int: while True: value = input(f'Please input stone position {axis}(0-2):') try: conv = int(value) if conv < 0 and conv > 2: print('please input 0-2') continue return conv except ValueError: print('Oops! Is not a integer. Try again...') def input_stone_pos(ttt: TicTacToe, stone: str): input_is_valid = False while not input_is_valid: print(f'Current turn is {stone}') x_pos = input_asInt('x') y_pos = input_asInt('y') print(f'{x_pos}, {y_pos}') if ttt.put_stone(x_pos, y_pos, stone): return print('Sorry failed to put stone. Try again...') if __name__ == "__main__": tic_tac_toe = TicTacToe() turn_num = 0 finish_state = TicTacToe.CELL_EMPTY while finish_state == TicTacToe.CELL_EMPTY: # 表示 display(tic_tac_toe.board()) # 今のターンの石の色を指定して、入力を受ける put_color = TicTacToe.PUTABLE_STONE[turn_num % 2] input_stone_pos(tic_tac_toe, put_color) # このターンの終了ステータスを取得する finish_state = tic_tac_toe.is_finished() turn_num += 1 if finish_state == TicTacToe.STATE_DRAW: print('Game is draw!') else: print(f'Winner is {finish_state}')
動作させた場合はこんな感じ
前略--- X _ O O O X X _ X Current turn is O Please input stone position x(0-2):1 Please input stone position y(0-2):2 1, 2 X _ O O O X X O X Current turn is X Please input stone position x(0-2):1 Please input stone position y(0-2):0 1, 0 Game is draw!
職業プログラマなら移植性とテスタビリティは考えるべきな気がしたため…