Skip to content
wannacode

How to think about a game

Problem

Where does a game start?

You want to write a game. Say, a dice race: a pawn starts on square 0, you roll a die and it moves forward until it reaches square 20.

You open the editor and... what do you write first? The menu? The die? The board? If you start with everything at once, an hour later you have a lot of code that still does not run.

It helps to ask the game five questions before you write any code.

The idea

Five questions for every game

  1. State. Which numbers describe the world right now? In the race it is the pawn's square and the turn number. In chess it is where every piece stands and whose turn it is.
  2. Rules. What is allowed, and what happens after it? "The pawn moves as many squares as the die shows."
  3. Input. What comes from outside? The player's choice or the number on the die.
  4. Loop. Turn after turn: input, update the state, show it. Then again.
  5. End. When is the game over, and who wins? "The pawn is on square 20."

The answers are almost the finished code: the state is variables, the rules are methods, the input is Scanner or Random, the loop is while or for, and the end is the loop's condition.

See it

Turn after turn

Random random = new Random(42);
int field = 0;
for (int turn = 1; field < 20; turn++) {
int roll = random.nextInt(6) + 1;
field = Math.min(field + roll, 20);
System.out.println("Turn " + turn + ": roll " + roll + ", square " + field);
}
System.out.println("Finish! The pawn reached square 20.");
Step by step, in words
  1. A generator with seed 42 and a pawn on square 0.
  2. Turn 1: the input is a roll of 3, the state becomes square 3, the screen shows the line.
  3. Turn 2: the input is a roll of 4, the state becomes square 7, the screen shows the line.
  4. Turn 3: the input is a roll of 1, the state becomes square 8, the screen shows the line.
  5. Turn 4: the input is a roll of 3, the state becomes square 11, the screen shows the line.
  6. Turn 5: the input is a roll of 1, the state becomes square 12, the screen shows the line.
  7. Turn 6: the input is a roll of 2, the state becomes square 14, the screen shows the line.
  8. Turn 7: the input is a roll of 6, the state becomes square 20, the screen shows the line. Square 20: the condition field < 20 is now false.
  9. The loop is over because the square reached 20. That is the end of the game.

The whole game is one loop. Each turn, input comes in (the die), the state is updated (the new square) and shown (one line on the screen). The loop's condition is the end: while the square is below 20, there is another turn.

Math.min keeps the pawn on the board: if the die sends it past 20, it stops on 20.

More precisely, if you are curious
  • This game is turn-based: nothing happens until the next input comes. Chess, card games and most of the games in this section work like that.
  • Real-time games (shooters, car races) also have a loop, but it does not wait for the player. It runs 60 times a second: it reads the keys that are pressed right now, moves everything by the time that has passed and draws the frame. If the player presses nothing, the world still moves.

The idea

Same seed, same game

If the rolls are truly random, every run is a different game. Then how do you check that the game works? You do not know what to expect.

That is why in these lessons Random gets a starting number, a seed. The same seed gives the same sequence, every time:

Random first = new Random(42);
Random second = new Random(42);
for (int i = 0; i < 5; i++) {
int a = first.nextInt(6) + 1;
int b = second.nextInt(6) + 1;
System.out.println(a + " and " + b);
}
Output
3 and 3
4 and 4
1 and 1
3 and 3
1 and 1

The two generators with 42 give the same numbers. Run the program again and they will be the same. That is why the examples and exercises here have exact output that can be checked.

Look at the output in the other tabs too. Each language has its own generator: from the same seed they give different numbers. That is why examples with random numbers have output for each language.

More precisely, if you are curious
  • When the game is ready for real players, you remove the seed: new Random(). Then the generator picks a seed from the clock by itself, and every game is different.
  • A common practice is to print the seed at the start of the game. If a player finds a bug, you can play exactly the same game with their seed.

The idea

The smallest working version first

The program from the scene is a whole game in about ten lines. It has no second player, no menu and no rules except "move forward". But it runs from start to end, and it shows that the loop and the end are right.

From there the game grows one rule at a time. The second rule: the pawn has to land on square 20 exactly. If the die sends it further, it bounces back by as many squares as it went over. From 17 with a roll of 5 it reaches 20 and goes back to 18.

You add a new rule only when the previous one works. If something breaks, you know it is in the last thing you wrote.

The idea

Logic returns, the screen prints

The "exactly 20" rule lives in a method that returns the new square. The end is a method that returns true or false:

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
Random random = new Random(7);
int field = 0;
int turn = 0;
while (!isFinished(field)) {
turn++;
int roll = random.nextInt(6) + 1;
field = move(field, roll);
System.out.println("Turn " + turn + ": roll " + roll + ", square " + field);
}
System.out.println("Exactly 20 after " + turn + " turns");
Output
Turn 1: roll 5, square 5
Turn 2: roll 3, square 8
Turn 3: roll 4, square 12
Turn 4: roll 5, square 17
Turn 5: roll 5, square 18
Turn 6: roll 5, square 17
Turn 7: roll 5, square 18
Turn 8: roll 6, square 16
Turn 9: roll 1, square 17
Turn 10: roll 1, square 18
Turn 11: roll 1, square 19
Turn 12: roll 1, square 20
Exactly 20 after 12 turns

Neither move nor isFinished prints anything. They only calculate. The printing is in main.

Why do it this way? You can call a method that returns a number with any values you like and check the result: move(17, 5) has to be 18. A method that prints can only be checked by eye. And if tomorrow the game moves from the console to a window, the rules stay the same; only the display changes.

Try it

Where will the pawn land?

What will the program print?

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
System.out.println(move(10, 4));
System.out.println(move(17, 3));
System.out.println(move(18, 5));
Answer

Try it

Find the bug

The pawn should move forward, but the program prints square 0 on every turn:

Output
Turn 1: roll 5, square 0
Turn 2: roll 3, square 0
Turn 3: roll 4, square 0

Which line is the bug on?

Pick the line with the bug.

Try it

When does the game end?

What is missing so that the end is exactly on square 20?

static boolean isFinished(int field) {
    return field  20;
}

Exercise

Three small games

Solve these in your editor. The logic goes in methods that return a value, and the printing goes in main.

1. A race for two. Ana and Boris take turns rolling, with new Random(3). The "exactly 20" rule applies, with move from the lesson. Their squares are in an array int[2]. Whoever reaches 20 first wins.

Output
Ana: roll 3, square 3
Boris: roll 3, square 3
Ana: roll 1, square 4
Boris: roll 2, square 5
Ana: roll 1, square 5
Boris: roll 1, square 6
Ana: roll 4, square 9
Boris: roll 5, square 11
Ana: roll 2, square 11
Boris: roll 2, square 13
Ana: roll 4, square 15
Boris: roll 5, square 18
Ana: roll 6, square 19
Boris: roll 5, square 17
Ana: roll 4, square 17
Boris: roll 5, square 18
Ana: roll 1, square 18
Boris: roll 4, square 18
Ana: roll 5, square 17
Boris: roll 2, square 20
Boris wins

2. Guess the number. The program secretly picks a number from 1 to 100 with new Random(2026). The player types guesses, one per line. After each one the program says "Higher", "Lower" or "Correct". At the end it says how many tries it took. The hint is a method String hint(int guess, int secret) that returns the text.

Input
50
75
88
94
97
99
100
Output
50: Higher
75: Higher
88: Higher
94: Higher
97: Higher
99: Higher
100: Correct
Guessed in 7 tries

The input is different for each language, because their generators pick a different secret number from the same seed.

3. Who won at tic-tac-toe? The first line is the number of boards. Each board is three lines of three characters: X, O or . for an empty square. Write a method char winner(char[][] board) that returns X, O, or . if there are no three in a row, column or diagonal.

Input
3
XOX
OXO
OOX
XXO
OOO
X.X
XOX
XOO
OXX
Output
X wins
O wins
No winner

More exercises

  • Traps. Square 13 sends the pawn back to square 5. Add the rule to move and run the race again.
  • Print the seed. Make the seed a variable, print it at the start and run the game with three different seeds.
  • Tic-tac-toe for two. The players type the row and column of their move. After each move, show the board and use winner to check whether the game is over.

Test

Check yourself

1. What is the state of the dice race?
2. Why does the game say new Random(42) and not new Random()?
3. Why is the move rule a method that returns the new square, and not a method that prints?
4. What is a good first version of a game?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·