Skip to content

Commit 3833ba1

Browse files
committed
feat:add Snake Water Gun game with unit test
1 parent 456d644 commit 3833ba1

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

games/snake_water_gun.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""
2+
Snake Water Gun Game
3+
=====================
4+
5+
A simple command-line game similar to Rock-Paper-Scissors.
6+
Choices: Snake, Water, Gun.
7+
8+
Rules:
9+
- Snake drinks Water → Snake wins.
10+
- Water damages Gun → Water wins.
11+
- Gun kills Snake → Gun wins.
12+
- Same choice → Tie.
13+
14+
Functions:
15+
play(player_choice: str) -> str
16+
Play a single round against the computer.
17+
main() -> None
18+
Run an interactive game loop until the user quits.
19+
"""
20+
21+
import random
22+
import sys
23+
from typing import NoReturn
24+
25+
26+
VALID_CHOICES = ("Snake", "Water", "Gun")
27+
28+
WIN_CONDITIONS = {
29+
"Snake": "Water", # Snake drinks Water
30+
"Water": "Gun", # Water damages Gun
31+
"Gun": "Snake", # Gun kills Snake
32+
}
33+
34+
35+
def play(player_choice: str) -> str:
36+
"""
37+
Play one round of Snake Water Gun against the computer.
38+
39+
The function normalises the player's input (case-insensitive) and
40+
validates it. The computer picks a random choice. It then
41+
determines the winner according to the rules and returns a
42+
descriptive string.
43+
44+
Args:
45+
player_choice: The player's selection. Any casing is accepted
46+
("snake", "SNAKE", "Snake", etc.).
47+
48+
Returns:
49+
A string in one of the following formats:
50+
- "You chose Snake, Computer chose Water. You win!"
51+
- "You chose Water, Computer chose Gun. You lose!"
52+
- "You chose Gun, Computer chose Gun. It's a tie!"
53+
- "Invalid choice: <input>. Please choose Snake, Water, or Gun."
54+
55+
Examples:
56+
>>> play("Snake")
57+
'You chose Snake, Computer chose Water. You win!' # if computer picks Water
58+
"""
59+
# Normalise input: strip whitespace, capitalise first letter only
60+
normalised = player_choice.strip().lower().capitalize()
61+
62+
if normalised not in VALID_CHOICES:
63+
return (
64+
f"Invalid choice: {player_choice}. "
65+
f"Please choose {', '.join(VALID_CHOICES)}."
66+
)
67+
68+
computer_choice = random.choice(VALID_CHOICES)
69+
70+
if normalised == computer_choice:
71+
result = "It's a tie!"
72+
elif WIN_CONDITIONS[normalised] == computer_choice:
73+
result = "You win!"
74+
else:
75+
result = "You lose!"
76+
77+
return f"You chose {normalised}, Computer chose {computer_choice}. {result}"
78+
79+
80+
def main() -> None:
81+
"""
82+
Run the interactive Snake Water Gun game loop.
83+
84+
The user is repeatedly prompted for a choice until they type
85+
``quit`` (case-insensitive). Each round's result is printed
86+
immediately.
87+
"""
88+
print("Welcome to Snake Water Gun!")
89+
print(f"Choices: {', '.join(VALID_CHOICES)}")
90+
print("Enter 'quit' to exit.\n")
91+
92+
while True:
93+
user_input = input("Your choice: ").strip()
94+
if user_input.lower() == "quit":
95+
print("Thanks for playing. Goodbye!")
96+
break
97+
result = play(user_input)
98+
print(result)
99+
print()
100+
101+
102+
if __name__ == "__main__":
103+
main()
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Unit tests for the Snake Water Gun game.
3+
4+
Tests cover:
5+
- All win/loss/tie combinations (3×3 matrix).
6+
- Case‑insensitivity and whitespace handling.
7+
- Invalid inputs.
8+
- The interactive ``main()`` loop (with mocked I/O).
9+
"""
10+
11+
import random
12+
import pytest
13+
from io import StringIO
14+
15+
from snake_water_gun import play, main, VALID_CHOICES, WIN_CONDITIONS
16+
17+
18+
# ---------------------------------------------------------------------------
19+
# Helper: parametrised tests for play()
20+
# ---------------------------------------------------------------------------
21+
22+
# Map computer choice to expected outcome for a given player choice
23+
OUTCOMES = {
24+
# player chooses Snake
25+
("Snake", "Snake"): "It's a tie!",
26+
("Snake", "Water"): "You win!",
27+
("Snake", "Gun"): "You lose!",
28+
# player chooses Water
29+
("Water", "Snake"): "You lose!",
30+
("Water", "Water"): "It's a tie!",
31+
("Water", "Gun"): "You win!",
32+
# player chooses Gun
33+
("Gun", "Snake"): "You win!",
34+
("Gun", "Water"): "You lose!",
35+
("Gun", "Gun"): "It's a tie!",
36+
}
37+
38+
39+
@pytest.mark.parametrize(
40+
"player_choice, computer_choice",
41+
[
42+
(p, c)
43+
for p in VALID_CHOICES
44+
for c in VALID_CHOICES
45+
],
46+
)
47+
def test_all_outcomes(player_choice: str, computer_choice: str, monkeypatch) -> None:
48+
"""
49+
Verify that every possible combination returns the correct result string.
50+
"""
51+
monkeypatch.setattr(random, "choice", lambda _: computer_choice)
52+
53+
result = play(player_choice)
54+
expected_outcome = OUTCOMES[(player_choice, computer_choice)]
55+
expected = (
56+
f"You chose {player_choice}, Computer chose {computer_choice}. "
57+
f"{expected_outcome}"
58+
)
59+
assert result == expected
60+
61+
62+
@pytest.mark.parametrize(
63+
"raw_input, normalised",
64+
[
65+
("snake", "Snake"),
66+
("SNAKE", "Snake"),
67+
(" water ", "Water"),
68+
("GuN", "Gun"),
69+
],
70+
)
71+
def test_case_insensitivity_and_whitespace(
72+
raw_input: str, normalised: str, monkeypatch
73+
) -> None:
74+
"""Input is normalised regardless of casing and surrounding spaces."""
75+
# Fix computer choice to something predictable
76+
monkeypatch.setattr(random, "choice", lambda _: "Gun")
77+
78+
result = play(raw_input)
79+
# We expect the result string to contain the normalised player choice
80+
assert f"You chose {normalised}" in result
81+
82+
83+
@pytest.mark.parametrize(
84+
"invalid_input",
85+
[
86+
"",
87+
" ",
88+
"rock",
89+
"paper",
90+
"scissors",
91+
"snakes",
92+
"gunwater",
93+
"123",
94+
],
95+
)
96+
def test_invalid_input_returns_error(invalid_input: str) -> None:
97+
"""Invalid choices produce an error message."""
98+
result = play(invalid_input)
99+
assert result.startswith("Invalid choice:")
100+
101+
102+
# ---------------------------------------------------------------------------
103+
# Tests for main() interactive loop
104+
# ---------------------------------------------------------------------------
105+
106+
def test_main_quit_immediately(monkeypatch, capsys) -> None:
107+
"""Typing 'quit' right away exits the loop with a goodbye message."""
108+
monkeypatch.setattr("sys.stdin", StringIO("quit\n"))
109+
main()
110+
captured = capsys.readouterr()
111+
assert "Thanks for playing. Goodbye!" in captured.out
112+
113+
114+
def test_main_one_round_then_quit(monkeypatch, capsys) -> None:
115+
"""Play a single round and then quit."""
116+
inputs = StringIO("Snake\nquit\n")
117+
monkeypatch.setattr("sys.stdin", inputs)
118+
# Force computer choice to make assertion deterministic
119+
monkeypatch.setattr(random, "choice", lambda _: "Water")
120+
121+
main()
122+
captured = capsys.readouterr()
123+
assert "You chose Snake, Computer chose Water. You win!" in captured.out
124+
assert "Thanks for playing. Goodbye!" in captured.out

0 commit comments

Comments
 (0)