見出し画像

ド・ミノ

ボードゲームをやっていると、モノミノ、ドミノ、トリオミノ、テトロミノ、ペントミノあたりは、日常用語だと思って過ごすことになる。もはや、落ち物パズルゲームとしてのテトリスだってそこまで遊ばれていないであろうことを考えると、本当にボードゲーマーというのは「訓練されている」と思う。

というわけで、今日は、ドミノを遊ぶ。


まずは粗々で生成

久々に、ChatGPTくんにプログラムを生成してもらおうではないか。私「Pythonでドミノゲームを作りたいです。具体的なコードを教えてもらえますか」

import random

class DominoGame:
    def __init__(self, players):
        self.players = players
        self.board = []
        self.stock = []
        self.hands = {player: [] for player in players}

    def start_game(self):
        self._initialize_dominoes()
        self._distribute_dominoes()
        self._start_round()

    def _initialize_dominoes(self):
        for i in range(7):
            for j in range(i, 7):
                self.stock.append((i, j))

    def _distribute_dominoes(self):
        random.shuffle(self.stock)
        num_players = len(self.players)
        num_dominoes_per_player = 7
        for i in range(num_dominoes_per_player):
            for player in self.players:
                domino = self.stock.pop()
                self.hands[player].append(domino)

    def _start_round(self):
        current_player = random.choice(self.players)
        while True:
            print(f"\nCurrent board: {self.board}")
            print(f"{current_player}'s turn.")
            print(f"Your hand: {self.hands[current_player]}")

            if self._is_game_over(current_player):
                print(f"{current_player} wins!")
                break

            domino = self._choose_domino(current_player)
            if domino:
                self.board.append(domino)
                self.hands[current_player].remove(domino)
            else:
                print("You cannot play. Drawing from the stock.")
                self._draw_domino(current_player)

            current_player = self._next_player(current_player)

    def _is_game_over(self, current_player):
        return len(self.hands[current_player]) == 0

    def _choose_domino(self, current_player):
        while True:
            domino_str = input("Choose a domino to play (e.g., '1-3'): ")
            try:
                domino = tuple(map(int, domino_str.split('-')))
                if domino in self.hands[current_player] and self._is_valid_move(domino):
                    return domino
                else:
                    print("Invalid move. Try again.")
            except ValueError:
                print("Invalid input. Try again.")

    def _is_valid_move(self, domino):
        left_end = self.board[0][0]
        right_end = self.board[-1][1]
        return domino[0] == left_end or domino[1] == left_end or \
               domino[0] == right_end or domino[1] == right_end

    def _draw_domino(self, current_player):
        if len(self.stock) > 0:
            domino = self.stock.pop()
            self.hands[current_player].append(domino)
            print(f"Drew {domino} from the stock.")
        else:
            print("Stock is empty.")

    def _next_player(self, current_player):
        index = self.players.index(current_player)
        return self.players[(index + 1) % len(self.players)]

if __name__ == "__main__":
    players = ["Player 1", "Player 2"]
    game = DominoGame(players)
    game.start_game()

一発でこんなにコード出してくれるんだ。。。優しい。。。

とりあえず実行してみるよ。

コマンドプロンプトから実行

お。なんか返ってきた。で、例えば「1-3」みたいな感じで入力しろ、と言っているので、「1-4」と入力してみると、エラー。

私「ドミノを選ぶと
Choose a domino to play (e.g., '1-3'): 2-4 Traceback (most recent call last):
File "c:\Pygames\SimpleDomino.py", line 86, in <module> game.start_game() File "c:\Pygames\SimpleDomino.py", line 13, in start_game self._start_round() File "c:\Pygames\SimpleDomino.py", line 40, in _start_round domino = self._choose_domino(current_player)
File "c:\Pygames\SimpleDomino.py", line 58, in _choose_domino if domino in self.hands[current_player] and self._is_valid_move(domino):
File "c:\Pygames\SimpleDomino.py", line 66, in _is_valid_move left_end = self.board[0][0] IndexError: list index out of range

というエラーが出ます。どうすればいいですか」

エラーが発生しているのは、self.boardが空のリストであり、その後のコードでインデックスにアクセスしようとしているためです。これは、最初の牌をプレイヤーが出したときに発生しています。

問題を解決するために、_start_roundメソッド内で最初の牌をボードに追加するコードを追加する必要があります。具体的には、以下のように変更します:

ChatGPTの出力

こんな感じで、基本的には実行→エラーを伝える→コードの提案→いいと思ったら「元のコードにマージ」という

というサイクルを繰り返すと、まぁ、良い感じのところまではゲームを作ってくれる。(完成度は80~100のどこかに落ちるようなイメージ。)

ドミノの補足をしておいたほうがいいな

ここまで読ませておいて、ドミノって倒す以外に遊べるの?勢を置いてけぼりにしてしまっていることに気がついた。

わりと様々なルールで遊べるドミノだが、今回は、すごく単純な数合わせのルールで、ざっくりいうと以下の仕様。

ドミノは0-6のものを使用。0-0、0-1、…0-6、1-1、1-2、…1-6、…、6-6までの28枚。(このセットのことをダブルシックスと呼んでいるよ。もっと数の多いのも、あるよ)
裏向きにドミノを混ぜて、手札は各プレイヤー7枚ずつ配る。
場札として1枚表向きにして、以降、右向きにどんどん繋げていく。
例えば最初の1枚が「3-5」だとすると、手から3または5の数字があるものを1枚出して、「3-5」「5-6」という風に繋げていく。
出せない場合は出せるタイルを引けるまで裏向きのドミノを引く。
先に手札をなくした方の、勝ち。

ざっくりルール

案外これだけでも、戦略性があって面白いよ。

85%完成

5回ぐらい押し問答した結果がこちら。

import random

class DominoGame:
    def __init__(self, players):
        self.players = players
        self.board = []
        self.stock = []
        self.hands = {player: [] for player in players}

    def start_game(self):
        self._initialize_dominoes()
        self._distribute_dominoes()
        self._start_round()

    def _initialize_dominoes(self):
        for i in range(7):
            for j in range(i, 7):
                self.stock.append((i, j))

    def _distribute_dominoes(self):
        random.shuffle(self.stock)
        num_players = len(self.players)
        num_dominoes_per_player = 7
        for i in range(num_dominoes_per_player):
            for player in self.players:
                domino = self.stock.pop()
                self.hands[player].append(domino)

    def _start_round(self):
        current_player = random.choice(self.players)
        
        while True:
            print(f"\nCurrent board: {self.board}")
            print(f"{current_player}'s turn.")
            print(f"Your hand: {self.hands[current_player]}")

            if self._is_game_over(current_player):
                print(f"{current_player} wins!")
                break

            if len(self.board) == 0:
                # 最初のタイルをプレイヤー1から取り出す
                domino = random.choice(self.hands[current_player])
                self.hands[current_player].remove(domino)
                self.board.append(domino)
                continue

            domino = self._choose_domino(current_player)
            if domino:
                self.board.append(domino)
                self.hands[current_player].remove(domino)

            current_player = self._next_player(current_player)

    def _is_game_over(self, current_player):
        return len(self.hands[current_player]) == 0

    def _choose_domino(self, current_player):
        while True:
            domino_str = input("Choose a domino to play (e.g., '1-3'): ")
            if not domino_str:
                print("Invalid input. Try again.")
                continue
            try:
                domino = tuple(map(int, domino_str.split('-')))
                if domino in self.hands[current_player] and self._is_valid_move(domino):
                    if self.board[-1][1] == domino[0]:
                        domino = (domino[1], domino[0])
                    elif self.board[-1][1] != domino[0] and self.board[-1][1] != domino[1]:
                        domino = (domino[1], domino[0])
                    self.hands[current_player].remove(domino)  # 修正点:選択されたドミノが手札に存在することを確認してから手札から削除する
                    return domino
                else:
                    print("Invalid move. Try again.")
            except ValueError:
                print("Invalid input. Try again.")
        
            # プレイヤーが手持ちのタイルから有効なタイルを選択できない場合、タイルを引く
            if not self._can_play(current_player):
                print("You cannot play. Drawing from the stock.")
                self._draw_domino(current_player)
                print(f"Your updated hand: {self.hands[current_player]}")  # 手札を表示する
                continue  # タイルを引いた後は再度選択を促す
            else:
                print(f"Your hand: {self.hands[current_player]}")  # 手札を表示する
                break

    def _is_valid_move(self, domino):
        left_end = self.board[0][0]
        right_end = self.board[-1][1]
        if domino[0] == left_end or domino[1] == left_end:
            return domino[1] != domino[0] and domino not in self.board
        elif domino[0] == right_end or domino[1] == right_end:
            return domino[0] != domino[1] and domino not in self.board
        else:
            return False

    def _draw_domino(self, current_player):
        if len(self.stock) > 0:
            domino = self.stock.pop()
            self.hands[current_player].append(domino)
            print(f"Drew {domino} from the stock.")
            print(f"Your updated hand: {self.hands[current_player]}")  # 手札を表示する
        else:
            print("Stock is empty.")

    def _next_player(self, current_player):
        index = self.players.index(current_player)
        return self.players[(index + 1) % len(self.players)]

    def _can_play(self, current_player):
        for domino in self.hands[current_player]:
            if self._is_valid_move(domino):
                return True
        return False

if __name__ == "__main__":
    players = ["Player 1", "Player 2"]
    game = DominoGame(players)
    game.start_game()

このくらいまで来たら、後は押し問答をするより、自分でコード修正したほうが(私は)はやいと思う。

まとめ

今日はなんだかプログラム気分だったので、ドミノ遊びの概形まで作った。…で、途中で、完成までは作らなくていいか、それよりこれをベースにおもしろアイデアを混ぜてから実装したいな…という気持ちになったので、今日はこのへんで寝かせておきたいと、思う。ほなね!

例のAI画像のコーナー

いやぁ、ドミノ。サイコロが上手く生成できないのに、ドミノが上手く生成できるわけ、ないよね!!

案の定、ですわ。

シリアスに変

今度、ちょっと時間を使って、サイコロとかドミノが上手く生成できるモデルがないか徹底調査してみようかな。。。

いただいたサポートは、きっと、ドイツアマゾンからの送料に変わると思います。 温かいご支援、お待ちしております。