Статьи

Создайте игру Танцующий палач в Corona: Геймплей

Конечный продукт
Что вы будете создавать

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

Игра читает словами из текстового файла, который содержит тысячи слов. Мы отфильтруем слова по их длине и добавим их в один из трех списков: слова длиной не более 5 символов, длиной не более 9 символов и длиной не более 13 символов.

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

Добавьте следующий код в gamescreen.lua .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function readTextFile()
    local path = system.pathForFile( «wordlist.txt», system.ResourceDirectory)
    local file = io.open( path, «r» )
    for line in file:lines() do
        —If targeting Windows Operating System comment the following line out
        — line = string.sub(line, 1, #line — 1)
         if(#line >=3 and #line <=5)then
            table.insert(words5,line)
        elseif (#line >=3 and #line<=9)then
            table.insert(words9,line)
        elseif (#line >=3 and #line<=13)then
            table.insert(words13,line)
         end
    end
    io.close( file )
    file = nil
end

Функция readTextFile читает текстовый файл wordlist.txt . Мы перебираем каждую строку wordlist.txt и, в зависимости от длины слова, вставляем ее в одну из трех таблиц words5 , words9 или words13 .

Системы на базе Windows и Unix по-разному обрабатывают окончания строк. В Windows будет дополнительный символ, который мы можем удалить с string.sub метода string.sub . Если вы используете компьютер с Windows, вам нужно добавить эту строку кода, удалив предшествующий символ.

Всякий раз, когда вы читаете файл, важно помнить, что когда вы закончите, вы должны close и nil файл, как показано в приведенной выше реализации.

Вызовите эту функцию в scene:create . Я поместил его в самый верх, над другими вызовами функций.

1
2
3
4
5
6
function scene:create( event )
    local group = self.view
    readTextFile()
    drawChalkBoard(1,1,1)
    —SNIP—
end

Функция createGuessWord отвечает за получение случайного слова из списка и его возврат. Добавьте следующий код в gamescreen.lua .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function createGuessWord()
    guessWord = {}
    local randomIndex = math.random(#words5)
    theWord = words5[randomIndex];
    print(theWord)
    for i=1, #theWord do
        local character= theWord:sub(i,i)
        if(character == «‘»)then
            guessWord[i] =»‘»;
        elseif(character==»-«)then
            guessWord[i] = «-«
        else
            guessWord[i]=»?»;
        end
    end
    local newGuessWord = table.concat(guessWord)
    return newGuessWord;
end

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

Сначала мы генерируем случайное число randomIndex , которое будет числом от 1 до randomIndex элементов в таблице. Затем мы используем это число, чтобы получить слово из таблицы.

Мы перебираем слово и получаем ссылку на текущий символ, используя метод string.sub . Если текущий символ — апостроф или тире, мы помещаем это в guessword таблицы, в противном случае мы ставим знак вопроса в таблице.

Затем мы преобразовываем таблицу guessWord в строку newGuessWord используя метод newGuessWord . Наконец, мы возвращаем newGuessWord .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function createGuessWordText()
    local options =
        {
            text = createGuessWord(),
            x = 384,
            y = 70,
            width = 700, —required for multi-line and alignment
            font = native.systemFontBold,
            fontSize = 50,
         align = «center» —new alignment parameter
        }
    guessWordText = display.newText(options)
    guessWordText:setFillColor(0,0,0)
    scene.view:insert(guessWordText)
end

Таблица options содержит различные параметры конфигурации для Text . Поскольку функция createGuessWord возвращает слово в виде строки, мы можем просто вызвать его при установке свойства text .

Мы создаем Text , вызывая метод newText в Display , передавая таблицу параметров. Затем мы устанавливаем его цвет и вставляем его в вид сцены.

Вызовите эту функцию в scene:create как показано ниже.

1
2
3
4
5
function scene:create( event )
    —SNIP—
    drawGallows()
    createGuessWordText()
end

В языке программирования Lua нет встроенной системы классов. Однако с помощью метатабельной конструкции Lua мы можем эмулировать систему классов. На веб-сайте Corona есть хороший пример , показывающий, как это реализовать.

Важно отметить, что объекты Display Corona не могут быть установлены как метатабельные. Это связано с тем, как базовый язык C взаимодействует с ними. Простой способ обойти это — установить объект Display в качестве ключа для новой таблицы, а затем установить эту таблицу в качестве метатабельной. Это подход, который мы будем использовать в этом уроке.

Если вы прочтете приведенную выше статью на веб-сайте Corona, вы __Index что в метатаблице использовался __Index . __Index работает так: когда вы пытаетесь получить доступ к отсутствующему полю в таблице, он вызывает интерпретатор для поиска __Index . Если __Index есть, он будет искать поле и предоставлять результат, в противном случае это приведет к nil .

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

Добавьте следующее к growtext.lua , который вы создали в первой части этой серии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
local growText = {}
local growText_mt = {__index = growText}
 
function growText.new(theText,positionX,positionY,theFont,theFontSize,theGroup)
    local theTextField = display.newText(theText,positionX,positionY,theFont,theFontSize)
    local newGrowText = {
    theTextField = theTextField}
        if(theGroup ~=nil)then
    theGroup:insert(theTextField)
    end
    return setmetatable(newGrowText,growText_mt)
end
 
function growText:setColor(r,b,g)
  self.theTextField:setFillColor(r,g,b)
end
 
function growText:grow()
    transition.to( self.theTextField, { xScale=4.0, yScale=4.0, time=2000, iterations = 1,onComplete=function()
            local event = {
                        name = «gameOverEvent»,
            }
            self.theTextField.xScale = 1
            self.theTextField.yScale = 1
    Runtime:dispatchEvent( event )
end
    } )
end
 
function growText:setVisibility(visible)
  if(visible == true)then
    self.theTextField.isVisible = true
  else
    self.theTextField.isVisible = false
  end
  self.theTextField.xScale = 1
  self.theTextField.yScale = 1
end
 
function growText:setText(theText)
    self.theTextField.text = theText
end
return growText

Мы создаем основную таблицу growText и таблицу, которая будет использоваться как growText_mt , growText_mt . В new методе мы создаем объект Text и добавляем его в таблицу newGrowText которая будет установлена ​​как метатабельная. Затем мы добавляем объект Text в group которая была передана в качестве параметра, который будет группой scene , в которой мы создаем экземпляр GrowText .

Важно убедиться, что мы добавили его в группу scene , чтобы он был удален при удалении сцены. Наконец, мы устанавливаем метатаблицу.

У нас есть четыре метода, которые обращаются к объекту Text и выполняют операции над его свойствами.

Метод setColor устанавливает цвет Text , вызывая метод setFillColor , который принимает в качестве параметров значения значений R, G и B от 0 до 1 .

  Метод grow использует библиотеку Transition для увеличения размера текста. Увеличивает текст, используя свойства xScale и yScale . Функция onComplete вызывается после завершения перехода.

В этой функции onComplete мы сбрасываем свойства Text xScale и yScale в 1 и отправляем событие. Причина, по которой мы отправляем событие здесь, состоит в том, чтобы сообщить игровому экрану, что Text завершил свой переход, и, следовательно, игровой раунд окончен.

Это все станет ясно в ближайшее время, но вы можете прочитать о dispatchEvent в документации.

Метод setVisibility просто устанавливает видимость Text зависимости от того, было ли передано значение true или false в качестве параметра. Мы также сбросили свойство xScale и yScale на 1 .

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

Наконец, мы возвращаем объект growText .

Функция createWinLoseText создает объект GrowText который будет отображать либо «ВЫ ВЫИГРЫВАЕТЕ!» или «ВЫ ПОТЕРЯЕТЕ!», в зависимости от того, выиграл пользователь или проиграл в раунде. Добавьте следующий код в gamescreen.lua .

1
2
3
4
function createWinLoseText()
    winLoseText = growText.new( «YOU WIN»,display.contentCenterX,display.contentCenterY-100, native.systemFontBold, 20,scene.view)
    winLoseText:setVisibility(false)
end

Мы вызываем эту функцию в scene:create как показано ниже.

1
2
3
4
5
function scene:create( event )
    —SNIP—
    drawGallows()
    createWinLoseText()
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function setupButtons()
    local xPos=150
    local yPos = 600
    for i=1, #alphabetArray do
        if (i == 9 or i == 17) then
            yPos = yPos + 65
            xPos = 150
        end
        if (i == 25) then
            yPos = yPos + 65
            xPos = 330
        end
        local tempButton = widget.newButton{
            label = alphabetArray[i],
            labelColor = { default ={ 1,1,1}},
            onPress = checkLetter,
            shape=»roundedRect»,
            width = 40,
            height = 40,
            cornerRadius = 2,
            fillColor = { default={0, 0, 0, 1 }, over={ 0.5, 0.5, 0.5, 0.4 } },
            strokeColor = { default={ 0.5, 0.5, 0.5, 0.4 }, over={ 0, 0, 0, 1 } },
            strokeWidth = 5
        }
        tempButton.x = xPos
        tempButton.y = yPos
         
    xPos = xPos + 60
    table.insert(gameButtons,tempButton)
    end
end

Сначала мы устанавливаем начальные x и y позиции кнопок. Затем мы запускаем цикл for alphabetArray , который, в свою очередь, создает кнопку для каждой буквы alphabetArray . Нам нужно восемь кнопок на строку, поэтому мы проверяем, равен ли i 9 или 17 , и, если это правда, мы увеличиваем переменную yPos чтобы создать новую строку и вернуть xPos в xPos позицию. Если i равен 25 , мы находимся в последнем ряду, и мы центрируем последние две кнопки.

Мы создаем tempButton , используя метод newButton класса виджета, который принимает таблицу параметров. Есть несколько способов повлиять на внешний вид кнопок. Для этой игры мы используем опцию Shape Construction . Я настоятельно рекомендую вам прочитать документацию по объекту Button, чтобы узнать больше об этих параметрах.

Мы устанавливаем label путем индексации на onPress и устанавливаем свойство onPress для вызова checkLetter . Остальные параметры связаны с визуальным оформлением и лучше объясняются при чтении документации, как упоминалось ранее. Наконец, мы вставляем tempButton в таблицу gameButtons чтобы мы могли ссылаться на нее позже.

Если вы теперь вызываете этот метод из scene:create , вы должны увидеть, что кнопки нарисованы на экране. Мы пока не можем их коснуться, потому что мы не создали функцию checkLetter . Мы сделаем это на следующем шаге.

1
2
3
4
5
6
function scene:create( event )
    —SNIP—
    createGuessWordText()
    createWinLoseText()
    setupButtons()
end

Логика игры живет в функции checkLetter . Добавьте следующий код в gamescreen.lua .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function checkLetter(event)
    local tempButton = event.target
    local theLetter = tempButton:getLabel()
    theLetter = string.lower(theLetter)
    local correctGuess = false
    local newGuessWord = «»
    tempButton.isVisible = false
    for i =1 ,#theWord do
        local character= theWord:sub(i,i)
        if(character == theLetter)then
            guessWord[i] = theLetter
            correctGuess = true
        end
    end
    newGuessWord = table.concat(guessWord)
    guessWordText.text = newGuessWord
    if(correctGuess == false)then
        numWrong = numWrong +1
        drawHangman(numWrong);
    end
    if(newGuessWord == theWord)then
        wonGame = true
        didWinGame(true)
    end
    if(numWrong == 6) then
        for i =1 , #theWord do
            guessWord[i] = theWord:sub(i,i)
            newGuessWord = table.concat(guessWord)
            guessWordText.text = newGuessWord;
        end
    didWinGame(false)
    end
end

Первое, что мы делаем, это получаем письмо, которое угадал пользователь, вызывая метод getLabel для кнопки. Это возвращает label кнопки, заглавную букву. Мы преобразуем эту букву в нижний регистр, вызывая метод lower для string , который принимает в качестве параметра строку для нижнего регистра.

Затем мы устанавливаем correctGuess в false , а newGuessWord — в пустую строку и скрываем кнопку, которую нажал пользователь, потому что мы не хотим, чтобы пользователь мог нажимать кнопку более одного раза за раунд.

Затем мы перебираем theWord , получаем текущий символ с помощью метода string.sub и сравниваем этот символ с theLetter . Если они равны, то пользователь сделал правильное предположение, и мы обновляем эту конкретную букву в guessWord , устанавливая correctGuess на true

Мы создаем newGuessWord , используя метод guessWordText , и обновляем guessWordText чтобы отразить любые изменения.

Если correctGuess все еще false , это означает, что пользователь сделал неверное предположение. В результате мы увеличиваем переменную numWrong и drawHangman функцию drawHangman , передавая значение numWrong . В зависимости от того, сколько неправильных предположений сделал пользователь, функция drawHangman будет рисовать палача соответствующим образом.

Если newGuessWord равен theWord , это означает, что пользователь угадал слово, а мы обновили wonGame до true , вызывая функцию didWinGame и передавая true .

Если numWrong равно 6 , это означает, что пользователь израсходовал все свои догадки, и палач был полностью прорисован. Мы перебираем theWord и устанавливаем каждый символ guessWord равный символам в theWord . Затем мы показываем пользователю правильное слово.

Этот фрагмент кода должен иметь смысл, поскольку мы уже делали нечто подобное пару раз раньше. Наконец, мы вызываем didWinGame и передаем false .

Функция didWinGame вызывается, когда использование либо выигрывает, либо проигрывает раунд.

01
02
03
04
05
06
07
08
09
10
11
12
function didWinGame(gameWon)
    hideButtons()
    winLoseText:setVisibility(true)
    if(gameWon == true)then
        winLoseText:setText(«YOU WIN!!»)
        winLoseText:setColor(0,0,1)
    else
        winLoseText:setText(«YOU LOSE!!»)
        winLoseText:setColor(1,0,0)
    end
     winLoseText:grow()
end

Первое, что мы делаем, это вызываем hideButtons , который, как следует из названия, скрывает все кнопки. Мы устанавливаем, чтобы winLoseText был видимым, и, в зависимости от того, выиграл пользователь или проиграл в раунде, установите его текст и цвет в зависимости от ситуации. Наконец, мы вызываем метод grow в winLoseText .

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

Функции showButtons и hideButtons показывают и скрывают кнопки, gameButtons таблицу gameButtons , устанавливая видимость каждой кнопки.

01
02
03
04
05
06
07
08
09
10
11
function hideButtons()
    for i=1, #gameButtons do
        gameButtons[i].isVisible = false
    end
end
 
function showButtons()
    for i=1, #gameButtons do
        gameButtons[i].isVisible = true
    end
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function drawHangman(drawNum)
    if(drawNum== 0) then
        drawGallows();
    elseif(drawNum ==1)then
        drawHead();
    elseif(drawNum == 2) then
        drawBody();
    elseif(drawNum == 3) then
        drawArm1();
    elseif(drawNum == 4) then
        drawArm2();
    elseif(drawNum == 5) then
        drawLeg1();
    elseif(drawNum == 6) then
        drawLeg2();
    end
end

Прошло довольно много времени с тех пор, как мы проверили наш прогресс, но если вы протестируете сейчас, вы сможете сыграть несколько раундов. Для сброса игры перейдите в « Файл»> «Перезапустить» в Corona Simulator. Помните, правильное слово выводится на консоль, так что это должно помочь вам проверить, что все работает как надо.

Когда winLoseText закончит расти, мы начнем новый раунд. Если пользователь выиграл раунд, мы отправимся в новую сцену, где палач будет танцевать счастливым. Если пользователь проиграл, мы сбросим все в gamescreen.lua и начнем новый раунд.

Однако прежде чем мы сделаем что-либо из этого, нам нужно прослушать gameOverEvent , который отправляется из класса growText .

Добавьте к scene:show метод scene:show .

1
2
3
4
5
6
function scene:show( event )
    —SNIP—
    if ( phase == «did» ) then
        Runtime:addEventListener( «gameOverEvent», gameOver )
    end
end

Мы передаем gameOverEvent в качестве первого аргумента метода addEventListener . Когда gameOverEvent , gameOver функция gameOver .

Мы должны также удалить слушателя события в некоторый момент. Мы делаем это в методе scene:hide как показано ниже.

1
2
3
4
5
6
function scene:hide( event )
    local phase = event.phase
    if ( phase == «will» ) then
        Runtime:removeEventListener( «gameOverEvent», gameOver )
    end
end

Добавьте следующий код в gamescreen.lua .

1
2
3
4
5
6
7
8
function gameOver()
    winLoseText:setVisibility(false)
    if(wonGame == true)then
        composer.gotoScene(«gameoverscreen»)
    else
        newGame()
    end
end

Если пользователь выиграл игру, мы gotoScene метод gotoScene для объекта composer и переходим на экран игры . Если нет, мы вызываем метод newGame , который сбрасывает игру и создает новое слово.

Функция newGame сбрасывает некоторые переменные, устанавливает видимость кнопок, очищает hangmanGroup и создает новое слово.

1
2
3
4
5
6
7
function newGame()
    clearHangmanGroup()
    drawHangman(0)
    numWrong = 0
    guessWordText.text = createGuessWord()
    showButtons()
end

Большая часть этого кода должна выглядеть вам знакомой. Функция clearHangmanGroup — единственная новинка, и мы рассмотрим эту функцию на следующем шаге.

clearHangmanGroup просто перебирает hangmanGroup numChildren и удаляет их. По сути, мы все очищаем, чтобы начать рисовать заново.

1
2
3
4
5
function clearHangmanGroup()
    for i = hangmanGroup.numChildren, 1 ,-1 do
        hangmanGroup[i]:removeSelf()
        hangmanGroup[i]=nil
end

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

Создайте новый файл gameoverscreen.lua и добавьте в него следующий код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
local composer = require( «composer» )
local scene = composer.newScene()
local hangmanSprite
local hangmanAudio
function scene:create( event )
    local group = self.view
    drawChalkBoard()
    local options = { width = 164,height = 264,numFrames = 86}
    local hangmanSheet = graphics.newImageSheet( «hangmanSheet.png», options )
    local sequenceData = {
     { start=1, count=86, time=8000, loopCount=1 }
    }
    hangmanSprite = display.newSprite( hangmanSheet, sequenceData )
    hangmanSprite.x = display.contentCenterX
    hangmanSprite.y = display.contentCenterY
    hangmanSprite.xScale = 1.5
    hangmanSprite.yScale = 1.5
    group:insert(hangmanSprite)
end
 
function scene:show( event )
    local phase = event.phase
    local previousScene = composer.getSceneName(«previous»)
    composer.removeScene(previousScene)
    if ( phase == «did» ) then
        hangmanSprite:addEventListener( «sprite», hangmanListener )
        hangmanSprite:play()
        hangmanAudio = audio.loadSound( «danceMusic.mp3» )
        audio.play(hangmanAudio)
   end
end
 
function scene:hide( event )
    local phase = event.phase
    if ( phase == «will» ) then
        hangmanSprite:removeEventListener( «sprite», hangmanListener )
        audio.stop(hangmanAudio)
        audio.dispose(hangmanAudio)
        end
end
 
function drawChalkBoard()
    local chalkBoard = display.newRect( 0, 0, display.contentWidth, display.contentHeight )
    chalkBoard:setFillColor(1,1,1 )
    chalkBoard.anchorX = 0
    chalkBoard.anchorY = 0
    scene.view:insert(chalkBoard)
end
 
 
 
function hangmanListener( event )
     
if ( event.phase == «ended» ) then
        timer.performWithDelay(1000,newGame,1)
    end
end
 
function newGame()
    composer.gotoScene(«gamescreen»)
end
scene:addEventListener( «create», scene )
scene:addEventListener( «show», scene )
scene:addEventListener( «hide», scene )
 
return scene

hangmanSprite — это SpriteObject который будет использоваться для танцевальной анимации. hangmanAudio — это AudioObject который будет использоваться для воспроизведения музыки во время танца палача.

Как я уже упоминал, hangmanSprite — это экземпляр SpriteObject и если hangmanSprite будет спрайтом вместо обычного изображения, мы можем его анимировать. hangmanSprite имеет 86 отдельных изображений, каждое из которых представляет собой отдельный кадр для анимации. Вы можете увидеть это, открыв hangmanSheet.png в графическом редакторе.

Таблица параметров содержит width , height и количество numFrames отдельных изображений в увеличенном изображении. Переменная numFrames содержит значение числа меньших изображений. hangmanSheet является экземпляром объекта ImageSheet , который принимает в качестве параметров изображение и таблицу параметров.

Переменная sequenceData используется экземпляром SpriteObject , ключ start — это изображение, с которого вы хотите запустить последовательность или анимацию, а ключ count — это общее количество изображений в анимации. Клавиша time показывает, сколько времени потребуется для воспроизведения анимации, а клавиша loopCount — сколько раз вы хотите, чтобы анимация воспроизводилась или повторялась.

Наконец, вы создаете экземпляр SpriteObject , передавая экземпляр ImageSheet и sequenceData .

Мы устанавливаем hangmanSprite x и y hangmanSprite и масштабируем его до 1,5 по сравнению с его обычным размером, используя свойства xScale и yScale . Мы вставляем его в group чтобы убедиться, что он удаляется при удалении scene .

Внутри scene:show , мы удаляем предыдущую сцену, добавляем прослушиватель событий в hangmanSprite и вызываем его метод play . Мы создаем экземпляр hangmanAudio , вызывая метод loadSound , передавая danceMusic.mp3 . Наконец, мы вызываем метод play чтобы начать воспроизведение звука.

В методе scene:hide мы удаляем прослушиватель событий из hangmanSprite , вызываем stop для экземпляра аудио и вызываем метод hangmanSprite . Метод dispose обеспечивает освобождение памяти, выделенной для экземпляра audio .

В hangmanListener мы проверяем, находится ли он в end фазе, и, если true, это означает, что анимация закончилась. Затем мы вызываем timer.performWithDelay . Таймер срабатывает через одну секунду, вызывая метод newGame , который использует composer для перехода обратно на игровой экран, чтобы начать новую игру.

Это был довольно длинный урок, но теперь у вас есть функциональная игра-палач с приятным поворотом. Как уже упоминалось в начале этого урока, попробуйте включить уровни сложности. Одним из вариантов будет иметь экран параметров и реализовать SegmentedControl где пользователь может выбирать между списками из 5, 9 и 13 букв   слова.

Я надеюсь, что вы нашли этот урок полезным и узнали что-то новое. Спасибо за прочтение.