changed 3 years ago
Linked with GitHub

Python實作課程

第三堂課 : Tic Tac Toe

2022 / 4 / 28

Tony


遊戲介紹


Tic Tac Toe 又叫井字遊戲
兩位玩家會在一個3*3的棋盤內輪流放置X和O佔領格子
當有連續的3個相同符號佔領一個橫排、直排、或對角線時,則該符號的玩家勝利。若所有格子都被佔領但雙方皆為滿足獲勝條件,則平局。


開始寫Code


要模板點我
這次會用到的技巧

  • Class 的各項工具:繼承, @staticmethod,
  • Try and Except 函式的運用
  • all() 函式的運用
  • list 的操作
  • in enumerate(list)的使用

建議開一個資料夾叫做"TicTacToe"
這次會需要兩個.py的檔案


一、設定Player class

  1. 創建一個player.py
    寫兩個玩家的class,都繼承Player class,
    一個代表電腦,一個代表真人玩家
    letter 代表這個玩家會用什麼符號玩遊戲('X','O')
    get_move是玩家決定下哪一步時執行的函式
class Player: def __init__(self, letter): # letter is x or o self.letter = letter # we want all players to get their next move def get_move(self, game): pass

class RandomComputerPlayer(Player): def __init__(self, letter): super().__init__(letter) def get_move(self, game): pass class HumanPlayer(Player): def __init__(self, letter): super().__init__(letter) def get_move(self, game): pass

二、遊戲初步設定

  1. 創建一個 game.py,寫一個TicTacToe的class並宣告一個list作為遊戲的棋盤,
    再宣告current_winner追蹤遊戲進行時隨時可能出現的贏家
class TicTacToe: def __init__(self): self.board = [' ' for _ in range(9)] self.current_winner = None

  1. print_board函式:
    執行後當前棋盤的模樣就會被印出來
def print_board(self): # get the rows # for row in [[0,1,2], [3,4,5], [6,7,8]] for row in [self.board[i*3:(i+1)*3] for i in range(3)]: print('|' + ' | '.join(row) + '|')

重要:i*3:(i+1)*3 for i in range(3)
這個概念要很清楚,之後還會用到!


  1. print_board_nums函式:
    印出各個位置代表的座標方便使用者放符號

這邊就需要 @staticmethod

@staticmethod def print_board_nums(): # it's going to print 0 | 1 | 2 , etc # (tell us what number corresponds to what position) number_board = [ [str(i) for i in range(j*3, (j+1)*3)] for j in range(3)] # [ [0,1,2],[3,4,5],[6,7,8] ] for row in number_board: print('|' + ' | '.join(row) + '|')

  1. available_moves函式:
    告訴使用者現在哪邊還可以下

in enumerate(list) : 把list裡面每一項跟它的index用tuple(一種不能改變資料的陣列)包起來並分開

def available_moves(self): # it'll return [] moves = [] for (i,spot) in enumerate(self.board): #i代表第幾格,spot代表那一格的東西 # ['x', 'x', 'o'] -> [(0, 'x'), (1, 'x'), (2, 'o')] if spot ==' ': moves.append(i) return moves # You can also use this: # return [i for i, spot in enumerate(self.board) if spot == ' ']

  1. 回到player.py,完成兩種玩家的get_move函式
import random #記得在程式一開始寫上它 # RandomComputerPlayer def get_move(self, game): # get a random valid move for its next move square = random.choice(game.available_moves()) return square

在寫HumanPlayer之前你要先知道
try and except:程式跑一跑遇到錯誤就會全盤中止,但我們希望碰到錯誤時不用終止而是通知使用者他做錯了,可以用try and except的方式處理

try: #他如果不是輸入數字這邊就會跳出ValueError a = int(input('輸入一個數字(1~10)')) if a > 10 or a < 1: raise ValueError #如果不在範圍就強制給他ValueError return True except ValueError: #如果有ValueError就會進來 print("你不是輸入1~10的數字喔,再試一次") print('離開try and except') #就算有ValueError他也會跑到這裡來,但其他種error就不會

HumanPlayer的get_move函式

#HumanPlayer def get_move(self, game): valid_square = False val = None #玩家指定的棋盤上的某格 while not valid_square: square = input(self.letter + '\'s turn. Input move(0-8): ') try: val = int(square) if val not in game.available_moves(): raise ValueError valid_square = True # If these are successful, then hurray! except ValueError: print('Invalid square. Try again.') # 當玩家選的格子是現在可以下的格子,就會到這裡 return val

恭喜完成player.py裡面所有東西了


  1. play函式

回到game.py在TicTacToe class外面創一個play函式,執行他就可以開始遊戲

  • game : TicTacToe class 的一個物件,可以想像成"遊戲"
  • x_player : 代表X的玩家
  • o_player : 代表O的玩家
  • print_game : 決定要不要印出數字1~9的盤面
def play(game, x_player, o_player, print_game=True): if print_game: print_board_nums() letter = 'x' #starting letter #以下開始遊戲

遊戲流程:

  1. 要放棋子的玩家決定下哪一步
  2. 如果盤面還有空格,把符號放進棋盤裡面並印出現在盤面
  3. 如果現在盤面產生贏家,遊戲結束並宣布贏家
    如果沒有,換玩家下並繼續遊戲

重複 1 ~ 3直到贏家產生 OR 沒空格

因為2,我們需要判斷盤面有無空格的function以及放棋子進盤面的function


empty_square(self) : 回傳一個步林值代表現在盤面還有沒有空格,寫在TicTacToe class 裡面

def empty_square(self): return ' ' in self.board

make_move(self) : 把現在玩家要放的符號放在他選擇的位置,如果操作可行,回傳True,否則就回傳False

def make_move(self, square, letter): if self.board[square] == ' ': self.boaard[square] = letter # 等等還要塞別的,但先這樣 return True return False #理論上不會走到這裡,But just in case

兩個函式寫完就可以回來寫play函式

def play(game, x_player, o_player, print_game=True): if print_game: TicTacToe.print_board_nums() letter = 'X' #starting letter #從這邊繼續 while game.empty_square(): # get move from the current player if letter == 'X': square = x_player.get_move(game) else: square = o_player.get_move(game) #If make move,inform us if game.make_move(square, letter): print(letter + f'makes a move to square {square}.') game.print_board() print('') #換行 #等等還要塞別的 #switch player if letter == 'X': letter = 'O' else: letter = 'X' time.sleep(0.8) #延遲一下,增加遊戲體驗 #如果到這邊來表示棋盤沒空格,也沒有贏家 -> 平局 if print_game: print('It\'s a tie !')

此時整個遊戲快要完成了
只差最後一步:判定贏家
判定的時機應該是在每一次放棋子之後

所以我們可以在if game.make_move(square, letter)裡面塞入以下額外程式碼

if game.current_winner != None: #一開始設定為None,不是None就代表有贏家 if print_game: print(letter + 'wins!') return letter #遊戲結束

因為要在每一次放棋子之後判斷,我們在make_move(self, square, letter)函式裡面呼叫偵測贏家的函式winner(self, square, letter)

make_move(self, square, letter)裡面補上以下程式碼

if self.board[square] == ' ': self.board[square] = letter #補上這個 if self.winner(square, winner): self.current_winner = letter return True return False

self.winner(square, letter)函式:判斷贏家
我們判斷三個地方:

  1. 該square所在橫列
  2. 該square所在直行
  3. 該square所在對角線 (如果有)

把這個函式寫在TicTacToe class裡面

def winner(self, square, letter): # 1. Row # 2. Column # 3. Diagonal

  1. 該square所在橫列

如果整列都同一個字母,則他為贏家
all()函式:該陣列裡面每一個元素是否都不為0或空格

row_ind = square // 3 #該square所在列數 row = self.board[row_ind*3: (row_ind_1)*3] if all([spot == letter for spot in row]): # if letter = 'X', ['O', ' ', 'X'] -> [0, 0, 1] return True

  1. 該square所在直行

如果整行都同一個字母,則他為贏家

col_ind = square % 3 #該square所在行數 column = [self.board[col_ind + i*3] for i in range(3)] #col_ind + i*3 : 每加3就會換到同行的下一個位置 if all(spot == letter for spot in column): return True

  1. 該square所在對角線 (如果有)

\ 左上右下 : 第 0, 4, 8 格
/ 右上左下 : 第 2, 4, 6 格

如果全部判斷完都沒有一個符合,則回傳False,表示現在還沒有獲勝的局面

if square % 2 == 0: diagonal1 = [self.board[i] for i in [0, 4, 8]] if all([spot == letter for spot in diagonal1]): return True diagonal2 = [self.board[i] for i in [2, 4, 6]] if all([spot == letter for spot in diagonal2]): return True return False

恭喜完成Play函式了!


  1. main函式處理

有兩種版本可以玩:

  • 真人 V.S.電腦
  • 真人 V.S 真人
  • (也可以電腦V.S.電腦,你爽就好)
if __name__ == '__main__': x_player = HumanPlayer('X') o_player = RandomComputerPlayer('O') T = TicTacToe() play(x_player, o_player, T, print_game=True)

恭喜各位完成最基礎的TicTacToe!

Select a repo