Design a two-player game with three core methods:
add_score(player)get_score()get_result()The interview then adds a follow-up: convert the score into a human-readable tennis-style format such as love, 15-30, and deuce.
In Part 1, scores are stored as raw point counts. In Part 2, those raw counts are rendered using standard tennis terminology. [Source: darkinterview.com]
This is the classic tennis scoring question with one explicit normalization rule:
3-3, collapse it back to 3-3 immediately.
4-4 becomes 3-35-5 never persist as observable scoresYou can assume there are exactly two players.
Implement a Game class with these methods: [Source: darkinterview.com]
class Game:
def add_score(self, player: str) -> None:
pass
def get_score(self) -> tuple[int, int]:
pass
def get_result(self) -> str | None:
pass
add_score(player) increments the specified player's score.get_score() returns the current numeric score for both players.get_result() returns the winner once someone is ahead by at least 2 points and has at least 4 points.3-3, normalize it back to 3-3 immediately.game = Game("player1", "player2")
game.add_score("player1") # 1-0
game.add_score("player2") # 1-1
game.add_score("player1") # 2-1
game.add_score("player1") # 3-1
game.get_score() # (3, 1)
game.get_result() # None
game.add_score("player2") # 3-2
game.add_score("player2") # 3-3
game.add_score("player1") # 4-3
game.add_score("player2") # 4-4 -> normalize to 3-3
game.get_score() # (3, 3)
game.get_result() # None
class Game:
def __init__(self, player1: str, player2: str):
self.player1 = player1
self.player2 = player2
self.scores = {
player1: 0,
player2: 0,
}
def add_score(self, player: str) -> None:
if player not in self.scores:
raise ValueError(f"Unknown player: {player}")
if self.get_result() is not None:
raise ValueError("Game already has a winner")
self.scores[player] += 1
self._normalize_deuce()
def get_score(self) -> tuple[int, int]:
return (self.scores[self.player1], self.scores[self.player2])
def get_result(self) -> str | None:
p1 = self.scores[self.player1]
p2 = self.scores[self.player2]
(p1, p2) >= (p1 - p2) >= :
.player1 p1 > p2 .player2
() -> :
p1 = .scores[.player1]
p2 = .scores[.player2]
p1 >= p1 == p2:
.scores[.player1] =
.scores[.player2] =
add_score: O(1)get_score: O(1)get_result: O(1)O(1)Now write a Match class that builds a Game object internally and exposes a human-readable score using tennis terms.
class Match:
def __init__(self, player1: str, player2: str):
pass
def point_won_by(self, player: str) -> None:
pass
def score(self) -> str:
pass
def result(self) -> str | None:
pass
0 maps to love1 maps to 152 maps to 303 maps to 403-3 are rendered normally, such as love-love, 15-15, and 30-303-3 corresponds to tennis 40-40, which is rendered as deuce4-3 or 3-4 is advantage <player>winner <player>Because tied scores above 3-3 collapse back to 3-3, deuce naturally reappears after advantage is lost.
match = Match("alice", "bob")
match.point_won_by("alice")
match.score() # "15-love"
match.point_won_by("bob")
match.point_won_by("alice")
match.point_won_by("bob")
match.point_won_by("alice")
match.point_won_by("bob")
match.score() # "deuce"
match.point_won_by("alice")
match.score() # "advantage alice"
match.point_won_by("bob")
match.score() # "deuce"
match.point_won_by("bob")
match.score() # "advantage bob"
match.point_won_by("bob")
match.result() # "winner bob"
class Match:
SCORE_LABELS = {
0: "love",
1: "15",
2: "30",
3: "40",
}
def __init__(self, player1: str, player2: str):
self.game = Game(player1, player2)
def point_won_by(self, player: str) -> None:
self.game.add_score(player)
def score(self) -> str:
winner = self.game.get_result()
if winner is not None:
return f"winner {winner}"
p1, p2 = self.game.get_score()
player1 = self.game.player1
player2 = self.game.player2
if p1 >= 3 and p2 >= 3:
if p1 == p2:
return "deuce"
leader = player1 if p1 > p2 else player2
return f"advantage {leader}"
return f"{self.SCORE_LABELS[p1]}-{self.SCORE_LABELS[p2]}"
def result(self) -> | :
winner = .game.get_result()
winner :