Курс 3.5 · Раздел 13 · Урок 13.2

Микро-судоку

Разберём создание микро-судоку: ввод цифр мышью, хранение поля и проверку результата.

Разберём создание микро-судоку: ввод цифр мышью, хранение поля и проверку результата.
13.2 — Микро-судоку

Материал урока

Задача: Судоку (Sudoku.py)

Судоку — это увлекательная игра, в которой нужно правильно расположить цифры на игровом поле, следуя строгим правилам. Для детей мы предлагаем упрощённый вариант игры на клетчатом поле 3х3. Задача игрока — расставить цифры от 1 до 3 так, чтобы они не повторялись ни по вертикали, ни по горизонтали, ни по одной из диагоналей. Чтобы сделать игру более увлекательной, при загрузке приложения мы случайным образом разместим на поле две цифры.

Давайте рассмотрим, что нам необходимо сделать, чтобы создать это замечательное приложение. Вот основные шаги:

  1. Создадим поле размером 3 на 3, используя функцию grid.
  2. Поскольку цифр всего три, для их ввода можно использовать клики мыши: левая кнопка — 1, средняя — 2, правая — 3. Повторное нажатие на уже заполненную клетку изменит её значение на новое. Важно понять, как в программе обрабатывать нажатия клавиш мыши.
  3. Необходимо где-то сохранять расставленные цифры, например, в списке списков ячеек. Давайте создадим такой список и заполним его нулями — пустыми клетками. Также потребуется функция, которая будет преобразовывать координаты клика мышкой на экране в индексы ячеек клетчатого поля.
  4. Затем мы выберем две случайные цифры из трёх и расположим их на поле в случайном порядке. Это можно сделать в отдельной функции new_game(), что будет удобно, если мы захотим запустить новую игру.
  5. Необходимо создать функцию, которая будет проходить по элементам списка списков и записывать цифры на клетчатом поле, располагая их в заданных клетках. Для этого понадобится функция, преобразующая координаты ячеек поля в координаты на экране. Обратите внимание: можно, конечно, просто писать цифры на игровом поле, но если игрок захочет изменить значение, то нам придётся стирать все, что написала эта Черепаха, и начинать заново.
  6. Чтобы проверить правильность ввода цифр, мы разработаем функцию, которая будет активироваться, когда всё поле окажется заполненным. В случае обнаружения ошибок неверные цифры будут выделены красным цветом. Если же все цифры введены верно, то на экране появится поздравительное сообщение. Чтобы после проверки выигрыша игрок не мог внести изменения в поле, мы исключим возможность взаимодействия функций с кликом мыши.
  7. Можно добавить новые функции, например, возможность начать новую игру одним нажатием пробела.

Этап 1

Давайте импортируем необходимые библиотеки, установим значения констант, определим функции, которые нам понадобятся, и напишем основную часть программы.

from turtle import Turtle, Screen, done

import random as rd

N, SIZE = 3, 150

HALF_WINDOW = N * SIZE // 2

def grid():

    # функция рисования сетки

    ...

def point(x, y):

    # приведение к середине клетки по х и чуть ниже по y

    # для размещения цифры

    ...

def indexes(x, y):

    # приведение экранных координат к индексам клетки

    ...

def cell(i, j):

    # приведение индексов клетки к экранным координатам

    ...

def write_digit():

    # пишем цифру

    ...

    check()  # проверяем выигрыш

def one(x, y):

    # помещаем 1 в board

    ...

    write_digit()  # пишем цифру

def two(x, y):

    # помещаем 2 в board

    ...

    write_digit()  # пишем цифру

def three(x, y):

    # помещаем 3 в board

    ...

    write_digit()  # пишем цифру

def new_game():

    # все стереть, разместить 2 случайные цифры

    ...

def check():

    # проверка выигрыша

    ...

    sc.onclick(None)  # удаляем связь клика с функцией

sc = Screen()

sc.setup(N * SIZE, N * SIZE)

board = [[0] * N for _ in range(N)]

tom = Turtle()

tom.speed(0)

tom.pu()

tom.ht()

new_game()

sc.onclick(one, btn=1)  # обработка клика левой кнопкой мыши

sc.onclick(two, btn=2)  # обработка клика средней кнопкой мыши

sc.onclick(three, btn=3)  # обработка клика правой кнопкой мыши

sc.listen()

grid()

done()

Этапы 2 и 3

Создадим функции, которые будут вызываться при нажатии на экран. Для определения индексов клеток по их координатам на экране используем функцию indexes().

def one(x, y):

    i, j = indexes(x, y)

    if board[i][j]:  # если в клетке уже что-то есть, то сотрем

        tom.clear()

    board[i][j] = 1

    write_digit()

def two(x, y):

    i, j = indexes(x, y)

    if board[i][j]:

        tom.clear()

    board[i][j] = 2

    write_digit()

def three(x, y):

    i, j = indexes(x, y)

    if board[i][j]:

        tom.clear()

    board[i][j] = 3

    write_digit()

def indexes(x, y):

    # приведение экранных координат к индексам клетки

    i = int((x + HALF_WINDOW) // SIZE)

    j = int((-y + HALF_WINDOW) // SIZE)

    return i, j

Этап 4

В начале игры мы очистим игровое поле от всех имеющихся на нём элементов, чтобы избежать повторений. Затем, случайным образом выберем две цифры из набора 1, 2, 3 и разместим их в разных клетках поля.

def new_game():

    tom.clear()

    digits = rd.sample([1, 2, 3], k=2)

    places = {(rd.randint(0, 2), rd.randint(0, 2))}

    while len(places) < 2:

        i, j = (rd.randint(0, 2), rd.randint(0, 2))

        places.add((i, j))

    for i, t in enumerate(places):

        x, y = t

        board[x][y] = digits[i]

    write_digit()

Этап 5

Теперь в списке board содержатся цифры, которые нужно отобразить на поле с помощью функции write_digit(). Для корректной работы этой функции необходимо определить экранные координаты каждой цифры, используя функцию cell(). Затем следует перейти в центр этой клетки, воспользовавшись функцией point().

def write_digit():

    for j in range(N):

        for i in range(N):

            if board[i][j]:

                point(*cell(i, j))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[i][j]), **params)

    check()

def point(x, y):

    # приведение к середине клетки по х и чуть ниже по y

    w = ((x + HALF_WINDOW) // SIZE - (N - 1) / 2) * SIZE

    h = ((y + HALF_WINDOW) // SIZE - 5 * (N - 1) / 8) * SIZE

    tom.goto(w, h)

def cell(i, j):

    # приведение индексов клетки к экранным координатам

    x = -HALF_WINDOW + (i + 0.5) * SIZE

    y = HALF_WINDOW - (j + 0.5) * SIZE

    return x, y

Этап 6

Теперь необходимо убедиться, что все цифры расставлены верно. Для этого создадим функцию check(), которую будем вызывать после каждой новой цифры.

def check():

    if any(0 in row for row in board):

        return

    for i in range(N):

        column = board[i]

        if sorted(column) != [1, 2, 3]:

            n = [x for x in column if column.count(x) > 1][0]

            ind = [j for j in range(3) if column[j] == n]

            tom.color('red')

            for j in ind:

                point(*cell(i, j))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[i][j]), **params)

            sc.onclick(None)

            return

    for i in range(N):

        row = [board[j][i] for j in range(3)]

        if sorted(row) != [1, 2, 3]:

            n = [x for x in row if row.count(x) > 1][0]

            ind = [j for j in range(3) if row[j] == n]

            tom.color('red')

            for j in ind:

                point(*cell(j, i))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[j][i]), **params)

            sc.onclick(None)

            return

    tom.color('green')

    tom.goto(-SIZE * 1.25, SIZE * 0.25)

    params = {'font': ('Comfortaa', 56, 'normal')}

    tom.write("YOU WIN!!!", **params)

    sc.onclick(None)

Соберите программу, добейтесь, чтобы все работало вот так:

Код всей игры Микро-судоку

from turtle import Turtle, Screen, done

import random as rd

N, SIZE = 3, 150

HALF_WINDOW = N * SIZE // 2

def grid():

    row, column = Turtle(), Turtle()

    for item in row, column:

        item.speed(0)

        item.ht()

        item.pu()

        item.goto(-(N * SIZE // 2), N * SIZE // 2)

    column.rt(90)

    for _ in range(N):

        row.goto(-(N * SIZE // 2), row.ycor() - SIZE)

        column.goto(column.xcor() + SIZE, N * SIZE // 2)

        for item in row, column:

            item.pd()

            item.fd(N * SIZE)

            item.pu()

def point(x, y):

    # приведение к середине клетки по х и чуть ниже по y

    w = ((x + HALF_WINDOW) // SIZE - (N - 1) / 2) * SIZE

    h = ((y + HALF_WINDOW) // SIZE - 5 * (N - 1) / 8) * SIZE

    tom.goto(w, h)

def indexes(x, y):

    # приведение экранных координат к индексам клетки

    i = int((x + HALF_WINDOW) // SIZE)

    j = int((-y + HALF_WINDOW) // SIZE)

    return i, j

def cell(i, j):

    # приведение индексов клетки к экранным координатам

    x = -HALF_WINDOW + (i + 0.5) * SIZE

    y = HALF_WINDOW - (j + 0.5) * SIZE

    return x, y

def write_digit():

    for i in range(N):

        for j in range(N):

            if board[i][j]:

                point(*cell(i, j))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[i][j]), **params)

    check()

def one(x, y):

    i, j = indexes(x, y)

    if board[i][j]:  # если в клетке уже что-то есть, то сотрем

        tom.clear()

    board[i][j] = 1

    write_digit()

def two(x, y):

    i, j = indexes(x, y)

    if board[i][j]:

        tom.clear()

    board[i][j] = 2

    write_digit()

def three(x, y):

    i, j = indexes(x, y)

    if board[i][j]:

        tom.clear()

    board[i][j] = 3

    write_digit()

def new_game():

    tom.clear()

    digits = rd.sample([1, 2, 3], k=2)

    places = {(rd.randint(0, 2), rd.randint(0, 2))}

    while len(places) < 2:

        i, j = (rd.randint(0, 2), rd.randint(0, 2))

        places.add((i, j))

    for i, t in enumerate(places):

        x, y = t

        board[x][y] = digits[i]

    write_digit()

def check():

    if any(0 in row for row in board):

        return

    for i in range(N):

        column = board[i]

        if sorted(column) != [1, 2, 3]:

            n = [x for x in column if column.count(x) > 1][0]

            ind = [j for j in range(3) if column[j] == n]

            tom.color('red')

            for j in ind:

                point(*cell(i, j))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[i][j]), **params)

            sc.onclick(None)

            return

    for i in range(N):

        row = [board[j][i] for j in range(3)]

        if sorted(row) != [1, 2, 3]:

            n = [x for x in row if row.count(x) > 1][0]

            ind = [j for j in range(3) if row[j] == n]

            tom.color('red')

            for j in ind:

                point(*cell(j, i))

                params = {'align': 'center',

                          'font': ('Comfortaa', 56, 'normal')}

                tom.write(str(board[j][i]), **params)

            sc.onclick(None)

            return

    tom.color('green')

    tom.goto(-SIZE * 1.25, SIZE * 0.25)

    params = {'font': ('Comfortaa', 56, 'normal')}

    tom.write("YOU WIN!!!", **params)

    sc.onclick(None)

sc = Screen()

sc.setup(N * SIZE, N * SIZE)

board = [[0] * N for _ in range(N)]

tom = Turtle()

tom.speed(0)

tom.pu()

tom.ht()

new_game()

sc.onclick(one, btn=1)  # обработка клика левой кнопкой мыши

sc.onclick(two, btn=2)  # обработка клика средней кнопкой мыши

sc.onclick(three, btn=3)  # обработка клика правой кнопкой мыши

sc.listen()

grid()

done()

Мы не стали подробно рассматривать последний, шестой этап, поскольку вы можете добавить в игру дополнительные функции самостоятельно. Тем более что для этого нужно совсем немного: связать нажатие пробела с вызовом функции, которая будет запускать новую игру.

Демонстрация игры «Микро-судоку»
Демонстрация игры «Микро-судоку»