Tennis Game¶
Description¶
This is a simple tennis game. The game is played by two players. Each player has a score of 0 at the beginning of the game. The first player to score 4 points wins the game.
States¶
serving
: Normal playdeuce
: When both players have a score of 3advantage
: When one player has a score of 4 and the other has a score of 3
import asyncio
import curses
import random
from kiwi_cogs import Machine, StateType
def check_if_score_is_deuce(context, _):
"""Checks if the score is deuce in a tennis game
:param context: The current context of the game, including the scores of both players
:type context: dict
:returns: True if the score is deuce, False otherwise
:rtype: bool
"""
return context["player1_score"] >= 3 and context["player2_score"] == context["player1_score"]
def check_if_score_is_advantage(context, _):
"""Checks if the score is advantage for one of the players in a tennis game
:param context: The current context of the game, including the scores of both players
:type context: dict
:returns: True if the score is advantage for one player, False otherwise
:rtype: bool
"""
if context["player1_score"] > context["player2_score"]:
return context["player1_score"] >= 4 and context["player1_score"] - context["player2_score"] == 1
else:
return context["player2_score"] >= 4 and context["player2_score"] - context["player1_score"] == 1
def check_if_score_is_game(context, _):
"""Checks if the score is a game for one of the players in a tennis game
:param context: The current context of the game, including the scores of both players
:type context: dict
:returns: True if the score is a game for one player, False otherwise
:rtype: bool
"""
if context["player1_score"] > context["player2_score"]:
return context["player1_score"] >= 4 and context["player1_score"] - context["player2_score"] >= 2
else:
return context["player2_score"] >= 4 and context["player2_score"] - context["player1_score"] >= 2
def increment_player1_score(context, _):
context["player1_score"] += 1
def increment_player2_score(context, _):
context["player2_score"] += 1
config = {
"name": "tennis_game",
"initial": "serving",
"context": {"player1_score": 0, "player2_score": 0},
"states": {
"serving": {
"transitions": [
{"target": "deuce", "cond": "isDeuce"}, # use a named guard for transitions
{"target": "advantage", "cond": "isAdvantage"},
{"target": "game", "cond": "isGame"},
],
"events": {
"PLAYER1_SCORES": {"actions": "incPlayer1Score"}, # use a named action for events
"PLAYER2_SCORES": {"actions": "incPlayer2Score"},
},
},
"deuce": {
"transitions": [
{"target": "advantage", "cond": "isAdvantage"},
{"target": "game", "cond": "isGame"},
],
"events": {
"PLAYER1_SCORES": {"actions": increment_player1_score}, # use a function for events as well!
"PLAYER2_SCORES": {"actions": increment_player2_score},
},
},
"advantage": {
"transitions": [
{"target": "deuce", "cond": check_if_score_is_deuce}, # Functions can be used as well
{"target": "game", "cond": check_if_score_is_game},
],
"events": {
"PLAYER1_SCORES": {"actions": increment_player1_score},
"PLAYER2_SCORES": {"actions": increment_player2_score},
},
},
"game": {
"transitions": [
{
"target": "player1_wins",
"cond": lambda context, _: context["player1_score"] > context["player2_score"],
}, # any callable can be used as a condition
{
"target": "player2_wins",
"cond": lambda context, _: context["player2_score"] > context["player1_score"],
},
],
},
"player1_wins": {"type": "final"}, # final states are used to indicate that the machine is finished
"player2_wins": {"type": "final"},
},
"guards": { # Register the guards, reusable conditions that can be used in transitions
"isDeuce": check_if_score_is_deuce,
"isAdvantage": check_if_score_is_advantage,
"isGame": check_if_score_is_game,
},
"actions": {
"incPlayer1Score": increment_player1_score,
"incPlayer2Score": increment_player2_score,
},
}
async def get_machine():
return await Machine.create(config)
def print_score(stdscr, state, score1, score2):
stdscr.clear()
# Create a new window for the scores, with a border
score_window = curses.newwin(5, 30, 3, 0)
score_window.box()
# Print scores
score_window.addstr(1, 1, f"State: {state}")
score_window.addstr(2, 1, f"Player 1: {score1}")
score_window.addstr(3, 1, f"Player 2: {score2}")
score_window.refresh()
async def tennis_game(stdscr):
machine = await get_machine()
events = ("PLAYER1_SCORES", "PLAYER2_SCORES")
while machine.state.type != StateType.final:
score_1 = machine.context["player1_score"]
score_2 = machine.context["player2_score"]
state = machine.state.value
print_score(stdscr, state, score_1, score_2)
event = random.choice(events) # randomly select a player to score
await asyncio.sleep(0.2)
await machine.event(event)
score_1 = machine.context["player1_score"]
score_2 = machine.context["player2_score"]
state = machine.state.value
print_score(stdscr, state, score_1, score_2)
await asyncio.sleep(3)
def run(stdscr):
asyncio.run(tennis_game(stdscr))
if __name__ == "__main__":
# Initialize curses
curses.wrapper(run)
Example output:
┌────────────────────────────┐
│State: player2_wins │
│Player 1: 3 │
│Player 2: 5 │
└────────────────────────────┘