Variables โ storing data
The program's memory. Without variables we can't store the score, the number of lives, the frog's position โ nothing at all. This is the foundation of everything.
1What is a variable?
A variable is a named slot in the machine's memory that we put data into and can retrieve or replace at any time. Every piece of game data lives in a variable.
let โ a variable whose value can change (e.g. lives count going down)
const โ a constant whose value cannot change (e.g. level width)
var โ old style, avoid โ not used in modern code
3 examples from our games
The game speed, which the buttons can change:
// let โ because the player changes it let DT = 0.6; // 0.3 = Child, 0.6 = Slow // 1.0 = Normal, 1.5 = Fast // const โ the level never exceeds 50 const MAX_LEVEL = 50;
The frog's position and life counter:
// let โ the frog moves, position changes let frogCol = 5.5; let lives = 3; // const โ grid size never changes const COLS = 12; const CELL = W / COLS; // 45px
Energy and the turtle's position:
// let โ energy can drain and recharge let energy = 100; let score = 0; // const โ level length is fixed const LEVEL_DIST = 600;
2Data types
A variable can hold more than just numbers. JavaScript has 6 basic types:
| Type | Example | Used for | In the game |
|---|---|---|---|
| number | 42 3.14 -5 | Counting, position, score | Lives, energy, coordinates |
| string | "Alex" 'Frog' | Storing text | Player name, labels |
| boolean | true false | Yes/no decisions | Jumping? Shield? Alive? |
| array | [1, 2, 3] | Storing a list | Enemies, eggs, towers |
| object | {x:10, y:20} | Complex data | Frog, turtle, castle |
| null / undefined | null | No value / doesn't exist | No tower on a tower slot |
let score = 1500; // number let name = "Alex"; // string let jumping = false; // boolean let enemies = ["goblin", "orc"]; // array let frog = { x: 5, y: 0 }; // object let tower = null; // null (no tower yet) // typeof shows the type console.log(typeof score); // "number" console.log(typeof name); // "string" console.log(typeof jumping); // "boolean"
Press F12 in the browser โ Console tab โ type JS code and see the result instantly. Try: typeof 42 then press Enter!
Open the browser console (F12 โ Console) and try these:
- Create a
let lives = 3variable, then print it to the console - Change it:
lives = lives - 1โ what's its value now? - Try to overwrite a const โ what error message does the console show?
- Create an object for your own turtle character with x, y, lives, energy properties
๐ง Which declaration is correct when the value changes multiple times?
Conditions โ the program decides
Without if-else there is no game. Every collision, every life loss, every victory is built on conditional logic.
1The if-else structure
// Basic structure if (condition) { // runs if TRUE } else if (anotherCondition) { // runs if first is false but this is true } else { // runs if none of the above were true }
3 examples from our games
Checking castle HP โ is the game over?
if (G.castle.hp <= 0) { G.phase = 'gameover'; return; } // HP bar colour based on percentage if (pct > 0.6) { color = '#3a8a3a'; // green } else if (pct > 0.3) { color = '#9a8a20'; // yellow } else { color = '#8a2020'; // red }
The frog dies โ what type of death?
if (ZONE.road.includes(f.row)) { // on road โ was it hit by a car? dieFrog('squish'); } if (ZONE.river.includes(f.row)) { // on water โ is there a log below? if (!onLog) { dieFrog('splash'); } }
Energy drain based on surface type:
if (surface === 'mud') { energy -= 0.22; // drains fast in mud } else if (surface === 'road') { energy += 0.07; // recharges on road } else { energy -= 0.05; // slow drain otherwise } if (energy <= 0) { loseLife(player); }
Comparison operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
=== | Equal (type too!) | 3 === 3 | true |
!== | Not equal | 3 !== 5 | true |
> | Greater than | hp > 0 | true if alive |
<= | Less than or equal | lives <= 0 | true if dead |
&& | AND โ both must be true | alive && hasEnergy | can play |
|| | OR โ at least one true | won || timesUp | game over |
! | NOT โ inverts | !jumping | not jumping |
== 3 only checks value: "3" == 3 โ true (wrong!)
=== 3 also checks type: "3" === 3 โ false (correct!)
Write conditions in the console or an HTML file:
- Create
let lives = 3, then use if to check: if 0, print "Game Over" - Write an if-else if-else chain: 3 lives = "Full health", 1โ2 = "Watch out!", 0 = "Game Over"
- Create
let isJumping = falseandlet onRock = trueโ use if to check: if not jumping AND on a rock โ "Died!"
๐ง What is the result of if ("3" === 3)?
Loops โ repetition
Every enemy, every egg, every tower must be checked every single frame. Without loops this would be impossible.
1The three main loops
// 1. for loop โ when we know how many times to run for (let i = 0; i < 5; i++) { console.log("Wave: " + i); } // 0, 1, 2, 3, 4 // 2. while loop โ while a condition is true let lives = 3; while (lives > 0) { console.log("Still alive: " + lives); lives--; } // 3. forEach โ for iterating arrays const enemies = ["goblin", "orc", "troll"]; enemies.forEach(enemy => { console.log("Enemy: " + enemy); });
3 examples from our games
Every tower finds the closest enemy:
// forEach: runs for every tower G.towers.forEach(tower => { let best = null; let bestDist = ts.range; // nested forEach: check enemies G.enemies.forEach(enemy => { const d = dist(tower, enemy); if (d < bestDist) { bestDist = d; best = enemy; } }); });
Moving all objects every frame:
// Check collision of all objects // (eggs, rocks, seagulls) with the frog G.objects.forEach(obj => { if (obj.done) return; // skip const dist = Math.hypot( frog.x - obj.x, frogY - obj.y ); if (dist < obj.r + 13) { collect(obj); } });
Drawing surface segments of the track:
// for loop โ exactly 5 segments for (let i = 0; i < segs.length; i++) { const surf = SURFACES[segs[i]]; const topY = -((i+1) * segH) + scrollY; const botY = -(i * segH) + scrollY; ctx.fillStyle = surf.col; ctx.fillRect(TL, topY, TW, botY-topY); }
for โ when you know exactly how many times to run (e.g. 5 segments, 50 levels)
while โ when it depends on a condition when to stop (e.g. while there are enemies)
forEach โ when you need to iterate an array (the most common in games!)
Write loops in the console:
- Use a for loop to print numbers 1 to 10
- Create an array of 5 enemy names, use forEach to print each one
- Use a while loop to simulate a game: start with 3 lives, subtract 1 each round, print when it reaches 0
- Find the line in Castle Siege where
G.enemies.forEachappears (Ctrl+F)
๐ง Which loop is most common in our games for iterating arrays?
Functions โ reusable code
If we do something more than once, we put it in a function. Castle Siege has 40+ functions โ each one performs one well-defined task.
1Function basics
// Simple function โ no parameter, no return value function startGame() { lives = 3; score = 0; phase = 'play'; } // Function with a parameter function addScore(amount) { score += amount; // amount = what we received as parameter } addScore(10); // called with 10 addScore(50); // called with 50 // With a return value function dist(ax, ay, bx, by) { return Math.hypot(bx-ax, by-ay); } const d = dist(0, 0, 3, 4); // d = 5
3 examples from our games
addFloat() โ displaying floating text:
// Parameters: where, what, what colour function addFloat(x, y, txt, col) { G.floats.push({ x, y, txt, col, life: 95, vy: -0.65 }); } // Calling โ same way everywhere: addFloat(enemy.x, enemy.y, '+10g', '#FFD700'); addFloat(p.x, p.y-25, '-1 โค๏ธ', '#f44');
dieFrog() โ handling death by type:
function dieFrog(type) { // Already dead โ don't run twice if (G.frog.dead) return; G.frog.dead = true; G.frog.deathType = type; // 'squish' or 'splash' G.lives--; G.phase = 'dead'; G.phTimer = 80; updateUI(); } // Two calls โ different parameters: dieFrog('squish'); // hit by a car dieFrog('splash'); // fell in water
getSurface() โ returns the current surface:
// Function with a return value function getSurface(dist, level) { const lv = LEVELS[level - 1]; const segH = LEVEL_DIST / lv.segs.length; const idx = Math.min( Math.floor(dist / segH), lv.segs.length - 1 ); return lv.segs[idx]; // e.g. 'mud' } // Usage: const surf = getSurface(250, 2); // surf === 'mud' โ slows down, drains energy
In modern JS we can also write functions using arrow syntax. Same result, just shorter:
// Traditional form function dist(ax, ay, bx, by) { return Math.hypot(bx-ax, by-ay); } // Arrow function โ same thing, shorter const dist = (ax, ay, bx, by) => Math.hypot(bx-ax, by-ay); // You'll see this inside forEach in Castle Siege: G.enemies.forEach(e => { // e = one enemy const d = dist(tower.x, tower.y, e.x, e.y); });
Write your own functions:
- Write a
loseLife()function that reduces lives and prints "Game Over" when it hits zero - Write an
addScore(amount)function that adds points and prints the new total - Write a
dist(x1,y1,x2,y2)function that returns the distance between two points (Math.hypot) - Find the
function drawFrogline in Froggy Rush โ how many parameters does it have?
๐ง What's the difference between a parameter and an argument?
Arrays โ managing lists
A game can have 20 enemies, 30 eggs and 6 towers active at once. They all live in arrays โ and every frame we iterate through all of them.
1Array basics
// Creating arrays let enemies = []; // empty array let levels = [1, 2, 3]; // with numbers let names = ["Goblin", "Orc", "Troll"]; // Indexing โ starts at 0! names[0] // "Goblin" names[2] // "Troll" names.length // 3 // Adding โ push() enemies.push({ x: 100, y: 200, hp: 50 }); // Removing โ splice() enemies.splice(0, 1); // removes element at index 0
The most important array methods
| Method | What it does | Used in game for |
|---|---|---|
push(elem) | Adds to the end | Spawning a new enemy or projectile |
filter(fn) | Keeps only elements where fn returns true | Removing dead enemies |
forEach(fn) | Runs a function on each element | Moving every enemy |
find(fn) | Returns the first matching element | Finding the closest enemy |
map(fn) | Transforms every element | Converting coordinates |
some(fn) | True if at least one matches | Is there still a living enemy? |
every(fn) | True if all match | Are all home pads filled? |
3 examples from our games
Removing dead projectiles with filter:
// filter: only the living ones remain G.hProj = G.hProj.filter(p => { p.x += p.vx * DT; p.y += p.vy * DT; p.life -= DT; // if life <= 0 or off screen โ remove return p.life > 0 && p.x > 0 && p.x < W; });
Checking if all home pads are filled:
// every: true only if ALL are filled if (G.homes.every(h => h.filled)) { G.phase = 'levelup'; G.score += 500 * G.level; }
Sorting and trimming a Top 10 list:
let topList = [ { name: "Alex", score: 1500 }, { name: "Sam", score: 2200 }, { name: "Tom", score: 800 }, ]; // Sort by score โ descending topList.sort((a, b) => b.score - a.score); // Keep only the top 10 topList = topList.slice(0, 10); // Print: "1. Sam: 2200 pts" topList.forEach((t, i) => { console.log(`${i+1}. ${t.name}: ${t.score}`); });
Arrays in the console:
- Create an empty
enemiesarray, use push to add 3 objects (with name, hp properties) - Use forEach to print every enemy's name and HP
- Use filter to remove those whose HP dropped to 0 or below (hp <= 0)
- Use find to locate the first enemy with hp < 50
- Use sort to arrange them by HP in descending order
๐ง What does the filter() method do?
Objects โ complex data
A turtle is not just one number โ it has an x position, y position, energy level, name, speed. All of this lives together in a single object.
1Object basics
// Creating an object โ curly braces const frog = { col: 5.5, // horizontal position row: 0, // row (on grid) lives: 3, // lives dead: false, // is it alive? riding: null // which log it's standing on }; // Reading โ dot operator console.log(frog.lives); // 3 console.log(frog.dead); // false // Writing โ same way frog.lives = 2; frog.dead = true;
3 examples from our games
G โ the entire game state in one object:
let G; // the entire game state function initLevel() { G = { hero: { x: 155, y: 300, hp: 100 }, castle: { hp: 200, maxHp: 200 }, enemies: [], // array inside object! towers: [], gold: 0, wave: 0, phase: 'play' }; } // Access: G.castle.hp, G.hero.x, etc.
makeTurtle() โ a function that creates objects:
function makeTurtle(x, isAI) { return { x, // shorthand: x: x vx: 0, lives: 3, energy: 100, jumping: false, inShell: false, isAI, // shorthand: isAI: isAI eggs: 0 }; } // Two turtles with the same structure: const player = makeTurtle(W/2-22, false); const ai = makeTurtle(W/2+22, true);
Nested objects โ player profile:
const profile = { name: "Alex", score: 4500, hero: { // nested object spd: 2, dmg: 3, level: 15 }, games: ["castle", "froggy"] // array inside }; // Chained access: profile.hero.spd // 2 profile.games[0] // "castle"
Designing objects:
- Create a
playerobject with name, lives, energy, x, y, score properties - Add a nested
heroobject with speed, damage, level fields - Write a
makeEnemy(type)function that returns an enemy object (hp, speed, gold, name based on type) - Call it 3 times with different types and put the results in an array
๐ง How do we access a property of a nested object?
Events โ keyboard
Player presses the arrow โ the frog jumps. This is event handling. Without it the game just stares back โ it doesn't react to anything.
1The addEventListener
// Adding an event listener document.addEventListener('keydown', (e) => { console.log(e.key); // prints which key was pressed }); // When a key is released document.addEventListener('keyup', (e) => { console.log("Released: " + e.key); });
3 examples from our games
K object โ tracks which keys are held:
const K = {}; // held keys document.addEventListener('keydown', e => { K[e.key.toLowerCase()] = true; if (e.key === 'e') doInteract(); }); document.addEventListener('keyup', e => { K[e.key.toLowerCase()] = false; }); // Checked continuously in the game loop: if (K['a'] || K['arrowleft']) hero.vx -= speed;
Moving the frog with arrow keys:
document.addEventListener('keydown', e => { const k = e.key; if (k === 'ArrowUp' || k === 'w') { dpad(0, 1); // up } else if (k === 'ArrowLeft' || k === 'a') { dpad(-1, 0); // left } // Space / Enter: restart if (k === ' ' || k === 'Enter') { e.preventDefault(); startGame(); } });
Jump and shield key handling:
document.addEventListener('keydown', e => { if (e.key === 'ArrowUp') { K.up = true; // jump e.preventDefault();// prevent page scroll } if (e.key === 'ArrowDown') { K.down = true; // shell } }); document.addEventListener('keyup', e => { if (e.key === 'ArrowUp') K.up = false; if (e.key === 'ArrowDown') K.down = false; });
Keyboard handling in your own HTML file:
- Create an HTML file with a canvas, and listen for keydown with addEventListener
- Press different keys and print the
e.keyvalue โ what do you get for ArrowLeft? - Create a K = {} object and implement the keydown/keyup pair
- Draw a small square on the canvas and move it with arrow keys based on the K object
๐ง What is the e.key value when the left arrow is pressed?
Mouse and touch handling
In Castle Siege we click on tower placement spots; in Galactic Conquest we click on hex cells. On mobile we steer by touch. Both are event handling.
1Mouse click on a canvas
// The canvas isn't always 1:1 on screen // getBoundingClientRect() gives its real position canvas.addEventListener('click', e => { const rect = canvas.getBoundingClientRect(); // Click position inside the canvas: const mx = (e.clientX - rect.left) * (W / rect.width); const my = (e.clientY - rect.top) * (H / rect.height); console.log(`Clicked: ${mx}, ${my}`); });
3 examples from our games
Galactic Conquest โ hex cell click:
CV.addEventListener('click', e => { const rect = CV.getBoundingClientRect(); const mx = (e.clientX - rect.left) * (W/rect.width); const my = (e.clientY - rect.top) * (H/rect.height); // Find the closest hex let best = null, bestD = HEX_SIZE * 1.1; G.hexes.forEach(h => { const {x,y} = hexCenter(h.col, h.row); const d = Math.hypot(mx-x, my-y); if (d < bestD) { bestD = d; best = h; } }); if (best) selectHex(best.col, best.row); });
Swipe touch handling on the canvas:
let touchStart = null; CV.addEventListener('touchstart', e => { touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY }; }, { passive: false }); CV.addEventListener('touchend', e => { const dx = e.changedTouches[0].clientX - touchStart.x; const dy = e.changedTouches[0].clientY - touchStart.y; if (Math.abs(dx) > Math.abs(dy)) dpad(dx > 0 ? 1 : -1, 0); // horizontal else dpad(0, dy < 0 ? 1 : -1); // vertical });
D-pad buttons โ touch controls:
<!-- HTML: touch button --> <div class="dp" ontouchstart="K.left=true" ontouchend="K.left=false">โฌ ๏ธ</div> // JS: the turtle moves based on K object if (K.left) turtle.vx -= accel; if (K.right) turtle.vx += accel;
Click and touch handling:
- Add a canvas click event to your HTML file โ print the click coordinates
- Draw a circle on the canvas wherever you clicked
- Add 4 HTML buttons (โฌ๏ธโฌ ๏ธ๐ขโก๏ธ) with ontouchstart/ontouchend attributes
- Test on mobile or in Chrome DevTools mobile view (F12 โ phone icon)
๐ง Why do we need getBoundingClientRect() for canvas clicks?
Math and randomness
Every egg appears in a random location. The enemy performs a sinusoidal motion. The shot follows a parabola. All of this is done with the Math object.
1The most important Math methods
| Method | Returns | Used in game for |
|---|---|---|
Math.random() | Random number between 0 and 1 | Egg position, enemy delay |
Math.floor(x) | Rounds down (integer) | Random number to integer |
Math.ceil(x) | Rounds up | Ensuring a minimum value |
Math.round(x) | Rounds to nearest integer | Displaying HP as whole number |
Math.min(a, b) | The smaller value | Capping energy at 100 |
Math.max(a, b) | The larger value | Preventing HP from going below 0 |
Math.abs(x) | Absolute value (no sign) | Distance checks |
Math.hypot(dx, dy) | Euclidean distance | Collision detection |
Math.sin(x) | Sine (between -1 and 1) | Wave motion, jump arc, breathing |
Math.PI | ฯ = 3.14159... | Circles, angles |
3 examples from our games
Random enemy position on spawn:
// Random y position within the level height const y = UIH + et.r + Math.random() * (GH - et.r * 2); // Energy recharge โ Math.min ensures // it doesn't exceed 100 p.energy = Math.min(100, p.energy + 40); // HP can't go below 0: G.castle.hp = Math.max(0, G.castle.hp - dmg);
Frog jump arc โ using Math.sin():
// jumpT goes from 0 to 22 // Math.sin() traces a 0โ1โ0 parabola p.jumpH = Math.sin(p.jumpT / 22 * Math.PI) * 38; // jumpH: 0 โ 38px (peak) โ 0 back down // If timer is up โ landed if (p.jumpT >= 22) { p.jumping = false; p.jumpH = 0; }
Walking animation and log bobbing:
// frame increments continuously (0, 1, 2, 3...) // sin(frame * 0.2) โ oscillates between -1 and 1 // This produces the walking animation const walk = Math.sin(frame * 0.2) * 2; // The turtle's legs step in opposite phase: ctx.ellipse(-10, 12 + walk, 7, 4, ...); ctx.ellipse( 10, 12 - walk, 7, 4, ...);
Between 0 and N: Math.floor(Math.random() * N)
Between MIN and MAX: MIN + Math.floor(Math.random() * (MAX - MIN))
e.g. 1โ6 (dice): 1 + Math.floor(Math.random() * 6)
Math operations in the console:
- Generate a random number between 1 and 100 โ run it 5 times
- Write a
clamp(val, min, max)function that limits using Math.min and Math.max - Print the Math.sin() value from frame=0 to frame=62 (one full circle) โ what pattern do you see?
- Calculate the distance between (0,0) and (100, 150) using Math.hypot
๐ง How do we generate a random integer between 1 and 6 (dice roll)?
๐ Phase Project โ Mini Quiz Game
A summary of all JavaScript basics in one small independent project. Variables, conditions, loops, functions, objects, events โ all at once.
1The project task
Build a browser quiz game using pure HTML + JavaScript (no Canvas โ using the DOM). The game must be able to:
โ At least 5 questions (stored in an array as objects)
โ 4 answer options for each question
โ Correct answer highlighted in green, wrong ones in red
โ Score counting and display
โ "Next question" button
โ Final results screen with score
// Questions in an array, as objects const questions = [ { q: "Which keyword creates a variable that cannot change?", options: ["var", "let", "const", "int"], answer: 2 // const (index) }, { q: "What does the filter() method do?", options: ["Sorts", "Filters", "Sums", "Searches"], answer: 1 }, // ... 3 more questions ... ]; // Game state let currentQ = 0; let score = 0; // Show a question function showQuestion() { const q = questions[currentQ]; document.getElementById('question').textContent = q.q; q.options.forEach((opt, i) => { document.getElementById('opt' + i).textContent = opt; }); } // Check an answer function checkAnswer(selectedIndex) { const correct = questions[currentQ].answer; if (selectedIndex === correct) { score++; // green highlight... } currentQ++; if (currentQ >= questions.length) { showResult(); } else { showQuestion(); } }
The finished project must include:
- let and const used correctly (score = let, questions = const)
- Questions stored as objects inside an array
- Answer options displayed using forEach
- if-else used to determine correct/wrong answers
- Functions: showQuestion(), checkAnswer(), showResult()
- Event listeners: clicking on answer buttons
- Math.round() used to display final score as a percentage
- CSS for a stylish appearance (at least dark background + coloured buttons)
โญ Shuffle question order (Math.random() based shuffle)
โญโญ Time limit per question (setTimeout, countdown)
โญโญโญ Save the best result in localStorage and reload it