Gaming Introduction
Video games have been an important part of computer systems since their early days. We will start by making the simplest possible video game.
Prerequisite
You must have a basic understanding of coding and have completed the Drawing lesson.
Back to the Basics
Python, C# and JavaScript can be used to build powerful and modern games, thanks to modern graphical frameworks and libraries, like DirectX. The problem with learning how to build games in the modern world is that all the basics are missed. Things forgotten are things like the mathematics needed to draw a shape like a circle and understanding pixels and how they are handled.
Our goal here is not to build a modern 3D game. The goal is to show how Pong was made in the 70s and how games are made on a foundation level. We will focus on using the small screen on the hardware.
Game Objective
The player will need to catch falling eggs in a basket. For simplicity, the eggs are just a circle and the basket is just a rectangle.
Basket Drawing
The player, which is a basket in this case, is just a DrawLine(), or we can use DrawFillRect() to make a thicker line.
Python
BrainPad.Display.Clear(0)
BrainPad.Display.DrawFillRect(1,10,60,20,3)
BrainPad.Display.Show()
C#
BrainPad.Display.Clear(0);
BrainPad.Display.DrawFillRect(1,10,60,20,3);
BrainPad.Display.Show();
JS
await BrainPad.Display.Clear(0);
await BrainPad.Display.DrawFillRect(1,10,60,20,3);
await BrainPad.Display.Show();
Basket Control
Buttons A and B will be used to control the player (basket). Instead of using the BtnDown() function, we will use DRead(). This is because we will need to know if the button is constantly down versus reading a button click. The B variable will be used for the basket’s position. We can create a subroutine that processes the basket.
In Python, we need to point out which variables we’ll be using inside the function we create so that it does not create new variables. So, we add the keyword ‘global‘. This ensures that we’re using the original variable we created at the beginning of our code.
Python
basketX = 10
def ProcessBasket():
global basketX
if (BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)) == 0:
basketX = basketX - 6
if (BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)) == 0:
basketX = basketX + 6
C#
var basketX = 10;
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)){
basketX = basketX + 6;
}
}
JS
var basketX = 10;
async function ProcessBasket() {
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp)) {
basketX = basketX - 6;
}
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonB, BrainPad.Pin.PullUp)) {
basketX = basketX + 6;
}
};
With the basket process in place, we can now create the game loop, to process and draw the “basket”.
Python
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.Show()
ProcessBasket()
C#
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
}
JS
while (true) {
await BrainPad.Display.Clear(0);
await BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
await BrainPad.Display.Show();
await ProcessBasket()
}
The entire code listing is here in case you have missed something.
Python
basketX = 10
def ProcessBasket():
global basketX
if (BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)) == 0:
basketX = basketX - 6
if (BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)) == 0:
basketX = basketX + 6
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.Show()
ProcessBasket()
C#
var basketX = 10;
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)){
basketX = basketX + 6;
}
}
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
}
JS
var basketX = 10;
async function ProcessBasket() {
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp)) {
basketX = basketX - 6;
}
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonB, BrainPad.Pin.PullUp)) {
basketX = basketX + 6;
}
};
while (true) {
await BrainPad.Display.Clear(0);
await BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
await BrainPad.Display.Show();
await ProcessBasket()
}
Try what we have so far. Did you notice how the basket can leave the screen if you keep pushing one of the buttons? Any ideas on how we can keep the basket from leaving the screen?
Stay on Screen
Keeping the basket on the screen is somewhat easy. We will check the basket’s position and only change its position if the buttons are pressed and also if the basket is not leaving the screen. Notice how we used “and” in the previous sentence. The code will also be exactly just that.
Can you see why we did 0 & 110?
Python
def ProcessBasket():
global basketX
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)==0) and basketX > 0):
basketX = basketX - 6
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)==False) and basketX < 110):
basketX = basketX + 6
C#
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp) && basketX > 0){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp) && basketX < 110){
basketX = basketX + 6;
}
}
JS
async function ProcessBasket() {
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp) && basketX > 0) {
basketX = basketX - 6;
}
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonB, BrainPad.Pin.PullUp) && basketX < 110) {
basketX = basketX + 6;
}
};
Falling Eggs
We’ll use a DrawCircle() function inside our while true loop and add egg variables eggY & eggX at the top of our code to track the egg’s x and y positions on the screen.
Python
eggY = 0
eggX = 20
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawCircle(1,eggX,eggY,3)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.Show()
ProcessBasket()
C#
var eggY = 0;
var eggX = 20;
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
}
JS
var eggY = 0;
var eggX = 20;
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
}
The eggs will always start at the top of the screen and fall. The “down movement” means the Y-axis location will increase. Remember, the top of the screen is 0. The eggY variable will keep track of the egg’s position as it’s falling.
Let’s create a function called ProcessEgg() to handle our egg. Inside the function let’s increment the eggY variable by +3. This will make our egg fall by 3 pixels every time the function is called.
Python
def ProcessEgg():
global eggY
eggY = eggY + 3
C#
void ProcessEgg(){
eggY = eggY + 3;
}
JS
async function ProcessEgg(){
eggY = eggY + 3;
}
Also we want to add some code to see when the egg reaches the bottom. We’ll use an if-statement to check and see if eggY is greater than 64. That means it has left the bottom of the screen. Once it does will can send it back to the top by setting eggY variable back to = 0.
Python
def ProcessEgg():
global eggY
eggY = eggY + 3
if eggY > 64:
eggY = 0
C#
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
}
}
JS
async function ProcessEgg() {
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
}
}
For the X-axis position, we will use our eggX variable. Let’s just keep it set at 20 for now. Here is the full code listing.
Python
basketX = 10
eggY = 0
eggX = 20
def ProcessBasket():
global basketX
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)==0) and basketX > 0):
basketX = basketX - 6
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)==0) and basketX < 110):
basketX = basketX + 6
def ProcessEgg():
global eggY
eggY = eggY + 3
if eggY > 64:
eggY = 0
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawCircle(1,eggX,eggY,3)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.Show()
ProcessBasket()
ProcessEgg()
C#
var basketX = 10;
var eggY = 0;
var eggX = 20;
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp) && basketX > 0){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp) && basketX < 110){
basketX = basketX + 6;
}
}
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
}
}
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
ProcessEgg();
}
JS
var basketX = 10;
var eggY = 0;
var eggX = 20;
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp) && basketX > 0){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp) && basketX < 110){
basketX = basketX + 6;
}
}
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
}
}
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.Show();
ProcessBasket();
ProcessEgg();
}
That is starting to look like a game. Now we need to change where the egg falls from the top. We want it coming down from a random location. Thankfully, random is a built-in feature in most programming languages. Let’s create a variable to hold our random number, and place it near the top with our other variables. Inside our ProcessEgg() function we’ll generate a random number every time our egg leaves the bottom of the screen.
Python
import random
def ProcessEgg():
global eggY
global eggX
eggY = eggY + 3
if eggY > 64:
eggY = 0
eggX = random.randint(0,128)
C#
var rnd = new Random();
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
eggX = rnd.Next(127);
}
}
JS
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
async function ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
eggY = 0;
eggX = getRandomArbitrary(0,127);
}
}
Keeping Score
The basket is moving and the egg is falling but where is the score? This is done by comparing the position of the egg to the basket. We only need to do this when the egg reaches the bottom so we can use the if-statement inside our ProcessEgg() function to check.
Create a variable to hold our score set it to 0, and place it with our other variables at the top. Increase the score by one inside the if-statement that we use to check to see if our egg and basket are in the same spot.
Python
score = 0
def ProcessEgg():
global eggY
global eggX
global basketX
global score
eggY = eggY + 3
if eggY > 64:
# Check for collision
if eggX > basketX and eggX < basketX + 20:
score = score + 1
# new egg
eggY = 0
eggX = random.randint(0,120)
C#
var score = 0;
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20){
score = score + 1;
}
//New Egg
eggY = 0;
eggX = rnd.Next(120);
}
}
JS
var score = 0;
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20){
score = score + 1;
}
//New Egg
eggY = 0;
eggX = rnd.Next(120);
}
}
We finally can show off our score! Let’s place it in the top left corner.
Python
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawCircle(1,eggX,eggY,3)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.DrawTextScale(score,1,0,0,3,2)
BrainPad.Display.Show()
ProcessBasket()
ProcessEgg()
BrainPad.System.Wait(10)
C#
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.DrawText(score.ToString(), 1, 0, 0);
BrainPad.Display.Show();
ProcessBasket();
ProcessEgg();
BrainPad.System.Wait(10);
}
JS
while (true) {
await BrainPad.Display.Clear(0);
await BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
await BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
await BrainPad.Display.DrawText(score.toString(), 1, 0, 0);
await BrainPad.Display.Show();
await ProcessBasket();
await ProcessEgg();
}
Making Noise!
How about some tone when the egg is caught and a different tone when lost? We can use System.Beep() to generate simple tones.
Python
def ProcessEgg():
global eggY
global eggX
global basketX
global score
eggY = eggY + 3
if eggY > 64:
# Check for collision
if eggX > basketX and eggX < basketX + 20:
score = score + 1
BrainPad.System.Beep(BrainPad.Pin.Piezo,1000,100)
else:
BrainPad.System.Beep(BrainPad.Pin.Piezo,256,100)
# new egg
eggY = 0
eggX = random.randint(0,120)
C#
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20){
score = score + 1;
BrainPad.System.Beep(BrainPad.Pin.Piezo, 1000, 100);
}
else {
BrainPad.System.Beep(BrainPad.Pin.Piezo, 256, 100);
}
//New Egg
eggY = 0;
eggX = rnd.Next(127);
}
}
JS
async function ProcessEgg() {
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20) {
score = score + 1;
await BrainPad.System.Beep(BrainPad.Pin.Piezo, 1000, 100);
}
else {
await BrainPad.System.Beep(BrainPad.Pin.Piezo, 256, 100);
}
//New Egg
eggY = 0;
eggX = getRandomArbitrary(0, 127);
}
}
Final Results
A playable game with under 50 lines of code! Not bad considering commercial games are usually thousands or millions of lines of code. Here is the complete code with all the parts we’ve shown above:
Python
from DUELink.DUELinkController import DUELinkController
availablePort = DUELinkController.GetConnectionPort()
BrainPad = DUELinkController(availablePort)
import random
basketX = 10
eggY = 0
eggX = 20
score = 0
def ProcessBasket():
global basketX
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp)==0) and basketX > 0):
basketX = basketX - 6
if ((BrainPad.Digital.Read(BrainPad.Pin.ButtonB,BrainPad.Pin.PullUp)==0) and basketX < 110):
basketX = basketX + 6
def ProcessEgg():
global eggY
global eggX
global basketX
global score
eggY = eggY + 3
if eggY > 64:
# Check for collision
if eggX > basketX and eggX < basketX + 20:
score = score + 1
BrainPad.System.Beep(BrainPad.Pin.Piezo,1000,100)
else:
BrainPad.System.Beep(BrainPad.Pin.Piezo,256,100)
# new egg
eggY = 0
eggX = random.randint(0,120)
while True:
BrainPad.Display.Clear(0)
BrainPad.Display.DrawCircle(1,eggX,eggY,3)
BrainPad.Display.DrawFillRect(1,basketX,60,20,3)
BrainPad.Display.DrawTextScale(score,1,0,0,3,2)
BrainPad.Display.Show()
ProcessBasket()
ProcessEgg()
BrainPad.System.Wait(10)
C#
using GHIElectronics.DUELink;
var availablePort = DUELinkController.GetConnectionPort();
var BrainPad = new DUELinkController(availablePort);
var basketX = 10;
var eggY = 0;
var eggX = 20;
var rnd = new Random();
var score = 0;
void ProcessBasket(){
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonA,BrainPad.Pin.PullUp) && basketX > 0){
basketX = basketX - 6;
}
if (!BrainPad.Digital.Read(BrainPad.Pin.ButtonB, BrainPad.Pin.PullUp) && basketX < 110){
basketX = basketX + 6;
}
}
void ProcessEgg(){
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20){
score = score + 1;
BrainPad.System.Beep(BrainPad.Pin.Piezo, 1000, 100);
}
else {
BrainPad.System.Beep(BrainPad.Pin.Piezo, 256, 100);
}
//New Egg
eggY = 0;
eggX = rnd.Next(127);
}
}
while (true){
BrainPad.Display.Clear(0);
BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
BrainPad.Display.DrawText(score.ToString(), 1, 0, 0);
BrainPad.Display.Show();
ProcessBasket();
ProcessEgg();
BrainPad.System.Wait(10);
}
JS
import { SerialUSB } from './serialusb.js';
import * as due from './duelink.js';
let BrainPad = new due.DUELinkController(new SerialUSB());
await BrainPad.Connect();
var basketX = 10;
var eggY = 0;
var eggX = 20;
async function ProcessBasket() {
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonA, BrainPad.Pin.PullUp) && basketX > 0) {
basketX = basketX - 6;
}
if (!await BrainPad.Digital.Read(BrainPad.Pin.ButtonB, BrainPad.Pin.PullUp) && basketX < 110) {
basketX = basketX + 6;
}
};
var score = 0;
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
async function ProcessEgg() {
eggY = eggY + 3;
if (eggY > 64) {
//Check for collision
if (eggX > basketX && eggX < basketX + 20) {
score = score + 1;
await BrainPad.System.Beep(BrainPad.Pin.Piezo, 1000, 100);
}
else {
await BrainPad.System.Beep(BrainPad.Pin.Piezo, 256, 100);
}
//New Egg
eggY = 0;
eggX = getRandomArbitrary(0, 127);
}
}
while (true) {
await BrainPad.Display.Clear(0);
await BrainPad.Display.DrawCircle(1, eggX, eggY, 3);
await BrainPad.Display.DrawFillRect(1, basketX, 60, 20, 3);
await BrainPad.Display.DrawText(score.toString(), 1, 0, 0);
await BrainPad.Display.Show();
await ProcessBasket();
await ProcessEgg();
}
What is Next?
Modify the code to change how the game works, or completely make a new game.
BrainStorm
Do video games detect collisions properly? Or is it just checking for location? Things get more advanced in 3D space and advanced computing but will the answer be going the easy route? What about “falling” objects? Did we do proper gravity physics calculations? It was a simple addition. Can you think of video games where proper physics calculations are a must?