mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 15:50:20 -08:00
182 lines
5.6 KiB
Python
182 lines
5.6 KiB
Python
"""
|
|
This version of evenwins.bas based on game decscription and does *not*
|
|
follow the source. The computer chooses marbles at random.
|
|
|
|
For simplicity, global variables are used to store the game state.
|
|
A good exercise would be to replace this with a class.
|
|
The code is not short, but hopefully it is easy for beginners to understand
|
|
and modify.
|
|
|
|
Infinite loops of the style "while True:" are used to simplify some of the
|
|
code. The "continue" keyword is used in a few places to jump back to the top
|
|
of the loop. The "return" keyword is also used to break out of functions.
|
|
This is generally considered poor style, but in this case it simplifies the
|
|
code and makes it easier to read (at least in my opinion). A good exercise
|
|
would be to remove these infinite loops, and uses of continue, to follow a
|
|
more structured style.
|
|
"""
|
|
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Literal, Tuple
|
|
|
|
PlayerType = Literal["human", "computer"]
|
|
|
|
|
|
@dataclass
|
|
class MarbleCounts:
|
|
middle: int
|
|
human: int
|
|
computer: int
|
|
|
|
|
|
def print_intro() -> None:
|
|
print("Welcome to Even Wins!")
|
|
print("Based on evenwins.bas from Creative Computing")
|
|
print()
|
|
print("Even Wins is a two-person game. You start with")
|
|
print("27 marbles in the middle of the table.")
|
|
print()
|
|
print("Players alternate taking marbles from the middle.")
|
|
print("A player can take 1 to 4 marbles on their turn, and")
|
|
print("turns cannot be skipped. The game ends when there are")
|
|
print("no marbles left, and the winner is the one with an even")
|
|
print("number of marbles.")
|
|
print()
|
|
|
|
|
|
def marbles_str(n: int) -> str:
|
|
return "1 marble" if n == 1 else f"{n} marbles"
|
|
|
|
|
|
def choose_first_player() -> PlayerType:
|
|
while True:
|
|
ans = input("Do you want to play first? (y/n) --> ")
|
|
if ans == "y":
|
|
return "human"
|
|
elif ans == "n":
|
|
return "computer"
|
|
else:
|
|
print()
|
|
print('Please enter "y" if you want to play first,')
|
|
print('or "n" if you want to play second.')
|
|
print()
|
|
|
|
|
|
def toggle_player(whose_turn: PlayerType) -> PlayerType:
|
|
return "computer" if whose_turn == "human" else "human"
|
|
|
|
|
|
def to_int(s: str) -> Tuple[bool, int]:
|
|
"""Convert a string s to an int, if possible."""
|
|
try:
|
|
n = int(s)
|
|
return True, n
|
|
except Exception:
|
|
return False, 0
|
|
|
|
|
|
def print_board(marbles: MarbleCounts) -> None:
|
|
print()
|
|
print(f" marbles in the middle: {marbles.middle} " + marbles.middle * "*")
|
|
print(f" # marbles you have: {marbles.human}")
|
|
print(f"# marbles computer has: {marbles.computer}")
|
|
print()
|
|
|
|
|
|
def human_turn(marbles: MarbleCounts) -> None:
|
|
"""get number in range 1 to min(4, marbles.middle)"""
|
|
max_choice = min(4, marbles.middle)
|
|
print("It's your turn!")
|
|
while True:
|
|
s = input(f"Marbles to take? (1 - {max_choice}) --> ")
|
|
ok, n = to_int(s)
|
|
if not ok:
|
|
print(f"\n Please enter a whole number from 1 to {max_choice}\n")
|
|
continue
|
|
if n < 1:
|
|
print("\n You must take at least 1 marble!\n")
|
|
continue
|
|
if n > max_choice:
|
|
print(f"\n You can take at most {marbles_str(max_choice)}\n")
|
|
continue
|
|
print(f"\nOkay, taking {marbles_str(n)} ...")
|
|
marbles.middle -= n
|
|
marbles.human += n
|
|
return
|
|
|
|
|
|
def game_over(marbles: MarbleCounts) -> None:
|
|
print()
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
print("!! All the marbles are taken: Game Over!")
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
print()
|
|
print_board(marbles)
|
|
if marbles.human % 2 == 0:
|
|
print("You are the winner! Congratulations!")
|
|
else:
|
|
print("The computer wins: all hail mighty silicon!")
|
|
print()
|
|
|
|
|
|
def computer_turn(marbles: MarbleCounts) -> None:
|
|
marbles_to_take = 0
|
|
|
|
print("It's the computer's turn ...")
|
|
r = marbles.middle - 6 * int(marbles.middle / 6)
|
|
|
|
if int(marbles.human / 2) == marbles.human / 2:
|
|
marbles_to_take = 1 if r < 1.5 or r > 5.3 else r - 1
|
|
elif marbles.middle < 4.2:
|
|
marbles_to_take = marbles.middle
|
|
elif r > 3.4:
|
|
if r < 4.7 or r > 3.5:
|
|
marbles_to_take = 4
|
|
else:
|
|
marbles_to_take = r + 1
|
|
|
|
print(f"Computer takes {marbles_str(marbles_to_take)} ...")
|
|
marbles.middle -= marbles_to_take
|
|
marbles.computer += marbles_to_take
|
|
|
|
|
|
def play_game(whose_turn: PlayerType) -> None:
|
|
marbles = MarbleCounts(middle=27, human=0, computer=0)
|
|
print_board(marbles)
|
|
|
|
while True:
|
|
if marbles.middle == 0:
|
|
game_over(marbles)
|
|
return
|
|
elif whose_turn == "human":
|
|
human_turn(marbles)
|
|
print_board(marbles)
|
|
whose_turn = toggle_player(whose_turn)
|
|
elif whose_turn == "computer":
|
|
computer_turn(marbles)
|
|
print_board(marbles)
|
|
whose_turn = toggle_player(whose_turn)
|
|
else:
|
|
raise Exception(f"whose_turn={whose_turn} is not 'human' or 'computer'")
|
|
|
|
|
|
def main() -> None:
|
|
print_intro()
|
|
|
|
while True:
|
|
whose_turn = choose_first_player()
|
|
play_game(whose_turn)
|
|
|
|
print()
|
|
again = input("Would you like to play again? (y/n) --> ").lower()
|
|
if again == "y":
|
|
print("\nOk, let's play again ...\n")
|
|
else:
|
|
print("\nOk, thanks for playing ... goodbye!\n")
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|