Doing it again
Tomas wants to know what four rounds add up to. In most languages this is a loop with a running total. qdx has no loops, so the running total becomes a parameter, and the function calls itself with the next value of it.
Add this to round.qdx, and change main.
fn round_score(first_die: int, second_die: int) -> int {
let sum = first_die + second_die;
if first_die == second_die { sum * 2 } else { sum }
}
fn total_of_rounds(rounds_left: int, total: int) -> int {
if rounds_left == 0 { total } else {
total_of_rounds(rounds_left - 1, total + round_score(rounds_left, 3))
}
}
fn main() -> int {
print(concat3("Tomas scores ", int_to_text(total_of_rounds(4, 0)), "\n"))
}To keep the example short, Tomas rolls the round number and a three each time.
bootstrap/qdx run std/*.qdx round.qdxTomas scores 28Reading it
total_of_rounds has two branches. When no rounds are left, the answer is the total so far. Otherwise it is the answer for one round fewer, with this round’s score added to the total. Each call hands the next one everything it needs, and nothing is changed along the way.
The pattern is always the same: one parameter counts down or walks along the data, another carries the result so far, and one branch stops.
It does not use up memory
A function that calls itself as the very last thing it does reuses its own stack frame. total_of_rounds works that way, so it runs ten million rounds in the same space as four.
This holds only for a function calling itself in last position. If something remains to be done after the call returns, such as 1 + count(rest), every call keeps its frame until the end, and a very long chain can run out of stack. Carrying the result in a parameter, as above, avoids that.
Scores so far are loose numbers. The next chapter ties each one to the player who made it.