Tic Tac Toe 又叫井字遊戲
兩位玩家會在一個3*3的棋盤內輪流放置X和O佔領格子
當有連續的3個相同符號佔領一個橫排、直排、或對角線時,則該符號的玩家勝利。若所有格子都被佔領但雙方皆為滿足獲勝條件,則平局。
要模板點我
這次會用到的技巧
Class
的各項工具:繼承, @staticmethod, …Try and Except
函式的運用all()
函式的運用list
的操作in enumerate(list)
的使用建議開一個資料夾叫做"TicTacToe"
這次會需要兩個.py的檔案
一、設定Player class
player.py
,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
二、遊戲初步設定
game.py
,寫一個TicTacToe的class並宣告一個list作為遊戲的棋盤,class TicTacToe: def __init__(self): self.board = [' ' for _ in range(9)] self.current_winner = None
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)
這個概念要很清楚,之後還會用到!
這邊就需要 @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) + '|')
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 == ' ']
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
裡面所有東西了
play
函式回到game.py
,在TicTacToe class
外面創一個play
函式,執行他就可以開始遊戲
def play(game, x_player, o_player, print_game=True): if print_game: print_board_nums() letter = 'x' #starting letter #以下開始遊戲
遊戲流程:
重複 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)
函式:判斷贏家
我們判斷三個地方:
把這個函式寫在TicTacToe class
裡面
def winner(self, square, letter): # 1. Row # 2. Column # 3. Diagonal
如果整列都同一個字母,則他為贏家
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
如果整行都同一個字母,則他為贏家
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
\ 左上右下 : 第 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函式了!
有兩種版本可以玩:
if __name__ == '__main__': x_player = HumanPlayer('X') o_player = RandomComputerPlayer('O') T = TicTacToe() play(x_player, o_player, T, print_game=True)
恭喜各位完成最基礎的TicTacToe!