FizzBuzz: beginner scripting tutorial

FizzBuzz is a math game which is known to be used to test a programmer’s skill. I think it’s good for a first introduction to lua.

In FizzBuzz, two people count upwards. They take turns. If their number is divisible by 3 (-> 3, 6, 9, etc.) , they must say “Fizz”, instead of the number. If it is divisible by 5 (-> 5, 10, 20, etc.), they must say “Buzz”. If it is divisible by 3 and 5 (-> 15, 30, 45, etc) they must say “FizzBuzz”.

Let’s make this game in Roblox. Instead of 2 people, we will just put the engine to work.

Before we start programming, it is useful (especially for more complicated systems) to create a simple chart or list of things we need our program to do.

  • Receive a value, which we’ll call num. In this case, we want our value to be an integer. An integer is a whole number, which can be positive or negative (or 0).
  • Is num divisible by 3?
    – If yes, print “Fizz” to the output.
  • Is num divisible by 5?
    – If yes, print “Buzz” to the output.
  • Is num divisible by 3 and 5?
    – If yes, print “FizzBuzz” to the output.

(Printing to the output means that our input is sent and displayed in the output window. If you do not see this window (unlikely, but anyways) you can open it in view → Output)

The goal of FizzBuzz (as a programming test) is not to see if you can do it. It is to see how you do it. There’s more than 1 way to Fizz a Buzz, but some ways are better than others. Besides the basic program, there’s practices you should implement, and some you should avoid.

  • Add comments to your code (these are basically notes you can type in your code, which will be ignored when the program is ran.You can initiate a comment in lua with “–”
print("This is code")
-- And this is a comment 
  • Write clear code. When you create variables, make it obvious what they are for. Try to write the code not so you can understand it, but so someone else can understand it without needing you.
local m = "Hello, world!" -- This isn't very clear.
local joinMessage  = "Hello, world!" -- This is!
  • Write flexible code. Make sure your code is easy to change and adapt for other use cases. That way, you won’t need to write it again.
print("Hello, world!") -- This is rather inflexible
print(joinMessage) -- This is more flexible

For flexibility, we’ll be using functions. Functions are bits of code that you can use over and over again. They can also take parameters.

function myFunction(parameter1 : string, parameter2 : number) --  : string, : number are used to define the expected type of the parameter. You don't need to do this, but it is a good idea.
print("My favourite food is " .. parameter1 .. ", and my favourite number is " .. parameter2) -- .. is used for string concatenation (this means adding strings together). If you want to print multiple things, you can put .. between each item.
end

If we call this function (execute it), we would write:

myFunction("pizza",22) 

Yep, that works.

So, FizzBuzz. Let’s create a function.

function FizzBuzz()
end

We’ll need a value to check. This value should always be an integer (as per the rules of the game), so we can give it an expected data type (: number). Let’s call the value inputValue

function FizzBuzz(inputValue : number)
end

If we were to simply:

check if it is divisible by 3, print,
check if it is divisible by 5, print,
check if it is divisible by both, print,

we would run into an issue: if the inputValue is divisible by both, it would print 3x (because the program goes from top to bottom, line by line. It would first find it IS divisible by 3 and print. Then 5, and print. Then 3&5, and print).

How do we fix this? One way is to create a “holder” variable, which starts out empty. We will add Fizz or Buzz to the holder, depending on the result.
Because the program runs line by line, if it is divisible by 3, it should add Fizz. If it is divisible by 5, it should add Buzz. If both are true, it will first add Fizz, then Buzz, resulting in FizzBuzz

Let’s also add a comment.

function FizzBuzz(inputValue : number)
    local totalHolder = "" -- This value is used to combine the string values, and will eventually be outputted.
end

We could now add lines to check if inputValue is divisible by 3 or 5. But let’s prepare for the future: what if we need to change the numbers, or add more, or remove some? Hardcoding the divisible check would be wasteful, as we would need to go back and replace it. Instead, let’s make another function: one that simply checks if it is divisible.

Modulo
To check if X is divisible by Y, we can use the modulo operator. Modulo divides X by Y, and returns the remainder. So if I divide 10 by 2, the remainder is 0 (as 10/2=5, a whole number).
However:
9/2=4.5
This means we can fit the 4 into the 9 twice, but no more: we are left with 1 (4+4+1=9)
The modulo would therefore return 1. I did not understand this for quite some time, and I expect it has to do with the language barrier. Here’s an analogy, if you’re like me:

Suppose that you have a large pizza with 8 slices, and you want to divide it equally among your friends. If you have 3 friends, you can give each friend 2 slices, and you will have 2 slices left over.

Mathematically, that would mean: 8 mod 3 = 2
In lua, we use the operator %, which means

print(8%3)  -- This prints 2 to the output

Why does the modulo matter? Well, if we want to check if X is divisible by Y (by which I mean the result will be a whole number), the remainder will be 0. Because the division “fits” (you can share 8 slices with 4 people, but 9 slices means there is 1 slice remaining. By the way, the solution to this is that I get the remaining slice

So if X is divisible by Y, the modulo will be 0. Before we add this as code, one more term.

return
Return is used to, well, return a value. Suppose we have a function for doubling a number (so multiplying by 2). If we want to use the result elsewhere, in the function we would return the result. We can then call the function, which will be the result that we returned.

local function doubleNumber(inputNumber : number)
	return inputNumber * 2
end

print(doubleNumber(5)) -- This will print 10 

local function isDivisible(inputToCheck : number, valueAsCheck : number) 
	return inputToCheck % valueAsCheck -- return the remainder after dividing inputToCheck with valueAsCheck. inputToCheck mod valueAsCheck = (returning)
end

function FizzBuzz(inputValue : number)
    local totalHolder = "" -- This value is used to combine the string values, and will eventually be outputted.
end

So now we could, in the FizzBuzz function, call isDivisible(inputValue, X), and we will get back the remainder. If it’s 0, then we can add the associated string. But how do we actually get the valueAsChecks, and their associated strings? Let’s talk about tables,

Tables
Specifically, arrays (a type of table)
Arrays are essentially queues of data. You can also imagine them as numbered lists. You can find a specific element in the array, or iterate over it, add/remove elements, etc.

local myFavouriteToppings = {"Pineapple", "Ananas", "Spongebob's house"}
print(myFavouriteToppings[1]) -- This will print "Pineapple"

We can use different datatypes, they don’t need to be strings. We can even nest other tables inside:

local myFavouriteToppingCombos = {
	{"Pineapple","Pesto","Parmesan"}, 
	{"Ansjovis","Ananas", "Artichoke"}
}
print(myFavouriteToppingsCombos[1][2]) -- This will print Pesto

For FizzBuzz, we can use an array which consists the number to check, and a string as a result. We can put these arrays into an array that holds them together, so we can iterate over it.

local holderArray = -- Place all valueAsChecks and their results here. 
{ 
	{3, "Fizz"},
	{5, "Buzz"}
}

Lastly, let’s go over loops and conditions.

Loops
There are a few types of loops. Loops are, as the name suggests, segments of code which will repeat. How often, depends on the loop. We will need a for loop.

A for loop takes a table, and will perform whatever is inside the loop for every element of that table. You’ll have access to the current index (the number of the current loop, so if it ran 5 times the index is 5), and the value associated with that. When we use an array, we can use ipairs (as opposed to pairs), which is faster and maintains the order of the table. This is important in our use-case, because we do not want the program to (for example) first check the 5, and then the 3, because we’ll end up with BuzzFizz.

local myNumbers = {1,3,21}

local function doubleNumber(inputNumber : number)
	return inputNumber * 2
end

for index,value in ipairs(myNumbers) do
	print(doubleNumber(index, value) 
end

-- This will print:
-- 2
-- 6
-- 42

Conditions
This is one of the most fundamental aspects of programming. Conditions are essentially logical operators (if you know anything about logic gates, you’ll understand these just fine).
We’ll just be using if condition then.The program will check if the condition is true. If it is, it runs the rest of the code inside the if block. If not, it continues without running the code.

You may notice we use = and ==.
For setting and declaring variables, we use =
For checking if X is equal to Y, we use ==. We can also add not or use ~=, to check if something is not true.

if dinner == "pizza" then 
	print("I am happy")
else
	print("I am not happy"
end

So let’s implement a for loop, which runs for every element in holderArray, and a condition which checks isDivisible.


local holderArray = -- Place all valueAsChecks and their results here. 
{ 
	{3, "Fizz"},
	{5, "Buzz"}
}


local function isDivisible(inputToCheck : number, valueAsCheck : number) 
	return inputToCheck % valueAsCheck -- return the remainder after dividing inputToCheck with valueAsCheck. inputToCheck mod valueAsCheck = (returning)
end

function FizzBuzz(inputValue : number)
	local totalHolder = "" -- This value is used to combine the string values, and will eventually be outputted.

	for index, value in ipairs(holderArray) do -- Loop over every element in holderArray
		if isDivisible(value[1]) == 0 then -- Remember: the elements in holderArray are also arrays. We want to check only the first value of these arrays. If it is 0, then that means we have no remainder.
			totalHolder = totalHolder .. value[2] -- Here we concatenate the strings, adding value[2] to the current totalHolder
		end
	end
	print(totalHolder)
end

Now this’ll work. But let’s turn it into a module script.

Modules
Similarly to functions, modules are blocks of code that can be run over and over again. Modules can, however, be used by other scripts. This means we can make a module, and then in another script we can use that module to do something. In this case, we will keep it really simple. We will turn our function into a variable, and return that variable.

Create a ModuleScript, and put it in workspace.


local holderArray = -- Place all valueAsChecks and their results here. 
	{ 
		{3, "Fizz"},
		{5, "Buzz"}
	}


local function isDivisible(inputToCheck : number, valueAsCheck : number) 
	return inputToCheck % valueAsCheck -- return the remainder after dividing inputToCheck with valueAsCheck. inputToCheck mod valueAsCheck = (returning)
end

local FizzBuzz = function(inputValue : number)
	local totalHolder = "" -- This value is used to combine the string values, and will eventually be outputted.

	for index, value in ipairs(holderArray) do -- Loop over every element in holderArray
		if isDivisible(inputValue, value[1]) == 0 then -- Remember: the elements in holderArray are also arrays. We want to check only the first value of these arrays. If it is 0, then that means we have no remainder.
			totalHolder = totalHolder .. value[2] -- Here we concatenate the strings, adding value[2] to the current totalHolder
		end
	end
	
	
	print(totalHolder)
end

return FizzBuzz

To use our module script, we’ll need to require it from another script. This is very easy and can be done in the commandline. If you use the command line, you’ll need to cut-paste the Module every time you make changes, and require it every time.

local fizzBuzzModule = require(workspace.ModuleScript)

Now, essentially, fizzBuzzModule is the FizzBuzz function. So we can try

fizzBuzzModule(15) -- This will return FizzBuzz

We’re very close, very close indeed. Slight issue: because the totalHolder string is empty initially, if neither Fizz nor Buzz is added, we print an empty line. This is against the rules of the game, because it should print the number if it isn’t Fizz/Buzz/FizzBuzz. Easy remedy: before we print totalHolder in the Module, we can check if it is equal to nil. If it is, we can set it to the inputValue.

totalHolder is expecting a string, and inputValue is a number. That won’t do. We can convert it with tostring(inputValue)

if totalHolder == "" then -- No check succeeded? Set to input
		totalHolder = tostring(inputValue)
	end

print(totalHolder)

Make sure to cut-paste the Module to refresh it.
In the command line, we’ll now create a loop which will loop over the numbers 1,X and FizzBuzz them. But first we require the module.

local fizzBuzzModule = require(workspace.FizzBuzz)
for i = 1,100 do
fizzBuzzModule(i)
end

I hope this tutorial helps someone!

10 Likes