Chapterبەش 6 · Lectureوانە 6.1
Building Tic-Tac-Toe in Pythonدروستکردنی یاریی ئێکسوئۆ بە Python
Build a two-player tic-tac-toe game in Python, step by step: the board as a list of lists, printing it, placing a move, checking the 8 winning lines with all(), checking a draw, and the game loop. Then find three input bugs in the course code (two crashes and a silent negative-index bug) and fix them with safe input. The finished file is the base for the minimax AI.
دوو یاریزانی بە هەنگاو بە هەنگاو دروست دەکەیت: تەختەکە وەک لە ، چاپکردنی، دانانی جووڵەیەک، پشکنینی 8 هێڵی بردنەوە بە all()، پشکنینی یەکسانبوون و یارییەکە. پاشان سێ لە کۆدی کۆرسەکەدا دەدۆزیتەوە (دوو و بێدەنگی ) و بە سەلامەت چاکیان دەکەیت. تەواوکراو بنەمایە بۆ یاریزانێکی کۆمپیوتەری کە بەکار دەهێنێت.
What you will learnچی فێر دەبیت
- Represent a 3 × 3 board as a list of lists and reach a cell with
board[row][col]. - Write and explain
print_board,can_insert,player_wonanddraw. - Build the game loop with a conditional expression that switches the player.
- Validate keyboard input so that the game never crashes and never accepts a negative index.
- تەختەیەکی 3 × 3 وەک لە پیشان بدەیت و بە
board[row][col]بگەیتە خانەیەک. print_board،can_insert،player_wonوdrawبنووسیت و ڕوونیان بکەیتەوە.- یارییەکە بە دروست بکەیت کە یاریزان دەگۆڕێت.
- پشتڕاست بکەیتەوە تاکو یارییەکە هەرگیز نەوەستێت و هەرگیز وەرنەگرێت.
The game and the planیارییەکە و پلانەکە
Tic-tac-toe is played on a 3 × 3 board by two players, x and o, who take turns. Each turn a player puts their symbol in an empty cell. The first player with three symbols in a row, a column or a diagonal wins. If the board is full and nobody has won, the game is a draw.
لەسەر تەختەیەکی 3 × 3 لە نێوان دوو یاریزاندا دەکرێت، x و o، کە نۆرە دەگرن. لە هەر نۆرەیەکدا یاریزان نیشانەکەی خۆی لە خانەیەکی بەتاڵدا دادەنێت. یەکەم یاریزان کە سێ نیشانەی لە ڕیزێک، ستوونێک یان هێڵێکی لاردا هەبێت، دەیباتەوە. ئەگەر تەختەکە پڕ بوو و کەس نەیبردەوە، یارییەکە یەکسان دەبێت.
In this lesson we build the game in Python for two people at one keyboard. In the next lesson the computer becomes one of the players. That is why the course's latest file already has two comments, # False = min and # True = max: they prepare the minimax algorithm, where one player tries to make the score as high as possible and the other as low as possible.
لەم وانەیەدا یارییەکە بە بۆ دوو کەس لەسەر یەک دروست دەکەین. لە وانەی داهاتوودا کۆمپیوتەرەکە دەبێتە یەکێک لە یاریزانەکان. بۆیە دوایین کۆرسەکە پێشتر دوو تێدایە، # False = min و # True = max: ئەمانە ئەلگۆریزمی ئامادە دەکەن، کە تێیدا یاریزانێک هەوڵ دەدات تا دەتوانێت بەرز بکاتەوە و ئەوی تر تا دەتوانێت نزمی بکاتەوە.
board: the 3 × 3 gridprint_board(): shows the boardcan_insert(): puts a symbol in an empty cellplayer_won()anddraw(): check the end of the gamemain(): the game loop
board: تۆڕی 3 × 3print_board(): تەختەکە پیشان دەداتcan_insert(): نیشانەیەک لە خانەیەکی بەتاڵدا دادەنێتplayer_won()وdraw(): کۆتایی یارییەکە دەپشکننmain(): یارییەکە
The board: a list of listsتەختەکە: لیستێک لە لیستەکان
The board is a 3 × 3 grid made of lists inside a list: a list of three rows, and each row is a list of three cells. Every cell starts empty, which we show with a space ' '. You reach one cell with two indexes: board[row][col].
تەختەکە تۆڕێکی 3 × 3ـە کە لە لە ناو پێکهاتووە: لە سێ ڕیز. هەر ڕیزێکیش خۆی لە سێ خانە. هەموو خانەیەک بە بەتاڵی دەست پێدەکات، کە بە بۆشاییەک ' ' پیشانی دەدەین. بە دوو دەگەیتە یەک خانە: board[row][col].
board = [
[' ', ' ', ' '], # row 0
[' ', ' ', ' '], # row 1
[' ', ' ', ' '], # row 2
]
board[1][2] = 'x' # row 1, column 2
print(board)
print(board[1]) # the whole of row 1
print(board[1][2]) # one cell[[' ', ' ', ' '], [' ', ' ', 'x'], [' ', ' ', ' ']] [' ', ' ', 'x'] x
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1-5
Three rows, each a list of three strings.
سێ ڕیز، هەریەکەیان لە سێ .
- Lineدێڕی 6
Row 1, column 2 gets an
'x'.ڕیزی 1، ستوونی 2
'x'وەردەگرێت.- Lineدێڕی 8-9
One index gives a whole row; two indexes give one cell.
یەک هەموو ڕیزێک دەدات؛ دوو یەک خانە دەدەن.
Printing the boardچاپکردنی تەختەکە
print_board() prints each row with its cells joined by " |". Under rows 0 and 1 it prints a line of 15 dashes. Only two dividers are needed for three rows, so a counter count remembers how many rows have been printed. A blank line follows at the end.
print_board() هەر ڕیزێک چاپ دەکات و خانەکانی بە " |" پێکەوە دەلکێنێت. لە ژێر ڕیزی 0 و 1دا هێڵێک لە 15 داش چاپ دەکات. بۆ سێ ڕیز تەنیا دوو هێڵی جیاکەرەوە پێویستە، بۆیە بە ناوی count لە بیری دەگرێت چەند ڕیز چاپ کراون. لە کۆتاییدا دێڕێکی بەتاڵ دێت.
def print_board(board):
count = 0 # how many rows are printed so far
for row in board:
print(" |".join(row)) # the 3 cells, joined by " |"
if count < 2: # only 2 divider lines for 3 rows
print("-" * 15)
count += 1
print()
board = [['x', ' ', 'o'],
[' ', 'x', ' '],
[' ', ' ', ' ']]
print_board(board)
print("|".join(['a', 'b', 'c'])) # how join() worksx | |o
---------------
|x |
---------------
| |
a|b|cExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
count= how many rows are printed so far.count= تا ئێستا چەند ڕیز چاپ کراون.- Lineدێڕی 4
" |".join(row)puts" |"between the three cells (see the last line of the output)." |".join(row)" |"دەخاتە نێوان سێ خانەکەوە (سەیری دوا دێڕی بکە).- Lineدێڕی 5-7
A divider only after the first two rows; then count up.
جیاکەرەوە تەنیا دوای دوو ڕیزی یەکەم؛ پاشان ژمارەکە زیاد بکە.
Making a moveجووڵەکردن
can_insert(board, row, col, player) checks the chosen cell. If it is empty (' '), it puts the player's symbol, 'x' or 'o', there and returns True. Otherwise the cell is taken, and it returns False.
can_insert(board, row, col, player) خانە هەڵبژێردراوەکە دەپشکنێت. ئەگەر بەتاڵ بوو (' ')، نیشانەی یاریزانەکە، 'x' یان 'o'، لەوێ دادەنێت و True دەگەڕێنێتەوە. ئەگینا خانەکە گیراوە و False دەگەڕێنێتەوە.
def can_insert(board, row, col, player):
if board[row][col] == ' ': # the cell is still empty
board[row][col] = player # put the symbol there
return True
return False # the cell is already taken
board = [[' ', ' ', ' '] for _ in range(3)]
print(can_insert(board, 0, 0, 'x')) # empty cell -> True
print(can_insert(board, 0, 0, 'o')) # taken -> False
print(board[0])True False ['x', ' ', ' ']
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-4
Empty cell: place the symbol and report success.
خانەی بەتاڵ: نیشانەکە دابنێ و سەرکەوتن ڕابگەیەنە.
- Lineدێڕی 5
Taken cell: change nothing and report failure.
خانەی گیراو: هیچ مەگۆڕە و شکست ڕابگەیەنە.
- Lineدێڕی 8
A new empty board, made row by row.
تەختەیەکی بەتاڵی نوێ، ڕیز بە ڕیز دروستکراو.
Checking for a winپشکنینی بردنەوە
player_won(board, player) returns True if any row, any column or either diagonal is full of the player's symbol. Each check uses the built-in function all(), which is True only when every item in a list is true (more in Python for the AI labs).
player_won(board, player) True دەگەڕێنێتەوە ئەگەر هەر ڕیزێک، هەر ستوونێک یان یەکێک لە دوو هێڵە لارەکە پڕ بێت لە نیشانەی یاریزانەکە. هەر پشکنینێک all() بەکار دەهێنێت، کە تەنیا کاتێک Trueـە کە هەموو دانەکانی ڕاست بن (زیاتر لە Python بۆ تاقیگەکانی ژیریی دەستکرد).
def player_won(board, player):
for row in board: # 3 rows
if all([cell == player for cell in row]):
return True
for col in range(3): # 3 columns
if all([board[row][col] == player for row in range(3)]):
return True
if all([board[i][i] == player for i in range(3)]): # diagonal \
return True
if all([board[i][2 - i] == player for i in range(3)]): # diagonal /
return True
return False
column = [['o', 'x', ' '],
['o', 'x', ' '],
[' ', 'x', 'o']]
diagonal = [[' ', 'o', 'x'],
['o', 'x', ' '],
['x', ' ', ' ']]
print(player_won(column, 'x'), player_won(column, 'o'))
print(player_won(diagonal, 'x'))
print([diagonal[i][2 - i] for i in range(3)]) # the cells of diagonal /True False True ['x', 'x', 'x']
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-4
Rows:
[cell == player for cell in row]makes threeTrue/Falsevalues (a list comprehension).ڕیزەکان:
[cell == player for cell in row]سێ بەهایTrue/Falseدروست دەکات ().- Lineدێڕی 5-7
Columns: for
col = 0, then 1, then 2, look at that column in every row (the course comment: “Iteration 1: col = 0”).ستوونەکان: بۆ
col = 0، پاشان 1، پاشان 2، سەیری ئەو ستوونە لە هەموو ڕیزەکاندا بکە ( کۆرسەکە: «Iteration 1: col = 0»).- Lineدێڕی 8
Diagonal : the cells 0 0, 1 1, 2 2 have the same row and column number:
board[i][i].هێڵی لاری : خانەکانی 0 0، 1 1، 2 2 هەمان ژمارەی ڕیز و ستوونیان هەیە:
board[i][i].- Lineدێڕی 10
Diagonal /: the cells 0 2, 1 1, 2 0: the column is
2 - i.هێڵی لاری /: خانەکانی 0 2، 1 1، 2 0: ستوونەکە
2 - iـە.
Checking for a drawپشکنینی یەکسانبوون
draw(board) returns True when every cell is filled and neither player has won. The order matters: a full board can also be a win, when the last move completes a line.
draw(board) کاتێک True دەگەڕێنێتەوە کە هەموو خانەکان پڕ بن و هیچ یاریزانێک نەیبردبێتەوە. ڕیزبەندییەکە گرنگە: تەختەیەکی پڕ دەکرێت بردنەوەش بێت، کاتێک دوایین جووڵە هێڵێک تەواو دەکات.
def player_won(board, player):
for row in board: # 3 rows
if all([cell == player for cell in row]):
return True
for col in range(3): # 3 columns
if all([board[row][col] == player for row in range(3)]):
return True
if all([board[i][i] == player for i in range(3)]): # diagonal \
return True
if all([board[i][2 - i] == player for i in range(3)]): # diagonal /
return True
return False
def draw(board):
full = all([cell != ' ' for row in board for cell in row])
return full and not player_won(board, 'x') and not player_won(board, 'o')
full_board = [['x', 'o', 'x'],
['x', 'o', 'o'],
['o', 'x', 'x']]
x_wins = [['x', 'x', 'x'],
['o', 'o', 'x'],
['x', 'o', 'o']]
print(draw(full_board), draw(x_wins))
print(all([True, True]), all([True, False]))True False True False
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 16
for row in board for cell in rowwalks through all 9 cells (twofors in one list comprehension).for row in board for cell in rowبە هەموو 9 خانەکەدا دەڕوات (دووforلە یەک ).- Lineدێڕی 17
Full and no winner.
پڕ و بێ براوە.
- Lineدێڕی 26
The second board is full but x has won, so it is not a draw.
تەختەی دووەم پڕە بەڵام x بردوویەتییەوە، بۆیە یەکسان نییە.
The game loopخولی یارییەکە
main() runs the game. Player 'x' starts. A repeats until the game ends: show the board, read the move, try to place it (if the cell is taken: “Invalid move. Try again.” and continue), check for a win (show the board, announce the winner, break), check for a draw (announce it, break), and finally switch the player.
main() یارییەکە بەڕێوە دەبات. یاریزانی 'x' دەست پێدەکات. دووبارە دەبێتەوە تا یارییەکە کۆتایی دێت: تەختەکە پیشان بدە، جووڵەکە بخوێنەوە، هەوڵ بدە دایبنێیت (ئەگەر خانەکە گیرابوو: «Invalid move. Try again.» و continue)، بردنەوە بپشکنە (تەختەکە پیشان بدە، براوەکە ڕابگەیەنە، break)، یەکسانبوون بپشکنە (ڕایبگەیەنە، break) و لە کۆتاییدا یاریزان بگۆڕە.
# Tic-tac-toe for two people at one keyboard (x starts). First version.
def print_board(board):
count = 0 # how many rows are printed so far
for row in board:
print(" |".join(row)) # the 3 cells, joined by " |"
if count < 2: # only 2 divider lines for 3 rows
print("-" * 15)
count += 1
print()
def can_insert(board, row, col, player):
if board[row][col] == ' ': # the cell is still empty
board[row][col] = player
return True
return False # the cell is already taken
def player_won(board, player):
for row in board: # 3 rows
if all([cell == player for cell in row]):
return True
for col in range(3): # 3 columns
if all([board[row][col] == player for row in range(3)]):
return True
if all([board[i][i] == player for i in range(3)]): # diagonal \
return True
if all([board[i][2 - i] == player for i in range(3)]): # diagonal /
return True
return False
def draw(board):
full = all([cell != ' ' for row in board for cell in row])
return full and not player_won(board, 'x') and not player_won(board, 'o')
def main():
board = [[' ', ' ', ' '] for _ in range(3)]
player = 'x'
while True:
print_board(board)
row, col = map(int, input(f"Player {player}, enter your move (row col): ").split())
if not can_insert(board, row, col, player):
print("Invalid move. Try again.")
continue
if player_won(board, player):
print_board(board)
print(f"Player {player} wins!")
break
if draw(board):
print_board(board)
print("It's a draw!")
break
player = 'o' if player == 'x' else 'x' # switch players
if __name__ == "__main__":
main() | |
---------------
| |
---------------
| |
Player x, enter your move (row col): 0 0
x | |
---------------
| |
---------------
| |
Player o, enter your move (row col): 1 1
x | |
---------------
|o |
---------------
| |
Player x, enter your move (row col): 0 0
Invalid move. Try again.
x | |
---------------
|o |
---------------
| |
Player x, enter your move (row col): 0 1
x |x |
---------------
|o |
---------------
| |
Player o, enter your move (row col): 2 2
x |x |
---------------
|o |
---------------
| |o
Player x, enter your move (row col): 0 2
x |x |x
---------------
|o |
---------------
| |o
Player x wins!Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 40
A fresh empty board for this game.
تەختەیەکی بەتاڵی نوێ بۆ ئەم یارییە.
- Lineدێڕی 44
As in the course:
input()gives a string such as'1 2';.split()makes['1', '2'];map(int, ...)turns both into integers.وەک کۆرسەکە:
input()وەک'1 2'دەدات؛.split()دەیکات بە['1', '2']؛map(int, ...)هەردووکیان دەکات بە .- Lineدێڕی 45-47
Taken cell: a message, then
continuejumps back to the start of the loop.خانەی گیراو: پەیامێک، پاشان
continueدەگەڕێتەوە بۆ سەرەتای .- Lineدێڕی 56
Switch players with a conditional expression (explained in Choosing a value in one line).
گۆڕینی یاریزان بە (لە هەڵبژاردنی بەهایەک لە یەک دێڕدا ڕوون کراوەتەوە).
- Lineدێڕی 59-60
Start the game only when the file is run, not when it is imported (the next lesson imports this file).
یارییەکە تەنیا کاتێک دەست پێبکە کە ڕاستەوخۆ کار پێ دەکرێت، نەک کاتێک import دەکرێت (وانەی داهاتوو ئەم import دەکات).
This is the course's first test game: x plays 0 0, o plays 1 1, x tries 0 0 again (taken, so “Invalid move”), then x 0 1, o 2 2 and x 0 2 — three x's in the top row. A second test in the course fills all nine cells (moves 0 0, 0 1, 0 2, 1 1, 1 0, 1 2, 2 1, 2 0, 2 2) and ends with “It's a draw!”. You will see that game at the end of the finished program's output, two screens on.
ئەمە یەکەم یاریی تاقیکردنەوەی کۆرسەکەیە: x 0 0 دەکات، o 1 1، x دیسان هەوڵی 0 0 دەدات (گیراوە، بۆیە «Invalid move»)، پاشان x 0 1، o 2 2 و x 0 2 — سێ x لە ڕیزی سەرەوەدا. تاقیکردنەوەی دووەمی کۆرسەکە هەموو نۆ خانەکە پڕ دەکات (جووڵەکانی 0 0، 0 1، 0 2، 1 1، 1 0، 1 2، 2 1، 2 0، 2 2) و بە «It's a draw!» کۆتایی دێت. ئەو یارییە لە کۆتایی تەواوکراودا دەبینیت، دوو پەڕە دواتر.
Bad input: three bugs, one fixنێوخستەی هەڵە: سێ باگ، یەک چارەسەر
The first version trusts the player completely. Tested with wrong input, it has three bugs. Two of them stop the program with an error (a ):
یەکەم وەشان بە تەواوی متمانە بە یاریزانەکە دەکات. کاتێک بە هەڵە تاقی کرایەوە، سێ هەیە. دووانیان بە دەوەستێنن ():
| |
---------------
| |
---------------
| |
Player x, enter your move (row col): 3 3
Traceback (most recent call last):
File "tic_tac_toe_v1.py", line 60, in <module>
main()
File "tic_tac_toe_v1.py", line 45, in main
if not can_insert(board, row, col, player):
File "tic_tac_toe_v1.py", line 14, in can_insert
if board[row][col] == ' ': # the cell is still empty
IndexError: list index out of rangePython 3.11 and newer also underline the failing part of each line with ^ signs; the error itself is the same.
3.11 و نوێتر بەشی هەڵەکەی هەر دێڕێک بە نیشانەی ^ هێڵ بە ژێردا دەکات؛ هەڵەکە خۆی هەمان شتە.
| |
---------------
| |
---------------
| |
Player x, enter your move (row col): a b
Traceback (most recent call last):
File "tic_tac_toe_v1.py", line 60, in <module>
main()
File "tic_tac_toe_v1.py", line 44, in main
row, col = map(int, input(f"Player {player}, enter your move (row col): ").split())
ValueError: invalid literal for int() with base 10: 'a'The third bug is worse, because nothing crashes. The player types -1 -1 and the program accepts it: the x appears in the bottom-right cell. In Python a counts from the end of a list, so board[-1][-1] is board[2][2] (see The negative-index trap).
سێیەم خراپترە، چونکە هیچ شتێک ناوەستێت. یاریزانەکە -1 -1 دەنووسێت و وەری دەگرێت: x لە خانەی خوارەوەی ڕاستدا دەردەکەوێت. لە لە کۆتایی دەژمێرێت، بۆیە board[-1][-1] هەمان board[2][2]ـە (بڕوانە داوی ئیندێکسی سالب).
| |
---------------
| |
---------------
| |
Player x, enter your move (row col): -1 -1
| |
---------------
| |
---------------
| |x
Player o, enter your move (row col): No error: the x lands in cell 2 2 and the game simply goes on with player o.
هیچ نییە: x لە خانەی 2 2دا دادەنرێت و یارییەکە بە ئاسایی بە یاریزانی o بەردەوام دەبێت.
Bad input: three bugs, one fixنێوخستەی هەڵە: سێ باگ، یەک چارەسەر
The fix: read_move()چارەسەرەکە: read_move()
The new function read_move() keeps asking until the input is good. It checks three things, in this order: two parts, whole numbers, and the range 0 to 2. Only then does it return the move. main() calls it instead of the map(int, ...) line. It is the same idea as A safe way to read a cell, written for the 3 × 3 board.
read_move() نوێیە و بەردەوام دەپرسێت تا باش دەبێت. سێ شت بەم ڕیزبەندییە دەپشکنێت: دوو بەش، و مەودای 0 تا 2. تەنیا ئەو کاتە جووڵەکە دەگەڕێنێتەوە. main() لەبری دێڕی map(int, ...) بانگی ئەم دەکات. هەمان بیرۆکەی ڕێگەیەکی سەلامەت بۆ خوێندنەوەی خانەیەکـە، بۆ تەختەی 3 × 3 نووسراوە.
# Tic-tac-toe for two people at one keyboard (x starts).
def print_board(board):
count = 0 # how many rows are printed so far
for row in board:
print(" |".join(row)) # the 3 cells, joined by " |"
if count < 2: # only 2 divider lines for 3 rows
print("-" * 15)
count += 1
print()
def can_insert(board, row, col, player):
if board[row][col] == ' ': # the cell is still empty
board[row][col] = player
return True
return False # the cell is already taken
def player_won(board, player):
for row in board: # 3 rows
if all([cell == player for cell in row]):
return True
for col in range(3): # 3 columns
if all([board[row][col] == player for row in range(3)]):
return True
if all([board[i][i] == player for i in range(3)]): # diagonal \
return True
if all([board[i][2 - i] == player for i in range(3)]): # diagonal /
return True
return False
def draw(board):
full = all([cell != ' ' for row in board for cell in row])
return full and not player_won(board, 'x') and not player_won(board, 'o')
def read_move(player):
"""Ask until the player types two whole numbers from 0 to 2."""
while True:
text = input(f"Player {player}, enter your move (row col): ")
parts = text.split()
if len(parts) != 2:
print("Please type two numbers, for example: 1 2")
continue
try:
row, col = int(parts[0]), int(parts[1])
except ValueError:
print("Only whole numbers, please.")
continue
if 0 <= row <= 2 and 0 <= col <= 2:
return row, col
print("Row and column must be 0, 1 or 2.")
def main():
board = [[' ', ' ', ' '] for _ in range(3)]
player = 'x'
while True:
print_board(board)
row, col = read_move(player)
if not can_insert(board, row, col, player):
print("Invalid move. Try again.")
continue
if player_won(board, player):
print_board(board)
print(f"Player {player} wins!")
break
if draw(board):
print_board(board)
print("It's a draw!")
break
player = 'o' if player == 'x' else 'x'
if __name__ == "__main__":
main() | |
---------------
| |
---------------
| |
Player x, enter your move (row col): -1 -1
Row and column must be 0, 1 or 2.
Player x, enter your move (row col): 3 3
Row and column must be 0, 1 or 2.
Player x, enter your move (row col): a b
Only whole numbers, please.
Player x, enter your move (row col): 1
Please type two numbers, for example: 1 2
Player x, enter your move (row col): 0 0
x | |
---------------
| |
---------------
| |
Player o, enter your move (row col): 0 1
x |o |
---------------
| |
---------------
| |
Player x, enter your move (row col): 0 2
x |o |x
---------------
| |
---------------
| |
Player o, enter your move (row col): 1 1
x |o |x
---------------
|o |
---------------
| |
Player x, enter your move (row col): 1 0
x |o |x
---------------
x |o |
---------------
| |
Player o, enter your move (row col): 1 2
x |o |x
---------------
x |o |o
---------------
| |
Player x, enter your move (row col): 2 1
x |o |x
---------------
x |o |o
---------------
|x |
Player o, enter your move (row col): 2 0
x |o |x
---------------
x |o |o
---------------
o |x |
Player x, enter your move (row col): 2 2
x |o |x
---------------
x |o |o
---------------
o |x |x
It's a draw!Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 43-46
Not exactly two parts (for example
1or1 2 3): ask again.ڕێک دوو بەش نییە (بۆ نموونە
1یان1 2 3): دیسان بپرسە.- Lineدێڕی 47-51
int()raisesValueErrorfor text likea;exceptcatches it, so nothing crashes.int()بۆ دەقێکی وەکaValueErrorدروست دەکات؛exceptدەیگرێت، بۆیە هیچ شتێک ناوەستێت.- Lineدێڕی 52-53
The range check also stops
-1(a negative index) and3(outside the board).پشکنینی مەوداکە
-1() و3(دەرەوەی تەختەکە)ش ڕادەگرێت.- Lineدێڕی 62
main()now gets a move that is always safe to use.ئێستا
main()جووڵەیەک وەردەگرێت کە هەمیشە بەکارهێنانی سەلامەتە.
Common mistakesهەڵە باوەکان
| Mistakeهەڵە | What happens / the fixچی ڕوودەدات / چارەسەر |
|---|---|
board = [[' '] * 3] * 3board = [[' '] * 3] * 3 | All three rows are the SAME list: one move appears in every row. Use [[' '] * 3 for _ in range(3)] (exercise 2).هەر سێ ڕیزەکە هەمان : یەک جووڵە لە هەموو ڕیزەکاندا دەردەکەوێت. [[' '] * 3 for _ in range(3)] بەکار بهێنە (ڕاهێنانی 2). |
map(int, input().split()) without checksmap(int, input().split()) بێ پشکنین | Letters and wrong counts crash the game; -1 is silently accepted. Validate (read_move()).پیت و ژمارەی هەڵە یارییەکە دەوەستێنن؛ -1 بە بێدەنگی وەردەگیرێت. بکە (read_move()). |
Checking draw() before player_won()پشکنینی draw() پێش player_won() | A full board that is also a win must be announced as a win. Check the win first (and draw() also excludes wins).تەختەیەکی پڕ کە بردنەوەشە دەبێت وەک بردنەوە ڕابگەیەنرێت. سەرەتا بردنەوە بپشکنە (و draw()ـیش بردنەوە لادەدات). |
Forgetting count += 1 in print_board()لەبیرچوونی count += 1 لە print_board()دا | A divider is printed after every row, also the last (exercise 3).دوای هەموو ڕیزێک جیاکەرەوە چاپ دەکرێت، دوای دواییشیان (ڕاهێنانی 3). |
| Switching the player after an invalid moveگۆڕینی یاریزان دوای جووڵەیەکی نادروست | The same player must try again: continue skips the switch.هەمان یاریزان دەبێت دیسان هەوڵ بداتەوە: continue گۆڕینەکە دەپەڕێنێت. |
Exercisesڕاهێنانەکان
Many people prefer to choose a cell with one number, like a phone keypad: 1 2 3 on the top row, 4 5 6 in the middle, 7 8 9 at the bottom. Write read_cell(player) that asks for 1–9, validates it, and returns (row, col).
زۆر کەس پێیان باشترە خانەیەک بە یەک ژمارە هەڵبژێرن، وەک تەختەی ژمارەی تەلەفۆن: 1 2 3 لە ڕیزی سەرەوە، 4 5 6 لە ناوەڕاست، 7 8 9 لە خوارەوە. read_cell(player) بنووسە کە داوای 1–9 دەکات، دەکات و (row, col) دەگەڕێنێتەوە.
Hintئاماژە
divmod(number - 1, 3) gives the row and the column at once.
divmod(number - 1, 3) ڕیز و ستوون پێکەوە دەدات.
Show answerوەڵامەکە پیشان بدە
def read_cell(player):
"""Ask for a cell number 1-9 (like a phone keypad) until it is valid."""
while True:
text = input(f"Player {player}, choose a cell (1-9): ").strip()
if not text.isdigit():
print("Please type a number.")
continue
number = int(text)
if 1 <= number <= 9:
return divmod(number - 1, 3) # 1 -> (0, 0), 5 -> (1, 1), 9 -> (2, 2)
print("The number must be from 1 to 9.")
row, col = read_cell('x')
print("row", row, "col", col)Player x, choose a cell (1-9): 0 The number must be from 1 to 9. Player x, choose a cell (1-9): 10 The number must be from 1 to 9. Player x, choose a cell (1-9): x Please type a number. Player x, choose a cell (1-9): 5 row 1 col 1
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5-7
isdigit()is true only for digits, sox,-1and an empty answer are refused.isdigit()تەنیا بۆ ژمارە ڕاستە، بۆیەx،-1و وەڵامی بەتاڵ ڕەت دەکرێنەوە.- Lineدێڕی 10
divmod(4, 3)is(1, 1): cell 5 is the centre.divmod(4, 3)بە(1, 1)ـە: خانەی 5 ناوەڕاستە.
Run this code and explain the first line of the output: bad = [[' '] * 3] * 3, then bad[0][0] = 'x' and print(bad).
ئەم کۆدە کار پێ بکە و دێڕی یەکەمی ڕوون بکەرەوە: bad = [[' '] * 3] * 3، پاشان bad[0][0] = 'x' و print(bad).
Show answerوەڵامەکە پیشان بدە
bad = [[' '] * 3] * 3 # ONE row, repeated 3 times
bad[0][0] = 'x'
print(bad)
good = [[' '] * 3 for _ in range(3)] # three different rows
good[0][0] = 'x'
print(good)[['x', ' ', ' '], ['x', ' ', ' '], ['x', ' ', ' ']] [['x', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
* 3 does not copy the row. It puts the same row object in the outer list three times, so changing one cell changes “all three rows”. The list comprehension builds three separate rows.
* 3 ڕیزەکە کۆپی ناکات. هەمان ڕیز سێ جار دەخاتە ناو دەرەوە، بۆیە گۆڕینی یەک خانە «هەر سێ ڕیزەکە» دەگۆڕێت. سێ ڕیزی جیاواز دروست دەکات.
In the course's short “Human VS Human” version, the board has an extra dashed line at the bottom. Run the function below and explain the bug.
لە وەشانە کورتەکەی «Human VS Human»ی کۆرسەکەدا، تەختەکە هێڵێکی داشی زیادەی لە خوارەوە هەیە. ئەم خوارەوە کار پێ بکە و ڕوون بکەرەوە.
def print_board(board):
count = 0
for row in board:
print(" |".join(row))
if count < 2:
print("-" * 15)
# count += 1 is missing here!
print()
print_board([['x', 'o', 'x'], [' ', 'o', ' '], [' ', ' ', ' ']])Show answerوەڵامەکە پیشان بدە
x |o |x
---------------
|o |
---------------
| |
---------------count is never increased, so it stays 0 and count < 2 is true for every row: three dividers instead of two. Add count += 1 at the end of the loop body, as in print_board() above.
count هەرگیز زیاد ناکرێت، بۆیە بە 0 دەمێنێتەوە و count < 2 بۆ هەموو ڕیزێک ڕاستە: سێ جیاکەرەوە لەبری دوو. count += 1 لە کۆتایی ناوەوەی زیاد بکە، وەک print_board()ی سەرەوە.
Summary and reviewکورتە و پێداچوونەوە
- The board is a list of three lists;
board[row][col]is one cell, numbered from 0. print_board(),can_insert(),player_won()(8 lines withall()) anddraw()are small functions thatmain()uses in a loop.- The player switches with a conditional expression:
'o' if player == 'x' else 'x'. - Input must be validated: count the parts, catch
ValueError, check the range 0–2 (negative indexes are a silent bug).
- تەختەکە لە سێ ؛
board[row][col]یەک خانەیە و ژمارەکان لە 0ەوە دەست پێدەکەن. print_board()،can_insert()،player_won()(8 هێڵ بەall()) وdraw()بچووکن کەmain()لە بەکاریان دەهێنێت.- یاریزان بە دەگۆڕێت:
'o' if player == 'x' else 'x'. - دەبێت پشتڕاست بکرێتەوە: بەشەکان بژمێرە،
ValueErrorبگرە، مەودای 0–2 بپشکنە ( بێدەنگن).
Why is the board a list of lists and not one list of 9 cells?بۆچی تەختەکە لیستێکە لە لیستەکان، نەک یەک لیست لە 9 خانە؟
Both work, but board[row][col] matches the way people think about a grid, and rows can be looped over directly.
هەردووکیان کار دەکەن، بەڵام board[row][col] لەگەڵ ئەو شێوەیەدا دەگونجێت کە خەڵک بیر لە تۆڕێک دەکەنەوە و دەتوانرێت ڕاستەوخۆ بە ناو ڕیزەکاندا بخولێتەوە.
What does all([]) return, and why could that matter?all([]) چی دەگەڕێنێتەوە و بۆچی ئەمە گرنگ بێت؟
True: there is no false item. Our lists always have 3 items, so it does not matter here, but it is a classic surprise.
True: هیچ دانەیەکی هەڵەی تێدا نییە. ئێمە هەمیشە 3 دانەیان هەیە، بۆیە لێرەدا گرنگ نییە، بەڵام سەرسوڕمانێکی ناسراوە.
What is the difference between a crash and a silent bug in this game?جیاوازیی نێوان لەکارکەوتن و باگێکی بێدەنگ لەم یارییەدا چییە؟
3 3 and a b stop the program with an error (crash). -1 -1 is accepted and puts the symbol in the wrong cell (silent bug).
3 3 و a b بە دەوەستێنن (). -1 -1 وەردەگیرێت و نیشانەکە لە خانەی هەڵەدا دادەنێت ( بێدەنگ).
Why does tic_tac_toe.py end with if __name__ == "__main__":?بۆچی tic_tac_toe.py بە if __name__ == "__main__": کۆتایی دێت؟
So that the game starts only when the file is run. The minimax lesson imports the functions from this file without starting a game.
تاکو یارییەکە تەنیا کاتێک دەست پێبکات کە کار پێ دەکرێت. وانەی لەم import دەکات بەبێ دەستپێکردنی یارییەک.
Videoڤیدیۆ
Watch a videoسەیری ڤیدیۆیەک بکە
A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.
A beginner builds a two-player tic-tac-toe game in Python step by step: the board, printing it, turns, wins, ties and checking the player's input.
فێرخوازێکی سەرەتایی هەنگاو بە هەنگاو بۆ دوو یاریزان بە دروست دەکات: تەختەکە و پیشاندانی، نۆرەکان، بردنەوە، یەکسانبوون و پشکنینی .
Starts at 2:49 (planning) and runs to the end. Careful: this video keeps the board in ONE list of 9 cells (you type 1–9) and uses global variables. Our lesson uses a list of lists, board[row][col], and passes the board to each function. Watch it for the plan and the steps (game loop, win and draw checks, switching players, checking input at 52:21), not for the exact code.
لە 2:49 (پلاندانان) دەست پێدەکات تا کۆتایی. ئاگادار بە: ئەم ڤیدیۆیە تەختەکە لە تەنها یەک هەڵدەگرێت کە 9 خانەی هەیە (ژمارەکانی 1 تا 9 دەنووسیت) و بەکاردەهێنێت. وانەکەی ئێمە لە بەکاردەهێنێت (board[row][col]) و تەختەکە وەک دەنێرێت بۆ هەر . بۆ پلان و هەنگاوەکان سەیری بکە ( یارییەکە، پشکنینی بردنەوە و یەکسانبوون، گۆڕینی یاریزان، پشکنینی لە 52:21)، نەک بۆ کۆدە وردەکەی.
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
Minimax: An AI That Never LosesMinimax: یاریزانێکی ژیر کە هەرگیز نادۆڕێت
Related lecturesوانە پەیوەندیدارەکان
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- Python tutorial: Lists (nested lists)
- Python documentation: Common sequence operations (negative indexes, note 3)
- Python documentation: Built-in functions (all, input, int, divmod)
- Python tutorial: Handling exceptions (try/except)
- Python FAQ: How do I create a multidimensional list? (the shared-row trap)
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.