iT邦幫忙

1

【人機對戰】用python打造經典井字遊戲

井字遊戲是一個經典的棋盤遊戲,
在一個3x3的棋盤上面玩,
誰的棋子先連成一條線就贏了,
我直接拿上次在【人機對戰】用python打造經典黑白棋遊戲寫的程式來改很快就改出來了。

我的版本將程式邏輯拆成三個部分:

  1. 井字遊戲的基本邏輯- 知道一個盤面可以走的棋步有哪些(這個遊戲很單純,空格都可走)
  2. 電腦ai下棋的邏輯(我偷懶直接讓電腦選擇隨機棋步)
  3. 互動程式的邏輯

完整範例程式碼(棋盤大小需固定3x3)

import random
import sys

# 寫井字遊戲的基本邏輯,棋子共'X','O'兩種
class TicTacToe():
    def __init__(self, height, width):
        self.width = width
        self.height = height
        self.board = [[' ']*self.height for i in range(self.width)]
    
    # 初始化棋盤
    def iniBoard(self):
        for i in range(self.width):
            for j in range(self.height):
                self.board[i][j]=' '
        
        
    def drawBoard(self) -> None:
        HLINE =  ' ' * 3 + '+---' * self.width  + '+'
        VLINE = (' ' * 3 +'|') *  (self.width +1)
        title = '     1'
        for i in range(1,self.width):
            title += ' ' * 3 +str(i+1)
        print(title)
        print(HLINE)
        for y in range(self.height):
            print(VLINE)
            print(y+1, end='  ')
            for x in range(self.width):
                print(f'| {self.board[x][y]}', end=' ')
            print('|')
            print(VLINE)
            print(HLINE)
    
    def isOnBoard(self, x, y):
        return 0 <= x < self.width and 0 <= y < self.height

    #檢查tile放在某個座標是否為合法棋步
    def isValidMove(self, tile, x, y):
        return self.isOnBoard(x, y) and self.board[x][y]==' '
    
    # 回傳現在盤面輪到tile走的所有合法棋步
    def getValidMoves(self, tile):
        return [[x, y] for x in range(self.width) for y in range(self.height) if self.isValidMove(tile, x, y)]
        

# 電腦ai下棋的邏輯 (這邊我偷懶直接讓電腦選擇隨機棋步)
class TicTacToeAI(TicTacToe):
    def __init__(self, board, height, width):
        super().__init__(height, width)
        self.board = board   
 
    # 給定盤面board,回傳電腦的選擇
    def getComputerMove(self, computerTile):
        possibleMoves = self.getValidMoves(computerTile)
        random.shuffle(possibleMoves) # 隨機性
        return possibleMoves[0]
        


# 寫互動程式的邏輯
class Game(TicTacToe):
    def __init__(self, height, width):
        super().__init__(height, width)
        self.turn = 'player'
        self.ai = TicTacToeAI(self.board,self.height, self.width)

    # 詢問玩家是否再玩一次
    def playAgain(self)-> bool:
        return input('你想再玩一次嗎?(輸入y或n)').lower().startswith('y')

    # 取得玩家的行動,回傳棋步[x, y](或'hints', 'quit'))
    def getPlayerMove(self, playerTile):
        DIGITS = [str(i) for i in range(1,10)]
        while True:
            move = input('請輸入棋步(先輸入x座標再輸入y座標),例如11是左上角。(或輸入quit)').lower()
            if move in {'quit'}:
                return move
            if len(move) == 2 and move[0] in DIGITS and move[1] in DIGITS:
                x = int(move[0]) - 1
                y = int(move[1]) - 1
                if self.isValidMove(playerTile, x, y):
                    break
            print('非合法棋步,請再試一次')
        return [x, y]
    
    # 判斷一個盤面是否有人贏了
    def check_TicTacToe(self):
        rows = list(map(''.join,self.board))
        cols = list(map(''.join, zip(*rows)))
        diags = list(map(''.join, zip(*[(r[i], r[2 - i]) for i, r in enumerate(rows)])))
        lines = rows + cols + diags

        if 'XXX' in lines:
            return 'X'  
        if 'OOO' in lines:
            return 'O' 
        return 'D'


    def gameloop(self):
        print("歡迎玩井字遊戲(玩家的棋子為'X')")

        while True:
            # 初始化棋盤
            self.iniBoard()
            playerTile, computerTile = ['X', 'O']
            print('玩家先手' if self.turn == 'player' else '電腦先手')
            
            while True:
                playerValidMoves = self.getValidMoves(playerTile)
                computerValidMoves = self.getValidMoves(computerTile)
                # 若無人可行動,結束遊戲
                if not playerValidMoves and not computerValidMoves or self.check_TicTacToe()!='D':
                    break

                if self.turn == 'player' and playerValidMoves:
                    self.drawBoard()
                    move = self.getPlayerMove(playerTile)
                    if move == 'quit':
                        print('Thanks for playing!')
                        sys.exit() # terminate the program
                    else:
                        self.board[move[0]][move[1]] = playerTile
                elif self.turn == 'computer' and computerValidMoves:
                    self.drawBoard()
                    input('按enter看電腦的下一步')
                    x, y = self.ai.getComputerMove(computerTile)
                    self.board[x][y] = computerTile
                self.turn = 'player' if self.turn=='computer' else 'computer'
                        
            # 顯示最後結果
            self.drawBoard()
            result = self.check_TicTacToe()
            if result=='X':
                print("恭喜你贏電腦了")
            elif result=='O':
                print("你輸了")
            else:
                print('平手')
                
            if not self.playAgain():
                break

reversi = Game(3,3)
reversi.gameloop()

遊戲範例

歡迎玩井字遊戲(玩家的棋子為'X')
玩家先手
     1   2   3
   +---+---+---+
   |   |   |   |
1  |   |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
2  |   |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
3  |   |   |   |
   |   |   |   |
   +---+---+---+
請輸入棋步(先輸入x座標再輸入y座標),例如11是左上角。(或輸入quit)33
     1   2   3
   +---+---+---+
   |   |   |   |
1  |   |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
2  |   |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
3  |   |   | X |
   |   |   |   |
   +---+---+---+
按enter看電腦的下一步
     1   2   3
   +---+---+---+
   |   |   |   |
1  |   |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
2  | O |   |   |
   |   |   |   |
   +---+---+---+
   |   |   |   |
3  |   |   | X |
   |   |   |   |
   +---+---+---+

1 則留言

1
clash110502
iT邦新手 5 級 ‧ 2020-07-10 03:44:31

看到OOXX有點壞念
小時候小有研究
如果隨機 電腦ai下棋的邏輯
玩家第一步只要下角落
玩家獲勝機率有八分之七
因為第一下角落時
第二步對方沒下中間就贏了

不過也沒差
因為電腦AI隨機的話
要贏太容易了

有空套這模板來練習一下
想想怎麼寫一個絕對能平手的邏輯

心原一馬 iT邦研究生 5 級 ‧ 2020-07-10 08:10:21 檢舉

嗨嗨~ 謝謝分享,
OOXX是小時候蠻多人玩的遊戲呢,
用OOXX來練習寫AI感覺不錯,
因為遊戲相對簡單,
可以很容易驗證有沒有寫對,
歡迎改改看電腦的邏輯~

我要留言

立即登入留言