← Blog Home

Creating Your Own Simon Says!

Most of the posts that I make tend to be very mathematical and aimed at experienced developers. Today's post is instead going to be aimed at those of you who are newer to the platform, and are looking for a project to practice your skills on. We will be going over how we can make our very own game of Simon says!


Before you start reading this post I recommend you brush up on a few things if you aren't familiar with them already.

Setup

The only thing I'll be going into this with is the model I made for Simon. You are by no means forced to use the exact model hierarchy that I use, but it's important that you have an idea of where my script is in relation to what.

img1

-- defined at the top of the script
local model = script.Parent
local centerClick = model:WaitForChild("Center"):WaitForChild("ClickDetector")
-- looped = true on this sound
local sound = model:WaitForChild("BasePart"):WaitForChild("Sound") 
-- store the colours as an array
local colours = model:WaitForChild("Colours"):GetChildren()
local numColours = #colours

The rules of the game

For those of you who have never played Simon says the rules are as follows:

  1. Round n starts
  2. A pattern of n-length is showed to the player
  3. The player must repeat back the pattern in order
  4. If the player completes the pattern, they move onto round n+1
  5. If the player fails the pattern, they restart at round 1

Thus, functions we will need are as follows:

  • Something to show we selected a colour
  • Something to generate a pattern
  • Something to play the pattern
  • Something to show we won the game
  • Something to show we lost the game

Selecting a colour

To start off we'll work on our function that shows a colour has been selected. It will be used for when the player selects a colour and when Simon is playing back a pattern or showing that we won or lost.

We'll define the function with three parameters. The first will be an index which represents which colour part we are using in the colours array. The second will be the time that the colour part lights up for, and the third will be the playback speed of the sound which we will use to control pitch.

function playColour(index, t, speed)
    -- access the part from the array
    local part = colours[index] 
    -- set the material to neon and play the sound
    part.Material = Enum.Material.Neon
    sound.PlaybackSpeed = speed
    sound:Play()
    -- wait t seconds
    wait(t)
    -- set the part back to plastic and stop the sound
    part.Material = Enum.Material.Plastic
    sound:Stop()
end

-- a quick test
while (true) do
    for i = 1, #colours do
        playColour(i, 0.2, i)
        wait()
    end
end

img2

Generating a pattern

Our next step will be to write a function that generates a pattern of n-length. We can create this pattern with the Random object and its method :NextInteger(min, max) which gives a random integer between min and max inclusive.

function genPattern(length, seed)
    local pattern = {}
    local r = Random.new(seed)
    for i = 1, length do
        pattern[i] = r:NextInteger(1, #colours)
    end
    return pattern
end

We can note that for each element in the pattern we pick a random number between 1 and #colours which means our pattern will be made up of indexes that correspond to a part in the colours array.

In other words, if our colour array and pattern looked like this for example:

colours = {blue, red, green, yellow}
pattern = {1, 3, 3, 2, 4, 1, 1}

-- then the pattern corresponds to
{blue, green, green, red, yellow, blue, blue}

Playing the pattern

Now that we have a pattern, playing it is as simple as iterating through it and playing each colour that corresponds to the index.

function playPattern(pattern)
    for i = 1, #pattern do
        playColour(pattern[i], 0.2, pattern[i])
        wait()
    end
end

-- a simple test
playPattern(genPattern(20, tick()))

img3

Starting a round

We can now start to write the function that will start a round of Simon says.

-- define these near the top
local roundNum = 0
local currentPattern = {}

function startRound()
    roundNum = roundNum + 1

    local pattern = genPattern(roundNum, tick())
    currentPattern = pattern
    playPattern(pattern)
end

This covers steps one and two of the initial rules process we defined at the beginning. All that's left to do is have the player repeat the pattern back.

Player input

For the player to repeat the pattern they need some way to interact with Simon. They way we will do this is with ClickDetectors and the MouseClick event. Our first step will be to create a click detector under each colour part in the colours array and connect a function that will fire when it's clicked.

-- define this near the top
local clickDetectors = {}

function onClicked(player, index)
    -- We'll write the code for this later
end

function initClickDetectors()
    for i = 1, numColours do
        local part = colours[i]
        local cdetect = Instance.new("ClickDetector")
        -- connect the function that defines what happens when the part is clicked
        cdetect.MouseClick:Connect(function(player) onClicked(player, i) end)
        cdetect.Parent = part
        -- dictionary where the part is the key and the click detector is the value
        clickDetectors[part] = cdetect
    end
end

Sometimes throughout the game of Simon says we don't want the player to be able to click any buttons. Thus, we'll create a function that can enable or disable player input by setting the parent of the click detectors.

function setInputEnabled(bool)
    -- iterate over the key and value
    for part, cdetect in pairs(clickDetectors) do
        if (bool) then
            cdetect.Parent = part
        else
            cdetect.Parent = nil
        end
    end
end

Now that we have this function we'll likely want to go back and adjust out pattern playing function so that the player can't click anything when the pattern is playing.

function playPattern(pattern)
    setInputEnabled(false)
    for i = 1, #pattern do
        playColour(pattern[i], 0.2, pattern[i])
        wait()
    end
    setInputEnabled(true)
end

Verifying the pattern

In order to complete step three, we will have to verify that when a player clicks a colour part it was the part that was next in the pattern. As such we will need a counter that tells us how far along the player is into the pattern. We will simply reset this back to zero every time a round begins.

-- define near the top
local inputIndex = 0

function startRound()
    inputIndex = 0 -- reset every round
    roundNum = roundNum + 1

    local pattern = genPattern(roundNum, tick())
    currentPattern = pattern
    playPattern(pattern)
end

function onClicked(player, index)
    -- play the colour that was just selected
    setInputEnabled(false)
    playColour(index, 0.2, index)
    setInputEnabled(true)

    -- comparing our input to the next index in the pattern
    inputIndex = inputIndex + 1

    if (index ~= currentPattern[inputIndex]) then
        -- what the player selected was not what was next in the pattern
        -- lose the round!
    elseif (inputIndex == #currentPattern) then
        -- from our previous check we know the player selected correctly
        -- we also know that the last input made was the total length of the pattern
        -- win the round!
    end
end

Win or lose the round

Our last two steps require we show the player they either won or lost and move them to the next round. Both these functions are quite easy to write by this point as we have the rest of the game logic in place.

-- define near the top
local timeBetweenRounds = 0.5

function winRound()
    setInputEnabled(false)
    -- play an animation that tells us we won!
    for i = 1, 3 do
        for j = 1, numColours do
            playColour(j, 0.05, j)
        end
    end
    wait(timeBetweenRounds)
    setInputEnabled(true)
    -- next round!
    startRound()
end

function loseRound()
    setInputEnabled(false)
    -- play an animations that tells us we lost
    for i = 1, 3 do
        for j =  numColours, 1, -1 do
            playColour(j, 0.05, j/numColours)
        end
    end
    wait(timeBetweenRounds)
    setInputEnabled(true)
    -- reset back to first round
    roundNum = 0
    startRound()
end

-- finally, plugging these functions into our onClicked function
function onClicked(player, index)
    -- play the colour that was just selected
    setInputEnabled(false)
    playColour(index, 0.2, index)
    setInputEnabled(true)

    -- comparing our input to the next index in the pattern
    inputIndex = inputIndex + 1

    if (index ~= currentPattern[inputIndex]) then
        loseRound()
    elseif (inputIndex == #currentPattern) then
        winRound()
    end
end

Initiating the game

Congratulations! You now have all the functions needed to have your simon says working! The absolute last thing we need to do is make sure the game starts running. We'll do this by having the player click the button in the middle.

initClickDetectors()
setInputEnabled(false)
-- wait for the player to click the center button
centerClick.MouseClick:Wait()
setInputEnabled(true)
-- start Simon says
startRound()

img4

You can find an un-copylocked version of the game here.

Conclusion

So that's it for now folks. Hope you enjoyed the post! As always, I'm looking for feedback, especially for this type of article as it's quite different than what I normally post.

Until next time!

Posted in Scripting Tips

Commentary

Leave a Comment

incapaz says: November 8, 2018
first + this awesome
green271 says: November 8, 2018
second + this awesome
EXpodo1234ALT says: November 8, 2018
third + this awesome
greatneil80 says: November 8, 2018
fourth + this awesome
Awak3nedZer3f says: November 8, 2018
5th + this awesome
abnotaddable says: November 8, 2018
sixth + this awesome
SummerEquinox says: November 9, 2018
Good post. I remember making something like this when I was first getting into Lua. Definitely a good project to hone some skills and advance your knowledge. .... also seventh, this awesome
DestinyAwakened says: November 10, 2018
8th + this awesome
LoganboyInCO says: November 10, 2018
Anyone else get triggered when he spelled 'color' as 'colour'? No one, just me? lol okay.
RayCurse says: November 11, 2018
^ Sorry, smart ppl don't speak simplified English
LoganboyInCO says: November 11, 2018
^ Actually smart people speak simplified english, because british english is just bad lol (there to obsessed with u)
rollercoaster125634 says: November 11, 2018
666th + this awesome
DjMusa2 says: November 11, 2018
Nice!