Ideas, Rules, Simulation: Coins, Dice and Rectangles
Requirements
The material for Ideas, Rules, Simulations promotes project based learning (interactive simulations and digital games) using multimedia resources. As such, it is complimentary to the material of Learn Programming, which introduces fundamentals and basic programming techniques for programming, with examples for JavaScript, Python, Lua and GDScript (for Godot Engine).
For Ideas, Rules, Simulations you will need a configured development environment for one of the previous languages:
- GDScript (Godot Engine);
- JavaScript;
- Python. For Python, it is also necessary to install PyGame. Text instructions are available at Libraries: Graphical Interface with Thonny. Video instructions are available in Python on the command line and using Thonny IDE, hosted on YouTube;
- Lua. In Lua, it is also necessary to install LÖVE (Love2D). Video instructions are available in Lua on the command line and using ZeroBraneStudio IDE, hosted on YouTube.
JavaScript (for browsers) and GDScript provide built-in support for multimedia content.
I am also considering improving the online programming editors provided in this website, to support an interactive course. Currently, the page with tools provide options for:
The editors support images in the browser.
However, Python and Lua use the JavaScript syntax for the canvas
.
If I create an abstraction and add support for audio, they could become valid tools (at least for the first activities).
However, it is worth configuring an Integrated Development Environment (IDE), or a combination of text editor with interpreter as soon as possible, to enable you to program using your machine with greater efficiency. Online environments are practical, though local environments are potentially more complete, efficient, faster and customizable. If you want to become a professional, you will need a configured development environment. The sooner you do it, the better.
Version in Video
This entry has video versions on the author's YouTube channel:
However, this text version is more complete.
Documentation
Practice consulting the documentation:
- GDScript: language documentation;
- JavaScript: language documentation;
- Python: language documentation and documentation for PyGame;
- Lua: language documentation (Lua 5.1) and documentation for LÖVE.
Most links have a search field. In Lua, the documentation is in a single page. You can search for entries using the shortcut Ctrl F (search).
Heads or Tails?
The previous topic (Randomness and Noise) of Ideas, Rules, Simulation introduced the use of random numbers (technically, pseudorandom numbers) to help the creation of stochastic simulations.
Throwing coins and dice are two of the simplest examples of simulations with randomness. The required implementation can be described in a few phrases:
For a coin, one can draw a random real number between 0.0 and 1.0, divide the interval by 0.5, and choose the interval 0.0 to 0.5 (non-inclusive) for heads, and 0.5 (inclusive) to 1.0 (non-inclusive) for tails. It is equally valid to invert the intervals for heads and tails.
Another possibility would be drawing a random integer number, use the remainder of a division by 2, and match 0 as the remainder to heads and 1 for tails (or vice-versa). To make the code easier to read, a constant
HEAD
with value 0 could be created for heads, and a constantTAIL
with value 1. If you would rather define the names using the plural terms, you could. The author considers the singular term more representative for the outcome of a single coin toss (though this is personal preference, as the terms use the singular form in Portuguese).For an honest die (that is, with equal probabilities for each possible result) with 6 faces, one can draw a random integer number, calculate the remainder of a division by 6, and add 1 to the result. The result would be a number ranging from 1 to 6.
Even simpler, the operations can be trivially performed using the random_integer()
function that was defined in the previous topic.
For an honest die with six faces, calling the function as random_integer(1, 6)
would be sufficient.
Analogously, random_integer(0, 1)
could be an alternative for a coin.
As the part of the rules for the simulation is simple, the remainder of this section focuses on conditional structures, the creation and use of functions and procedures, repetition structures, and refactoring. The purpose is explaining them to people who have not followed the material from Learn Programming.
If you can already use them, you can skip to the next section to start creating graphics for the output.
Nevertheless, it is interesting that, often, it is possible to create prototypes of simulations without using graphics. In some cases, it is simpler and faster to test an idea using a program for console (terminal) than starting with graphics. Once the implementation of the prototype is satisfactory, you can start creating the graphics for a better presentation of the results.
Variables and Assignments
For a gentle introduction, the explanation will start from variables.
As the simulation is simple, a variable to store the random number is sufficient.
It can be called result
.
To draw a random number that varies each time the program is run, a seed must be generated. Next, the function to generate a pseudorandom number can be called to draw the number.
extends Control
func _ready():
randomize()
var result = randi()
print(result)
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(0, 1)
console.log(result)
import random
random.seed()
result = random.randint(0, 1)
print(result)
math.randomseed(os.time())
local result = math.random(0, 1)
print(result)
In Python and Lua, random.randint()
and math.random()
allow choosing a range for the numbers.
In JavaScript, the implementation of random_integer()
also allows doing that.
However, randi()
in GDScript does not.
For an example that can be applied to any programming language, the implementation could modify the range to something like 1 to 10000.
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(1, 10000)
console.log(result)
import random
random.seed()
result = random.randint(1, 10000)
print(result)
math.randomseed(os.time())
local result = math.random(1, 10000)
print(result)
This way, the generate number became large in all implementations. Thus, it will be necessary to convert it to two values to represent heads or tails.
Once again, this choice has didactic purposes.
For instance, you should call math.random(0, 1)
directly in Lua.
Arithmetic Operations
Computers operate on four main data types:
- Integer numbers;
- Real numbers, typically represented as floating-numbers, also called
float
; - Logic values:
True
orFalse
; - Strings.
At a lower level, all types are encoded as binary numbers (refer to Learn Programming: Variables and Constants). At a lower level still, every value is a combination of presence or absence of electricity (or magnetism, or other technology used for the memory) in circuits.
In other words, there will not exist heads nor tails for the computer. There will only exist an abstraction, that will use the random number that has been drawn.
Thus, we will need to reduce the value of result
to two values.
This can be achieved using a arithmetic operation called division remainder (modulo).
For two values, one can use modulo 2.
extends Control
func _ready():
randomize()
var result = randi() % 2
print(result)
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(1, 10000) % 2
console.log(result)
import random
random.seed()
result = random.randint(1, 10000) % 2
print(result)
math.randomseed(os.time())
local result = math.random(0, 10000) % 2
print(result)
In the new version, the result will always be 0 or 1. Therefore, two values, as it is necessary for heads and tails.
Computational Thinking: Abstraction
Once gain, a computer does not know what is heads, nor tails. On the other hand, it can process an integer number.
The new goal is to associate a code that maps the values 0 and 1 to heads and tails.
For the computer, result
will still be 0 or 1.
For someone using the program, the goal is writing heads
or tails
following the association that has been defined in the program.
Conditional Structures
Simulating a coin flip is a simple way to understand how to use an if/then/else structure. A Lua example can be illustrative. The emoji probably will not work in many command line interpreters; it only serves to mark the spot on which the drawing code will be written at the appropriate time.
extends Control
func _ready():
randomize()
var result = randi() % 2
print(result)
if (result == 0):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(1, 10000) % 2
console.log(result)
if (result === 0) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
console.log("End")
import random
random.seed()
result = random.randint(1, 10000) % 2
print(result)
if (result == 0):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
math.randomseed(os.time())
local result = math.random(0, 1)
print(result)
if (result == 0) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
print("End")
In this example, print(result)
(or console.log(result)
) only serves the purpose of displaying the random number that was drawn.
It will be removed in later examples.
Likewise, print("End")
(or console.log("End")
) is only intended to show that the program returns to the normal flow after completing the conditional structure.
It could be removed as well.
For a visualization of the code, you can refer to the following chart, created in Flowgorithm (used in Learn Programming). The flowchart provides text in Portuguese and English, for use in both translations of this topic. In the flowchart, you can notice that the conditional structure (represented by the diamond) defines two mutually exclusive execution flows. In other words, running one inhibits the execution of the other.
Another good way of visualizing how the program is running consists in using a debugger. You can learn how to use one in Learn Programming: Tests and Debugging. The video version illustrates how to use it on practice.
To explain the code, we can consider the Lua version.
The variable result
stores the result of drawing a random integer number.
If result
is equal to 0, the program runs the lines 7 and 8, and ignores the lines 10 and 11.
Thus, the process runs only the part (code block) on which the condition is true (then-part).
Next, the program runs line 14, and ends.
Otherwise, the program runs the lines 10 and 11, and ignores the lines 7 and 8. Thus, the process runs only the part on which the condition is false (else-part). Next, the program runs the line 14, and ends.
If one wishes, she/he may use the random number directly as part of the condition (instead of storing it on the result
variable).
extends Control
func _ready():
randomize()
if (randi() % 2 == 0):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
if (random_integer(1, 10000) % 2 === 0) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
console.log("End")
import random
random.seed()
if (random.randint(1, 10000) % 2 == 0):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
math.randomseed(os.time())
if (math.random(0, 1) == 0) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
print("End")
The new version is equivalent to the previous one. It can be used when an intermediate (or temporary) result will not be further required as part of the implementation.
However, if one needs to store the value or use it many times (in other words, two or more times), the variable will be required. In Lua with LÖVE, this will be necessary to preserve the value of the random number. To the current version using the console (terminal), this is not necessary. On the other hand, if one wishes to write the result as it has been done previously, the use of the variable would be required. After all, a new call to the function that generates pseudorandom numbers could generate a different value each time.
If there existed more than two possible results of interest, one nest several conditional structures to handle each case.
This can be achieved by combining a elseif
in Lua, elif
in Python and GDScript, and else if
in JavaScript (JavaScript does not have a specialized version, hence it uses a new conventional structure).
Later, this kind of construction will be used to draw faces of a die.
The next subsection explains this use.
Handling More than Two Cases
In the case of a real coin, the results only can be heads or tails. On the other hand, in a simulation, you could imagine other scenarios.
After all, it is your simulation. You are the magician. In other words, you are the one who makes the rules of the simulation. Your ideas, your rules, your simulations.
For instance, perhaps in 20% of the cases the window could float in the air. In this new scenario, the chance of heads occurring would become 40%, and so would that change for tails. The simulation could draw numbers from 1 to 100 (or 0 to 99, using a remainder of a division by 100) to generate the percentages. In this case in particular, it would also be possible to use values between 1 and 10 (or 0 to 9, if using the remainder of a division by 10). Or even by 5.
Next, conditions could be defined using relational operations and comparisons.
extends Control
func _ready():
randomize()
var result = randi() % 10
print(result)
if (result < 4):
print(":) 🙂")
print("Heads")
else:
if (result < 8):
print("$1 👑")
print("Tails")
else:
print("The coin is floating on the air...")
print("End")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(1, 10000) % 10
console.log(result)
if (result < 4) {
console.log(":) 🙂")
console.log("Heads")
} else {
if (result < 8) {
console.log("$1 👑")
console.log("Tails")
} else {
console.log("The coin is floating on the air...")
}
}
console.log("End")
import random
random.seed()
result = random.randint(1, 10000) % 10
print(result)
if (result < 4):
print(":) 🙂")
print("Heads")
else:
if (result < 8):
print("$1 👑")
print("Tails")
else:
print("The coin is floating on the air...")
print("End")
math.randomseed(os.time())
local result = math.random(1, 10)
print(result)
if (result < 4) then
print(":) 🙂")
print("Heads")
else
if (result < 8) then
print("$1 👑")
print("Tails")
else
print("The coin is floating on the air...")
end
end
print("End")
It is important noticing that, in this new version, the code requires the use of a variable to store the random value, as it is used in two comparisons.
As the cases are mutually exclusive (due to the use of else
), the second nested if
has a condition of (result >= 4) and (result < 8)
, because the program already knows that the number is not less than 4.
Likewise, the last else
has the condition (result >= 8)
, for the value cannot be less than 8 at this point.
The and
is a logic operator.
Thus, the first verification handles the values 0, 1, 2 and 3 for heads (thus, the 40% chances for heads). The second part handles the values 4, 5, 6 and 7 for tails (the 40% for tails). Finally, the last part handles the values 8 and 9 for coin floating on air (the remaining 20%). As a remainder of division by 10 generates numbers from 0 to 9, the created conditional structure handle all cases as proposed.
As mutually exclusive cases are common, many programming languages provide a reserved word such as elif
or elseif
(or else if
, is there is none) to make it easier to write the code.
extends Control
func _ready():
randomize()
var result = randi() % 10
print(result)
if (result < 4):
print(":) 🙂")
print("Heads")
elif (result < 8):
print("$1 👑")
print("Tails")
else:
print("The coin is floating on the air...")
print("End")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
let result = random_integer(1, 10000) % 10
console.log(result)
if (result < 4) {
console.log(":) 🙂")
console.log("Heads")
} else if (result < 8) {
console.log("$1 👑")
console.log("Tails")
} else {
console.log("The coin is floating on the air...")
}
console.log("End")
import random
random.seed()
result = random.randint(1, 10000) % 10
print(result)
if (result < 4):
print(":) 🙂")
print("Heads")
elif (result < 8):
print("$1 👑")
print("Tails")
else:
print("The coin is floating on the air...")
print("End")
math.randomseed(os.time())
local result = math.random(1, 10)
print(result)
if (result < 4) then
print(":) 🙂")
print("Heads")
elseif (result < 8) then
print("$1 👑")
print("Tails")
else
print("The coin is floating on the air...")
end
print("End")
For more information, you can refer to Learn Programming: Conditional Structures.
The new version of the implementation is equivalent to the first one. If the result is not heads, nor tails, then the coin is floating on the air. It is that simple; it is not necessary to explain the Physics. Simulation can be metaphysics, for they are originated in your imagination. In other words, from your ideas.
The remainder of this topic assumes the existence of gravity. Thus, the result will always be heads or tails.
Constants
One can define constants to make the code easier to read.
extends Control
const HEAD = 0
const TAIL = 1
func _ready():
randomize()
if ((randi() % 2) == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
if ((random_integer(1, 10000) % 2) === HEAD) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
console.log("End")
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
random.seed()
if ((random.randint(1, 10000) % 2) == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
local HEAD = 0
local TAIL = 1
math.randomseed(os.time())
if (math.random(0, 1) == HEAD) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
print("End")
The next image presents a similar implementation in Flowgorithm, for visualization.
As Flowgorithm does not allow defining constants, variables were used instead.
In the image, the values for the constants HEAD
and TAIL
are reversed; as the values are symbolic, the reversion does not affect the correctness of the program.
Since Lua 5.4, it is also possible to define a true constant using <const>
.
local HEAD <const> = 0
local TAIL <const> = 1
math.randomseed(os.time())
if (math.random(0, 1) == HEAD) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
print("End")
As ZeroBrane Studio does not include version 5.4 of the interpreter (when this topic was written), the previous code will result in a syntactic error.
Subroutines: Functions and Procedures
To make it even simpler to read the code, and write a block that can be reused several times, one can define a subroutine, such as a procedure or a function. Functions return results; procedures are used for a side effect (as, for instance, changing the memory, or input and output operations -- such as writing text or drawing on a screen).
For instance, the drawing of a random value could be implemented as a function to flip a coin called throw_coin()
.
You could also select other name for it (such as flip_coin()
, toss_coin()
, or heads_or_tails()
).
extends Control
const HEAD = 0
const TAIL = 1
func throw_coin():
return randi() % 2
func _ready():
randomize()
if (throw_coin() == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function throw_coin() {
return random_integer(0, 1)
}
if (throw_coin() === HEAD) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
console.log("End")
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def throw_coin():
return random.randint(0, 1)
random.seed()
if (throw_coin() == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
print("End")
local HEAD = 0
local TAIL = 1
function throw_coin()
return math.random(0, 1)
end
math.randomseed(os.time())
if (throw_coin() == HEAD) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
print("End")
The next images present a similar implementation in Flowgorithm, for visualization.
Now it is possible to call throw_coin()
whenever one wishes to flip a coin.
Functions and procedures can receive parameters (arguments).
To illustrate how to create and use a procedure with an argument, one could define a procedure print_coin()
to write the face of the result based on a parameter storing the value of the face
.
extends Control
const HEAD = 0
const TAIL = 1
func throw_coin():
return randi() % 2
func print_coin(face):
if (face == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
func _ready():
randomize()
print_coin(throw_coin())
print("End")
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function throw_coin() {
return random_integer(0, 1)
}
function print_coin(face) {
if (face === HEAD) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
}
print_coin(throw_coin())
console.log("End")
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def throw_coin():
return random.randint(0, 1)
def print_coin(face):
if (face == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
random.seed()
print_coin(throw_coin())
print("End")
local HEAD = 0
local TAIL = 1
function throw_coin()
return math.random(0, 1)
end
function print_coin(face)
if (face == HEAD) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
end
math.randomseed(os.time())
print_coin(throw_coin())
print("End")
The next images present a similar implementation in Flowgorithm, for visualization.
The function throw_coin()
has the same flowchart defined previously.
With this change, it is simple to understand what the program does from reading print_coin(throw_coin())
.
Assuming that the names of the subroutines are coherent with their implementations, the program prints to the console the result of flipping a coin.
Many programming languages define a main()
function as entry point (as defined in Entry Point and Program Structure) of the program.
Lua does not define one (the program starts at the first line of code from the script provided to the interpreter), though we could modify the code to create one.
extends Control
const HEAD = 0
const TAIL = 1
func throw_coin():
return randi() % 2
func print_coin(face):
if (face == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
func _ready():
randomize()
print_coin(throw_coin())
print("End")
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function throw_coin() {
return random_integer(0, 1)
}
function print_coin(face) {
if (face === HEAD) {
console.log(":) 🙂")
console.log("Heads")
} else {
console.log("$1 👑")
console.log("Tails")
}
}
function main() {
print_coin(throw_coin())
console.log("End")
}
main()
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def throw_coin():
return random.randint(0, 1)
def print_coin(face):
if (face == HEAD):
print(":) 🙂")
print("Heads")
else:
print("$1 👑")
print("Tails")
def main():
random.seed()
print_coin(throw_coin())
print("End")
if (__name__ == "__main__"):
main()
local HEAD = 0
local TAIL = 1
function throw_coin()
return math.random(0, 1)
end
function print_coin(face)
if (face == HEAD) then
print(":) 🙂")
print("Heads")
else
print("$1 👑")
print("Tails")
end
end
function main()
math.randomseed(os.time())
print_coin(throw_coin())
print("End")
end
main()
As main()
implementation from the example does not return a value, technically it would be a procedure instead of a function.
In many programming languages, main()
returns an integer value used as a code to inform whether the process ended successfully (or if it failed).
This is useful to create shell scripts.
To learn how to create programs for the command line, you can refer to Learn Programming: Command Line Input.
Subroutines for Coins and Dice in GDScript, JavaScript, Python and Lua
The previous steps can be performed for any modern programming language.
The next example implements throw_coin()
, throw_dice()
and print_coin()
in GDScript, JavaScript, Python and Lua, using random_integer()
as previously defined.
Grammar-wise, throw_die()
may be a better name than throw_dice()
, though.
extends Node
const HEAD = 0
const TAIL = 1
func random_integer(inclusive_minimum, inclusive_maximum):
var minimum = ceil(inclusive_minimum)
var maximum = floor(inclusive_maximum)
return randi() % int(maximum + 1 - minimum) + minimum
func random_float():
return randf()
func throw_coin():
return (random_integer(0, 1))
func throw_dice():
return (random_integer(1, 6))
func print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
func _ready():
randomize()
print_coin(throw_coin())
print(throw_dice())
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function random_float() {
return Math.random()
}
function throw_coin() {
return (random_integer(0, 1))
}
function throw_dice() {
return (random_integer(1, 6))
}
function print_coin(face) {
if (face === HEAD) {
console.log("Heads")
} else {
console.log("Tails")
}
}
function main() {
print_coin(throw_coin())
console.log(throw_dice())
}
main()
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def random_integer(inclusive_minimum, inclusive_maximum):
return random.randint(inclusive_minimum, inclusive_maximum)
def random_float():
return random.random()
def throw_coin():
return (random_integer(0, 1))
def throw_dice():
return (random_integer(1, 6))
def print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
def main():
random.seed()
print_coin(throw_coin())
print(throw_dice())
if (__name__ == "__main__"):
main()
local HEAD = 0
local TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum)
return math.random(inclusive_minimum, inclusive_maximum)
end
function random_float()
return math.random()
end
function throw_coin()
return (random_integer(0, 1))
end
function throw_dice()
return (random_integer(1, 6))
end
function print_coin(face)
if (face == HEAD) then
print("Heads")
else
print("Tails")
end
end
function main()
math.randomseed(os.time())
print_coin(throw_coin())
print(throw_dice())
end
main()
In the implementations, throw_dice()
draws a value ranging from 1 to 6 to represent the resulting face.
Subroutines: Parameters with Default Value
For a more sophisticated die, the number of faces could be a parameter. Thus, it could draw a value between 1 and the desired number of faces.
In particular, it is also possible to define a default value for a parameter, as it has been commented in Learn Programming: Records.
If the parameter is omitted on the call, the implementation uses the predefined default value.
For instance, throw_dice()
could use 6 as the default value.
On the other hand, a call with a parameter such as throw_dice(20)
would draw a value between 1 and 20.
extends Node
const HEAD = 0
const TAIL = 1
func random_integer(inclusive_minimum, inclusive_maximum):
var minimum = ceil(inclusive_minimum)
var maximum = floor(inclusive_maximum)
return randi() % int(maximum + 1 - minimum) + minimum
func random_float():
return randf()
func throw_coin():
return (random_integer(0, 1))
func throw_dice(faces = 6):
return (random_integer(1, faces))
func print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
func _ready():
randomize()
print_coin(throw_coin())
print(throw_dice())
print(throw_dice(20))
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function random_float() {
return Math.random()
}
function throw_coin() {
return (random_integer(0, 1))
}
function throw_dice(faces = 6) {
return (random_integer(1, faces))
}
function print_coin(face) {
if (face === HEAD) {
console.log("Heads")
} else {
console.log("Tails")
}
}
function main() {
print_coin(throw_coin())
console.log(throw_dice())
console.log(throw_dice(20))
}
main()
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def random_integer(inclusive_minimum, inclusive_maximum):
return random.randint(inclusive_minimum, inclusive_maximum)
def random_float():
return random.random()
def throw_coin():
return (random_integer(0, 1))
def throw_dice(faces = 6):
return (random_integer(1, faces))
def print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
def main():
random.seed()
print_coin(throw_coin())
print(throw_dice())
print(throw_dice(20))
if (__name__ == "__main__"):
main()
local HEAD = 0
local TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum)
return math.random(inclusive_minimum, inclusive_maximum)
end
function random_float()
return math.random()
end
function throw_coin()
return (random_integer(0, 1))
end
function throw_dice(faces)
faces = faces or 6
return (random_integer(1, faces))
end
function print_coin(face)
if (face == HEAD) then
print("Heads")
else
print("Tails")
end
end
function main()
math.randomseed(os.time())
print_coin(throw_coin())
print(throw_dice())
print(throw_dice(20))
end
main()
As Lua does not allow defining a default value at the signature of the subroutine, the use of an or
allows changing the value if it is omitted at the call.
The received value would be nil
; as nil
in an or
results in false
, the expression will receive the value defined in the beginning of the implementation (assuming no other value is provided).
Loops: Throwing Coins and Dice Thousands of Times
Finally, to throw coins and dice thousands of times, Repetition Structures (or Loops) can be used. In particular, one could use an accumulator to count results of interest.
The next examples count the number of occurrences of heads and tails after flipping a coin 10000 times.
extends Node
const HEAD = 0
const TAIL = 1
func random_integer(inclusive_minimum, inclusive_maximum):
var minimum = ceil(inclusive_minimum)
var maximum = floor(inclusive_maximum)
return randi() % int(maximum + 1 - minimum) + minimum
func random_float():
return randf()
func throw_coin():
return (random_integer(0, 1))
func throw_dice(faces = 6):
return (random_integer(1, faces))
func print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
func _ready():
randomize()
var heads_count = 0
var tails_count = 0
for i in range(10000):
if (throw_coin() == HEAD):
heads_count += 1
else:
tails_count += 1
print("Total Heads: " + str(heads_count))
print("Total Tails: " + str(tails_count))
const HEAD = 0
const TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function random_float() {
return Math.random()
}
function throw_coin() {
return (random_integer(0, 1))
}
function throw_dice(faces = 6) {
return (random_integer(1, faces))
}
function print_coin(face) {
if (face === HEAD) {
console.log("Heads")
} else {
console.log("Tails")
}
}
function main() {
let heads_count = 0
var tails_count = 0
for (let i = 0; i < 10000; ++i) {
if (throw_coin() === HEAD) {
++heads_count
} else {
++tails_count
}
}
console.log("Total Heads: " + heads_count)
console.log("Total Tails: " + tails_count)
}
main()
import random
from typing import Final
HEAD: Final = 0
TAIL: Final = 1
def random_integer(inclusive_minimum, inclusive_maximum):
return random.randint(inclusive_minimum, inclusive_maximum)
def random_float():
return random.random()
def throw_coin():
return (random_integer(0, 1))
def throw_dice(faces = 6):
return (random_integer(1, faces))
def print_coin(face):
if (face == HEAD):
print("Heads")
else:
print("Tails")
def main():
random.seed()
heads_count = 0
tails_count = 0
for i in range(10000):
if (throw_coin() == HEAD):
heads_count += 1
else:
tails_count += 1
print("Total Heads: " + str(heads_count))
print("Total Tails: " + str(tails_count))
if (__name__ == "__main__"):
main()
local HEAD = 0
local TAIL = 1
function random_integer(inclusive_minimum, inclusive_maximum)
return math.random(inclusive_minimum, inclusive_maximum)
end
function random_float()
return math.random()
end
function throw_coin()
return (random_integer(0, 1))
end
function throw_dice(faces)
faces = faces or 6
return (random_integer(1, faces))
end
function print_coin(face)
if (face == HEAD) then
print("Heads")
else
print("Tails")
end
end
function main()
math.randomseed(os.time())
local heads_count = 0
local tails_count = 0
for i = 1, 10000 do
if (throw_coin() == HEAD) then
heads_count = heads_count + 1
else
tails_count = tails_count + 1
end
end
print("Total Heads: " .. heads_count)
print("Total Tails: " .. tails_count)
end
main()
Run the code of the project a few times and check the results. The values of heads and tails should be close in most cases.
For an better approach, you can add a repetition structure to run the simulation several times. The next code blocks suggest how to update the entry point to illustrate the approach.
func _ready():
randomize()
for j in range(10):
var heads_count = 0
var tails_count = 0
for i in range(10000):
if (throw_coin() == HEAD):
heads_count += 1
else:
tails_count += 1
print("Total Heads: " + str(heads_count))
print("Total Tails: " + str(tails_count))
print()
function main() {
for (let j = 0; j < 10; ++j) {
let heads_count = 0
let tails_count = 0
for (let i = 0; i < 10000; ++i) {
if (throw_coin() === HEAD) {
++heads_count
} else {
++tails_count
}
}
console.log("Total Heads: " + heads_count)
console.log("Total Tails: " + tails_count)
console.log()
}
}
def main():
random.seed()
for j in range(10):
heads_count = 0
tails_count = 0
for i in range(10000):
if (throw_coin() == HEAD):
heads_count += 1
else:
tails_count += 1
print("Total Heads: " + str(heads_count))
print("Total Tails: " + str(tails_count))
print()
function main()
math.randomseed(os.time())
for j = 1, 10 do
local heads_count = 0
local tails_count = 0
for i = 1, 10000 do
if (throw_coin() == HEAD) then
heads_count = heads_count + 1
else
tails_count = tails_count + 1
end
end
print("Total Heads: " .. heads_count)
print("Total Tails: " .. tails_count)
print()
end
end
In the future, we will repeat simulations hundreds or thousands of times to create Monte Carlo Simulations. In this topic, the repetition are only a curiosity to practice using repetition structures.
Drawing Primitives: Squares and Rectangles
To draw a coin, one could draw a circle or an arc with a number inside it, representing the resulting face. Analogously, the drawing of a die could use a square with circles or a number inside it.
Besides Points, Lines, and Arcs (that we already know how to draw), drawing primitives typically include rectangles and squares. Before using them, it can be worth implementing our own to understand how they can be implemented.
HTML Canvas for JavaScript
As in the Introduction of Ideas, Rules, Simulation, JavaScript requires an auxiliary HTML file to declare the canvas.
You can choose the name of the HTML file (for instance, index.html
).
The follow example assumes that the JavaScript fill is named script.js
and the canvas will have the identifier (id
) canvas
.
If you change the values, remember to modify them in the files as needed.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>www.FrancoGarcia.com</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div style="text-align: center;">
<canvas id="canvas"
width="1"
height="1"
style="border: 1px solid #000000;">
>
Accessibility: alternative text for graphical content.
</canvas>
</div>
<!-- NOTE Updated with the name of the JavaScript file. -->
<script src="./script.js"></script>
</body>
</html>
The file also keeps the reminder about accessibility. In this topic, the content will become even more inaccessible for people with certain vision disabilities, due to the addition of graphical content without alternatives to convey the contents.
In the future, the intention is discussing ways to make simulations more accessible. At this time, this reminder is only informative, to raise awareness of the importance of accessibility.
Drawing Squares and Rectangles
It is simple to draw a rectangle or a square when one already knows how to draw pixels: it suffices to repeat drawing lines in adjacent pixels. Another way to understand the idea consists of thinking about a line as a rectangle on which one of the measurements (width or height) is equal to 1 pixel.
Thus, to draw a colored rectangle, it is sufficient to use a repetition structure that draws a horizontal line for each pixel of the desired height.
The procedure franco_draw_rectangle()
implements this approach.
To draw a square, one can draw a rectangle with equal values to the width and height, as performed in franco_draw_square()
.
# Root must be a Node that allows drawing using _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
func franco_draw_rectangle(x, y, width, height, color):
var end_x = x + width
var end_y = y + height
for current_y in range(y, end_y):
franco_draw_line(x, current_y,
end_x, current_y,
color)
func franco_draw_square(x, y, side, color):
franco_draw_rectangle(x, y, side, side, color)
func _ready():
randomize()
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_rectangle(x, y, width, height, color) {
let end_x = x + width
let end_y = y + height
for (let current_y = y; current_y < end_y; ++current_y) {
franco_draw_line(x, current_y,
end_x, current_y,
color)
}
}
function franco_draw_square(x, y, side, color) {
franco_draw_rectangle(x, y, side, side, color)
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
}
function main() {
document.title = "Olá, meu nome é Franco!"
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import pygame
import math
import random
import sys
from pygame import gfxdraw
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Olá, meu nome é Franco!")
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_rectangle(x, y, width, height, color):
end_x = x + width
end_y = y + height
for current_y in range(y, end_y):
franco_draw_line(x, current_y,
end_x, current_y,
color)
def franco_draw_square(x, y, side, color):
franco_draw_rectangle(x, y, side, side, color)
def main():
random.seed()
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
function franco_draw_rectangle(x, y, width, height, color)
local end_x = x + width
local end_y = y + height
for current_y = y, end_y - 1 do
franco_draw_line(x, current_y,
end_x, current_y,
color)
end
end
function franco_draw_square(x, y, side, color)
franco_draw_rectangle(x, y, side, side, color)
end
function love.load()
math.randomseed(os.time())
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
end
The resulting drawing is shown in the next canvas
.
If one wished to only draw the stroke of a rectangle, she/he could draw the four lines that delimit the shape.
Drawing Primitives for Squares and Rectangles
As it has happened in Ideas, Rules, Simulation: Pixels and Drawing Primitives (Points, Lines, and Arcs), drawing APIs typically provide subroutines to draw rectangles. In fact:
- Godot provides
draw_rect()
; - JavaScript with
canvas
providesfillRect()
(which has been used previously to draw the background color); - Python with PyGame has
pygame.draw.rect()
; - Lua with LÖVE defines
love.graphics.rectangle()
.
On the other hand, as a square is a rectangle, not every API provides a subroutine to draw a square.
Thus, franco_draw_square()
will draw a square using the primitive to draw rectangles.
It would also be possible to keep the previous implementation, using franco_draw_rectangle()
on the implementation of franco_draw_square()
to draw a square.
# Root must be a Node that allows drawing using _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
func franco_draw_rectangle(x, y, width, height, color):
draw_rect(Rect2(x, y, width, height), color, true)
func franco_draw_square(x, y, side, color):
draw_rect(Rect2(x, y, side, side), color)
func _ready():
randomize()
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
function franco_draw_rectangle(x, y, width, height, color) {
context.fillStyle = color
context.fillRect(x, y, width, height)
}
function franco_draw_square(x, y, side, color) {
context.fillStyle = color
context.fillRect(x, y, side, side)
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
}
function main() {
document.title = "Olá, meu nome é Franco!"
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import pygame
import math
import random
import sys
from pygame import gfxdraw
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Olá, meu nome é Franco!")
def franco_draw_rectangle(x, y, width, height, color):
pygame.draw.rect(window, color, (x, y, width, height))
def franco_draw_square(x, y, side, color):
pygame.draw.rect(window, color, (x, y, side, side))
def main():
random.seed()
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
function franco_draw_rectangle(x, y, width, height, color)
love.graphics.setColor(color)
love.graphics.rectangle("fill", x, y, width, height)
end
function franco_draw_square(x, y, side, color)
love.graphics.setColor(color)
love.graphics.rectangle("fill", x, y, side, side)
end
function love.load()
math.randomseed(os.time())
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_rectangle(10, 20, 80, 40, WHITE)
franco_draw_rectangle(10, 100, 50, 50, WHITE)
franco_draw_square(100, 100, 50, WHITE)
end
The resulting drawing is shown in the next canvas
.
Thus, the current inventory of drawing primitives include pixels (points), lines, arcs, rectangles and square. In other words, we already have everything that is needed for this topic.
Drawing Coins
It is time to use your artistic skills to draw the results of a coin flip. It is likely that your artistic skills are enough to create a better drawing than the author's one.
In the next example, the goal is drawing the results of throwing a coin (in other words, head or tail) inside an arc. To do this, we can combine parts of the code of previous examples to simulate flipping a coin, then generate a drawing for heads or tails. The drawing will replace the text and the emoji in the console with simple graphics on a window.
It is important to generate a single value at a time, to ensure that the result is not changed when redrawing the window.
In the code, the result of the random drawing is stored in result
.
The longest part of the implementation is the code to draw each face of the coin.
The use of values as a function (as a proportion) of WIDTH
and HEIGHT
allows resizing the drawing if the value of the constants is changed.
To improve the code, the values could be updated when resizing the window manually when the project is running.
For a simpler version, one could keep a fixed size for the window and use specific values for the drawings.
# Root must be a Node that allows drawing using _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const HEAD = 0
const TAIL = 1
var result = null
func random_integer(inclusive_minimum, inclusive_maximum):
var minimum = ceil(inclusive_minimum)
var maximum = floor(inclusive_maximum)
return randi() % int(maximum + 1 - minimum) + minimum
func franco_draw_line(x0, y0, x1, y1, color):
draw_line(Vector2(x0, y0), Vector2(x1, y1), color)
const POINT_COUNT = 100
func franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color):
draw_arc(Vector2(center_x, center_y),
radius,
start_angle, end_angle,
POINT_COUNT,
color)
func throw_coin():
return (random_integer(0, 1))
func _ready():
randomize()
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
result = throw_coin()
func _draw():
VisualServer.set_default_clear_color(BLACK)
if (result == HEAD):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * PI, WHITE)
franco_draw_arc(0.6 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, WHITE)
franco_draw_arc(0.4 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, WHITE)
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.20 * HEIGHT, 0, PI, WHITE)
else:
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * PI, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.60 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.40 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.35 * HEIGHT, 0.45 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.45 * WIDTH, 0.50 * HEIGHT, 0.50 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.50 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const HEAD = 0
const TAIL = 1
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function franco_draw_line(x0, y0, x1, y1, color) {
context.strokeStyle = color
// context.fillStyle = color
context.beginPath()
context.moveTo(x0, y0)
context.lineTo(x1, y1)
context.closePath()
context.stroke()
}
function franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color) {
context.strokeStyle = color
context.beginPath()
context.arc(center_x, center_y,
radius,
start_angle, end_angle)
context.stroke()
context.closePath()
}
function throw_coin() {
return (random_integer(0, 1))
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
let result = throw_coin()
if (result === HEAD) {
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * Math.PI, WHITE)
franco_draw_arc(0.6 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, WHITE)
franco_draw_arc(0.4 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, WHITE)
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.20 * HEIGHT, 0, Math.PI, WHITE)
} else {
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * Math.PI, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.60 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.40 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.35 * HEIGHT, 0.45 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.45 * WIDTH, 0.50 * HEIGHT, 0.50 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.50 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
}
}
function main() {
document.title = "Olá, meu nome é Franco!"
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import pygame
import math
import random
import sys
from pygame import gfxdraw
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)
HEAD: Final = 0
TAIL: Final = 1
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Olá, meu nome é Franco!")
def random_integer(inclusive_minimum, inclusive_maximum):
return random.randint(inclusive_minimum, inclusive_maximum)
def franco_draw_line(x0, y0, x1, y1, color):
pygame.draw.line(window, color, (x0, y0), (x1, y1))
def franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color):
pygame.gfxdraw.arc(window,
int(center_x), int(center_y),
int(radius),
int(math.degrees(start_angle)), int(math.degrees(end_angle)),
color)
def throw_coin():
return (random_integer(0, 1))
def main():
random.seed()
result = throw_coin()
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
if (result == HEAD):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 1.999 * math.pi, WHITE)
franco_draw_arc(0.6 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, WHITE)
franco_draw_arc(0.4 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, WHITE)
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.20 * HEIGHT, 0, math.pi, WHITE)
else:
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 1.999 * math.pi, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.60 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.40 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.35 * HEIGHT, 0.45 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.45 * WIDTH, 0.50 * HEIGHT, 0.50 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.50 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
pygame.display.flip()
if (__name__ == "__main__"):
main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local HEAD = 0
local TAIL = 1
local result
function random_integer(inclusive_minimum, inclusive_maximum)
return math.random(inclusive_minimum, inclusive_maximum)
end
function franco_draw_line(x0, y0, x1, y1, color)
love.graphics.setColor(color)
love.graphics.line(x0, y0, x1, y1)
end
local POINT_COUNT = 100
function franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color)
love.graphics.setColor(color)
love.graphics.arc("line", "open",
center_x, center_y,
radius,
start_angle, end_angle,
POINT_COUNT)
end
function throw_coin()
return (random_integer(0, 1))
end
function love.load()
math.randomseed(os.time())
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
result = throw_coin()
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
if (result == HEAD) then
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * math.pi, WHITE)
franco_draw_arc(0.6 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, WHITE)
franco_draw_arc(0.4 * WIDTH, 0.4 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, WHITE)
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.20 * HEIGHT, 0, math.pi, WHITE)
else
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.33 * HEIGHT, 0, 2 * math.pi, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.60 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.60 * HEIGHT, 0.40 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.40 * WIDTH, 0.35 * HEIGHT, 0.45 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.45 * WIDTH, 0.50 * HEIGHT, 0.50 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.60 * HEIGHT, 0.60 * WIDTH, 0.35 * HEIGHT, WHITE)
franco_draw_line(0.60 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
franco_draw_line(0.50 * WIDTH, 0.35 * HEIGHT, 0.55 * WIDTH, 0.50 * HEIGHT, WHITE)
end
end
A possible result of the drawing is displayed on the next canvas
.
The example implemented for the canvas canvas
adds an animation updated every second.
Thus, if you wait some time, you should see both results.
The time of the transition between the images can vary, because it uses random results for the coin.
In all versions, the drawing of the external circle could be moved before the conditional structure, because it is performed for both cases. You can do that change to verify the statement.
The Python version with PyGame approximates 2 * math.pi
due to a limitation of pygame.gfxdraw.arc()
commented in Pixels and Drawing Primitives (Points, Lines, and Arcs).
After we introduce a subroutine to draw circles, we will be able to use it instead of drawing arcs.
Drawing Dice
A drawing for a die can use a square and a rectangle for the outlines. For the number, one can either draw the value using arcs or circles, or write it as text.
The next example draws the value with arcs (forming circles). The code draws the number of circles corresponding to the random number that was drawn. Odd values draw a circle at the central position of the window. Values that are larger than 1 distribute circles in two (even values) or three columns (odd values).
# Root must be a Node that allows drawing using _draw().
extends Control
const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
var result = null
func random_integer(inclusive_minimum, inclusive_maximum):
var minimum = ceil(inclusive_minimum)
var maximum = floor(inclusive_maximum)
return randi() % int(maximum + 1 - minimum) + minimum
func franco_draw_rectangle(x, y, width, height, color):
draw_rect(Rect2(x, y, width, height), color, true)
const POINT_COUNT = 100
func franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color):
draw_arc(Vector2(center_x, center_y),
radius,
start_angle, end_angle,
POINT_COUNT,
color)
func throw_dice(faces = 6):
return (random_integer(1, faces))
func _ready():
randomize()
OS.set_window_size(Vector2(WIDTH, HEIGHT))
OS.set_window_title("Olá, meu nome é Franco!")
result = throw_dice(6)
func _draw():
VisualServer.set_default_clear_color(BLACK)
franco_draw_rectangle(0.1 * WIDTH, 0.1 * HEIGHT, 0.8 * WIDTH, 0.8 * HEIGHT, WHITE)
if (result == 1):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
elif (result == 2):
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
elif (result == 3):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
elif (result == 4):
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
elif (result == 5):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
else:
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * PI, BLACK)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
function random_integer(inclusive_minimum, inclusive_maximum) {
let minimum = Math.ceil(inclusive_minimum)
let maximum = Math.floor(inclusive_maximum)
return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}
function franco_draw_rectangle(x, y, width, height, color) {
context.fillStyle = color
context.fillRect(x, y, width, height)
}
function franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color) {
context.strokeStyle = color
context.beginPath()
context.arc(center_x, center_y,
radius,
start_angle, end_angle)
context.stroke()
context.closePath()
}
function throw_dice(faces = 6) {
return (random_integer(1, faces))
}
function draw() {
context.clearRect(0, 0, WIDTH, HEIGHT)
context.fillStyle = BLACK
context.fillRect(0, 0, WIDTH, HEIGHT)
let result = throw_dice(6)
franco_draw_rectangle(0.1 * WIDTH, 0.1 * HEIGHT, 0.8 * WIDTH, 0.8 * HEIGHT, WHITE)
if (result == 1) {
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
} else if (result == 2) {
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
} else if (result == 3) {
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
} else if (result == 4) {
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
} else if (result == 5) {
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
} else {
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * Math.PI, BLACK)
}
}
function main() {
document.title = "Olá, meu nome é Franco!"
canvas.width = WIDTH
canvas.height = HEIGHT
draw()
}
main()
import pygame
import math
import random
import sys
from pygame import gfxdraw
from typing import Final
WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Olá, meu nome é Franco!")
def random_integer(inclusive_minimum, inclusive_maximum):
return random.randint(inclusive_minimum, inclusive_maximum)
def franco_draw_rectangle(x, y, width, height, color):
pygame.draw.rect(window, color, (x, y, width, height))
def franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color):
pygame.gfxdraw.arc(window,
int(center_x), int(center_y),
int(radius),
int(math.degrees(start_angle)), int(math.degrees(end_angle)),
color)
def throw_dice(faces = 6):
return (random_integer(1, faces))
def main():
random.seed()
result = throw_dice(6)
while (True):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
pygame.quit()
sys.exit(0)
window.fill(BLACK)
franco_draw_rectangle(0.1 * WIDTH, 0.1 * HEIGHT, 0.8 * WIDTH, 0.8 * HEIGHT, WHITE)
if (result == 1):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
elif (result == 2):
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
elif (result == 3):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
elif (result == 4):
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
elif (result == 5):
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
else:
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 1.999 * math.pi, BLACK)
pygame.display.flip()
if (__name__ == "__main__"):
main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local HEAD = 0
local TAIL = 1
local result
function random_integer(inclusive_minimum, inclusive_maximum)
return math.random(inclusive_minimum, inclusive_maximum)
end
function franco_draw_rectangle(x, y, width, height, color)
love.graphics.setColor(color)
love.graphics.rectangle("fill", x, y, width, height)
end
local POINT_COUNT = 100
function franco_draw_arc(center_x, center_y, radius, start_angle, end_angle, color)
love.graphics.setColor(color)
love.graphics.arc("line", "open",
center_x, center_y,
radius,
start_angle, end_angle,
POINT_COUNT)
end
function throw_dice(faces)
faces = faces or 6
return (random_integer(1, faces))
end
function love.load()
math.randomseed(os.time())
love.window.setMode(WIDTH, HEIGHT)
love.window.setTitle("Olá, meu nome é Franco!")
result = throw_dice(6)
end
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_rectangle(0.1 * WIDTH, 0.1 * HEIGHT, 0.8 * WIDTH, 0.8 * HEIGHT, WHITE)
if (result == 1) then
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
elseif (result == 2) then
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
elseif (result == 3) then
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
elseif (result == 4) then
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
elseif (result == 5) then
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
else
franco_draw_arc(0.3 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.3 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(0.7 * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
end
end
A possible result of the drawing is displayed on the next canvas
.
The canvas
draws values every second to show different results over time.
Except in the case of a generator with a degenerated distribution, or if one knows the seed and the algorithm to generate the numbers, the next value will be a surprise.
It will be between 1 and 6, though what will the value be?
The implementation of the example is quite simple. In fact, it contains duplicated code, that could be removed with a more elegant solution.
The solution would also be more difficult to read and understand. To avoid introducing unnecessary complexity in these first topics of Ideas, Rules, Simulation, the author will keep the duplicated lines in the code of the example. The most concise solution is not always the most readable one.
If you wish to practice and remove duplicated lines, there are different approaches to change the code. For instance, you could draw arcs at every position depending on the number, or use a repetition structure to increment offsets to move between each arc.
Show/Hide a Possible Solution in Lua
function love.draw()
love.graphics.setBackgroundColor(BLACK)
franco_draw_rectangle(0.1 * WIDTH, 0.1 * HEIGHT, 0.8 * WIDTH, 0.8 * HEIGHT, WHITE)
if ((result == 1) or (result == 3) or (result == 5)) then
franco_draw_arc(0.5 * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
end
if ((result == 2) or (result == 3) or (result == 6)) then
for i = 0.3, 0.7, 0.4 do
franco_draw_arc(i * WIDTH, 0.5 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
end
end
if ((result == 4) or (result == 5) or (result == 6)) then
for i = 0.3, 0.7, 0.4 do
franco_draw_arc(i * WIDTH, 0.3 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
franco_draw_arc(i * WIDTH, 0.7 * HEIGHT, 0.05 * HEIGHT, 0, 2 * math.pi, BLACK)
end
end
end
A more advanced solution could map the values in a 3x3 matrix, drawing values marked to be drawn on the matrix. This solution would be simpler to read and understand, though data structure will be a future topic in Ideas, Rules, Simulation. If you have already studied Learn Programming: Arrays, Strings, Collections and Data Structures, you can try to implement it.
Concepts Covered From Learn Programming
Concepts from Learn Programming covered in this topic:
- Entry point;
- Output;
- Data types;
- Variables and constants;
- Arithmetic;
- Relational operations and comparisons;
- Logic operations;
- Conditional structures;
- Subroutines (functions and procedures);
- Repetition structures (or loops);
- Libraries.
It is worth noticing that even drawing primitives can use almost every basic programming concept.
Once again, the values for colors in Lua used tables (dictionaries) as array, described in Collections.
New Items for Your Inventory
Computational Thinking Abilities:
Tools:
- List of emojis.
Skills:
- Prototyping simulations using a version for a console (terminal);
- Drawing rectangles using repetition structures;
- Drawing rectangles using drawing primitives;
- Drawings using proportional sizes, exploring the window's width and height.
Concepts:
- Drawing primitives: rectangles;
- Drawing primitives: squares;
Programming resources:
- Drawing rectangles and squares.
Practice
To learn programming, deliberate practice must follow the concepts. Try doing the next exercises to practice.
Change the example of the die to use 8 faces instead of 6. Update the drawing code to support the new results.
Instead of drawing values, write the value as text message inside the circle (coin) or square (die).
Depending on the programming language, you will need to convert the drawn integer number to a string. To learn how to do that, refer to Learn Programming: Data Types.
To draw the results as text, refer to Ideas, Rules, Simulation: Introduction (Window and Hello, world!).
Why should you create drawings using values of width and height instead of specific sizes? What are the advantages of such approach? What are the disadvantages of the approach?
If the implementation of the examples use sizes based on the window's height and width, why does not the drawing update automatically when the window is resized while the program is running?
Tip. The value of the constants is never modified. To consider new values, they would need to be variables that were changed at every modification of the window's dimensions.
Deepening
In Ideas, Rules, Simulation, this section provides complementary content.
Emojis
An emoji é encoded as a character. This is the reason that it was declared as text as part of a string in the example of flipping a coin in the console (terminal) using Lua.
The encoding used for text that supports emojis is called Unicode. It is also used to encode text with accents, as well as other special characters and characters from hundreds of (human) languages. On computer, text is stored as numbers (binary numbers, to be more exact). To learn more about how computers store text, you can refer to Learn Programming: Data Types.
To discover the code of an emoji, you can access this official list.
To create an accessible emoji in HTML for Web content, such as this 👑, it is necessary to use a special tag with a textual description.
<span role="img" aria-label="Emoji of a crown">👑</span>
This way, people who use technologies such as screen readers will be able to understand the content. For instance, this is important for blind people.
Unfortunately, this technique cannot be applied to other programming languages. We shall consider accessibility for simulations on future topics of Ideas, Rules, Simulation.
Next Steps
To program, it is fundamental to dominate computational thinking skills. The material of Learn Programming argues that programming is solving problems. Programming languages are mere tools to implement your solution.
Thus, although this topic could be shorter, the author has chosen to detailed the process of how to write a computer program. The purpose is helping you to become more independent and able to solve problems using computers. After all, the goal is allowing you to create your own systems, applications (apps), and simulations in the future.
To create a simulation, we have converted an idea to computer code by implementing rules to process data. This is achieved by using abstractions for data decomposition (for instance, using variables or records) and functional decomposition (for instance, using subroutines such as functions and procedures).
This time, instead of creating a chaotic drawing, we have used the result from a random draw to illustrate the obtained result. We have abstracted the random draw two values as a coin, and of six values as a die.
However, the drawings would become more appealing if they were colored (instead of being simple strokes). For a better presentation, it would be worth filling the strokes of the arcs with a color to truly draw a circle.
Tools to draw images typically provide a feature represented by a bucket of paint, that fills a region with a color. We can implement it as an algorithm.
Thus, the next topic introduces some additional drawing primitives -- this time, to draw circles, ellipses, and polygons. Both as strokes and as filled shapes.
Furthermore, if you have created a creative or interesting illustration, consider sharing it. Alternatively, if you have found this material useful, you can also share it. If possible, use the hashtags #IdeasRulesSimulation and #FrancoGarciaCom.
I thank you for your attention. See you soon!
Ideas, Rules, Simulation
- Motivation;
- Introduction: Window and Hello World;
- Pixels and drawing primitives (points, lines, and arcs);
- Randomness and noise;
- Coins and dice, rectangles and squares;
- Drawing with drawing primitives (strokes and fillings for circles, ellipses and polygons);
- Saving and loading image files;
- ...
This material is a work in progress; therefore, if you have arrived early and the previous items do not have links, please return to this page to check out updates.
If you wish to contact me or have any questions, you can chat with me by:
- E-mail: francogarcia@protonmail.com
- GitHub: francogarcia
- GitLab: francogarcia
- Bitbucket: francogarcia
- YouTube: channel UCxbFFDZ4BmnT-Mhm8z1JsOA
- Instagram: @francogarciacom
- Twitter: @francogarciacom
Information about contact and social networks are also available at the footer of every page.
Your opinion about the series will be fundamental to enable me to create a material that is more accessible and simpler for more people.