
Introduction:
Welcome to the Super Farmer Python Series, where we embark on an exciting journey beyond the realms of pytest. In this inaugural post, we'll lay the foundation by exploring the requirements of our project – the creation of the Super Farmer Game. From game rules to its intriguing history, we'll delve into the essentials before diving into Python code.
Section 1: Understanding the Super Farmer Game
Super Farmer is a classic board game that originated in Poland during World War II and was created by mathematician Karol Borsuk. It involves collecting and exchanging different animals and avoiding predators.
The game is suitable for 2 to 6 players, aged 7 and above, and takes about 15 to 30 minutes to play.
The game consists of 12-sided dice with animal symbols, cardboard tokens of different animals and dogs, and a rulebook.
The goal of the game is to collect at least one of each animal: rabbit, sheep, pig, cow, and horse, and become the super farmer.
On each turn, the player rolls the dice and either gains, loses, or exchanges animals with the main herd or other players, depending on the outcome.
The player has to watch out for the fox and the wolf, which can attack and steal the animals, unless the player has a small or a big dog to protect them.
The game is based on a mathematical model of animal breeding and exchange, and teaches the players about counting, probability, and strategy.
The game has a historical and cultural significance, as it was created during the Nazi occupation of Poland and helped people cope with the hardships of war.
The game has been reissued several times, with different editions and variations, such as Super Farmer Deluxe, Super Farmer Card Game, and Super Farmer with Stork
Section 2: Unveiling Our Python Project
Connecting Super Farmer to Python development
First, we need to decide how we want to implement the game logic and the user interface. Do we want to make a command-line version, a graphical version, or a web-based version of the game? Depending on the choice, we might need to use different libraries or frameworks, such as Pygame, Tkinter, Flask, or Django.
Second, we need to design the data structures and the functions that will represent the game state and the game rules. For example, we will use classes, dictionaries, lists, or tuples to store the information about the players, the animals, the dice, and the exchange rates. We will also use functions, methods, loops, or conditional statements to implement the game logic, such as rolling the dice, gaining or losing animals, exchanging animals, checking for predators, and determining the winner .
Third, we need to test our code and make sure it works as expected. We can use the unittest and parameterized libraries to write and run unit tests for our code. This will help us find and fix any errors or bugs in our code, and ensure that our code meets the game requirements.
Why Python, unittest, and parametrized?
Python is a versatile, easy-to-learn, and powerful programming language that can be used for various applications, such as data analysis, web development, automation, and machine learning.
Library unittest is a built-in testing framework in Python that supports test automation, aggregation, and independence. It helps you write and run unit tests to ensure that your code works correctly and prevent bugs.
Parametrized is a third-party library that allows you to write parameterized tests in Python with any test framework. It enables you to test your code with different inputs and outputs without repeating yourself. It also makes your tests more readable and maintainable.
By using these tools, you can improve the quality, reliability, and efficiency of your code. You can also save time and effort by avoiding manual testing and debugging. I hope this helps you understand the benefits of using Python, unittest, and parametrized.
A sneak peek into the Python code structure:
So this is basic overview of classes. I decided that I will keep game states as enumeration- since it is a finite number. Also it will be easier to keep animals as classes (objects). To make sure all animals follow the same patter we use Animal as superclass then let all the animal classes inherit from it.
class GameState(Enum):
MAIN_MENU = 0
IN_GAME = 1
GAME_OVER = 2
class Animal:
"""base Animal class"""
max_count = 0
def init(self, count):
self.count = min(count, self.max_count)
class Rabbit(Animal):
max_count = 60
We need a class to hold the PLAYER data. Which is mostly players` herd. In constructor (__init__ method) we define empty herd and some methods to get and update herd.
class Player:
def init(self):
self.herd = {"Rabbit": 0, "Sheep": 0, "Pig": 0, "Cow": 0, "Horse": 0, "Foxhound": 0, "Wolfhound": 0}
def get_herd(self):
return self.herd
def update_herd(self, new_herd):
"""Update herd with new dict"""
self.herd.update(new_herd)
Since in game rules we can only get some animals via exchange we will require the exchange rules to be stored somewhere. Best to keep it as an object.
class ExchangeBoard:
"""class to handle animals exchange rates."""
exchange_rates = {}
@classmethod
def set_exchange_rate(cls, from_animal, to_animal, ratio):
cls.exchange_rates[(from_animal, to_animal)] = ratio
@classmethod
def get_exchange_rate(cls, from_animal, to_animal):
return cls.exchange_rates.get((from_animal, to_animal), 0)
And of course most important class- out GameManager! Where we initliaze players (for now only two), build the bank or 'main herd' using the default numbers classes come with, initialize Exchange board to be ready for use and set the inital state of the game- MainMenu.
class GameManager:
def init(self):
self.players = [Player(), Player()]
self.current_player_index = 0
self.main_herd = [Rabbit(), Sheep(), Pig(), Cow(), Horse(), Foxhound(), Wolfhound()]
self.exchange_board = ExchangeBoard()
self.state = GameState.MAIN_MENU
Once we got this, we need to also add a method to roll the dice. Since this is done simultanously we can keep it in one method and return a tuple. First we define each dice using the faces, then use random class method choice to pick randomly from list.
def roll_dice(self):
dice_green_faces = ['Cow', 'Pig', 'Rabbit', 'Rabbit', 'Rabbit', 'Sheep', 'Rabbit', 'Sheep', 'Rabbit', 'Sheep',
'Rabbit', 'Wolf']
dice_red_faces = ['Horse', 'Pig', 'Rabbit', 'Sheep', 'Rabbit', 'Rabbit', 'Pig', 'Rabbit', 'Rabbit', 'Sheep',
'Rabbit', 'Fox']
result_green = random.choice(dice_green_faces)
result_red = random.choice(dice_red_faces)
return result_green, result_red
For now there is not really much to test right? We could only test methods tied to Player and to Exchange board. Method roll_dice since it returns random results is not testable.
Now we define method to process the dice results! We take in player object to eb able to retrieve player herd, we take in dice results and determine if herd has to be updated or not.
def process_dice(self, current_player: Player, result_green: str, result_red: str):
green_animal_count = current_player.get_herd().get(result_green, 0)
red_animal_count = current_player.get_herd().get(result_red, 0)
if green_animal_count == 0 and red_animal_count == 0:
print("No animals in player herd to update.")
if result_green == result_red:
print("Same animal on both dice. Updating herd.")
print(str(self.main_herd[0].__class__))
Now to test the process_dice method in manual way would be very cumbersome. We would have to wait for pair to be rolled to see if animal is added to the player herd. Instead we can write some tests.
We define a class "TestProcessDice" that inhjerits from unittest.TestCase. Then I add two tests for now... Trying to keep the gherkin naming convention.
For now we define in each new Player class and GameManager objects. This will be subjected to refactoring in later.
class TestProcessDice(unittest.TestCase):
def test_given_is_empty_player_herd_and_different_animals_rolled_herd_is_not_updated(self):
test_player = Player()
game_manager = GameManager()
game_manager.process_dice(test_player, "Rabbit", "Sheep")
player_herd = test_player.get_herd()
for animal_type in player_herd.values():
self.assertEqual(animal_type, 0)
def test_given_is_empty_player_herd_and_same_animals_rolled_herd_is_updated(self):
test_player = Player()
game_manager = GameManager()
game_manager.process_dice(test_player, "Rabbit", "Rabbit")
player_herd = test_player.get_herd()
self.assertEqual(player_herd["Rabbit"], 2)
Conclusion:
As we conclude this first post, we've laid the groundwork for our Super Farmer Python Series. The next chapters will bring this game to life with Python code, exploring its nuances and intricacies. Stay tuned for an engaging journey of code development and continuous improvement.
Links:
Comments