Project: Guessing Game
This is the capstone project for Phase 1. Over the first four chapters, you've learned about variables, operators, functions, collections, and control flow. Now it's time to put all of those skills together by building a complete interactive program: a number guessing game.
Here's how the game works: the program picks a secret number between 1 and 100. The player types in a guess, and the program says "Too small!" or "Too big!" until the player gets it right. We'll build this in four stages, adding one feature at a time:
- Reading input -- get a guess from the player and print it
- Adding a loop -- let the player guess repeatedly with feedback
- Random numbers -- pick a different secret number each game
- Error handling -- deal with invalid (non-number) input gracefully
Setting Up
Create a new Auto project for this chapter:
$ automan new guessing_game
$ cd guessing_gameOpen src/main.at in your editor. This is where you'll write all the code. Every version of the game we build will live in this single file.
Reading Input
The first version of the game doesn't play yet -- it just asks the player for input and echoes it back. This lets us learn two essential built-in functions: input() and print().
Let's walk through this line by line.
The print Function
print("Guess the number!")print is a built-in function that writes text to the terminal. It takes a single str argument and adds a newline at the end. We use it twice: once to announce the game, and once to prompt the player.
The input Function
var guess = input()input() is a built-in function that reads one line of text from the player and returns it as a str. The program pauses here and waits for the user to type something and press Enter. We store the result in guess, declared with var because we'll be reassigning it in later versions.
String Concatenation with +
print("You guessed: " + guess)The + operator concatenates two strings. Auto infers that the result is also a str. If you wrote "a" + 42, the compiler would report a type error -- you need to convert the number to a string first. We'll see how to do that with parse_int in the next section.
Running the Program
$ automan run
Guess the number!
Please input your guess.
76
You guessed: 76The program prints the prompts, waits for your input, and echoes it back. Not much of a game yet, but the foundation is in place.
Notice how each target language handles user input differently:
- Rust uses a custom
read_line()function. The result is aStringthat the transpiler manages. - Python maps directly to the built-in
input()function. This is the closest match -- Python'sinput()does exactly what Auto'sinput()does. - C uses
fgets()to read into a fixed-size buffer, then strips the trailing newline withstrcspn. Manual memory management is required. - TypeScript uses the
readlinemodule with a callback-based API. The callback receives the user's input as astring.
Adding a Loop
Right now the player gets one guess and the program exits. A real game needs to keep asking until the player wins. We also need to convert the string input to a number so we can compare it with the secret number, and give feedback about whether the guess is too high or too low.
There's a lot of new material here. Let's break it down.
The for true Infinite Loop
for true {
// ...
}for true creates an infinite loop -- it runs until something inside it explicitly stops it. In Auto, for is the only loop keyword, and it handles all looping patterns. for true means "loop forever." The transpiler maps this to loop in Rust, while True in Python, while (1) in C, and a recursive function in TypeScript.
The parse_int Function
var guess = parse_int(guess_str)input() returns a str, but we need a number to compare with the secret. parse_int converts a string to an integer. If the string is "76", it returns the integer 76. Auto infers that guess has type int.
Note: in this version, if the player types something that isn't a number (like "hello"), parse_int will crash the program. We'll fix that in the final version.
Comparison Operators and if/else
if guess == secret {
print("You win!")
break
}
if guess < secret {
print("Too small!")
} else {
print("Too big!")
}We use == for equality, < for less-than. The if statement runs its block when the condition is true. The else block runs when the if condition is false.
The break keyword exits the loop immediately. When the player guesses correctly, we print "You win!" and then break, which jumps to the code after the for block -- and since there's nothing after it, the program ends.
Running the Program
$ automan run
Guess the number!
10
You guessed: 10
Too small!
50
You guessed: 50
Too big!
42
You guessed: 42
You win!The game works. But there's a problem: the secret number is always 42. If you play twice, you already know the answer. Let's fix that.
Random Numbers
To make the game replayable, we need a different secret number each time. The built-in function random_int(min, max) generates a random integer in the given range, inclusive on both ends.
The only change from Listing 5-2 is the first line inside main:
var secret = random_int(1, 100)Instead of hard-coding 42, we call random_int(1, 100) to get a random integer between 1 and 100. The type of secret is inferred as int. We also update the opening message to tell the player the range.
Now each time you run the program, you get a different secret number. Try it a few times:
$ automan run
Guess the number between 1 and 100!
50
You guessed: 50
Too big!
25
You guessed: 25
Too small!
37
You guessed: 37
You win!Much better. But there's still one more issue to fix.
Error Handling
What happens if the player types something that isn't a number? Right now, parse_int("hello") crashes the program. That's not a great user experience. Instead, we should detect the bad input, print a message, and let the player try again.
In Auto, parse_int returns an optional integer: ?int. An optional value is either a real integer or none (meaning "no value"). We can check for none to handle the error case.
This is the final version of the game. Let's look at what's new.
Optional Types and none
var guess = parse_int(guess_str)
if guess == none {
print("Please enter a valid number!")
continue
}parse_int now returns ?int (read "optional int") instead of plain int. An optional type means the value can either be an integer or none. When the player types "hello", parse_int returns none -- there's no valid integer to extract. When the player types "42", it returns 42.
We check guess == none to detect the error case. If the input was invalid, we print a message and use continue to skip the rest of the loop body and start the next iteration, asking the player again.
In Phase 2, you'll learn about Auto's full error handling with
!Tand the unwrap operator. For now, checking== noneis the basic way to handle optional values.
The continue Keyword
continue skips the rest of the current loop iteration and goes back to the condition check. Combined with for true, it effectively means "start the loop over from the top." This is perfect for skipping bad input without counting it as a guess.
Counting Attempts
var attempts = 0
// ...
attempts = attempts + 1We added an attempts counter that starts at 0 and increments by 1 each time the player enters a valid guess. When the player wins, we include the count in the victory message. Note that we only increment attempts after the none check, so invalid inputs don't count.
Running the Final Version
$ automan run
Guess the number between 1 and 100!
Please input your guess.
hello
Please enter a valid number!
Please input your guess.
50
You guessed: 50
Too big!
Please input your guess.
25
You guessed: 25
Too small!
Please input your guess.
37
You guessed: 37
You win! You got it in 3 tries!The game handles all the edge cases: non-number input, too-high guesses, too-low guesses, and correct guesses. It counts attempts, gives feedback, and exits cleanly when the player wins.
Look at how each target language handles the "invalid input" check:
- Rust uses
Option<i64>with.ok()(convertsResulttoOption),.is_none()for the check, and.unwrap()to extract the value after the check. - Python uses a
try/except ValueErrorblock. Ifint()fails on bad input, it raisesValueError, and theexceptbranch handles it. - C uses
strtoll()which setsendptrto the first non-numeric character. If*endptr != '\0', the input contained non-digit characters. - TypeScript checks
isNaN(guess)afterparseInt(). If the input wasn't a valid number,parseIntreturnsNaN(Not a Number).
Summary
Congratulations -- you've built a complete interactive game in Auto! This project brought together everything you learned in Phase 1:
- Variables (
var) --secret,attempts,guess,guess_strall usevarto hold values that change during the game - Functions (
fn) -- the entire game lives inmain(), and we called built-in functions likeprint(),input(),parse_int(), andrandom_int() - Operators --
==,<,+for comparison and concatenation - Control flow --
if/elsefor branching,for truefor looping,breakto exit,continueto skip - Types --
int,str,?int(optional int), with the compiler inferring types from context - Collections -- not used directly in this game, but you now know arrays, objects, maps, and sets from Chapter 4
This is the end of Phase 1. You now have a solid foundation in Auto's core syntax and semantics. In Phase 2, we'll go deeper:
- Phase 2 introduces
letfor immutable bindings, theiskeyword for pattern matching, custom types withtype, extension methods withext, and full error handling with!T. - Phase 3 covers generics, interfaces, concurrency, and more advanced language features.
Take a moment to review what you've learned. Try modifying the guessing game on your own: change the range, add a maximum number of attempts, or print a different message depending on how quickly the player wins. You have all the tools you need.