Learn Python in 30 Days — Day 25: Program Structure & Planning

Day 25: Program Structure & Planning

Welcome to Day 25 of the Learn Python in 30 Days series!

Today’s lesson marks a huge mindset shift: learning how to break big problems into small functions, organise your code, and think like a programmer.

You’ve already built lots of scripts from loops to user input, file reading, JSON, and even simple classes. Now we take all of that and learn how to structure a full program so it stays readable, testable, expandable, and fun to work on.

Instead of dry examples, today we’ll use three mini projects, a battle simulator, a magic 8-ball, and a tiny text adventure to show how planning and structure make everything easier.

All example files for this series are available on my GitHub: Learn-Python-in-30-Days

Why Structure Matters

As soon as you write anything more than a few lines of code, everything becomes easier if you:

  • give each part of your program a single job

  • move repeated logic into helper functions

  • keep your main loop clean and readable

  • separate input, logic, and output

You’re not just writing Python, you’re organising ideas, behaviour, and data.

By breaking things into smaller pieces, you:

  • avoid repeated code
  • fix bugs faster
  • write more readable programs
  • reuse components later
  • prepare for bigger projects

Let’s see this in action with fun examples.

Example 1 – Tiny Monster Battle Simulator

We start with something most beginners write as a single messy block, a random damage battle.

You can see everything is lumped together:
import random print("Battle Start!") hp = 20 monster_hp = 15 while hp > 0 and monster_hp > 0: dmg = random.randint(1, 6) monster_hp -= dmg print("You hit the monster for", dmg) if monster_hp <= 0: break mdmg = random.randint(1, 4) hp -= mdmg print("The monster hits you for", mdmg) print("You win!" if hp > 0 else "Monster wins!")

Whilst this works, but imagine adding spells, special moves, or different monsters.
It instantly becomes unmanageable.

Try it yourself, hopefully you'll see something like that shown below: -

You can also download this example from my GitHub here and run it yourself.

Now lets see a structured version of the same code split into clean pieces: -
import random def roll_damage(min_dmg, max_dmg): return random.randint(min_dmg, max_dmg) def player_turn(monster_hp): dmg = roll_damage(2, 6) monster_hp -= dmg print(f"You deal {dmg} damage!") return monster_hp def monster_turn(player_hp): dmg = roll_damage(1, 4) player_hp -= dmg print(f"The monster hits you for {dmg}!") return player_hp def print_status(player_hp, monster_hp): print(f"Player HP: {player_hp} | Monster HP: {monster_hp}\n") def main(): player_hp = 20 monster_hp = 15 print("=== Tiny Monster Battle ===") while player_hp > 0 and monster_hp > 0: monster_hp = player_turn(monster_hp) print_status(player_hp, monster_hp) if monster_hp <= 0: break player_hp = monster_turn(player_hp) print_status(player_hp, monster_hp) print("🎉 You win!" if player_hp > 0 else "💀 The monster defeated you!") main()

Try it yourself, hopefully you'll see something like that shown below: -

You can also download this example from my GitHub here and run it yourself.

Why this structure is better

1. Each function has ONE job

  • roll_damage() → handles randomness

  • player_turn() → updates monster HP

  • monster_turn() → updates player HP

  • print_status() → handles formatting/output

  • main() → controls the game flow

If something breaks, you instantly know which function to fix.

2. Your main loop is readable

Instead of a wall of code, you get:

monster_hp = player_turn(monster_hp) player_hp = monster_turn(player_hp)

This reads like a story.

3. Easy to extend

Want spells, crit hits, or healing potions?
Add new functions without touching the rest.

Example 2 – Magic 8-Ball with Good Structure

Perfect for showing how even simple programs benefit from functions.

First the unstructured version: -

import random answers = ["Yes", "No", "Ask again", "Definitely", "Not sure"] q = input("Ask your question: ") print(random.choice(answers))

Fast, fun, but very limited and hard to extend.

Try it yourself, hopefully you'll see something like that shown below: -

You can also download this example from my GitHub here and run it yourself.

Now lets try the structured version: -
import random def get_question(): return input("Ask the Magic 8-Ball a question: ") def get_random_answer(): answers = [ "Yes!", "No!", "Ask again later...", "It is certain.", "Very doubtful.", "Absolutely!", "Not today." ] return random.choice(answers) def print_answer(answer): print("\n✨ The Magic 8-Ball says…") print("👉", answer) def main(): question = get_question() answer = get_random_answer() print_answer(answer) main()

Try it yourself, hopefully you'll see something like that shown below: -

You can also download this example from my GitHub here and run it yourself.

Why this is better

Clear separation of roles: -
  • Input: get_question()

  • Logic: get_random_answer()

  • Output: print_answer()

  • Flow control: main()

Perfect demonstration of the “program layers” concept.

Easy to expand, want:
  • A silly mode?

  • A “rude answers” mode?

  • A “wise Yoda answers” mode?

Just modify one function.

Less repetition all formatting handled in one place.

Example 3 – Tiny Text Adventure Room Navigator

This is a brilliant stepping stone toward the final project on Days 28–30.

This shows:

  • data-driven design

  • helper functions

  • keeping main loop clean

  • how to grow complexity safely

First the unstructured version: -
room = "start" while True: if room == "start": print("You are in a quiet room. Exits: north") cmd = input("> ") if cmd == "north": room = "forest" elif room == "forest": print("You are in a dark forest. Exits: south") cmd = input("> ") if cmd == "south": room = "start"

Hardcoded rooms = hardcoded pain.

Next well look at the structured, scalable version.

First, put the world in a dictionary:
rooms = { "start": { "desc": "You are in a quiet room.", "exits": {"north": "forest"} }, "forest": { "desc": "You are in a dark forest.", "exits": {"south": "start"} } }

This is data, not code, improvement would be storing this in a JSON file and removing it from the code all together.

Then create helper functions:
def describe_room(name): data = rooms[name] print(data["desc"]) print("Exits:", ", ".join(data["exits"].keys())) def move(room, direction): exits = rooms[room]["exits"] if direction in exits: return exits[direction] else: print("You can't go that way.") return room
And finally the clean main loop:
def main(): room = "start" while True: describe_room(room) cmd = input("> ") room = move(room, cmd) main()

Try it yourself, hopefully you'll see something like that shown below: -

You can also download this example from my GitHub here and run it yourself.

Why this works so well

  • The data drives the game, you can add 20 rooms by editing one dictionary — no new if/elif chains.
  • The logic is reusable move() doesn’t care about the room design — it only uses the data.
  • The main loop is 3 lines, nice, simple and neat.

Next Up — Day 26 – Testing & Debugging

Tomorrow, you’ll look print() debugging, tracing logic, catching errors cleanly.

All example files for this series are available on my GitHub: Learn-Python-in-30-Days
You can see the full series here Learn Python in 30 Days series!

Hope you have enjoyed this post, thanks Matty

Comments

Popular posts from this blog

6502 - Part 2 Reset and Clock Circuit

Math Behind Logic Gates

Building a 6502 NOP Test