Статьи

Создайте игру в покер в Corona: Game Logic

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


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


Обновите реализацию функции enableDealButton как показано ниже.

1
2
3
4
5
function enableDealButton()
  disableDealButton()
  dealButton:addEventListener(‘tap’,doDeal)
  instructionsText.text = » «
end

Сначала мы вызываем disableDealButton , который удаляет все ранее добавленные прослушиватели, и добавляем прослушиватель doDeal , который вызывает doDeal . Метод addEventListener принимает событие и обратный вызов. Есть ряд событий, которые вы можете прослушивать, в зависимости от контекста, в котором вы их вызываете.


Как я упоминал в предыдущем разделе, в disableButton мы удаляем все ранее добавленные слушатели.

1
2
3
function disableDealButton()
  dealButton:removeEventListener(‘tap’,doDeal)
end

В enableBetButtons мы добавляем прослушиватели betMaxButton в betMaxButton и betButton и даем игроку некоторые инструкции о том, как сделать свою ставку.

1
2
3
4
5
function enableBetButtons()
  betMaxButton:addEventListener(‘tap’,betMax)
  betButton:addEventListener(‘tap’,bet)
  instructionsText.text = «Place your Bet or Bet Max ($15)»
end

Как и в disableDealButton , мы удаляем все ранее добавленные слушатели в disableBetButtons .

1
2
3
4
function disableBetButtons()
  betMaxButton:removeEventListener(‘tap’,betMax)
  betButton:removeEventListener(‘tap’,bet)
end

В enableHoldButtons мы holdButtons таблицу holdButtons и добавляем прослушиватель holdButtons к каждой кнопке.

1
2
3
4
5
function enableHoldButtons()
  for i=1, #holdButtons do
      holdButtons[i]:addEventListener(‘tap’,holdCard)
  end
end

В функции disableHoldButtons мы также holdButtons таблицу holdButtons , но удаляем ранее добавленные прослушиватели вместо добавления новых прослушивателей.

1
2
3
4
5
function disableHoldButtons()
  for i=1, #holdButtons do
      holdButtons[i]:removeEventListener(‘tap’,holdCard)
  end
end

Реализация generateCard требует немного большего объяснения. Сначала мы генерируем случайное число от 1 до длины таблицы deck . Затем мы создаем временную карту, используя deck["randIndex]..".png" и сохраняем ссылку в tempCard . То, что tempCard deck["randIndex]..".png" случайного элемента из таблицы deck , который будет что-то вроде c1 или h5 и добавляет .png к нему. Поскольку Lua является динамическим языком, мы можем добавлять новые свойства к объектам. В этом примере мы добавляем свойство isHolding , которое сообщает нам, держит ли игрок карту, свойство cardNumber , получая подстроку выбранного элемента deck , и мы делаем то же самое для свойства cardSuit . Наконец, мы удаляем выбранный элемент из таблицы deck и возвращаем массив.

01
02
03
04
05
06
07
08
09
10
function generateCard()
  local randIndex = math.random(#deck)
  local tempCard = display.newImage(deck[randIndex]..».png»)
  tempCard.anchorX, tempCard.anchorY = 0,0
  tempCard.isHolding = false
  tempCard.cardNumber = tonumber(string.sub(deck[randIndex],2,3))
  tempCard.cardSuit = string.sub(deck[randIndex],1,1)
  table.remove(deck,randIndex);
  return tempCard;
end

В getCard мы устанавливаем cardPosition , который является координатой x первой карты в интерфейсе игры. Мы генерируем карту, добавляем ее в таблицу playerHand и cardIndex переменную cardIndex , которая будет числом от 1 до 5 , представляющим одну из пяти карт. Это позволяет нам расположить карты в правильном порядке в таблице playerHand . Мы устанавливаем положение каждой карты, используя смещение (93 * (cardIndex - 1)) . Это означает, что карты расположены на расстоянии 93 пикселя друг от друга.

1
2
3
4
5
6
7
function getCard(index)
  local cardPosition = 199
  local tempCard = generateCard()
  playerHand[cardIndex] = tempCard
  tempCard.x = cardPosition + (93*(cardIndex-1))
  tempCard.y = 257;
end

В holdCard мы сначала получаем ссылку на кнопку, которая была нажата, используя ее свойство buttonNumber . Это позволяет нам проверить, есть ли карта в таблице playerHand . Если это так, мы устанавливаем для isHolding значение false и обновляем координату y карты. Если карты нет в таблице playerHand , мы устанавливаем для isHolding значение true и обновляем координату y карты. Если игрок выбирает держать карту, его координата y уменьшается, что означает, что карта немного смещена вверх.

01
02
03
04
05
06
07
08
09
10
function holdCard(event)
  local index = event.target.buttonNumber
  if (playerHand[index].isHolding == true) then
    playerHand[index].isHolding = false
    playerHand[index].y = 257
  else
    playerHand[index].isHolding = true
    playerHand[index].y = 200
  end
end

В resetCardsYPosition мы playerHand таблицу playerHand и видим, playerHand ли какая-либо из карт. Те, которые находятся, возвращаются в исходное положение с помощью библиотеки переходов . Библиотека Transition позволяет очень легко перемещать объекты и изменять их свойства.

1
2
3
4
5
6
7
function resetCardsYPosition()
  for i=1,#playerHand do
    if (playerHand[i].isHolding) then
      transition.to(playerHand[i], {time=200,y=257})
    end
  end
end

Мы хотим, чтобы наша игра могла сохранять значения или данные в разных сеансах. Мы можем создать решение самостоятельно, используя библиотеку Corona io , но в этом руководстве мы собираемся использовать стороннее решение. Артур Сосинс создал удобный небольшой модуль для сохранения данных во время игровых сессий.

Загрузите библиотеку и добавьте два файла, которые она содержит, в ваш проект. Чтобы использовать библиотеку, добавьте следующую строку вверху main.lua .

1
saver = require(«dataSaver»)

Чтобы библиотека работала в нашем проекте, нам нужно внести несколько небольших изменений в dataSaver.lua . Откройте этот файл и измените require "json" на local json = require "json" .

Изменить:

1
require «json»

Для того, чтобы:

1
local json = require «json»

Второе изменение, которое нам нужно сделать, — это изменить все вхождения system.ResourceDirectory на system.DocumentsDirectory как показано ниже.

Изменить:

1
system.ResourceDirectory

Для того, чтобы:

1
system.DocumentsDirectory

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function createDataFile()
  gameData = saver.loadValue(«gameData»)
  if (gameData == nil) then
    gameData = {}
    gameData.numberOfCredits = 100
    gameData.numberOfGames = 0
    creditText.text = «100»
    gamesText.text = «0»
    saver.saveValue(«gameData»,gameData)
  else
    creditText.text = gameData.numberOfCredits
    gamesText.text = gameData.numberOfGames
   end
end

В createDataFile мы сначала пытаемся загрузить ключ gameData из заставки в переменную gameData . Метод loadValue возвращает nil если ключ не существует. Если он не существует, мы инициализируем таблицу gameData , добавляем свойства numberOfCredits и numberOfGames , обновляем соответствующие текстовые поля и сохраняем таблицу gameData , вызывая saveValue для saver . Если ключ существует, то мы уже сделали это и можем заполнить текстовые поля правильными значениями.

На следующем шаге мы createDataFile функцию createDataFile функции setup как показано ниже.

1
2
3
4
5
6
function setup()
  math.randomseed(os.time())
  setupButtons()
  setupTextFields()
  createDataFile()
end

В betMax мы начинаем с загрузки наших данных в gameData . Если количество кредитов больше или равно 15 , мы продолжаем и вызываем doDeal . В противном случае у игрока недостаточно кредитов, чтобы поставить максимум 15 кредитов, и мы показываем ему предупреждение.

01
02
03
04
05
06
07
08
09
10
11
12
13
function betMax()
  local gameData = saver.loadValue(«gameData»)
  local numberOfCredits = gameData.numberOfCredits
  if (numberOfCredits >= 15) then
    enableDealButton()
    betAmount = 15;
    betText.text = betAmount
    instructionsText.text = » «
    doDeal()
  else
    local alert = native.showAlert( «Not Enough Credits», «You must have 15 or more Credits to Bet Max», { «OK»})
  end
end

В функции bet мы betMaxButton кнопку раздачи и удаляем слушателя из betMaxButton . Поскольку игрок делает обычную ставку, он не может одновременно играть максимальную ставку. Нам нужно проверить, больше или равно ли количество кредитов 5 чтобы убедиться, что игрок не пытается поставить больше кредитов, чем у него осталось. Если betAmount равен 15 , мы вызываем doDeal поскольку они сделали максимальную ставку.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
function bet()
  enableDealButton()
  betMaxButton:removeEventListener(‘tap’,betMax)
  instructionsText.text = » «
  local numberOfCredits = tonumber(creditText.text — betAmount)
  if (numberOfCredits >= 5) then
    betAmount = betAmount + 5
    betText.text = betAmount
  else
    doDeal()
  end
 
  if (betAmount == 15) then
    doDeal()
  end
end

Функция doDeal координирует doDeal карт. Если это новая игра, мы сдаем начальную руку. В противном случае мы раздаем игроку новые карты.

1
2
3
4
5
6
7
8
function doDeal()
  if(isNewGame == true) then
    isNewGame = false
    dealInitialHand()
  else
    dealNewCards()
  end
end

Мы отключаем кнопки ставок в dealInitialHand и dealInitialHand кнопки удержания. Мы вызываем getCard пять раз, который генерирует первые пять карт. Затем мы загружаем gameData , обновляем currentCredits , рассчитываем newCredits , обновляем текстовое поле кредита и сохраняем gameData .

01
02
03
04
05
06
07
08
09
10
11
12
13
function dealInitialHand()
  disableBetButtons()
  enableHoldButtons()
  for i=1, 5 do
    getCard(i)
  end
  local gameData = saver.loadValue(«gameData»)
  local currentCredits = gameData.numberOfCredits
  local newCredits = currentCredits — betAmount
  creditText.text = newCredits
  gameData.numberOfCredits = newCredits
  saver.saveValue(«gameData»,gameData)
end

В dealNewCards мы проверяем, держит ли игрок карту. Если это так, тогда мы получаем ссылку на текущую карту, вызываем removeSelf , устанавливаем ее removeSelf nil и получаем новую карту, вызывая getCard(i) .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function dealNewCards()
  disableDealButton()
  disableHoldButtons()
  for i = 1, 5 do
    if (playerHand[i].isHolding == false) then
      local tempCard = playerHand[i]
      tempCard:removeSelf()
      tempCard = nil
      getCard(i)
    end
  end
  resetCardsYPosition()
  getHand()
end
Всякий раз, когда вы удаляете что-то с экрана, вы должны всегда устанавливать для него значение nil чтобы убедиться, что оно правильно настроено для сборки мусора.

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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
function getHand()
  table.sort(playerHand, function(a,b) return a.cardNumber < b.cardNumber end)
  local frequencies = {}
  for i=1, 13 do
    table.insert(frequencies,0)
  end
 
  for i=1,#playerHand do
    frequencies[playerHand[i].cardNumber] = frequencies[playerHand[i].cardNumber] + 1
  end
 
  local numberOfPairs = 0
  local hasThreeOfAKind = false
  local hasFourOfAKind = false
  local winningHand = «Nothing»
  local cashAward = 0
  local isStraight = true
  local isRoyalStraight = false
  local isFlush = true
 
  for i=0, #frequencies do
    if (frequencies[i] == 2) then
      numberOfPairs = numberOfPairs+1
    end
 
    if (frequencies[i] == 3) then
      hasThreeOfAKind = true
    end
 
    if (frequencies[i] == 4) then
      hasFour = true
    end
  end
 
  if (numberOfPairs > 0) then
    if(numberOfPairs == 1)then
      winningHand = «1 pair»
      cashAward = 1 * betAmount
    else
      winningHand = «2 pair»
      cashAward = 2 * betAmount
    end
  end
 
  if (hasThreeOfAKind) then
    winningHand = «3 of A Kind»
    cashAward = 3 * betAmount
  end
 
  if (hasFour) then
    winningHand = «Four of A Kind»
    cashAward = 7 * betAmount
  end
 
  if (numberOfPairs == 1 and hasThreeOfAKind) then
    winningHand = «Full House»
    cashAward = 6 * betAmount
  end
 
  if (playerHand[1].cardNumber == 1 and playerHand[2].cardNumber == 10 and playerHand[3].cardNumber == 11 and playerHand[4].cardNumber == 12 and playerHand[5].cardNumber == 13 )then
    isRoyalStraight = true
  end
 
  for i=1, 4 do
    if (playerHand[i].cardNumber+1 ~= playerHand[i+1].cardNumber) then
      isStraight = false
      break
    end
  end
 
  for i=1, 5 do
    if(playerHand[i].cardSuit ~= playerHand[1].cardSuit) then
      isFlush = false
      break
    end
  end
 
  if (isFlush) then
    winningHand = «Flush»
    cashAward = 5 * betAmount
  end
 
  if (isStraight) then
    winningHand = «Straight»
    cashAward = 4 * betAmount
  end
 
  if (isRoyalStraight)then
    winningHand = «Straight»
    cashAward = 4 * betAmount
  end
 
  if (isFlush and isStraight) then
    winningHand = «Straight Flush»
    cashAward = 8 * betAmount
  end
 
  if (isFlush and isRoyalStraight) then
    winningHand = «Royal Flush»
    cashAward = 9 * betAmount
  end
 
  awardWinnings(winningHand, cashAward)
end

Мы начинаем с вызова table.sort playerHand таблицы playerHand . Метод sort выполняет sort на месте, он использует оператор < чтобы определить, должен ли элемент таблицы располагаться до или после другого элемента. Мы можем передать функцию сравнения в качестве второго аргумента. В нашем примере мы проверяем, меньше ли свойство cardNumber чем предыдущее. Если это так, то это меняет местами два элемента.

Затем мы создаем таблицу frequencies , заполняем ее тринадцатью нулями ( 0 ), playerHand таблицу playerHand и увеличиваем число каждого индекса, если playerHand содержит это число. Например, если у вас есть два три и три пятерки, то таблица frequencies будет 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0 .

На следующем шаге мы объявляем и устанавливаем ряд локальных переменных, которые нам нужны за несколько минут. Затем мы перебираем таблицу frequencies и проверяем числа, чтобы увидеть, содержит ли индекс 2 , что означает, что у нас есть пара, 3 , что означает, что у нас есть три вида, или 4 , что означает, что у нас есть четыре из Добрый. Затем мы проверяем количество пар, три в своем роде и четыре в своем роде, и winningHand cashAward значения winningHand и cashAward .

Чтобы проверить Royal Straight , нам нужно проверить, равна ли первая карта тузу, а оставшиеся карты равны десяти, Джеку, Королеве и Королю. Чтобы проверить наличие обычного playerHand мы перебираем playerHand и проверяем, больше ли каждый последующий номер cardNumber на единицу, чем предыдущий. Чтобы проверить наличие сброса , мы проверяем, все cardSuit ключи cardSuit карты были равны ключу cardSuit первой карты.

В конце getHand мы вызываем awardWinnings .


В awardWinnings мы показываем игроку, какая у него рука, и обновляем настройки gameData . Мы сохраняем gameData и вызываем newGame с задержкой в ​​три секунды.

01
02
03
04
05
06
07
08
09
10
11
12
13
function awardWinnings(theHand, theAward)
  instructionsText.text = «You got «..theHand
  winText.text = theAward
  local gameData = saver.loadValue(«gameData»)
  local currentCredits = gameData.numberOfCredits
  local currentGames = gameData.numberOfGames
  local newCredits = currentCredits + theAward
  local newGames = currentGames + 1
  gameData.numberOfCredits = newCredits
  gameData.numberOfGames = newGames
  saver.saveValue(«gameData»,gameData)
  timer.performWithDelay( 3000, newGame,1 )
end

В newGame мы проходим и сбрасываем все переменные, создаем новую колоду карт и проверяем, равен ли gameData.numberOfCredits нулю. Если это так, то игрок потратил все свои кредиты, поэтому мы присуждаем им еще 100 кредитов. Наконец, мы обновляем текстовые поля.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function newGame()
  for i=1,#playerHand do
    playerHand[i]:removeSelf()
    playerHand[i] = nil
  end
  playerHand = {}
  deck = {}
  betAmount = 0
  isNewGame = true
  createDeck()
  enableBetButtons()
  instructionsText.text = «Place your Bet or Bet Max ($15)»
  winText.text = «»
  betText.text = «»
  local gameData = saver.loadValue(«gameData»)
 
  if (gameData.numberOfCredits == 0)then
    gameData.numberOfCredits = 100
    saver.saveValue(«gameData»,gameData)
  end
 
  creditText.text = gameData.numberOfCredits
  gamesText.text = gameData.numberOfGames
end

Обновите функцию setup , вызвав функцию createDeck как показано ниже, и проверьте окончательный результат.

1
2
3
4
5
6
7
function setup()
  math.randomseed(os.time())
  createDeck();
  setupButtons()
  setupTextFields()
  createDataFile()
end

В этом уроке мы создали интересную и интересную игру в покер. Мы еще не использовали кнопку для вывода денег, но вы можете сделать это в своей игре. Я надеюсь, что вы узнали что-то полезное в этом уроке. Оставьте свой отзыв в комментариях ниже.