Пишем игру «Палочки» на Python — Tkinter
Всем привет. Если вы новичок в Python и пришли программировать игры, то я могу научить вас делать простую игру с графическим интерфейсом. После выбора IDE, вы можете начать писать код. В этом посте вы узнаете:
- О правилах игры в «Палочки»
- О создании GUI интерфейса на Tkinter с нуля
- О механике игры в «скрытой» части кода
Суть игры такова — на столе есть 20 палочек. Играют два игрока. Первый игрок берет 1, 2, или 3 палочки. Второй игрок также берет 1, 2, или 3 палочки. И так по очереди. Тот, кто возьмет последнюю палочку — проиграет.
Пишем код
Для начала импортируем библиотеки tkinter и random. Они встроенные, и ничего не надо устанавливать дополнительно. Мы импортируем из них *, то есть всё, а значит, теперь вместо random.randint мы можем писать просто randint.
from tkinter import * from random import *
Далее нам нужно разобраться с самим интерфейсом. Сама механика — позже.
left = 20 #Устанавливаем кол-во палочек root = Tk() #Присваиваем класс Ткинтера переменной root root.geometry("550x200") #Устанавливаем размер окна 550 на 200. Вы можете поставить другой, но тогда вам придется настраивать расположение и размер GUI-элементов вручную root.resizable(0, 0) #Делаем так, чтобы размер нашего окна не мог меняться по осям х и у root.title("Sticks") #Устанавливаем название окна root.mainloop() #Запускаем окно
Если мы запустим данный код сейчас, то получим вот такое окошко:

Оно имеет размер 550 на 200, название «Sticks», а также мы не можем менять его размер. Но окошко пустое, надо заполнить его. У нас будут располагаться:
- Текст «Сколько палочек будем брать»
- Кнопки для взятия палочек
- Палочки
- Кнопка хода компьютера
Начинаем работать МЕЖДУ переменными root.geometry и root.resizable
text1 = Label(root, text="Сколько палочек будем брать?") #Задаем окно расположения текста и сам текст text1.pack() #Пакуем текст butt1 = Button(root, text="1") #Настраиваем кнопку butt1.place(x=210, y=30) #Распологаем кнопку по указанным координатам butt2 = Button(root, text="2") butt2.place(x=265, y=30) butt3 = Button(root, text="3") butt3.place(x=320, y=30) sticks = Label(root, text = left * "| ") #Выводим палочки sticks.config(font = ("Arial", 30, 'bold')) #Устанавливаем шрифт Arial, размер текста 30 и делаем его жирным sticks.place(x=50, y=70) pc_butt = Button(root, text = "Ход компьютера", widht = 30) #Создаем кнопку хода компьютера с шириной в 30 пикселей pc_butt.place(x=170, y=150)

Уже похоже на игру, верно? Так вот, мы закончили делать интерфейс, пора переходить к самому главному механизму.
Если вы запустили код, то увидели, что кнопки ничего не делают. Все потому что мы не задали им команду которую нужно выполнять. Это можно сделать с помощью аргумента command, который должен располагаться в настройках кнопки. Для этого нужно сделать примерно так:
def hello(): print("Hello, world!") button = Button(root, text="Вывод", command = hello) button.pack()
Теперь при нажатии кнопки в консоль будет выводится надпись «Hello, world!». То есть указывая название функции (без скобок в конце) мы будем делать то, что в ней написано. Давайте настроим кнопки.
#Напишу вкратце, ибо будет долго заново писать все расположения и все остальное #pass говорит о том, что данный кусок кода будет заполнен позже. Это своего рода затычка def s1(): pass def s2(): pass def s3(): pass def pc(): pass butt1 = Button(root, text="1", command = s1) butt2 = Button(root, text="2", command = s2) butt3 = Button(root, text="3", command = s3) pc_butt = Button(root, text="Ход компьютера", command = pc)
Теперь наша графическая часть точно готова. Давайте приступим к написанию функций
def s1(): global left #Получаем глобальный доступ к переменной left u = 1 #Указываем что мы берем одну палочку left = left - u #Вычитаем от кол-ва палочек одну if left
Функции s2 и s3 заполняем похоже, меняя лишь переменную u
def s2(): global left u = 2 left = left - u if left
Игра почти готова. Уже сейчас в нее можно играть. Однако одному играть неинтересно, поэтому добавим компьютер
def pc(): global left a = randint(1, 3) #Загадываем число от 1 до 3 left = left - int(a) if left
Игра уже готова. Но компьютер без интеллекта, он просто вычитывает палочки, не задумываясь о возможности проиграть или выиграть. Давайте добавим его хоть немного интеллекта.
def pc(): global left a = randint(1, 3) if left == 4: #Если осталось 4 палочки a = 3 #Компьютер убирает 3 elif left == 3: #Если осталось 3 палочки a = 2 #Компьютер убирает 2 elif left == 2: #Если осталось 2 палочки a = 1 #Компьютер убирает 1 left = left - a if left
Ну вот, теперь на финальных стадиях игры компьютер имеет шансы выиграть, поэтому теперь мы не сможем выиграть, забирая любое кол-во палочек.
Готовый код без комментариев
Перед копированием кода, хочу предупредить, что здесь Tab содержит 2 пробела, а не 4, из-за чего вам придется либо писать код вручную либо переделывать Tab'ы
#Версия Python - 3.8.6 from tkinter import * from random import * left = 20 def s1(): global left u = 1 left = left - int(u) if left
Три крутые игры на Python с исходниками
Уверен, что вы хоть раз играли в эту интересную, но простую игру.
Цель этой игры, отбивать мяч от платформы и не упустить его.
При создании игры используются такие библиотеки как:
- tkinter, эта библиотека предустановленна на большинстве версиях Python и используется для создания самого оконного приложения.
- time, в нашем случае будет использоваться, что бы задать скорость мячу и платформе. Для установки зайдите в командную строку от имени администратора и напишите: pip install time проверьте что pip у вас установлен.
- random, в нашем случае будет использоваться , что бы мяч отскакивал в разных направлениях. Эта библиотека так же предустановленна на всех версиях Python.
- pygame, используется для создания графического интерфейса нашей игры. Эту библиотеку нужно скачать через командную строку, прописав: pip install pygame
from tkinter import * import time import random import pygame class Ball(): def __init__(self, canvas, platform, color): self.canvas = canvas self.platform = platform self.oval = canvas.create_oval(200, 200, 215, 215, fill=color) self.dir = [-3, -2, -1, 1, 2, 3] self.x = random.choice(self.dir) self.y = -1 self.touch_bottom = False def touch_platform(self, ball_pos): platform_pos = self.canvas.coords(self.platform.rect) if ball_pos[2] >= platform_pos[0] and ball_pos[0] <= platform_pos[2]: if ball_pos[3] >= platform_pos[1] and ball_pos[3] <= platform_pos[3]: return True return False def draw(self): self.canvas.move(self.oval, self.x, self.y) pos = self.canvas.coords(self.oval) if pos[1] <= 0: self.y = 3 if pos[3] >= 400: self.touch_bottom = True if self.touch_platform(pos) == True: self.y = -3 if pos[0] <= 0: self.x = 3 if pos[2] >= 500: self.x = -3 class Platform(): def __init__(self, canvas, color): self.canvas = canvas self.rect = canvas.create_rectangle(230, 300, 330, 310, fill=color) self.x = 0 self.canvas.bind_all('', self.left) self.canvas.bind_all('', self.right) def left(self, event): self.x = -2 def right(self, event): self.x = 2 def draw(self): self.canvas.move(self.rect, self.x, 0) pos=self.canvas.coords(self.rect) if pos[0] <= 0: self.x = 0 if pos[2] >= 500: self.x = 0 window = Tk() window.title("Аркада") window.resizable(0, 0) window.wm_attributes("-topmost", 1) canvas = Canvas(window, width=500, height=400) canvas.pack() platform = Platform(canvas, 'green') ball = Ball(canvas, platform, 'red') while True: if ball.touch_bottom == False: ball.draw() platform.draw() else: break window.update() time.sleep(0.01) window.mainloop()=>
Вот и весь код для этой интересной, простой и увлекательной игры. Вы так же можете модернизировать игру, добавив в неё например количество балов за отбитые мячи, или второй мяч.
Игра №2. Тетрис.
Эту игру знают все! Главная задача игрока не дать разным, геометрическим фигурам достигнуть "ФИНИША".
import sys, random from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal from PyQt5.QtGui import QPainter, QColor
Вы видите библиотеки, которые будут использоваться при создании данной игры, всех их нужно загрузить через командную строку вашего компьютера.
После того как установили нужные нам библиотеки, создаём класс с нашими переменными. класс назовём Tetris и будем использовать свойства отцовского класса, чтобы каждый раз не прописывать все переменные заново для каждого последующего класса.
class Tetris(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.tboard = Board(self) self.setCentralWidget(self.tboard) self.statusbar = self.statusBar() self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage) self.tboard.start() self.resize(180, 380) self.center() self.setWindowTitle('Tetris') self.show() def center(self): screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
После создания отцовского класса Tetris, создаём все остальные классы и переменные для уже других функций.
class Board(QFrame): msg2Statusbar = pyqtSignal(str) BoardWidth = 10 BoardHeight = 22 Speed = 300 def __init__(self, parent): super().__init__(parent) self.initBoard() def initBoard(self): self.timer = QBasicTimer() self.isWaitingAfterLine = False self.curX = 0 self.curY = 0 self.numLinesRemoved = 0 self.board = [] self.setFocusPolicy(Qt.StrongFocus) self.isStarted = False self.isPaused = False self.clearBoard() def shapeAt(self, x, y): return self.board[(y * Board.BoardWidth) + x] def setShapeAt(self, x, y, shape): self.board[(y * Board.BoardWidth) + x] = shape def squareWidth(self): return self.contentsRect().width() // Board.BoardWidth def squareHeight(self): return self.contentsRect().height() // Board.BoardHeight def start(self): if self.isPaused: return self.isStarted = True self.isWaitingAfterLine = False self.numLinesRemoved = 0 self.clearBoard() self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.newPiece() self.timer.start(Board.Speed, self) def pause(self): if not self.isStarted: return self.isPaused = not self.isPaused if self.isPaused: self.timer.stop() self.msg2Statusbar.emit("paused") else: self.timer.start(Board.Speed, self) self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.update()
def paintEvent(self, event): painter = QPainter(self) rect = self.contentsRect() boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight() for i in range(Board.BoardHeight): for j in range(Board.BoardWidth): shape = self.shapeAt(j, Board.BoardHeight - i - 1) if shape != Tetrominoe.NoShape: self.drawSquare(painter, rect.left() + j * self.squareWidth(), boardTop + i * self.squareHeight(), shape) if self.curPiece.shape() != Tetrominoe.NoShape: for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY - self.curPiece.y(i) self.drawSquare(painter, rect.left() + x * self.squareWidth(), boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(), self.curPiece.shape()) def keyPressEvent(self, event): if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape: super(Board, self).keyPressEvent(event) return key = event.key() if key == Qt.Key_P: self.pause() return if self.isPaused: return elif key == Qt.Key_Left: self.tryMove(self.curPiece, self.curX - 1, self.curY) elif key == Qt.Key_Right: self.tryMove(self.curPiece, self.curX + 1, self.curY) elif key == Qt.Key_Down: self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY) elif key == Qt.Key_Up: self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY) elif key == Qt.Key_Space: self.dropDown() elif key == Qt.Key_D: self.oneLineDown() else: super(Board, self).keyPressEvent(event) def timerEvent(self, event): if event.timerId() == self.timer.timerId(): if self.isWaitingAfterLine: self.isWaitingAfterLine = False self.newPiece() else: self.oneLineDown() else: super(Board, self).timerEvent(event) def clearBoard(self): for i in range(Board.BoardHeight * Board.BoardWidth): self.board.append(Tetrominoe.NoShape) def dropDown(self): newY = self.curY while newY > 0: if not self.tryMove(self.curPiece, self.curX, newY - 1): break newY -= 1 self.pieceDropped() def oneLineDown(self): if not self.tryMove(self.curPiece, self.curX, self.curY - 1): self.pieceDropped() def pieceDropped(self): for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY - self.curPiece.y(i) self.setShapeAt(x, y, self.curPiece.shape()) self.removeFullLines() if not self.isWaitingAfterLine: self.newPiece()
def removeFullLines(self): numFullLines = 0 rowsToRemove = [] for i in range(Board.BoardHeight): n = 0 for j in range(Board.BoardWidth): if not self.shapeAt(j, i) == Tetrominoe.NoShape: n = n + 1 if n == 10: rowsToRemove.append(i) rowsToRemove.reverse() for m in rowsToRemove: for k in range(m, Board.BoardHeight): for l in range(Board.BoardWidth): self.setShapeAt(l, k, self.shapeAt(l, k + 1)) numFullLines = numFullLines + len(rowsToRemove) if numFullLines > 0: self.numLinesRemoved = self.numLinesRemoved + numFullLines self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.isWaitingAfterLine = True self.curPiece.setShape(Tetrominoe.NoShape) self.update() def newPiece(self): self.curPiece = Shape() self.curPiece.setRandomShape() self.curX = Board.BoardWidth // 2 + 1 self.curY = Board.BoardHeight - 1 + self.curPiece.minY() if not self.tryMove(self.curPiece, self.curX, self.curY): self.curPiece.setShape(Tetrominoe.NoShape) self.timer.stop() self.isStarted = False self.msg2Statusbar.emit("Game over") def tryMove(self, newPiece, newX, newY): for i in range(4): x = newX + newPiece.x(i) y = newY - newPiece.y(i) if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: return False if self.shapeAt(x, y) != Tetrominoe.NoShape: return False self.curPiece = newPiece self.curX = newX self.curY = newY self.update() return True def drawSquare(self, painter, x, y, shape): colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] color = QColor(colorTable[shape]) painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, self.squareHeight() - 2, color) painter.setPen(color.lighter()) painter.drawLine(x, y + self.squareHeight() - 1, x, y) painter.drawLine(x, y, x + self.squareWidth() - 1, y) painter.setPen(color.darker()) painter.drawLine(x + 1, y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + self.squareHeight() - 1) painter.drawLine(x + self.squareWidth() - 1, y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
После создаём класс уже для самих объектов( фигур), назовём его Tetrominoe.
class Tetrominoe(object): NoShape = 0 ZShape = 1 SShape = 2 LineShape = 3 TShape = 4 SquareShape = 5 LShape = 6 MirroredLShape = 7
И ещё один класс, но уже будем задавать координаты фигурам.
class Shape(object): coordsTable = ( ((0, 0), (0, 0), (0, 0), (0, 0)), ((0, -1), (0, 0), (-1, 0), (-1, 1)), ((0, -1), (0, 0), (1, 0), (1, 1)), ((0, -1), (0, 0), (0, 1), (0, 2)), ((-1, 0), (0, 0), (1, 0), (0, 1)), ((0, 0), (1, 0), (0, 1), (1, 1)), ((-1, -1), (0, -1), (0, 0), (0, 1)), ((1, -1), (0, -1), (0, 0), (0, 1)) ) def __init__(self): self.coords = [[0,0] for i in range(4)] self.pieceShape = Tetrominoe.NoShape self.setShape(Tetrominoe.NoShape) def shape(self): return self.pieceShape def setShape(self, shape): table = Shape.coordsTable[shape] for i in range(4): for j in range(2): self.coords[i][j] = table[i][j] self.pieceShape = shape def setRandomShape(self): self.setShape(random.randint(1, 7)) def x(self, index): return self.coords[index][0] def y(self, index): return self.coords[index][1] def setX(self, index, x): self.coords[index][0] = x def setY(self, index, y): self.coords[index][1] = y def minX(self): m = self.coords[0][0] for i in range(4): m = min(m, self.coords[i][0]) return m
И завершаем наш код
def maxX(self): m = self.coords[0][0] for i in range(4): m = max(m, self.coords[i][0]) return m def minY(self): m = self.coords[0][1] for i in range(4): m = min(m, self.coords[i][1]) return m def maxY(self): m = self.coords[0][1] for i in range(4): m = max(m, self.coords[i][1]) return m def rotateLeft(self): if self.pieceShape == Tetrominoe.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, self.y(i)) result.setY(i, -self.x(i)) return result def rotateRight(self): if self.pieceShape == Tetrominoe.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, -self.y(i)) result.setY(i, self.x(i)) return result if __name__ == '__main__': app = QApplication([]) tetris = Tetris() sys.exit(app.exec_())
Прикрепил код по кусочкам, код длинный единым кодом его не прикрепить.
Это немного не то, о чём вы подумали, это танки "на бумаге" они работают без графического интерфейса, выводя информацию на экран.
Для создания данной игры нам потребуется всего ода библиотека, random.
Создадим два обычных танка, которые будут иметь рандомный домаг, и один супер танк, у которого будет много xp и урона. У всех танков будет определённое количество xp, урона и брони, а так же свой экипаж.
import random class Tank: """Template of tanks""" def __init__(self, model, armor, min_damage, max_damage, health): self.model = model self.armor = armor self.damage = random.randint(min_damage, max_damage) self.health = health def print_info(self): print(f"
Но если вы запустите нашу игру, ничего не произойдёт. Нужно прописать команду, которой танки будут стрелять друг по другу.
tank1 = Tank("T-34", 90, 20, 30, 100) tank2 = Tank("Tiger", 120, 10, 50, 120) tank1.print_info() tank2.print_info() tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2)
Вот полный код программы:
import random class Tank: """Template of tanks""" def __init__(self, model, armor, min_damage, max_damage, health): self.model = model self.armor = armor self.damage = random.randint(min_damage, max_damage) self.health = health def print_info(self): print(f"
Вы можете менять количество выстрелов и т.п.
Если хотите больше крутых кодов на Python, поддержите копеечкой 🙂 вот если что, номер карты: 2202 2067 9305 7600
Пишем игру Ним на Python
Вчера мы рассказали, как теория игр работает на практике и помогает победить. Вот кратко основное:
- теория игр — это не только про игры, а про любые ситуации, в которых может что-то происходить, меняться и зависеть от разных факторов;
- игрок — это тот, кто находится в этой ситуации и принимает решения;
- смысл теории игр в том, чтобы понять, как нужно действовать игрокам в разных ситуациях, чтобы получить нужный результат. Но не интуитивно, а с точки зрения законов математики.
В прошлой статье мы разобрали игру Ним с точки зрения логики и математики. Сегодня мы реализуем её на Python.
Классические правила игры Ним звучат так:
Есть несколько кучек, в каждой из которых лежит сколько-то камней. За один ход игрок может взять из любой одной кучки любое ненулевое число камней. Игроки берут камни по очереди. Побеждает тот, кто забирает последние камни.
Что делаем
Пишем игру Ним с двумя кучками — количество камней в кучках будет больше одного, за раз можно брать сколько угодно камней.
Играть будем против компьютера — он случайным образом будет выбирать кучку и количество камней, которые будет забирать. Кто заберёт последние камни, тот и победил.
Для этого нам понадобится Python — с ним получится быстро написать код и сразу проверить его в деле.
Логика проекта
Сегодня сделаем простую версию игры, которая будет работать так:
- есть две кучки камней, случайным образом выбираем, где сколько лежит;
- игрок и компьютер ходят по очереди;
- текущий участник выбирает номер кучки и количество камней, которые нужно оттуда забрать;
- алгоритм проверяет, остались ли после этого камни в кучках: если не осталось — игра заканчивается;
- ход переходит к другому участнику, и всё повторяется заново.
Компьютер будет выбирать кучку и количество камней не по сложной логике, а просто случайным выбором. Мы не будем пока добавлять логику проверки выигрышности хода, а сосредоточимся на механике игры, а проверку прикрутим в следующий раз.
Подготавливаем переменные
Для игры нам нужен модуль случайных чисел и переменные для кучек. Ещё добавим название текущего игрока — это поможет при проверках в дальнейшем. Начинать будет игрок, поэтому сразу пропишем его в переменной:
# подключаем модуль для работы со случайными числами import random # формируем кучки с камнями stack_1 = random.randint(2,10) stack_2 = random.randint(2,10) # выбранная кучка select = 0 # сколько камней взяли из кучки taken = 0 # кто делает текущий ход current_player = 'Игрок'
Забираем камни из кучки
Чтобы взять камни из кучки, нам нужно знать две вещи: из какой кучки забирать и сколько камней брать. Сделаем отдельную функцию, в которой на вход подаётся кучка и её номер; а внутри будет выбор, что делать для каждого игрока.
Если ходит человек, то он сам выбирает количество камней, которые нужно забрать, и мы это значение возьмём из переменной taken. А если ходит компьютер, мы случайным образом выберем для него кучку, в которой есть камни, и количество камней для отбора.
После этого нам остаётся вывести состояние текущего хода и уменьшить кучку на выбранное число камней. Результат работы функции — новое количество камней в выбранной кучке:
# забираем камни из кучки def take(stack, num): # будем менять глобальную переменную внутри функции global taken # если ход делает компьютер if current_player == 'Компьютер': # случайным образом выбираем количество камней, которые заберём из кучки taken = random.randint(1,stack) # выводим состояние текущего хода print(current_player + ' взял ' + str(taken) + ' камней из ' + str(num) + ' кучки') # уменьшаем кучку на выбранное количество камней stack = stack - taken # возвращаем количество камней в кучке return(stack)
Выводим текущее количество камней
На старте игры и после каждого хода нам важно знать, сколько камней лежит в каждой кучке. Для этого напишем простую функцию — она сообщит нам о состоянии кучек и заодно проверит, не выиграл ли кто-то после своего хода. Если выиграл — выводится сообщение и игра останавливается.
# выводим текущее состояние кучек def result(): # сообщаем, сколько камней в каждой кучке print('Камней в 1 кучке: ' + str(stack_1)) print('Камней в 2 кучке: ' + str(stack_2)) # если в кучках не осталось камней — текущий игрок победил if stack_1 == 0 and stack_2 == 0: print('Игра окончена, победил ' + current_player) # выходим из программы exit()
Программируем ход компьютера
Для начала компьютеру нужно проверить, в каких кучках есть камни: если в одной кучке нет камней, значит, берём из другой. Если камни есть в обеих кучках — случайным способом выбираем номер кучки и количество камней, которые надо забрать. Так как мы не проверяем выигрышность, ходы компьютера могут быть совсем не оптимальными:
# ход компьютера def take_computer(): # будем работать с глобальными переменными global stack_1, stack_2, current_player # если в первой кучке нет камней if stack_1 == 0: # берём камни из второй stack_2 = take(stack_2, 2) # то же самое для второй кучки elif stack_2 == 0: stack_1 = take(stack_1, 1) # если камни в кучках есть else: # выбираем случайным образом номер кучки choice = random.randint(1,2) # если выбрана первая кучка — берём камни оттуда if choice == 1: stack_1 = take(stack_1, 1) # то же самое для второй кучки else: stack_2 = take(stack_2, 2)
Запускаем игру
Последнее, что нам осталось сделать, — это запустить саму игру. Для этого на старте выводим начальное состояние кучек и запускаем бесконечный цикл. Бесконечный — для того, чтобы он работал всё время, пока в кучках есть камни. Как только они закончатся, функция result() выведет сообщение о победе и остановит игру.
Ещё на каждом ходу мы меняем в конце текущего игрока: если был компьютер, ставим игрока, и наоборот. С игроком действуем чуть иначе, чем с компьютером, — нам нужно узнать у игрока номер кучки и количество камней, а потом отправить эти данные в функцию отбора камней.
# выводим на старте состояние кучек result() # запускаем бесконечный цикл while True: # если ходит компьютер if current_player == 'Компьютер': # он делает свой ход take_computer() # выводим состояние кучек result() # меняем текущего игрока current_player = 'Игрок' # если ходит игрок else: # спрашиваем у него номер кучки и количество камней select = int(input('Выберите кучку: ')) taken = int(input('Сколько камней забрать: ')) # если игрок выбрал первую кучку — берём оттуда if select == 1: stack_1 = take(stack_1, 1) # а если вторую — то оттуда else: stack_2 = take(stack_2, 2) # выводим состояние кучек result() # меняем текущего игрока current_player = 'Компьютер'

Готовый код
# подключаем модуль для работы со случайными числами import random # формируем кучки с камнями stack_1 = random.randint(2,10) stack_2 = random.randint(2,10) # выбранная кучка select = 0 # сколько камней взяли из кучки taken = 0 # кто делает текущий ход current_player = 'Игрок' # забираем камни из кучки def take(stack, num): # будем менять глобальную переменную внутри функции global taken # если ход делает компьютер if current_player == 'Компьютер': # случайным образом выбираем количество камней, которые заберём из кучки taken = random.randint(1,stack) # выводим состояние текущего хода print(current_player + ' взял ' + str(taken) + ' камней из ' + str(num) + ' кучки') # уменьшаем кучку на выбранное количество камней stack = stack - taken # возвращаем количество камней в кучке return(stack) # выводим текущее состояние кучек def result(): # сообщаем, сколько камней в каждой кучке print('Камней в 1 кучке: ' + str(stack_1)) print('Камней в 2 кучке: ' + str(stack_2)) # если в кучках не осталось камней — текущий игрок победил if stack_1 == 0 and stack_2 == 0: print('Игра окончена, победил ' + current_player) # выходим из программы exit() # ход компьютера def take_computer(): # будем работать с глобальными переменными global stack_1, stack_2, current_player # если в первой кучке нет камней if stack_1 == 0: # берём камни из второй stack_2 = take(stack_2, 2) # то же самое для второй кучки elif stack_2 == 0: stack_1 = take(stack_1, 1) # если камни в кучках есть else: # выбираем случайным образом номер кучки choice = random.randint(1,2) # если выбрана первая кучка — берём камни оттуда if choice == 1: stack_1 = take(stack_1, 1) # то же самое для второй кучки else: stack_2 = take(stack_2, 2) # выводим на старте состояние кучек result() # запускаем бесконечный цикл while True: # если ходит компьютер if current_player == 'Компьютер': # он делает свой ход take_computer() # выводим состояние кучек result() # меняем текущего игрока current_player = 'Игрок' # если ходит игрок else: # спрашиваем у него номер кучки и количество камней select = int(input('Выберите кучку: ')) taken = int(input('Сколько камней забрать: ')) # если игрок выбрал первую кучку — берём оттуда if select == 1: stack_1 = take(stack_1, 1) # а если вторую — то оттуда else: stack_2 = take(stack_2, 2) # выводим состояние кучек result() # меняем текущего игрока current_player = 'Компьютер'
Что дальше
Кажется, что программа получилась большой, но на самом деле в ней хватает недочётов:
- Нет проверки на номер кучки: игрок может ввести число 3, и программа остановится с ошибкой.
- То же самое — с количеством камней, которые берёт игрок. При этом у компьютера с этим всё в порядке, он не может взять больше, чем там есть.
- Нет проверки на выигрышность хода — и это самое главное.
Исправим всё это в следующий раз и сразу добавим режим работы с двумя игроками. Подпишитесь, чтобы не пропустить продолжение проекта.
Вообще за Python платят
На Python можно писать не только текстовые игры, но и полноценные веб-приложения с мощным бэкендом. Это полноценная востребованная профессия. Билет туть:
Пишем игру на Python
Прежде чем мы начнём программировать что-то полезное на Python, давайте закодим что-нибудь интересное. Например, свою игру, где нужно не дать шарику упасть, типа Арканоида. Вы, скорее всего, играли в детстве во что-то подобное, поэтому освоиться будет просто.
Логика игры
Есть игровое поле — простой прямоугольник с твёрдыми границами. Когда шарик касается стенки или потолка, он отскакивает в другую сторону. Если он упадёт на пол — вы проиграли. Чтобы этого не случилось, внизу вдоль пола летает платформа, а вы ей управляете с помощью стрелок. Ваша задача — подставлять платформу под шарик как можно дольше. За каждое удачное спасение шарика вы получаете одно очко.
Алгоритм
Чтобы реализовать такую логику игры, нужно предусмотреть такие сценарии поведения:
- игра начинается;
- шарик начинает двигаться;
- если нажаты стрелки влево или вправо — двигаем платформу;
- если шарик коснулся стенок, потолка или платформы — делаем отскок;
- если шарик коснулся платформы — увеличиваем счёт на единицу;
- если шарик упал на пол — выводим сообщение и заканчиваем игру.
Хитрость в том, что всё это происходит параллельно и независимо друг от друга. То есть пока шарик летает, мы вполне можем двигать платформу, а можем и оставить её на месте. И когда шарик отскакивает от стен, это тоже не мешает другим объектам двигаться и взаимодействовать между собой.
Получается, что нам нужно определить три класса — платформу, сам шарик и счёт, и определить, как они реагируют на действия друг друга. Поле нам самим определять не нужно — для этого есть уже готовая библиотека. А потом в этих классах мы пропишем методы — они как раз и будут отвечать за поведение наших объектов.
Весь кайф в том, что мы всё это задаём один раз, а потом объекты сами разбираются, как им реагировать друг на друга и что делать в разных ситуациях. Мы не прописываем жёстко весь алгоритм, а задаём правила игры — а для этого классы подходят просто идеально.
По коням, пишем на Python
Для этого проекта вам потребуется установить и запустить среду Python. Как это сделать — читайте в нашей статье.
Начало программы
Чтобы у нас появилась графика в игре, используем библиотеку Tkinter. Она входит в набор стандартных библиотек Python и позволяет рисовать простейшие объекты — линии, прямоугольники, круги и красить их в разные цвета. Такой простой Paint, только для Python.
Чтобы создать окно, где будет видна графика, используют класс Tk(). Он просто делает окно, но без содержимого. Чтобы появилось содержимое, создают холст — видимую часть окна. Именно на нём мы будем рисовать нашу игру. За холст отвечает класс Canvas(), поэтому нам нужно будет создать свой объект из этого класса и дальше уже работать с этим объектом.
Если мы принудительно не ограничим скорость платформы, то она будет перемещаться мгновенно, ведь компьютер считает очень быстро и моментально передвинет её к другому краю. Поэтому мы будем искусственно ограничивать время движения, а для этого нам понадобится модуль Time — он тоже стандартный.
Последнее, что нам глобально нужно, — задавать случайным образом начальное положение шарика и платформы, чтобы было интереснее играть. За это отвечает модуль Random — он помогает генерировать случайные числа и перемешивать данные.
Запишем всё это в виде кода на Python:
# подключаем графическую библиотеку from tkinter import * # подключаем модули, которые отвечают за время и случайные числа import time import random # создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке tk = Tk() # делаем заголовок окна — Games с помощью свойства объекта title tk.title('Game') # запрещаем менять размеры окна, для этого используем свойство resizable tk.resizable(0, 0) # помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить tk.wm_attributes('-topmost', 1) # создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру canvas = Canvas(tk, width=500, height=400, highlightthickness=0) # говорим холсту, что у каждого видимого элемента будут свои отдельные координаты canvas.pack() # обновляем окно с холстом tk.update()
Мы подключили все нужные библиотеки, сделали и настроили игровое поле. Теперь займёмся классами.
Любишь Python? Зарабатывай на нём!

Шарик
Сначала проговорим словами, что нам нужно от шарика. Он должен уметь:
- задавать своё начальное положение и направление движение;
- понимать, когда он коснулся платформы;
- рисовать сам себя и понимать, когда нужно отрисовать себя в новом положении (например, после отскока от стены).
Этого достаточно, чтобы шарик жил своей жизнью и умел взаимодействовать с окружающей средой. При этом нужно не забыть о том, что каждый класс должен содержать конструктор — код, который отвечает за создание нового объекта. Без этого сделать шарик не получится. Запишем это на Python:
# Описываем класс Ball, который будет отвечать за шарик class Ball: # конструктор — он вызывается в момент создания нового объекта на основе этого класса def __init__(self, canvas, paddle, score, color): # задаём параметры объекта, которые нам передают в скобках в момент создания self.canvas = canvas self.paddle = paddle self.score = score # цвет нужен был для того, чтобы мы им закрасили весь шарик # здесь появляется новое свойство id, в котором хранится внутреннее название шарика # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом self.id = canvas.create_oval(10,10, 25, 25, fill=color) # помещаем шарик в точку с координатами 245,100 self.canvas.move(self.id, 245, 100) # задаём список возможных направлений для старта starts = [-2, -1, 1, 2] # перемешиваем его random.shuffle(starts) # выбираем первый из перемешанного — это будет вектор движения шарика self.x = starts[0] # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y self.y = -2 # шарик узнаёт свою высоту и ширину self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width() # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False self.hit_bottom = False # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки) def hit_paddle(self, pos): # получаем кординаты платформы через объект paddle (платформа) paddle_pos = self.canvas.coords(self.paddle.id) # если координаты касания совпадают с координатами платформы if pos[2] >= paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3] = self.canvas_height: # помечаем это в отдельной переменной self.hit_bottom = True # выводим сообщение и количество очков canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red') # если было касание платформы if self.hit_paddle(pos) == True: # отправляем шарик наверх self.y = -2 # если коснулись левой стенки if pos[0] = self.canvas_width: # движемся влево self.x = -2
Платформа
Сделаем то же самое для платформы — сначала опишем её поведение словами, а потом переведём в код. Итак, вот что должна уметь платформа:
- двигаться влево или вправо в зависимости от нажатой стрелки;
- понимать, когда игра началась и можно двигаться.
А вот как это будет в виде кода:
# Описываем класс Paddle, который отвечает за платформы class Paddle: # конструктор def __init__(self, canvas, color): # canvas означает, что платформа будет нарисована на нашем изначальном холсте self.canvas = canvas # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) # задаём список возможных стартовых положений платформы start_1 = [40, 60, 90, 120, 150, 180, 200] # перемешиваем их random.shuffle(start_1) # выбираем первое из перемешанных self.starting_point_x = start_1[0] # перемещаем платформу в стартовое положение self.canvas.move(self.id, self.starting_point_x, 300) # пока платформа никуда не движется, поэтому изменений по оси х нет self.x = 0 # платформа узнаёт свою ширину self.canvas_width = self.canvas.winfo_width() # задаём обработчик нажатий # если нажата стрелка вправо — выполняется метод turn_right() self.canvas.bind_all('', self.turn_right) # если стрелка влево — turn_left() self.canvas.bind_all('', self.turn_left) # пока игра не началась, поэтому ждём self.started = False # как только игрок нажмёт Enter — всё стартует self.canvas.bind_all('', self.start_game) # движемся вправо def turn_right(self, event): # будем смещаться правее на 2 пикселя по оси х self.x = 2 # движемся влево def turn_left(self, event): # будем смещаться левее на 2 пикселя по оси х self.x = -2 # игра начинается def start_game(self, event): # меняем значение переменной, которая отвечает за старт self.started = True # метод, который отвечает за движение платформы def draw(self): # сдвигаем нашу платформу на заданное количество пикселей self.canvas.move(self.id, self.x, 0) # получаем координаты холста pos = self.canvas.coords(self.id) # если мы упёрлись в левую границу if pos[0] = self.canvas_width: # останавливаемся self.x = 0
Счёт
Можно было не выделять счёт в отдельный класс и каждый раз обрабатывать вручную. Но здесь реально проще сделать класс, задать нужные методы, чтобы они сами потом разобрались, что и когда делать.
От счёта нам нужно только одно (кроме конструктора) — чтобы он правильно реагировал на касание платформы, увеличивал число очков и выводил их на экран:
# Описываем класс Score, который отвечает за отображение счетов class Score: # конструктор def __init__(self, canvas, color): # в самом начале счёт равен нулю self.score = 0 # будем использовать наш холст self.canvas = canvas # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color) # обрабатываем касание платформы def hit(self): # увеличиваем счёт на единицу self.score += 1 # пишем новое значение счёта self.canvas.itemconfig(self.id, text=self.score)
Игра
У нас всё готово для того, чтобы написать саму игру. Мы уже провели необходимую подготовку всех элементов, и нам остаётся только создать конкретные объекты шарика, платформы и счёта и сказать им, в каком порядке мы будем что делать.
Смысл игры в том, чтобы не уронить шарик. Пока этого не произошло — всё движется, но как только шарик упал — нужно показать сообщение о конце игры и остановить программу.
Посмотрите, как лаконично выглядит код непосредственно самой игры:
# создаём объект — зелёный счёт score = Score(canvas, 'green') # создаём объект — белую платформу paddle = Paddle(canvas, 'White') # создаём объект — красный шарик ball = Ball(canvas, paddle, score, 'red') # пока шарик не коснулся дна while not ball.hit_bottom: # если игра началась и платформа может двигаться if paddle.started == True: # двигаем шарик ball.draw() # двигаем платформу paddle.draw() # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться tk.update_idletasks() # обновляем игровое поле, и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано tk.update() # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно time.sleep(0.01) # если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру time.sleep(3)
ПОЛНЫЙ КОД ПРОГРАММЫ
# подключаем графическую библиотеку from tkinter import * # подключаем модули, которые отвечают за время и случайные числа import time import random # создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке tk = Tk() # делаем заголовок окна — Games с помощью свойства объекта title tk.title('Game') # запрещаем менять размеры окна, для этого используем свойство resizable tk.resizable(0, 0) # помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить. Попробуйте :) tk.wm_attributes('-topmost', 1) # создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру canvas = Canvas(tk, width=500, height=400, highlightthickness=0) # говорим холсту, что у каждого видимого элемента будут свои отдельные координаты canvas.pack() # обновляем окно с холстом tk.update() # Описываем класс Ball, который будет отвечать за шарик class Ball: # конструктор — он вызывается в момент создания нового объекта на основе этого класса def __init__(self, canvas, paddle, score, color): # задаём параметры объекта, которые нам передают в скобках в момент создания self.canvas = canvas self.paddle = paddle self.score = score # цвет нужен был для того, чтобы мы им закрасили весь шарик # здесь появляется новое свойство id, в котором хранится внутреннее название шарика # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом self.id = canvas.create_oval(10,10, 25, 25, fill=color) # помещаем шарик в точку с координатами 245,100 self.canvas.move(self.id, 245, 100) # задаём список возможных направлений для старта starts = [-2, -1, 1, 2] # перемешиваем его random.shuffle(starts) # выбираем первый из перемешанного — это будет вектор движения шарика self.x = starts[0] # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y self.y = -2 # шарик узнаёт свою высоту и ширину self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width() # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False self.hit_bottom = False # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки) def hit_paddle(self, pos): # получаем кординаты платформы через объект paddle (платформа) paddle_pos = self.canvas.coords(self.paddle.id) # если координаты касания совпадают с координатами платформы if pos[2] >= paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3] = self.canvas_height: # помечаем это в отдельной переменной self.hit_bottom = True # выводим сообщение и количество очков canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red') # если было касание платформы if self.hit_paddle(pos) == True: # отправляем шарик наверх self.y = -2 # если коснулись левой стенки if pos[0] = self.canvas_width: # движемся влево self.x = -2 # Описываем класс Paddle, который отвечает за платформы class Paddle: # конструктор def __init__(self, canvas, color): # canvas означает, что платформа будет нарисована на нашем изначальном холсте self.canvas = canvas # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) # задаём список возможных стартовых положений платформы start_1 = [40, 60, 90, 120, 150, 180, 200] # перемешиваем их random.shuffle(start_1) # выбираем первое из перемешанных self.starting_point_x = start_1[0] # перемещаем платформу в стартовое положение self.canvas.move(self.id, self.starting_point_x, 300) # пока платформа никуда не движется, поэтому изменений по оси х нет self.x = 0 # платформа узнаёт свою ширину self.canvas_width = self.canvas.winfo_width() # задаём обработчик нажатий # если нажата стрелка вправо — выполняется метод turn_right() self.canvas.bind_all('', self.turn_right) # если стрелка влево — turn_left() self.canvas.bind_all('', self.turn_left) # пока платформа не двигается, поэтому ждём self.started = False # как только игрок нажмёт Enter — всё стартует self.canvas.bind_all('', self.start_game) # движемся вправо def turn_right(self, event): # будем смещаться правее на 2 пикселя по оси х self.x = 2 # движемся влево def turn_left(self, event): # будем смещаться левее на 2 пикселя по оси х self.x = -2 # игра начинается def start_game(self, event): # меняем значение переменной, которая отвечает за старт движения платформы self.started = True # метод, который отвечает за движение платформы def draw(self): # сдвигаем нашу платформу на заданное количество пикселей self.canvas.move(self.id, self.x, 0) # получаем координаты холста pos = self.canvas.coords(self.id) # если мы упёрлись в левую границу if pos[0] = self.canvas_width: # останавливаемся self.x = 0 # Описываем класс Score, который отвечает за отображение счетов class Score: # конструктор def __init__(self, canvas, color): # в самом начале счёт равен нулю self.score = 0 # будем использовать наш холст self.canvas = canvas # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color) # обрабатываем касание платформы def hit(self): # увеличиваем счёт на единицу self.score += 1 # пишем новое значение счёта self.canvas.itemconfig(self.id, text=self.score) # создаём объект — зелёный счёт score = Score(canvas, 'green') # создаём объект — белую платформу paddle = Paddle(canvas, 'White') # создаём объект — красный шарик ball = Ball(canvas, paddle, score, 'red') # пока шарик не коснулся дна while not ball.hit_bottom: # если игра началась и платформа может двигаться if paddle.started == True: # двигаем шарик ball.draw() # двигаем платформу paddle.draw() # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться tk.update_idletasks() # обновляем игровое поле и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано tk.update() # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно time.sleep(0.01) # если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру time.sleep(3)
Что дальше
На основе этого кода вы можете сделать свою модификацию игры:
- добавить второй шарик;
- раскрасить элементы в другой цвет;
- поменять размеры шарика; поменять скорость платформы;
- сделать всё это сразу;
- поменять логику программы на свою.