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()⁩، پشکنینی یەکسانبوون و یارییەکە. پاشان سێ لە کۆدی کۆرسەکەدا دەدۆزیتەوە (دوو و بێدەنگی ) و بە سەلامەت چاکیان دەکەیت. تەواوکراو بنەمایە بۆ یاریزانێکی کۆمپیوتەری کە بەکار دەهێنێت.

15 min read13 خولەک خوێندنەوە · 13 stepsهەنگاو

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_won and draw.
  • 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 grid
  • print_board(): shows the board
  • can_insert(): puts a symbol in an empty cell
  • player_won() and draw(): check the end of the game
  • main(): the game loop
  • board: تۆڕی ⁦3 × 3⁩
  • print_board(): تەختەکە پیشان دەدات
  • can_insert(): نیشانەیەک لە خانەیەکی بەتاڵدا دادەنێت
  • player_won() و draw(): کۆتایی یارییەکە دەپشکنن
  • main(): یارییەکە
A tic-tac-toe board with the coordinates “row col” in every cell, from 0 0 at the top left to 2 2 at the bottom right. Row numbers are on the left, column numbers on top. A blue x sits in cell 1 2, and the text says board[1][2] = 'x' means row 1, column 2. A dashed green line marks the diagonal board[i][i] (0 0, 1 1, 2 2) and a dashed orange line the diagonal board[i][2 - i] (0 2, 1 1, 2 0). The text says there are 8 ways to win: 3 rows, 3 columns and 2 diagonals.تەختەیەکی یاریی ئێکس‌وئۆ کە لە هەر خانەیەکدا ژمارەکانی «ڕیز ستوون» نووسراون، لە 0 0 لە سەرەوەی چەپەوە تا 2 2 لە خوارەوەی ڕاست. ژمارەی ڕیزەکان لە لای چەپن و ژمارەی ستوونەکان لە سەرەوە. x ـێکی شین لە خانەی 1 2دایە و دەقەکە دەڵێت board[1][2] = 'x' واتە ڕیزی 1، ستوونی 2. هێڵێکی سەوزی پچڕپچڕ هێڵە لارەکەی board[i][i] (0 0، 1 1، 2 2) نیشان دەدات و هێڵێکی پرتەقاڵیی پچڕپچڕ هێڵە لارەکەی board[i][2 - i] (0 2، 1 1، 2 0). دەقەکە دەڵێت 8 ڕێگەی بردنەوە هەیە: 3 ڕیز، 3 ستوون و 2 هێڵی لار.
Every cell has a row number and a column number, both from 0 to 2.هەر خانەیەک ژمارەی ڕیز و ژمارەی ستوونی هەیە، هەردووکیان لە 0 تا 2.CêIT
Quick checkپشکنینی خێراHow many different lines can win a game of tic-tac-toe?چەند هێڵی جیاواز لە دەتوانن یارییەکە ببەنەوە؟

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].

The board and one cellتەختەکە و یەک خانەboard_demo.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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
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.

یەک هەموو ڕیزێک دەدات؛ دوو یەک خانە دەدەن.

Quick checkپشکنینی خێراWhich cell is board[2][0]?board[2][0] کام خانەیە؟

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 لە بیری دەگرێت چەند ڕیز چاپ کراون. لە کۆتاییدا دێڕێکی بەتاڵ دێت.

print_board()کرداری ⁦print_board()⁩print_board_demo.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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() works
Explain 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.

جیاکەرەوە تەنیا دوای دوو ڕیزی یەکەم؛ پاشان ژمارەکە زیاد بکە.

Quick checkپشکنینی خێراWhat does "-".join(['a', 'b', 'c']) give?"-".join(['a', 'b', 'c']) چی دەدات؟

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 دەگەڕێنێتەوە.

can_insert()کرداری ⁦can_insert()⁩insert_demo.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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])
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.

تەختەیەکی بەتاڵی نوێ، ڕیز بە ڕیز دروستکراو.

Quick checkپشکنینی خێراboard[1][1] is 'o'. What does can_insert(board, 1, 1, 'x') do?board[1][1] بە 'o'ـە. can_insert(board, 1, 1, 'x') چی دەکات؟

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 بۆ تاقیگەکانی ژیریی دەستکرد).

player_won()کرداری ⁦player_won()⁩win_demo.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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 /
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 2-4

Rows: [cell == player for cell in row] makes three True/False values (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ـە.

Quick checkپشکنینی خێراWhich cells does [board[i][2 - i] for i in range(3)] look at?[board[i][2 - i] for i in range(3)] سەیری کام خانانە دەکات؟

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 دەگەڕێنێتەوە کە هەموو خانەکان پڕ بن و هیچ یاریزانێک نەیبردبێتەوە. ڕیزبەندییەکە گرنگە: تەختەیەکی پڕ دەکرێت بردنەوەش بێت، کاتێک دوایین جووڵە هێڵێک تەواو دەکات.

draw()کرداری ⁦draw()⁩draw_demo.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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]))
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 16

for row in board for cell in row walks through all 9 cells (two fors 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 بردوویەتییەوە، بۆیە یەکسان نییە.

Quick checkپشکنینی خێراWhy does draw() also call player_won()?بۆچی draw() بانگی player_won()ـیش دەکات؟

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) و لە کۆتاییدا یاریزان بگۆڕە.

The first version of the whole gameیەکەم وەشانی هەموو یارییەکەtic_tac_toe_v1.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
# 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()
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 continue jumps 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!⁩» کۆتایی دێت. ئەو یارییە لە کۆتایی تەواوکراودا دەبینیت، دوو پەڕە دواتر.

Quick checkپشکنینی خێراPlayer x types a cell that is already taken. Why does x get another try, and not o?یاریزانی x خانەیەکی گیراو دەنووسێت. بۆچی x هەلێکی تر وەردەگرێت، نەک o؟

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 ):

یەکەم وەشان بە تەواوی متمانە بە یاریزانەکە دەکات. کاتێک بە هەڵە تاقی کرایەوە، سێ هەیە. دووانیان بە دەوەستێنن ():

Bug 1: a number that is too big (3 3) → IndexErrorباگی 1: ژمارەیەکی زۆر گەورە ⁦(3 3)⁩ ← IndexError
    |    | 
---------------
    |    | 
---------------
    |    | 

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 range

Python 3.11 and newer also underline the failing part of each line with ^ signs; the error itself is the same.

⁦3.11⁩ و نوێتر بەشی هەڵەکەی هەر دێڕێک بە نیشانەی ^ هێڵ بە ژێردا دەکات؛ هەڵەکە خۆی هەمان شتە.

Bug 2: letters (a b) → ValueErrorباگی 2: پیت ⁦(a b)⁩ ← ValueError
    |    | 
---------------
    |    | 
---------------
    |    | 

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]ـە (بڕوانە داوی ئیندێکسی سالب).

Bug 3: -1 -1 is accepted as 2 2باگی ⁦3: -1 -1⁩ وەک ⁦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⁩ نووسراوە.

The finished game with safe inputیاریی تەواوکراو بە نێوخستەیەکی سەلامەتtic_tac_toe.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
# 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()
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 43-46

Not exactly two parts (for example 1 or 1 2 3): ask again.

ڕێک دوو بەش نییە (بۆ نموونە 1 یان 1 2 3): دیسان بپرسە.

Lineدێڕی 47-51

int() raises ValueError for text like a; except catches it, so nothing crashes.

int() بۆ دەقێکی وەک a ValueError دروست دەکات؛ except دەیگرێت، بۆیە هیچ شتێک ناوەستێت.

Lineدێڕی 52-53

The range check also stops -1 (a negative index) and 3 (outside the board).

پشکنینی مەوداکە -1 () و 3 (دەرەوەی تەختەکە)ش ڕادەگرێت.

Lineدێڕی 62

main() now gets a move that is always safe to use.

ئێستا main() جووڵەیەک وەردەگرێت کە هەمیشە بەکارهێنانی سەلامەتە.

Quick checkپشکنینی خێراWhich input does read_move() accept?read_move() کام وەردەگرێت؟

Common mistakesهەڵە باوەکان

MistakeهەڵەWhat happens / the fixچی ڕوودەدات / چارەسەر
board = [[' '] * 3] * 3board = [[' '] * 3] * 3All 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ڕاهێنانەکان

ExerciseڕاهێنانChoose a cell with 1–9خانەیەک بە ⁦1–9⁩ هەڵبژێرە

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وەڵامەکە پیشان بدە
number_keys.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5-7

isdigit() is true only for digits, so x, -1 and 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 ناوەڕاستە.

ExerciseڕاهێنانThe shared-row trapداوی ڕیزی هاوبەش

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وەڵامەکە پیشان بدە
aliasing.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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)

* 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 ڕیزەکە کۆپی ناکات. هەمان ڕیز سێ جار دەخاتە ناو دەرەوە، بۆیە گۆڕینی یەک خانە «هەر سێ ڕیزەکە» دەگۆڕێت. سێ ڕیزی جیاواز دروست دەکات.

ExerciseڕاهێنانFind the divider bugباگی جیاکەرەوەکە بدۆزەوە

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⁩»ی کۆرسەکەدا، تەختەکە هێڵێکی داشی زیادەی لە خوارەوە هەیە. ئەم خوارەوە کار پێ بکە و ڕوون بکەرەوە.

divider_bug.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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وەڵامەکە پیشان بدە
Outputدەرئەنجام
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 with all()) and draw() are small functions that main() 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⁩ بپشکنە ( بێدەنگن).
Review questionsپرسیارەکانی پێداچوونەوە
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.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.

Python for absolute beginners 2019 - TIC TAC TOE project (+Special Appearance!)Clever Programmer · Englishئینگلیزی · 408K viewsبینین · 8.8K likesلایک · Partبەشی 2:49–1:05:04 (ofلە 1:05:04)

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زاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

Minimax: An AI That Never LosesMinimax: یاریزانێکی ژیر کە هەرگیز نادۆڕێت

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.