Карточная игра с помощью pygame

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

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

Правила игры

Чтобы упростить этот учебник, мы ограничим нашу игру 2 игроками. Вот правила нашей игры для 2 игроков:

  • Колода тасуется и распределяется поровну между двумя игроками.
  • Каждый игрок по очереди вытягивает карту и кладет ее лицом вверх поверх последней вытянутой карты.
  • Если выложенная карта совпадает (по значению) с предыдущей картой, то игрок, который первым назовет «Snap!», выигрывает всю стопку карт. Эти карты добавляются к их руке.
  • Если игрок называет «Snap!», а выложенная карта не совпадает (т.е. ложно называет «Snap!»), то стопку карт забирает другой игрок.
  • Первый игрок, у которого закончились карты, проигрывает, а другой игрок выигрывает.

Создание нового проекта

Давайте перейдем в Replit и создадим новый проект. Выберите Pygame в качестве шаблона для создания repl. Теперь дайте этой реплике имя, например, «SnaPy».

После того как repl загрузится, вы должны увидеть файл main.py. Мы будем использовать этот файл для основного цикла игры, но мы создадим и другие файлы для игровых моделей и логики.

Для нашей игры Snap нам понадобятся изображения карт для отображения. Создайте папку под названием images в проводнике файлов вашего repl.

Теперь загрузите изображения карт на свой компьютер. Распакуйте файл и перетащите содержащиеся в нем изображения в папку images в вашей программе repl.

Начало работы с pygame

Популярным игровым фреймворком в Python является pygame. В нем есть функции для рисования фигур и изображений на экране, получения пользовательского ввода, воспроизведения звуков и многое другое. В этой игре мы будем использовать некоторые из базовых функций, чтобы увидеть, как она работает.

Мы можем импортировать его в наш проект, добавив следующую строку в файл main.py:

import pygame
Вход в полноэкранный режим Выйти из полноэкранного режима

Чтобы запустить фреймворк pygame, нам нужно добавить некоторый код инициализации:

pygame.init()
bounds = (1024, 768)
window = pygame.display.set_mode(bounds)
pygame.display.set_caption("SnaPy")
Войти в полноэкранный режим Выход из полноэкранного режима
  • Строка 1 запускает систему pygame, инициализируя ее модули (например, код шрифта, звука и графики).
  • В строке 2 создается новый кортеж bounds. Этот кортеж содержит размеры окна, в котором мы будем запускать нашу игру Snap.
  • В строке 3 создается новое окно, в котором будет отображаться наша игра.
  • Строка 4 дает окну надпись, или заголовок. Это может быть любое название, которое вы захотите дать игре.

Если вы запустите проект с помощью кнопки «Run» в верхней центральной части Repl, вы увидите небольшое пустое окно. Это означает, что все инициализировано и пока работает. Не много, но это наш чистый холст для начала работы!

Проектирование игровой модели

Python — это объектно-ориентированный язык. При объектно-ориентированном программировании мы определяем различные части и сущности игры и моделируем их в виде классов и объектов. Давайте нарисуем игру и посмотрим, какие части нам нужно смоделировать.

Из приведенного выше изображения видно, что мы можем смоделировать следующее:

  • Отдельные карты
  • Колода карт, которая является коллекцией всех карт
  • Игроки с их картами на руках и именами
  • Стопка карт, которая представляет собой коллекцию карт, лежащих на столе лицевой стороной вверх.

Обратите внимание, что это один из способов группировки частей игры, но не единственный. Существует множество способов идентификации отдельных объектов, и важным фактором является то, насколько детальной должна быть группировка и как распределяются обязанности. Это отчасти искусство, отчасти опыт и отчасти компьютерная наука. Часто мы начинаем с дизайна модели, а затем уточняем его со временем, по мере того как узнаем больше об игре и динамике модели.

Нам также понадобится логика для управления правилами и состоянием игры. Часто эту часть программы называют «игровым движком». Мы также смоделируем это как класс.

Вот диаграмма всех классов, которые мы создадим.

Классы имеют некоторые ключевые свойства и методы, которые им понадобятся.

Построение игровой модели

Давайте начнем с создания класса Card. Этот класс будет представлять одну карту.

Создайте новый файл models.py в Replit. Внутри этого файла мы можем определить класс Card.

В верхней части файла добавьте импорт пакетов:

from enum import Enum
import pygame
import random
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь добавим масти, которые понадобятся для карт. Для определения различных мастей мы будем использовать класс Enum.

class Suits(Enum):
  CLUB = 0
  SPADE = 1
  HEART = 2
  DIAMOND = 3
Вход в полноэкранный режим Выход из полноэкранного режима

Хорошо, с шаблонами разобрались. Теперь давайте определим класс Card.

class Card:
  suit = None
  value = None
  image = None

  def __init__(self, suit, value):
    self.suit = suit
    self.value = value
    self.image = pygame.image.load('images/' + self.suit.name + '-' + str(self.value) + '.svg')
Вход в полноэкранный режим Выход из полноэкранного режима

Из этого небольшого класса мы можем увидеть различные части класса Python. Первая строка — это определение класса. Следующие 3 строки — это свойства класса (переменные, которыми управляют объекты, созданные на основе класса). Затем у нас есть метод __init__, который называется методом конструктора. Здесь мы инициализируем класс. В данном случае мы инициализируем свойства масти и значения для карты. Мы также загрузим изображение для карты с помощью функции Pygame image.load, при этом путь к файлу будет построен из масти и значения. Это означает, что все изображения карт будут названы в соответствии с мастью и значением.

Теперь, когда у нас есть масти и карты, давайте создадим колоду карт.

class Deck:
  cards = None

  def __init__(self):
    self.cards = []
    for suit in Suits:
      for value in range(1,14):
        self.cards.append(Card(suit, value))

  def shuffle(self):
    random.shuffle(self.cards)

  def deal(self):
    return self.cards.pop()

  def length(self):
    return len(self.cards)
Вход в полноэкранный режим Выход из полноэкранного режима

Класс Deck представляет собой коллекцию карт. В нем есть список cards и несколько методов для работы со списком.

Конструктор __init__ инициализирует список cards и заполняет его всеми картами в колоде, используя 2 цикла for. Первый цикл выполняет итерацию по перечислению Suits, а второй цикл выполняет итерацию по значениям от 1 до 13, то есть по всем картам в колоде.

Перед использованием каждую колоду карт нужно перетасовать, поэтому мы определили метод shuffle. В модуле random языка Python есть очень удобная функция shuffle, которую мы можем использовать. shuffle берет список и переставляет содержимое этого списка местами (т.е. не возвращает новый список).

Затем у нас есть метод deal, который удаляет последнюю карту из списка и возвращает ее. Мы будем использовать его для раздачи карт игрокам.

У нас есть метод length, который мы будем использовать, чтобы определить, остались ли карты в колоде. Это будет полезно при раздаче карт, чтобы знать, когда остановиться.

Класс Deck теперь завершен.

Давайте перейдем к классу Pile. Этот класс моделирует стопку карт на столе лицевой стороной вверх, которую каждый игрок добавляет в процессе игры.

class Pile:
  cards = None

  def __init__(self):
    self.cards = []

  def add(self, card):
    self.cards.append(card)

  def peek(self):
    if (len(self.cards) > 0):
      return self.cards[-1]
    else:
      return None

  def popAll(self):
    return self.cards

  def clear(self):
    self.cards = []

  def isSnap(self):
    if (len(self.cards) > 1):
      return (self.cards[-1].value == self.cards[-2].value)
    return False
Вход в полноэкранный режим Выход из полноэкранного режима

Класс Pile имеет одно основное свойство: список карт, который инициализируется в конструкторе.

Метод add используется, когда игрок разыгрывает карту, т.е. добавляет ее в кучу.

Метод peek возвращает верхнюю карту из кучи, сохраняя ее в куче. Мы будем использовать его для извлечения верхней карты, так как она лежит лицевой стороной вверх. Если в стопке нет карт, на что указывает длина массива карт, равная 0, мы вернем None.

Когда игрок выигрывает, он получает все карты из стопки. Метод popAll обрабатывает это, возвращая список карт. Затем мы можем вызвать метод clear, чтобы удалить все карты из кучи.

Если игрок говорит «Snap!», нам нужно проверить, являются ли две верхние карты одинакового достоинства. Для проверки мы будем использовать метод isSnap. В Python есть удобная функция отрицательных индексов. Это означает, что индекс -1 возвращает последний элемент в списке, -2 — второй последний и так далее. Это позволяет нам легко получить 2 последние карты, добавленные в стопку, и проверить, одинакового ли они достоинства. Обратите внимание, что в правилах Snap важно только значение — масть не используется.

Последняя из моделей — класс Player.

class Player:
  hand = None
  flipKey = None
  snapKey = None
  name = None

  def __init__(self, name, flipKey, snapKey):
    self.hand = []
    self.flipKey = flipKey
    self.snapKey = snapKey
    self.name = name

  def draw(self, deck):
    self.hand.append(deck.deal())

  def play(self):
    return self.hand.pop(0)
Вход в полноэкранный режим Выход из полноэкранного режима

Класс Player имеет несколько свойств. Свойство hand — это список карт, которые есть у игрока.

Свойства flipKey и snapKey — это клавиши, назначенные игроку, которые он использует для переворачивания и защелкивания карт.

Свойство name — это имя игрока.

Метод draw используется для извлечения карты из deck, переданной в аргументах. Метод draw вызывает метод deal на deck, и добавляет карту в список hand.

Метод play pop добавляет карту из списка hand, используется, когда игрок разыгрывает карту.

Создание игрового движка

Итак, пора создать игровой движок, который будет координировать взаимодействие между созданными нами моделями.

Создайте новый файл engine.py. Давайте начнем с добавления библиотек, которые нам понадобятся.

from enum import Enum
import pygame
from models import *
Вход в полноэкранный режим Выйти из полноэкранного режима

Кроме библиотек Enum и pygame, мы импортируем все, *, из нашего файла models.py. Таким образом, мы можем использовать все классы, которые мы определили в models.

Общим элементом в игровых движках является отслеживание состояния игры. Мы определим перечисление GameState для отслеживания состояния игры.

class GameState(Enum):
  PLAYING = 0
  SNAPPING = 1
  ENDED = 2
Вход в полноэкранный режим Выход из полноэкранного режима

У нас есть только 3 состояния, или фазы, игры, которые мы будем отслеживать. Первая — PLAYING, это основная фаза игры, когда игроки по очереди кладут карты. Вторая — SNAPPING. Это состояние, в котором находится игра, когда игрок произносит «Snap!». В этом состоянии мы проверяем, действительна ли защелка, а также ждем, пока игроки будут готовы возобновить игру. Третье состояние — ENDED, это фаза, когда игра закончена, т.е. у одного из игроков больше нет карт для игры.

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

class SnapEngine:
  deck = None
  player1 = None
  player2 = None
  pile = None
  state = None
  currentPlayer = None
  result = None

  def __init__(self):
    self.deck = Deck()
    self.deck.shuffle()
    self.player1 = Player("Player 1", pygame.K_q, pygame.K_w)
    self.player2 = Player("Player 2", pygame.K_o,pygame.K_p)
    self.pile = Pile()
    self.deal()
    self.currentPlayer = self.player1
    self.state = GameState.PLAYING
Вход в полноэкранный режим Выход из полноэкранного режима

В свойствах мы определяем колоду карт, игроков, кучу карт и состояние игры. У нас также есть свойство currentPlayer, которое отслеживает игрока, чей сейчас ход. Свойство result используется для передачи результатов раунда или всей игры.

Конструктор инициализирует объекты и сохраняет их в свойствах. Обратите внимание, что мы присваиваем игрокам имена и ключи по умолчанию.

Мы также вызываем метод, который нам еще предстоит определить, deal, чтобы раздать карты игрокам.

Мы начинаем игру, установив state в PLAYING, а currentPlayer в player1.

Давайте добавим еще несколько методов и логики в движок. Для начала, метод deal:

  def deal(self):
    half = self.deck.length() // 2
    for i in range(0, half):
      self.player1.draw(self.deck)
      self.player2.draw(self.deck)
Войти в полноэкранный режим Выйти из полноэкранного режима

Метод deal отвечает за то, чтобы каждый игрок получил половину колоды карт. Есть несколько способов сделать это — в данном коде используется дословный перевод deal, путем сдачи по одной карте каждому игроку поочередно. На практике мы будем использовать для этого цикл for, а в цикле вызовем метод draw для каждого из игроков, чтобы вытянуть карту.

Поскольку каждый цикл цикла for берет по одной карте для каждого игрока, по 2 карты за цикл, нам нужно сделать цикл только для половины количества карт в колоде.

Чтобы найти середину колоды, мы получим длину колоды и разделим ее на 2. Обратите внимание, что мы используем оператор целочисленного деления //, чтобы получить целое число после деления, так как значение середины — это индекс средней карты. Число с десятичными дробями не имело бы здесь никакого смысла.

Далее мы можем добавить вспомогательный метод для переключения текущего игрока. Мы будем использовать его после того, как каждый игрок сыграет карту, чтобы указать, что наступила очередь следующего игрока.

  def switchPlayer(self):
    if self.currentPlayer == self.player1:
      self.currentPlayer = self.player2
    else:
      self.currentPlayer = self.player1
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь мы проверяем, какой игрок является текущим, и переключаем currentPlayer на другого игрока.

Последний вспомогательный метод, который нам нужен в движке, это метод, который обрабатывает победу игрока в раунде (правильно вызвав «Snap!», или другой игрок ложно вызвал «Snap!»). Этот метод изменит состояние игры. Он также добавит все карты из кучи в руку победителя. Затем он очистит стопку, чтобы можно было начать следующий раунд:

  def winRound(self, player):
    self.state = GameState.SNAPPING
    player.hand.extend(self.pile.popAll())
    self.pile.clear()
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы переходим к основной логике движка. Этот метод будет вызываться из нашего основного цикла игры, который мы определим позже. Давайте начнем с определения метода и некоторых базовых проверок. Затем мы будем добавлять логику по секциям. Начнем с добавления этого метода в движок:

  def play(self, key):
    if key == None: 
      return

    if self.state == GameState.ENDED:
      return
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы назовем этот основной логический метод play. Он принимает любую клавишу, нажатую в данный момент, и обрабатывает логику для этого. Первое, что мы проверяем, была ли нажата клавиша. Если нет, мы возвращаемся, так как в состоянии игры нечего обновлять.

Следующим шагом будет проверка того, закончилась ли игра. Если да, то мы возвращаемся, так как опять же, делать нечего. Если вы хотите улучшить игру, вы можете прослушать нажатие клавиши, чтобы перезапустить игру.

Теперь давайте добавим немного логики. Прежде всего, необходимо проверить, нажал ли текущий игрок клавишу, чтобы сыграть или перевернуть карту в стопку. Если он нажал flipKey, мы вызываем его метод play и добавляем возвращенную карту в стопку. Затем мы переключаемся на следующего игрока, вызывая наш метод switchPlayer.

    if key == self.currentPlayer.flipKey:
      self.pile.add(self.currentPlayer.play())
      self.switchPlayer()
Вход в полноэкранный режим Выход из полноэкранного режима

Далее давайте проверим, вызвал ли кто-нибудь из игроков команду «Snap!». Нам нужно будет отслеживать несколько вещей: кто вызвал «Snap!», кто не вызвал «Snap!», и есть ли действительное условие snap на свайпе. Добавьте эту логику в метод play:

    snapCaller = None
    nonSnapCaller = None
    isSnap = self.pile.isSnap()

    if (key == self.player1.snapKey):
      snapCaller = self.player1
      nonSnapCaller = self.player2
    elif (key == self.player2.snapKey):
      snapCaller = self.player2
      nonSnapCaller = self.player1
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь мы создаем две переменные, snapCaller и nonSnapCaller, которые отслеживают игрока, вызвавшего «Snap!», и игрока, не вызвавшего «Snap!». Мы также создаем переменную isSnap, чтобы отслеживать, есть ли на свайке условие действительной защелки. Затем мы проверяем, вызвал ли кто-нибудь из игроков команду «Snap!». Если да, то мы устанавливаем переменные snapCaller и nonSnapCaller в зависимости от ситуации.

Теперь мы знаем, была ли вызвана команда «Snap!» и какой игрок ее вызвал. Давайте добавим логику, чтобы узнать, выиграет или проиграет игрок, который вызвал «Snap!». Добавьте эту логику в метод play:

    if isSnap and snapCaller:
      self.winRound(snapCaller)
      self.result = {
        "winner": snapCaller,
        "isSnap": True,
        "snapCaller": snapCaller 
      }
      self.winRound(snapCaller)
    elif not isSnap and snapCaller:
      self.result = {
        "winner": nonSnapCaller,
        "isSnap": False,
        "snapCaller": snapCaller 
      }
      self.winRound(nonSnapCaller)
Войти в полноэкранный режим Выйти из полноэкранного режима

У нас есть два случая: один для валидного снэпа, а другой для невалидного снэпа.

Если свайп является действительным, мы вызываем метод winRound для игрока, который вызвал «Snap!». Затем мы устанавливаем свойство result в словарь с именем победителя, действительным ли это был свайп, и игрока, который вызвал «Snap!». Это будет использоваться для информации, когда мы будем создавать пользовательский интерфейс игры (UI).

Аналогично, для недействительного снапа мы вызываем метод winRound для игрока, который не вызвал «Snap!». Затем мы устанавливаем свойство result в словарь с победителем в качестве nonSnapCaller.

В обоих случаях мы вызываем метод winRound с тем игроком, который выиграл карты. Напомним, что в методе winRound мы присваиваем стопку руке игрока. Затем мы очищаем стопку и устанавливаем gameState в SNAPPING.

Осталось проверить последнее: не закончились ли у кого-нибудь из игроков карты. Если да, то это означает, что другой игрок победил. Добавьте эту логику в метод play:

    if len(self.player1.hand) == 0:
      self.result = {
        "winner": self.player2,
      }
      self.state = GameState.ENDED
    elif len(self.player2.hand) == 0:
      self.result = {
        "winner": self.player1,
      }
      self.state = GameState.ENDED

Войти в полноэкранный режим Выход из полноэкранного режима

Если у одного из игроков закончились карты, то мы устанавливаем свойство result в словарь с победителем в качестве другого игрока. Затем мы устанавливаем свойство state в GameState.ENDED.

Настройка цикла игры

Мы создали модель и логику в движке для игры в Snap. Теперь нам нужно определить игровой цикл для запуска игры.

Снова откройте файл main.py. Начнем с добавления ссылок на модели и движок, чтобы мы могли обращаться к ним при создании пользовательского интерфейса. Добавьте следующие импорты прямо под строкой import pygame:

from models import *
from engine import *
Войти в полноэкранный режим Выйти из полноэкранного режима

Начнем с создания нового объекта игрового движка. Добавьте его в файл main.py, под кодом инициализации Pygame:

gameEngine = SnapEngine()
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь игровой цикл. Задача игрового цикла — слушать ввод данных пользователем, вызывать метод движка play для обработки этих данных и обновлять пользовательский интерфейс с результатом. Добавьте это в файл main.py:

run = True
while run:
  key = None; 
  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      run = False
    if event.type == pygame.KEYDOWN:
      key = event.key

  gameEngine.play(key)
  renderGame(window)
  pygame.display.update()

  if gameEngine.state == GameState.SNAPPING:
    pygame.time.delay(3000)
    gameEngine.state = GameState.PLAYING
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы начинаем с определения переменной run. Затем мы используем ее в качестве условия для цикла while. Пока run истинна, цикл будет непрерывно выполнять код внутри.

В Pygame мы слушаем события в очереди событий. Мы используем метод pygame.event.get(), чтобы получить все события, произошедшие с момента последней проверки. Затем мы перебираем все события, проверяя, является ли какое-либо из них событием QUIT. Если это так, мы устанавливаем run в false и выходим из цикла, чтобы завершить программу. Событие QUIT отправляется, если пользователь нажимает на кнопку закрытия окна.

Если любое из событий является событием KEYDOWN, мы устанавливаем переменную key на клавишу, которая была нажата.

Затем мы обращаемся к игровому движку, чтобы он обработал нажатую клавишу.

Как только это будет сделано, мы можем обновить пользовательский интерфейс. Мы вызываем метод renderGame, который мы рассмотрим далее.

После обновления пользовательского интерфейса мы вызываем метод pygame.display.update(), чтобы нарисовать его на экране.

Последняя проверка заключается в том, находится ли игра в состоянии SNAPPING. Если да, то мы ждем 3 секунды, прежде чем переключиться обратно в состояние PLAYING. Это делается для того, чтобы у игроков было достаточно времени, чтобы сделать паузу и посмотреть, что произошло. Помните, что игровой цикл будет проходить очень быстро, поэтому задержка поможет им увидеть любые сообщения, которые мы выводим в методе renderGame.

Рендеринг игры

Последняя основная задача — это рендеринг игры через пользовательский интерфейс. Здесь мы будем использовать Pygame для вывода игры на экран.

В цикле игры мы вызываем метод renderGame. Этот метод принимает параметр window, который является окном Pygame, созданным нами в файле main.py. Окно — это графическая поверхность, на которую мы будем рисовать. Метод renderGame будет смотреть на состояние и результат работы gameEngine, а также игроков, и рисовать соответствующий пользовательский интерфейс в окне.

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

cardBack = pygame.image.load('images/BACK.png')
cardBack = pygame.transform.scale(cardBack, (int(238*0.8), int(332*0.8)))

def renderGame(window):
  window.fill((15,0,169))
  font = pygame.font.SysFont('comicsans',60, True)

  window.blit(cardBack, (100, 200))
  window.blit(cardBack, (700, 200))

  text = font.render(str(len(gameEngine.player1.hand)) + " cards", True, (255,255,255))
  window.blit(text, (100, 500))

  text = font.render(str(len(gameEngine.player2.hand)) + " cards", True, (255,255,255))
  window.blit(text, (700, 500))

  topCard = gameEngine.pile.peek()
  if (topCard != None):
    window.blit(topCard.image, (400, 200))
Войти в полноэкранный режим Выйти из полноэкранного режима

Сначала мы загружаем изображение обратной стороны карты вне функции. Мы делаем это для того, чтобы не загружать его каждый раз при рендеринге экрана — таким образом оно загружается только один раз. Мы будем использовать это изображение для представления карт в руках игроков, которые лежат лицом вниз. Функция pygame transform.scale используется для масштабирования изображения до 0.8 размера обычной карты. Это просто визуально указывает на то, что они находятся на заднем плане по отношению к основной части пользовательского интерфейса, которой будет карта, находящаяся в верхней части стопки.

Внутри функции renderGame мы сначала очищаем окно. Мы используем метод fill, чтобы заполнить окно цветом, (15,0,169) в цветовой нотации RGB (Red Green Blue), который является темно-синим цветом.

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

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

Чтобы показать текущий счет в игре, мы отобразим текст, показывающий количество карт, находящихся на руках у каждого игрока. Сначала мы создадим текстовый объект с помощью метода render шрифта. Мы передаем текст, который хотим отобразить (это количество карт игрока и слово «cards»), и цвет, который мы хотим использовать. Затем мы выводим текст на экран, закрашивая его в окно.

Последняя часть кода рисует карту на вершине стопки. Мы используем метод peek, который мы реализовали в классе Pile, чтобы получить верхнюю карту. Если верхней карты нет, мы ничего не рисуем. Вспомните, что для каждой карточки мы загрузили ее изображение в конструкторе. Это означает, что мы можем получить объект изображения из свойства image на карточке, не загружая его здесь. Мы снова выводим изображение карты на экран, на этот раз передавая координаты между двумя руками.

Следующая часть пользовательского интерфейса, которую нужно нарисовать, — это индикация текущего состояния игры и того, чей сейчас ход. Мы будем использовать свойство state свойства gameEngine, чтобы определить, что рисовать. Мы будем использовать операторы if, чтобы нарисовать соответствующий пользовательский интерфейс, основываясь на состоянии игры. Есть три состояния, которые необходимо рассмотреть:

  • PLAYING Игра идет, и мы ждем, пока текущий игрок перевернет карту или один из игроков скажет «Snap!». В этом случае мы выводим имя текущего игрока и сообщение, указывающее на то, что сейчас его очередь переворачивать карту. Это сообщение написано белым цветом (255,255,255) в левой верхней части окна.
  • SNAPPING Игрок вызвал команду snap. В этом случае отображаемое сообщение зависит от того, была ли защелка действительной или нет. Мы можем получить эту информацию из свойства result gameEngine.
  • ENDING Игра окончена. В этом случае мы выводим имя победителя и сообщение о том, что он выиграл.

Добавьте этот код в функцию renderGame, чтобы реализовать описанную выше логику:

  if gameEngine.state == GameState.PLAYING:
    text = font.render(gameEngine.currentPlayer.name + " to flip", True, (255,255,255))
    window.blit(text, (20,50))

  if gameEngine.state == GameState.SNAPPING:
    result = gameEngine.result
    if result["isSnap"] == True:
      message = "Winning Snap! by " + result["winner"].name
    else:
      message = "False Snap! by " + result["snapCaller"].name + ". " + result["winner"].name + " wins!"
    text = font.render(message, True, (255,255,255))
    window.blit(text, (20,50))

  if gameEngine.state == GameState.ENDED:
    result = gameEngine.result
    message = "Game Over! " + result["winner"].name + " wins!"
    text = font.render(message, True, (255,255,255))
    window.blit(text, (20,50))
Войти в полноэкранный режим Выход из полноэкранного режима

Запуск игры

Теперь, когда мы реализовали игру, мы можем провести ее тестовый запуск. Нажмите на большую зеленую кнопку «Run» в верхней части вашего ответа. Вы должны увидеть, как игра запустится и предложит игроку 1 перевернуть карту.

Клавиши игрока 1 — q для переворачивания карты и w для вызова снапа.
Клавиши игрока 2: o для переворачивания карты и p для вызова щелчка.

Позовите друга и посмотрите, кто быстрее всех нажмет на кнопку «Snap!».

Следующие шаги

Поздравляем! Вы реализовали карточную игру. Вы можете использовать идеи и принципы из этого руководства, чтобы реализовать свою собственную карточную игру.

Некоторые идеи по добавлению функций в эту игру следующие:

  • Добавить в игру таймер, чтобы у игрока было ограниченное количество времени на то, чтобы перевернуть карту.
  • Добавьте звуковые эффекты, используя модуль Pygame mixer.
  • Добавьте клавишу для перезапуска игры после ее окончания. В настоящее время по окончании игры игрокам необходимо закрыть окно и начать игру заново.
  • Попробуйте использовать разные карты. Например, вы можете использовать упрощенный набор карт, возможно, тематически связанный с чем-то, что вы любите, например, с покемонами, динозаврами, автомобилями, знаменитостями и т.д.

Вы можете найти наш ответ ниже:

Оцените статью
devanswers.ru
Добавить комментарий