To practice my python skills, I didn't want to just do a load of traditional katas, so I decided instead to turn my hand to trying to implement some of my favourite board games as Terminal games, in a series I'm calling 'Python Plays' (PP).
Machi Koro is a small and simple card game where players compete to build their town into the largest city in the region. Starting with just a Wheat Field and a Bakery, and the plans to build 4 landmarks, players take it in turns to add to their town. The first player to buld all 4 of their landmark cards, wins. Each turn the player rolls one or two dice, and the sum then 'activates' cards with that number
- blue cards (Primary Industries) trigger for everyone when their number is rolled, and earn money from the the Bank;
- green cards (Secondary Industries) trigger only for the active player, and earn money from the the Bank;
- red cards (Restaurants) trigger for everyone except the active player, and earn money from the active player;
- purple cards (Major Establishments) trigger only for the active player, and allow for special actions to be taken.
Card effects stack, so having multiple of the same card can win big! Similarly, a throw that triggers your rivals red cards can cast big if they've stacked their hand that way.
Cash flows fast between players, and with the most expensive landmark requiring 22 cash in hand it can be a really tricky game to get right.
Players begin only with one dice to roll - building the Train Station allows a player to choose to roll one or two dice - a Wheat Field (triggered on a roll of 1) and a Bakery (triggered on a roll of 2 or 3).
Card | Roll | Trigger | Build Cost | Activation |
---|---|---|---|---|
1 | Everyone | 1 | Get 1 from the Bank | |
2 | Everyone | 1 | Get 1 from the Bank | |
2 or 3 | Active Player | 1 | Get 1 from the Bank | |
3 | Opponents | 2 | Take 1 from Active Player | |
4 | Active Player | 2 | Get 3 from the Bank | |
5 | Everyone | 3 | Get 1 from the Bank | |
6 | Active Player | 6 | Take 2 from each opponent | |
6 | Active Player | 7 | Take 5 from one opponent | |
6 | Active Player | 8 | Swap one establishment with one opponent | |
7 | Active Player | 5 | Get 3 from Bank per Ranch owned | |
8 | Active Player | 3 | Get 3 from Bank per Forest/Mine owned | |
9 | Everyone | 6 | Get 5 coins from the Bank | |
9 or 10 | Opponents | 3 | Take 2 from Active Player | |
10 | Everyone | 3 | Get 3 from the Bank | |
11 or 12 | Active Player | 2 | Get 2 from Bank per Field/Orchard owned |
Players who have built a more expensive landmark, the Amusement Park (16 cash), are allowed to take another turn if they roll a double. And players who have built the most expensive landmark, the Radio Tower (22 cash) can reroll the dice once per turn.
I wanted to use colours, to match the cards in the original game, so played around with ANSI escape code. I ended up with a 'reference' module, that held a List of RGB values, and also a List of ANSI codes and hex-codes based on that list of RGB values.
rgbColours = {
'red': [208,13,13],
'green': [13,156,13],
'blue': [13,13,208],
'purple': [104,13,208],
'orange': [208,104,13]
}
ansiColours = {
'red': f"\033[38;2;{rgbColours['red'][0]};{rgbColours['red'][1]};{rgbColours['red'][2]};74m",
'green': f"\033[38;2;{rgbColours['green'][0]};{rgbColours['green'][1]};{rgbColours['green'][2]};74m",
'blue': f"\033[38;2;{rgbColours['blue'][0]};{rgbColours['blue'][1]};{rgbColours['blue'][2]};74m",
'purple': f"\033[38;2;{rgbColours['purple'][0]};{rgbColours['purple'][1]};{rgbColours['purple'][2]};74m",
'orange': f"\033[38;2;{rgbColours['orange'][0]};{rgbColours['orange'][1]};{rgbColours['orange'][2]};74m",
'reset': f"\033[39m"
}
hexColours = {
'red': '#%02x%02x%02x' % (rgbColours['red'][0], rgbColours['red'][1], rgbColours['red'][2]),
'green': '#%02x%02x%02x' % (rgbColours['green'][0], rgbColours['green'][1], rgbColours['green'][2]),
'blue': '#%02x%02x%02x' % (rgbColours['blue'][0], rgbColours['blue'][1], rgbColours['blue'][2]),
'purple': '#%02x%02x%02x' % (rgbColours['purple'][0], rgbColours['purple'][1], rgbColours['purple'][2]),
'orange': '#%02x%02x%02x' % (rgbColours['orange'][0], rgbColours['orange'][1], rgbColours['orange'][2]),
}
adding one of these 'ansiColours' to the start of an f-string for terminal output will colour the text that colour - for example:
print(f"{reference["ansiColours"]["red"]}Cafe{reference["ansiColours"]["reset"]}")
print(f"{reference["ansiColours"]["green"]}Bakery{reference["ansiColours"]["reset"]}")
print(f"{reference["ansiColours"]["blue"]}Wheat Field{reference["ansiColours"]["reset"]}")
will output:
$\color{#d00d0d}{\textsf{Cafe}}$
$\color{#0d9c0d}{\textsf{Bakery}}$
$\color{#0d0dd0}{\textsf{Wheat Field}}$
I then stored these values against the card types that want them - so all 'Red Cards' have a property of colorize that will output the relevant f-string ANSI code, meaning that the above can become:
print(f"{card.colorize}{card.title}{card.reset}")
$\color{#d00d0d}{\textsf{Cafe}}$