← Back to Python series
🛠
Practice Projects · ★
Random · Input · Loops · Hint logic

Project 2 — Number Guessing Game

Guess the secret number with too-high/too-low hints. Add difficulty levels (attempts limit) and a high-score leaderboard.

whilerandominputint
Duration
1–2 hours
Level
📊 Basic Applied
Prerequisite
🎯 Basic Weeks 4–5
OUTCOME
An interactive guessing game with difficulty levels and a leaderboard

What you'll learn

  • 1Generate a secret number with random.randint
  • 2Count attempts and enforce a limit by difficulty
  • 3Give meaningful feedback on each guess
  • 4Track and display high scores

Project Overview

  • Three difficulty levels: Easy (1–50, 10 tries), Normal (1–100, 7 tries), Hard (1–200, 5 tries)
  • Hints: Too high / Too low / Very close (within 5)
  • Score = remaining attempts × difficulty multiplier
  • Keep a top-5 leaderboard for the session

Sample Session

text
Difficulty (1=Easy 2=Normal 3=Hard): 2
Guess the number (1–100), 7 attempts.
Guess: 50  →  Too low
Guess: 75  →  Too high
Guess: 62  →  Very close!
Guess: 63  →  Correct! (4 attempts, score=30)

--- Leaderboard ---
1. Alice  30
2. Bob    24

Design — Where State Lives

Two ideas carry this game. First, keep settings like difficulty in one place (a dict) instead of branching with if/elif. Second, wrap a single round in its own function so the main loop only orchestrates: play, show the board, ask to play again. That keeps the code easy to extend with scores, stats, or tests later.

  • LEVELS dict — maps each difficulty to (max_number, tries, multiplier). Adding a level never adds a branch.
  • play(leaderboard) — generates the secret, gives hints, counts attempts, and records the score on a win.
  • show_board(lb) — sorts and prints the top 5; sorting happens at display time, not on every append.
  • the main loop — only decides: play a round, show the board, play again? It doesn't know the details (separation of concerns).
💡

Have functions return values (a score, a result) instead of only printing — it makes adding a leaderboard or automated tests far easier.

Reading the Key Code

1) Difficulty as a dict — data, not branches

Branching difficulty with if/elif grows every time you add a level. Pack (max, tries, multiplier) into a dict and pull it out in one line.

python
LEVELS = {1: (50, 10, 1), 2: (100, 7, 2), 3: (200, 5, 3)}
hi, tries, mult = LEVELS.get(d, LEVELS[2])   # default to Normal
secret = random.randint(1, hi)               # both ends inclusive

2) Attempt loop with a 'very close' hint

range(1, tries + 1) numbers the attempts 1..tries. Compare the guess to the secret; when the gap is small (<= 5) give a warmer 'very close' hint before the plain high/low.

python
for attempt in range(1, tries + 1):
    guess = int(input(f"Guess [{tries-attempt+1} left]: "))
    if guess < secret:
        print("  Too low!" if secret - guess > 5 else "  Very close! (low)")
    elif guess > secret:
        print("  Too high!" if guess - secret > 5 else "  Very close! (high)")
    else:
        score = (tries - attempt) * mult * 10
        return score

3) Leaderboard — sort on display

Append (name, score) as the game runs, then sort only when you print. Sorting by -score gives descending order; slicing [:5] keeps the top five.

python
def show_board(lb):
    for i, (n, s) in enumerate(sorted(lb, key=lambda x: -x[1])[:5], 1):
        print(f"  {i}. {n:<15} {s}")
⚠️

int(input()) raises ValueError on non-numeric input. Wrap it in try/except and re-prompt, and decide whether a bad entry should cost an attempt (usually it shouldn't).

Common Mistakes (FAQ)

Q. Does random.randint(1, 100) include 100?

Yes — both ends are inclusive (1 and 100 are both possible). Use random.randrange(1, 100) if you want to exclude the upper bound.

Q. The program crashes when I type letters.

int(input()) raises ValueError. Wrap it in try/except ValueError and continue so the game re-prompts instead of crashing.

Q. How is 'Very close' decided?

By the gap: abs(secret - guess) <= 5. Check it inside the too-low / too-high branch so you still tell the player the direction.

Q. My leaderboard isn't sorted.

Sort at display time with sorted(lb, key=lambda x: -x[1])[:5]. Sorting on every append is wasteful and easy to get wrong — keep append cheap and sort once when showing the board.

Wrap-up

The takeaways are the same three you'll reuse in the next projects (calculator, to-do): reduce branches with data (a dict), wrap one unit of work in a function that returns a result, and never trust raw input.

  • Keep settings in a dict so new levels need no new branches
  • Return values (scores, results) instead of only printing
  • Validate int conversions with try/except and decide the attempt-cost policy

💻 Examples

Run these examples and check the output yourself.

solution.pyNumber guessing game with difficulty & leaderboard
CODE
import random

LEVELS = {1: (50, 10, 1), 2: (100, 7, 2), 3: (200, 5, 3)}

def play(leaderboard):
    d = int(input("Difficulty (1=Easy 2=Normal 3=Hard): "))
    hi, tries, mult = LEVELS.get(d, LEVELS[2])
    secret = random.randint(1, hi)
    print(f"Guess the number (1–{hi}), {tries} attempts.")
    for attempt in range(1, tries + 1):
        guess = int(input(f"Guess [{tries-attempt+1} left]: "))
        if guess < secret:
            print("  Too low!" if secret-guess > 5 else "  Very close! (low)")
        elif guess > secret:
            print("  Too high!" if guess-secret > 5 else "  Very close! (high)")
        else:
            score = (tries - attempt) * mult * 10
            print(f"  Correct! ({attempt} attempts, score={score})")
            name = input("  Your name: ")
            leaderboard.append((name, score))
            return
    print(f"  Out of attempts! The answer was {secret}.")

def show_board(lb):
    print("\n--- Leaderboard ---")
    for i, (n, s) in enumerate(sorted(lb, key=lambda x: -x[1])[:5], 1):
        print(f"  {i}. {n:<15} {s}")

lb = []
while True:
    play(lb)
    show_board(lb)
    if input("\nPlay again? (y/n): ").lower() != "y": break

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Build the guessing game

Goal: Implement difficulty levels, hints, and a session leaderboard.

Requirements
  • 3 difficulty levels with different ranges and attempt limits
  • Too-high/too-low/very-close hints
  • Score calculation
  • Top-5 leaderboard
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗