技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

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!

職業プログラマなら移植性とテスタビリティは考えるべきな気がしたため…