Get Latest Code

πŸš€ Beginning of Lesson 3 β€” Two Options

Each lesson starts with a new machine, you need to pull the latest code first, then either resume your own progress or start from the official snapshot.

Clone your fork (replace <YOUR_GITHUB_USERNAME>):

git clone https://github.com/<YOUR_GITHUB_USERNAME>/snake-tutorial.git
cd ~/sandbox/snake-tutorial

Check out your saved progress to a branch:

git checkout -b lesson-03-work origin/lesson-02-work

βœ… You're now ready to start Lesson 3 from your own progress.

πŸ…±οΈ Option B β€” Start from Official Course Snapshot

Check out and push to your origin the starting point for Lesson 3 :

git remote add upstream https://github.com/GNAT-Academic-Program/snake-tutorial.git
git fetch upstream
git checkout -b lesson-03-work upstream/lesson-03-start
git push -u origin lesson-03-work

βœ… You're now starting Lesson 3 from the clean official reference code.

Add the Loop

Let's create a game loop! This will handle our different gameplay states and keep the game running.

  1. Edit snake_game.adb to add a loop around the existing code
  2. Add loop structure around the Noki.Log call:
    loop
       Noki.Log ("Welcome to my Snake Game!");
    end loop;
    
  3. Add a delay to slow down the loop so we can see the output:
    • On the line after Noki.Log ("Welcome to my Snake Game!") add:
    delay 1.0;
    

Your complete code inside the Snake_Game procedure should look like this:

loop
   Noki.Log ("Welcome to my Snake Game!");
   delay 1.0;
end loop;

πŸ“‹ Code Review - Complete snake_game.adb

Your complete file should look like this:

with Noki;

procedure Snake_Game is
begin
   loop
      Noki.Log ("Welcome to my Snake Game!");
      delay 1.0;
   end loop;
end Snake_Game;

Build and run:

cd ~/sandbox/snake-tutorial/noki/snake_game
alr build
bin/snake_game

❌ If something goes wrong: If you don't see Build finished successfully in ..., re-check the instructions carefully and start again from step 1.

βœ… Expected result: Welcome to my Snake Game! appearing every second over and over.

🀯 Heads Up! This program will run forever! Use Ctrl+C in the terminal to stop it.

Create a Custom Game State Type

Let's create our first user-defined Ada type! We'll use an enumeration to manage different game states.

  1. Add the type definition to the declarative part of the Snake_Game procedure:
    • After procedure Snake_Game is and before begin, add:
    type Game_State_T is (Welcome, Play, Game_Over, Undefined);
    Game_State : Game_State_T := Welcome;
    
  2. Understanding the syntax:
    • First line creates a new enumeration type Game_State_T with four possible values
    • Second line declares a variable Game_State of type Game_State_T, initialized to Welcome
    • Ada syntax: variable_name : variable_type := initial_value
  3. Replace the simple log with state handling:
    • Remove the Noki.Log ("Welcome to my Snake Game!"); line
    • Add a case statement between loop and end loop:
    case Game_State is
       when Welcome =>
          Noki.Log ("Welcome to Snakotron!");
          Game_State := Play;
       when Play =>
          Noki.Log ("We are playing.");
          Game_State := Game_Over;
       when Game_Over =>
          Noki.Log ("Game Over!");
          Game_State := Undefined;
       when others =>
          Noki.Log ("Press Ctrl+C to abort program!");
    end case;
    

πŸ“‹ Code Review - Complete snake_game.adb

Your complete snake_game.adb should look exactly like this:

with Noki;

procedure Snake_Game is
   type Game_State_T is (Welcome, Play, Game_Over, Undefined);
   Game_State : Game_State_T := Welcome;
begin
   loop
      case Game_State is
         when Welcome =>
            Noki.Log ("Welcome to Snakotron!");
            Game_State := Play;
         when Play =>
            Noki.Log ("We are playing.");
            Game_State := Game_Over;
         when Game_Over =>
            Noki.Log ("Game Over!");
            Game_State := Undefined;
         when others =>
            Noki.Log ("Press Ctrl+C to abort program!");
      end case;
      delay 1.0;
   end loop;
end Snake_Game;

Build and run:

alr build
bin/snake_game

❌ If something goes wrong: Check that your case statement syntax matches exactly, including the when clauses and semicolons.

βœ… Expected result:

Build finished successfully in ... seconds.
Welcome to Snakotron!
We are playing.
Game Over!
Press Ctrl+C to abort program!
Press Ctrl+C to abort program!
...

🀯 Heads Up! This program will run forever! Use Ctrl+C in the terminal to stop it.

Control the Game Rendering Using ANSI Escape Codes

Let's control our terminal rendering! Right now our output just appends to the terminal. For a proper game, we need to update what's displayed instead.

Quick ANSI background: ESC (ASCII 27, hex 0x1B) + [ = Control Sequence Introducer (CSI). This tells the terminal "what follows is a command, not text to print."

  1. Create a reusable CSI constant in noki.ads:
    • Before the procedure Log (S : String); line, add:
    CSI : constant String := Character'Val (16#1B#) & '[';
    
  2. Add a screen clearing function right after the CSI line:
    function Clear_Screen return String is (CSI & "2J" & CSI & "H");
    

    This builds the ANSI command: clear screen (2J) + move cursor to home (H).
  3. Use it in our game loop by opening snake_game.adb and adding this right after loop:
    Noki.Log (Noki.Clear_Screen);
    

πŸ“‹ Code Review - Complete snake_game.adb

Your complete snake_game.adb should look like this:

with Noki;

procedure Snake_Game is
   type Game_State_T is (Welcome, Play, Game_Over, Undefined);
   Game_State : Game_State_T := Welcome;
begin
   loop
      Noki.Log (Noki.Clear_Screen);
      case Game_State is
         when Welcome =>
            Noki.Log ("Welcome to Snakotron!");
            Game_State := Play;
         when Play =>
            Noki.Log ("We are playing.");
            Game_State := Game_Over;
         when Game_Over =>
            Noki.Log ("Game Over!");
            Game_State := Undefined;
         when others =>
            Noki.Log ("Press Ctrl+C to abort program!");
      end case;
      delay 1.0;
   end loop;
end Snake_Game;

Build and run:

alr build
bin/snake_game

βœ… Expected result: Instead of scrolling text, you'll see only the current message updating in place as the game state advances.

🀯 Heads Up! The terminal now clears and redraws each frame - this is the foundation of all terminal-based games!

Save Your Progress

🎯 End of Lesson 3 β€” Save Your Progress

To keep your work safe and accessible, push it to your fork.

Make sure you're at the root of the repo:

cd ~/sandbox/snake-tutorial

Make sure you're on your work branch:

git status
  • 🚨 If you're NOT on branch lesson-03-work, run:
    git checkout -b lesson-03-work
    

Stage and commit your progress:

git add .
git commit -m "Lesson 3 progress"

Push to your fork:

git push origin lesson-03-work

βœ… Expected result: Your fork now has a lesson-3-work branch with your code so far.

Level up your Server Side game β€” Join 15,000 engineers who receive insightful learning materials straight to their inbox