Building a Difficulty Curve

Introduction: The Concept of Progression

The secret to a great game is the balance between challenge and skill. If a game is too hard from the start, players get frustrated; if it stays at the same speed forever, they get bored. This is called the "Flow State." To keep players in the flow, we need a Difficulty Curve.

Our game now has all the infrastructure: menus, sound, and scoring. Now, we'll add the "hook"—making the hazards fall faster and faster as the player gets better.

Finding the Right Hook: The Milestone Check

We don't want the game to speed up randomly. We want to reward the player for reaching milestones. In our game, a milestone occurs when an obstacle is successfully dodged and recycled:

Python
            if obstacle.y > HEIGHT:
                # ... recycling logic ...
                score += 1
                point_sound.play()
                # THIS is where the magic happens!

By checking for difficulty increases inside this specific block, we ensure that the speed only changes exactly when the score does.

The Modulo Operator (`%`)

To trigger an increase every five points, we use the Modulo operator. Modulo returns the remainder of a division.

  • 5 % 5 is 0 (Milestone!)
  • 6 % 5 is 1
  • 10 % 5 is 0 (Milestone!)

By checking if score % 5 == 0:, we create a predictable curve that challenges the player at regular intervals.

Python
                if score % 5 == 0:
                    obstacle_speed += 1

Consistency: Resetting the Curve

Because we've built a restart system, we have to be careful. If a player reaches a score of 50 and the speed is now very high, hitting "Restart" should not leave the speed at that high level. It would make the game impossible for the next run!

We solve this using Named Constants. By defining INITIAL_OBSTACLE_SPEED = 4 at the top of our script, we create a "source of truth." Our reset_game() function should always return this constant to ensure every run starts fair.

The Art of Tuning

Now that the logic is done, it's time to put on your "Game Designer" hat. Tuning involves tweaking numbers until the game "feels" right.

  • PLAYER_SPEED: Increasing this makes the game feel more responsive and "arcadey."
  • INITIAL_OBSTACLE_SPEED: This sets the baseline. A lower number gives players more time to learn.
  • OBSTACLE_COUNT: This is the most dangerous dial. Adding more obstacles creates a "bullet hell" experience where there's very little room to move.

Pro-tip: When tuning, only change one number at a time! If you change the speed and the count simultaneously, you won't know which one made the game better or worse.

Conclusion

Congratulations! You've taken a simple rectangle-moving script and turned it into a polished, professional-feeling game with a title screen, replayability, music, and a dynamic difficulty curve. You now have the blueprint to build even more complex Pygame adventures!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal