top of page

Building Super Farmer in Python: A Conclusion to the TDD Journey

Introduction

After weeks of coding, designing, and testing, my Python Super Farmer project has reached its conclusion, and I couldn’t be more excited to share the final outcome.


What started as a TDD (Test-Driven Development) experiment quickly evolved into an exploration of game design, algorithmic challenges, and UI flexibility.


As a QA by trade, I don’t consider myself a professional programmer with immense coding skills. But with TDD, I’ve found a reliable way to write cleaner, more maintainable code, breaking down the complexity that an experienced developer might tackle with ease. It was this methodical approach that helped me stay on track and avoid tangled code as the project grew.


In this final post, I’ll walk through the journey, key takeaways, and my decision to use a Flask-based UI to bring the game to life.


Recap: The Super Farmer Concept and TDD Approach

At the beginning of this journey, I set out to recreate Super Farmer, a classic board game, in Python using Test-Driven Development (TDD). The idea was not only to practice building a game but also to follow a structured approach that ensured every feature was backed by corresponding tests.


Why TDD?

TDD was chosen because it's a powerful methodology for iterative development, especially when building systems with clear, testable mechanics. As a QA, this approach helped me write better code by:

  • Breaking down game mechanics into manageable units like dice rolls, animal exchanges, and win conditions.

  • Writing tests before coding features, ensuring each functionality was fully covered before moving forward.

  • Refactoring code confidently, knowing that tests would catch any regressions or issues.


While Super Farmer may seem like a simple game on the surface, as I started developing the mechanics—such as animal trading and dice outcomes—it quickly became more complex than anticipated. TDD allowed me to manage this complexity by focusing on small, testable chunks at each stage.


From Tests to Gameplay: Implementing Super Farmer

Once the core game mechanics were in place, thanks to TDD, the focus shifted toward making the game playable and user-friendly. I had already covered:

  • Dice Mechanics: Implementing two 12-sided dice to generate animal outcomes.

  • Animal Trading: Managing the logic for trading animals based on dice rolls and the player’s farm inventory.

  • Win Condition: Ensuring players need to collect one of each type of animal to win.


Each feature was carefully built and validated through the TDD cycle: write a test, make it pass, then refactor if necessary.


The User Interface: Choosing Flask over Tkinter

Initially, I considered using Tkinter for the game's UI. It seemed like an obvious choice for a basic desktop application. However, as the game's complexity grew and user interaction became a larger focus, I realized a web-based UI would offer more flexibility and scalability.


Why Flask?

After experimenting with Tkinter, I found it somewhat limiting in terms of:

  • Layout flexibility: Tkinter’s GUI works for simple applications but isn’t as responsive or customizable as a web interface.

  • Accessibility: Flask runs in the browser, making the game accessible on multiple devices.

  • Future scalability: With Flask, I can expand the game to include online multiplayer or host it on a server—possibilities that are difficult with Tkinter.


Building the Flask-Based UI

Switching to Flask allowed me to create a clean, responsive web interface where players can:

  • Roll dice via buttons and see dynamic results.

  • View their farm's status in a visual format, showing how many animals they currently own.

  • Trade animals through an intuitive interface.

  • Track progress toward winning the game, with indicators for how many animals they still need.

Here’s an example of how I integrated Flask into the game:

```python 
app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY')

# Create an instance of GameManager
game_manager = GameManager()

def get_game_manager():
    return game_manager

@app.route('/')
def main_menu():
    """Render the main game page."""
    return render_template('main_menu.html')

@app.route('/start-game', methods=['POST'])
def start_game():
    """Start the game with the given player names."""
    data = request.get_json()
    player_names = data.get('player_names', [])
    ref_game_manager = get_game_manager()

    if len(player_names) == len(set(player_names)) and all(player_names):  # Ensure no empty or duplicate names
        # Store player names (which are serializable) in the session
        session['player_names'] = player_names

        # Initialize the GameManager with the player names
        for index, name in enumerate(player_names):
            ref_game_manager.players.append(Player(name, index))

        # Redirect to the game page
        return redirect(url_for('game'))

    return jsonify(error="Invalid player names, please try again."), 400

@app.route('/game')
def game():
    """Render the game page."""
    ref_game_manager = get_game_manager()

    if not ref_game_manager.players:
        return redirect(url_for('main_menu'))

    # Get player names from session to pass to the game page
    players = session.get('player_names', [])

    return render_template('game.html',
                           players=players,  # Player names to display in the game
                           herd=ref_game_manager.main_herd.get_herd(),  # Main herd to display in the game)

@app.route('/roll-dice', methods=['POST'])
def roll_dice_for_current_player():
    """Route to roll the dice for the current player."""
    player_index = game_manager.current_player_index  # Get the current player's index
    current_player = game_manager.players[player_index]  # Fetch the current player object

    print(f"Processing dice roll for player: {current_player.name}")

    # Process the dice roll
    result_green, result_red = roll_dice()
    game_manager.process_dice(current_player, result_green, result_red)

    # Prepare data to send back to the client (before updating the player index)
    response_data = {
        'green': result_green,
        'red': result_red,
        'current_player_index': player_index,  # Send the current player index (before updating it)
        'player_herd': current_player.get_herd(),  # Player herd after rolling
        'main_herd': game_manager.main_herd.get_herd()  # Main herd
    }

    # Update to the next player
    game_manager.current_player_index = (player_index + 1) % len(game_manager.players)
    print(f"Next player will be: {game_manager.players[game_manager.current_player_index].name}")

    return jsonify(response_data)
```

This setup allowed me to:

  • Use HTML templates for a simple yet flexible UI layout.

  • Add buttons and forms for user interaction.

  • Handle game state updates via Flask’s routing and form handling.


Why I Ditched TDD for Frontend Development

While TDD worked exceptionally well for implementing the backend logic, once I started working on the UI, I quickly realized how hard it is to apply TDD to frontend development. Testing frontend interactions in a web-based UI often requires JavaScript, which I wasn’t as fluent in, and adding another framework to handle this complexity felt like overkill.


I found that Python with Flask worked beautifully for backend-driven UI rendering, but trying to test frontend interactions like button clicks, form submissions, and dynamic page updates would have required tools and frameworks outside of my Python comfort zone. So, instead of focusing on TDD for the frontend, I shifted to building the UI in an iterative fashion, testing manually as I went along.

Despite ditching TDD for the frontend, I was confident that the core game logic was solid thanks to the thorough testing I had done earlier in the process. If something broke, I knew it was likely in the frontend or integration layer, which narrowed down my debugging efforts.




Challenges and Lessons Learned

Throughout this project, I encountered a few challenges and learned some key lessons:

  1. TDD is Ideal for Complex (Game) Logic: Implementing game mechanics in a structured way—especially in a game like Super Farmer—benefits enormously from TDD. The ability to write tests for each rule or logic branch made development far more manageable and reduced bugs later.

  2. Confidence in Core Logic: Even in the later stages, when I started ditching TDD for new features, I was still confident that the core game logic was solid. Since the backend mechanics had been thoroughly tested, it narrowed the search for bugs to the frontend or integration layer whenever something broke. This saved me countless hours in debugging.

  3. UI Flexibility is Crucial: Choosing the right UI framework makes all the difference. I underestimated how much flexibility I would need for the game’s UI. Switching to Flask opened up many possibilities, from responsive design to potential future features like multiplayer.

  4. Refactoring is Less Stressful with Tests: Refactoring game logic, especially when adding new features, was far less stressful thanks to TDD. The test suite caught issues early, ensuring nothing broke unexpectedly.

  5. UI Development Takes Time: One of the key lessons was that adding a UI layer—no matter how simple—always takes longer than expected. Flask helped speed things up, but creating a smooth user experience still required time for design and testing.


Conclusion: The Journey of Building Super Farmer

Building Super Farmer in Python using TDD has been a rewarding experience. TDD helped me navigate the complexities of game logic, while Flask provided a flexible, web-based UI that set the stage for future enhancements, like multiplayer functionality or online hosting.

This project has taught me valuable lessons about breaking down complexity, choosing the right tools for the job, and managing time effectively. I hope this series inspires others to build their own games using Python and TDD, or to explore the possibilities of web-based UIs with Flask.


Call to Action

Have you tried building games using Python and TDD? Or used Flask for game UIs? Share your experiences in the comments—I’d love to hear about your projects and any lessons you’ve learned along the way!


Further Reading

  • Test-Driven Development with Python by Kent Beck – A great resource for developers getting started with TDD.

  • Flask Documentation – Get up to speed with Flask’s powerful yet simple framework.

  • Common Pitfalls in TDD – Learn how to avoid common mistakes, like over-reliance on tests that pass even when requirements change.

Comments


Subscribe to QABites newsletter

Thanks for submitting!

  • Twitter
  • Facebook
  • Linkedin

© 2023 by QaBites. Powered and secured by Wix

bottom of page