Skip to content
Lesson 1.4Computer, Game, and Code Foundations

Programming Fundamentals

Build a guarded gold-purchase function and use it to understand variables, branches, test cases, collections, and ways to organize code.

Lesson 1.4
Read or listenPrint chapter or book
About 40 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 1.4Key ideas, reference and questions

A one-page summary of “Programming Fundamentals” you can keep beside your notes. Plain Markdown.

Lesson 1.4 — Programming Fundamentals: cheatsheet

  • Remember: An algorithm is a plan of ordered steps. Rust is just one exact way to write that plan down.
  • Also useful: Amounts just below, equal to, and above the price reveal where affordability changes. They catch a mistaken greater-than check where at-least was intended.

Key terms

  • Data: Data is information stored or sent as bytes.
  • literal: The number 100 is a literal: a value written directly in the code.
  • cost: gold and cost are names we chose.
  • mut: mut means the value named gold may change.
  • gold: Before the assignment, gold is 100; afterward, it is 75.
  • inputs: Gold and cost are inputs to spend_if_possible; its returned amount is an output.
  • local: A local name belongs to a scope such as one function.
  • data structure: That record is a data structure: a chosen way to organize related values.
  • Vec<T>: T in Vec<T> names the stored item type.
  • invariant: The earlier health-range requirement is an invariant: when player state is valid, health <= max_health must hold.

Review

  • Q: A player has 10 gold and an item costs 25. What does spend_if_possible(10, 25) return? A: 10, because the gold is below the cost, so the subtraction is skipped
  • Q: How are data, a value and a type related? A: Data is stored bytes, a value is what they mean, and a type says how to read them
Download cheatsheet (.md)

We have already followed one value through a CPU instruction (Lesson 1.2) and grouped it with one player’s other values (Lesson 1.3). Now we can write a small program that uses those ideas. We will start with 100 gold and ask what happens when an item costs 25. Lesson 1.8 will show the bytes this program uses before we try to find them in a running game.

Call the two players in this lesson Ada and Bo. Ada starts with 100 gold and Bo with 40. The short Rust fragments each explain one idea, and the next lesson runs a separate complete program. Here we will also compare program logic, collections, and ways to organize code.

Data is information stored or sent as bytes. A value is what those bytes mean when interpreted, and a type tells the program how to interpret and use them. For example, the four bytes 50 00 00 00 can represent the number 80 when read as a little-endian u32. We will work through the byte order in Lesson 1.8. For now, notice that “four bytes” and “80 health” describe different levels of the same information.

50
00
00
00
four stored bytes
read as a u32: the value 80
A type tells code how to interpret stored data.

The number 100 is a literal: a value written directly in the code. A variable is a name for a value the program can use. This first fragment names the current gold and the cost:

let mut gold: u32 = 100;
let cost: u32 = 25;

let introduces a variable. gold and cost are names we chose. The colon gives each variable a type. Game Fundamentals used u32 for a non-negative whole number stored in four bytes. The equals sign sets the starting value, and the semicolon ends the statement. mut means the value named gold may change. We do not need to change cost, so it has no mut.

The type keeps different kinds of value apart. 80 can be a u32 health amount, "Ada" can be text, and true can be a bool answer to a question. Adding two health amounts makes sense; adding the player name to health does not. Later, Lesson 3.1 will show how types can even distinguish two numbers that happen to use the same underlying bytes, such as a player ID and a health amount.

At this point, gold is 100 and cost is 25. To calculate a purchase, the program reads both values, subtracts cost, and stores the result back in gold:

gold = gold - cost;

The right side is calculated first: 100 − 25 = 75. The equals sign then assigns 75 to gold. The cost stays 25.

gold before100
cost25
gold after75
read 100 and 25, subtract, then store 75
The assignment changes gold. It does not change the cost.

The program’s state is the information it remembers at one moment. Before the assignment, gold is 100; afterward, it is 75. The cost is an input to that calculation. The subtraction result is a temporary value until it is stored as the new gold. A screen might then show the derived text "Gold: 75", but that text is not necessarily the stored amount the next purchase checks.

input: buy an item for 25
calculate 100 − 25
store gold = 75
draw Gold: 75

This input → calculation → stored state → display path helps us ask which value a later piece of code actually reads. Game Fundamentals first showed that the display may hold a copy; Hacking Fundamentals will turn that observation into an experiment.

What if gold is only 10 and the cost is 25? A purchase should not be allowed. Because u32 cannot represent a negative number, blindly subtracting would also be the wrong operation. We need the branch from Lesson 1.2:

if gold >= cost {
gold = gold - cost;
}

>= asks whether gold is greater than or equal to cost. The answer is a boolean value, either true or false. if runs the instructions between the braces only when the answer is true. Starting with 100 gold, the comparison is true and gold becomes 75. Starting with 10, it is false and gold remains 10. The braces mark the group of statements controlled by the branch.

yes
no
read gold and cost
gold ≥ cost?
subtract cost from gold
keep the current gold
resulting gold

This gives us a repeatable algorithm, meaning a procedure with ordered steps: read the current gold, compare it with the cost, and subtract only if the player can pay. The same procedure works for 100 gold and for 10 gold. An algorithm is the plan; Rust code is one exact way to express it. Its three building blocks are already visible here: do steps in sequence, use if to select a path, and later use a loop to repeat work for several players.

Predict first

A player has exactly 25 gold, and the item costs 25. The code is the branch above: if gold >= cost { gold = gold - cost; }

What is gold after this code runs?

Answer: 0. 25 >= 25 is true, because >= also accepts equal values. The branch runs and stores 25 − 25 = 0, which is a valid u32.

if gold >= cost { gold = gold - cost; }Try the exact-payment case with both comparisons. They agree everywhere except when gold equals the cost.

Compare first, then subtract only if allowed

The check

Stored values
gold 25
cost 25
gold >= cost
25 >= 25: true

Accepted

Subtract
25 − 25 = 0
Gold afterward
0stored back in gold

25 >= 25 is true, so the branch runs: 25 − 25 = 0 is stored as the new gold.

You can first write the plan in pseudocode, ordinary steps arranged to look like code without committing to a language’s exact syntax:

receive the current gold and the item cost
if the gold is at least the cost:
give back gold minus cost
otherwise:
give back the original gold

This is a sketch to discuss and trace, rather than a program Rust can run. With 100 and 25, it gives back 75. With 10 and 25, it gives back 10. Naming the inputs and both outcomes makes the next function easier to write.

One purchase algorithm, different inputs. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

gold = starting_gold
cost = item_cost
can_pay = gold >= cost
if can_pay: gold = gold - cost
otherwise keep gold unchanged
return gold

Result: 75 gold. The cost stays 25.

Final values: gold = 100; cost = 25; stored_gold = 75; item_cost = 25; can_pay = true.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.

    gold = 100; cost = 25.

  2. Step 2, line 2. The price is an input; this algorithm changes only gold.

    gold = 100; cost = 25; stored_gold = 100; item_cost = 25.

  3. Step 3, line 3. 100 >= 25 is true.

    gold = 100; cost = 25; stored_gold = 100; item_cost = 25; can_pay = true.

  4. Step 4, line 4. The accepted purchase subtracts the price.

    gold = 100; cost = 25; stored_gold = 75; item_cost = 25; can_pay = true.

  5. Step 5, line 6. Result: 75 gold. The cost stays 25.

    gold = 100; cost = 25; stored_gold = 75; item_cost = 25; can_pay = true.

A function gives a behavior a name so another part of the program can use it. This one takes a gold amount and a cost, then gives back the gold amount after a permitted purchase:

fn spend_if_possible(gold: u32, cost: u32) -> u32 {
if gold >= cost {
return gold - cost;
}
return gold;
}

fn begins the function definition. The names inside parentheses are its parameters: the two input values it will receive. Each has a u32 type. The arrow -> u32 says the returned value is also a u32. return sends the value back to whoever used the function. If there is enough gold, it returns the subtraction. If there is not, it returns the original amount. The final return also shows that functions do not have to run every line: the first return ends that call.

For example, spend_if_possible(100, 25) returns 75. spend_if_possible(10, 25) returns 10. Those expressions call the same function with different inputs. They do not change a variable by themselves; the caller must store the returned value:

gold = spend_if_possible(gold, cost);

This simple result does not tell the caller whether a purchase happened when the cost is zero. A later lesson introduces types that can represent success or failure explicitly. We only need the resulting gold amount here.

Gold and cost are inputs to spend_if_possible; its returned amount is an output. The caller can store that output in its mutable gold variable. cost stays fixed during the calculation. Input, output, and mutable are therefore overlapping descriptions, not three separate kinds of variable. They answer “where does the value flow?” and “may it change?”

Two other descriptions concern where a name is usable and how long its storage exists. A local name belongs to a scope such as one function. People often use global for a name available outside a single function, although the language’s visibility controls which code can use it. A Rust static item has storage for the entire program run. That storage duration does not by itself mean its contents can never change; Rust separately checks how code obtains shared or editable access; a long lifetime does not grant permission to mutate. See the Rust Reference’s static-item explanation for the complete access requirements. Our purchase calculation needs only its supplied inputs, so it does not need a shared global gold value.

The ordinary 100-gold purchase is a useful baseline case, sometimes informally called a base case. In recursive algorithms, “base case” has a more specific meaning: a case solved directly without another recursive call. For this affordability check, we are checking ordinary and unusual inputs. It is not enough to check only that example. An edge case exercises an unusual end of the allowed range, such as having zero gold. A boundary case checks where a decision changes: just below, exactly at, and just above the price. A corner case combines unusual conditions, such as zero gold and a zero price. These labels can overlap; the important part is writing down the inputs and expected result:

Case Gold Cost Expected returned gold
Ordinary purchase 100 25 75
No gold 0 25 0
Just below the price 24 25 24
Exactly the price 25 25 0
Just above the price 26 25 1
No gold and a free item 0 0 0

The exact-price case checks that >= allows payment when gold equals cost. Changing it to > would reject that purchase. The free-item case also shows the limitation already mentioned: unchanged gold alone cannot tell us whether a purchase happened. The Rust Primer demonstrates an automated test for its stamina check; Hacking Fundamentals will check the conditions that permit a memory write.

We can group gold, health, and maximum health into one record. For this example, Ada starts with 100 gold and 80 health so the program can perform the purchase itself. That record is a data structure: a chosen way to organize related values. Rust uses struct to describe its shape:

struct Player {
gold: u32,
health: u32,
max_health: u32,
}

Each line inside the braces is a named field. Now we can create one player value:

let ada = Player {
gold: 100,
health: 80,
max_health: 100,
};

The braces supply the three field values. A comma separates fields; the semicolon ends the let statement. ada.gold reads Ada’s gold field. ada.health reads her health. The dot selects a field from this particular record.

The pair health and max_health has a required relationship: a valid player’s health must be between zero and the maximum. For Ada, 80 is within 0 to 100. If a mistaken update made health 120 while the maximum stayed 100, the record would violate that relationship. We will call such a condition an invariant below. For now, check the relationship instead of trusting either field alone.

Suppose Ada has 100 gold and Bo has 40. We want to charge each of them 25. A collection stores several values. The square brackets below make a fixed-size array containing two Player records:

let mut players = [
Player { gold: 100, health: 80, max_health: 100 },
Player { gold: 40, health: 50, max_health: 80 },
];

We make players mutable because its records’ gold values will change. A loop repeats one operation for each record:

for player in &mut players {
player.gold = spend_if_possible(player.gold, 25);
}

Read for player in ... as “take each player in turn.” The &mut players part lets the loop access the existing records so it can change them; Rust calls this a mutable borrow. We will study borrowing more fully when a tool begins reading another process in Chapter 3. Here, the important effect is that the loop changes the records in the collection, rather than temporary copies.

On the first pass, Ada’s 100 becomes 75. On the second, Bo’s 40 becomes 15. If Bo had only 10, his gold would stay 10 because the function’s comparison would reject that purchase. The same named operation works for both players; adding a third record would not require writing another affordability check and subtraction.

Ada before100
Ada after75
Bo before40
Bo after15
first loop pass
second loop pass
The same guarded 25-gold subtraction visits two records and produces two results.

The Player record groups fields belonging to one player; the array groups several players. Both are data structures, chosen for different questions. We will compare other useful collections after tracing this array.

The function, record, and loop fragments above show both outcomes without seeing a longer program yet:

Player Gold before Cost Gold after Health afterward
Ada 100 25 75 80/100
Bo 40 25 15 50/80

The same purchase function visits each record. It changes gold, not health. The Rust Primer runs a separate stamina-based program with two runners and shows how Rust lends each record to a function in turn.

Programming logic: sequence, selection, and repetition

Section titled “Programming logic: sequence, selection, and repetition”

The purchase program combines three simple ways for instructions to flow. A sequence does one step and then the next. A selection chooses a path based on a condition, as if did for the purchase. Repetition runs steps again, as for did for the two players. These are not three special kinds of program; they are building blocks you can combine.

ExampleThree prices in order

Suppose Ada meets three prices in order: 25, 90, and 10. Start with 100 gold and trace this short Rust fragment:

let mut gold = 100;
for cost in [25, 90, 10] {
if gold >= cost {
gold = gold - cost;
}
}

The array supplies prices in sequence. The for loop repeats the body once per price. The if selects whether subtraction runs on that pass. Follow the stored gold, not just the prices:

Pass Price Comparison What happens to gold?
Start — — 100
1 25 100 ≥ 25 is true 100 → 75
2 90 75 ≥ 90 is false remains 75
3 10 75 ≥ 10 is true 75 → 65

The final gold is 65. Order matters: if the 90-gold purchase came first, it could succeed while the later 25-gold purchase would fail. This is why reading unfamiliar logic means finding both which steps can run and when they run. A while loop also repeats, but it keeps going while its condition remains true; the for loop here has a known collection of three prices. Later lessons combine these same forms into scanners, parsers, and game bots.

Once values are in a collection, the question determines the work. After the earlier two-player purchase, Ada has 75 gold and Bo has 15. For a new item costing 25, we can ask several different questions:

Question Kind of result Answer for Ada and Bo
Can Bo afford the item? Decision: a yes/no answer No, because 15 is below 25.
How many players can afford it? Count: the number that meet a condition One: Ada.
What is the least or greatest gold amount? Minimum or maximum: an extreme value Minimum 15; maximum 75.

Counting requires checking each player’s gold against the price. Finding a maximum requires comparing the gold amounts with one another. Neither requires sorting all players first. If there are no players, the count is zero, but there is no player’s minimum or maximum to return. We will use Rust’s Option in the next lesson to represent a missing answer. Clarifying the desired result helps you choose a collection and an algorithm, instead of copying a solution to a different question.

The array above has two fixed Player slots. A vector can grow when the number of values changes; this small history gains one new sample:

let mut recent_gold = vec![100, 75];
recent_gold.push(65);

The values are now 100, 75, and 65. A hash map instead looks up a value by key, and a queue takes waiting items in arrival order. T in Vec<T> names the stored item type. A grid groups rows and columns; a graph records connections. The operation you need determines the useful arrangement. Lesson 3.3 compares these choices with the full table and decision diagram before its bounded byte examples.

Programming paradigms: ways to organize a purchase

Section titled “Programming paradigms: ways to organize a purchase”

A programming paradigm organizes instructions and responsibilities. The same guarded purchase still gives 75 for (100, 25) and keeps 10 for (10, 25). These four styles decide where and when that work happens:

Check gold >= cost, then update gold only on the permitted path. Our named function keeps that check in one place instead of copying it into every caller.

Object-oriented: keep behavior with its data

Section titled “Object-oriented: keep behavior with its data”

A method such as ada.buy(25) groups the purchase behavior with Ada’s record. Rust’s impl syntax in the next lesson shows how a method receives the record it uses.

spend_if_possible(100, 25) calculates 75 without secretly changing Ada. The caller decides whether to store the returned value.

Event-driven: respond when something happens

Section titled “Event-driven: respond when something happens”

A Buy click can produce an event. Its handler asks the purchase function to update that player’s gold. The event decides when the calculation runs; the affordability check still decides whether spending is allowed.

One game can combine these styles. Lesson 3.9 keeps the complete comparison and source fragments beside the object-oriented code clues they help interpret.

An abstraction hides details behind a smaller interface

Section titled “An abstraction hides details behind a smaller interface”

Calling spend_if_possible(100, 25) lets another part of the program use the purchase calculation without repeating its branch and subtraction. That is an abstraction: a smaller interface hides useful steps. It still has to keep its promise. If the caller and function disagree about the inputs, the friendly name does not repair the mistake. Lesson 6.7 later explains source-level APIs and machine-level ABIs when we begin calling compiled game functions.

An invariant is a condition valid state keeps true

Section titled “An invariant is a condition valid state keeps true”

The earlier health-range requirement is an invariant: when player state is valid, health <= max_health must hold. A small function can check it:

fn valid_health(health: u32, max_health: u32) -> bool {
health <= max_health
}

For our u32 values, zero is already the smallest possible number. If Ada has 80 health and a maximum of 100, the function returns true; for 120 and 100 it returns false. The purchase function has its own condition: only subtract a cost when the player can pay. An invariant gives you a concrete check when unfamiliar state or a recovered field might be wrong. Lesson 14.5 uses such conditions to distinguish valid and invalid game state.

Return to spend_if_possible rather than trying to hold the whole page in your head. One useful route is to ask five questions:

  1. What enters? Gold and cost, both u32 values.
  2. What can stop or change the path? gold >= cost chooses the return.
  3. What state changes inside? None; this function calculates a result.
  4. What leaves? A u32: 75 for (100, 25), or 10 for (10, 25).
  5. Who stores that result? The caller’s assignment to gold or player.gold.

The same questions work for a new function or event handler: find the inputs and types, checks, state changes, and output. A dense line can be broken into smaller expressions to see which value each one produces.

mut permits gold to change while cost stays fixed. The 10-gold case takes the failed branch of gold >= cost, so spend_if_possible returns unchanged gold. One loop applies that same function to Ada and Bo. The three-price trace combines ordered steps, a branch, and repetition.

A vector keeps a growing history; a hash map answers a lookup by key; a queue keeps pending actions in arrival order. A visible “Gold: 75” label can be separate from the gold field the calculation reads. Procedural, object-oriented, functional, and event-driven styles can organize the same check and subtraction in different places, while valid_health checks that player health stays within its allowed range.

The Rust Primer uses a different game action to make the Rust syntax executable. Hacking Fundamentals then turns an observed purchase into a question about a running game: which value actually controls it?

Knowledge check

Check before spending

A player has 10 gold and an item costs 25. What does spend_if_possible(10, 25) return?

Finished Lesson 1.4?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 1: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.