Evenwins (Python): Refactored

This commit is contained in:
Martin Thoma
2022-04-02 09:01:47 +02:00
parent 8b0bd1ad65
commit 71bae5ded3
4 changed files with 91 additions and 130 deletions

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# This data is meant to be read-only, so we are storing it in a tuple
import json import json
# This data is meant to be read-only, so we are storing it in a tuple
with open("data.json") as f: with open("data.json") as f:
DATA = tuple(json.load(f)) DATA = tuple(json.load(f))

View File

@@ -1,7 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# CHOMP
# """
# Converted from BASIC to Python by Trevor Hobson CHOMP
Converted from BASIC to Python by Trevor Hobson
"""
class Canvas: class Canvas:

View File

@@ -1,7 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# CUBE
# """
# Converted from BASIC to Python by Trevor Hobson CUBE
Converted from BASIC to Python by Trevor Hobson
"""
import random import random
from typing import Tuple from typing import Tuple

View File

@@ -1,41 +1,36 @@
# evenwins.py """
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.
# This version of evenwins.bas based on game decscription and does *not* A good exercise would be to replace this with a class.
# follow the source. The computer chooses marbles at random. The code is not short, but hopefully it is easy for beginners to understand
# and modify.
# 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.
#
# global variables Infinite loops of the style "while True:" are used to simplify some of the
marbles_in_middle = -1 code. The "continue" keyword is used in a few places to jump back to the top
human_marbles = -1 of the loop. The "return" keyword is also used to break out of functions.
computer_marbles = -1 This is generally considered poor style, but in this case it simplifies the
whose_turn = "" 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.
"""
def serious_error(msg): from dataclasses import dataclass
""" from typing import Literal, Tuple
Only call this function during development for serious errors that are due
to mistakes in the program. Should never be called during a regular game. PlayerType = Literal["human", "computer"]
"""
print("serious_error: " + msg)
exit(1)
def print_intro(): @dataclass
class MarbleCounts:
middle: int
human: int
computer: int
def print_intro() -> None:
print("Welcome to Even Wins!") print("Welcome to Even Wins!")
print("Based on evenwins.bas from Creative Computing") print("Based on evenwins.bas from Creative Computing")
print() print()
@@ -50,22 +45,19 @@ def print_intro():
print() print()
def marbles_str(n): def marbles_str(n: int) -> str:
if n == 1: if n == 1:
return "1 marble" return "1 marble"
return f"{n} marbles" return f"{n} marbles"
def choose_first_player(): def choose_first_player() -> PlayerType:
global whose_turn
while True: while True:
ans = input("Do you want to play first? (y/n) --> ") ans = input("Do you want to play first? (y/n) --> ")
if ans == "y": if ans == "y":
whose_turn = "human" return "human"
return
elif ans == "n": elif ans == "n":
whose_turn = "computer" return "computer"
return
else: else:
print() print()
print('Please enter "y" if you want to play first,') print('Please enter "y" if you want to play first,')
@@ -73,18 +65,15 @@ def choose_first_player():
print() print()
def next_player(): def toggle_player(whose_turn: PlayerType) -> PlayerType:
global whose_turn
if whose_turn == "human": if whose_turn == "human":
whose_turn = "computer" return "computer"
elif whose_turn == "computer":
whose_turn = "human"
else: else:
serious_error(f"play_game: unknown player {whose_turn}") return "human"
# Converts a string s to an int, if possible. def to_int(s: str) -> Tuple[bool, int]:
def to_int(s): """Convert a string s to an int, if possible."""
try: try:
n = int(s) n = int(s)
return True, n return True, n
@@ -92,142 +81,108 @@ def to_int(s):
return False, 0 return False, 0
def print_board() -> None: def print_board(marbles: MarbleCounts) -> None:
global marbles_in_middle
global human_marbles
global computer_marbles
print() print()
print(f" marbles in the middle: {marbles_in_middle} " + marbles_in_middle * "*") print(f" marbles in the middle: {marbles.middle} " + marbles.middle * "*")
print(f" # marbles you have: {human_marbles}") print(f" # marbles you have: {marbles.human}")
print(f"# marbles computer has: {computer_marbles}") print(f"# marbles computer has: {marbles.computer}")
print() print()
def human_turn(): def human_turn(marbles: MarbleCounts) -> None:
global marbles_in_middle """get number in range 1 to min(4, marbles.middle)"""
global human_marbles max_choice = min(4, marbles.middle)
# get number in range 1 to min(4, marbles_in_middle)
max_choice = min(4, marbles_in_middle)
print("It's your turn!") print("It's your turn!")
while True: while True:
s = input(f"Marbles to take? (1 - {max_choice}) --> ") s = input(f"Marbles to take? (1 - {max_choice}) --> ")
ok, n = to_int(s) ok, n = to_int(s)
if not ok: if not ok:
print() print(f"\n Please enter a whole number from 1 to {max_choice}\n")
print(f" Please enter a whole number from 1 to {max_choice}")
print()
continue continue
if n < 1: if n < 1:
print() print("\n You must take at least 1 marble!\n")
print(" You must take at least 1 marble!")
print()
continue continue
if n > max_choice: if n > max_choice:
print() print(f"\n You can take at most {marbles_str(max_choice)}\n")
print(f" You can take at most {marbles_str(max_choice)}")
print()
continue continue
print() print(f"\nOkay, taking {marbles_str(n)} ...")
print(f"Okay, taking {marbles_str(n)} ...") marbles.middle -= n
marbles_in_middle -= n marbles.human += n
human_marbles += n
return return
def game_over(): def game_over(marbles: MarbleCounts) -> None:
global marbles_in_middle
global human_marbles
global computer_marbles
print() print()
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
print("!! All the marbles are taken: Game Over!") print("!! All the marbles are taken: Game Over!")
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
print() print()
print_board() print_board(marbles)
if human_marbles % 2 == 0: if marbles.human % 2 == 0:
print("You are the winner! Congratulations!") print("You are the winner! Congratulations!")
else: else:
print("The computer wins: all hail mighty silicon!") print("The computer wins: all hail mighty silicon!")
print() print()
def computer_turn(): def computer_turn(marbles: MarbleCounts) -> None:
global marbles_in_middle
global computer_marbles
global human_marbles
marbles_to_take = 0 marbles_to_take = 0
print("It's the computer's turn ...") print("It's the computer's turn ...")
r = marbles_in_middle - 6 * int(marbles_in_middle / 6) # line 500 r = marbles.middle - 6 * int(marbles.middle / 6)
if int(human_marbles / 2) == human_marbles / 2: # line 510 if int(marbles.human / 2) == marbles.human / 2:
if r < 1.5 or r > 5.3: # lines 710 and 720 if r < 1.5 or r > 5.3:
marbles_to_take = 1 marbles_to_take = 1
else: else:
marbles_to_take = r - 1 marbles_to_take = r - 1
elif marbles_in_middle < 4.2: # line 580 elif marbles.middle < 4.2:
marbles_to_take = marbles_in_middle marbles_to_take = marbles.middle
elif r > 3.4: # line 530 elif r > 3.4:
if r < 4.7 or r > 3.5: if r < 4.7 or r > 3.5:
marbles_to_take = 4 marbles_to_take = 4
else: else:
marbles_to_take = r + 1 marbles_to_take = r + 1
print(f"Computer takes {marbles_str(marbles_to_take)} ...") print(f"Computer takes {marbles_str(marbles_to_take)} ...")
marbles_in_middle -= marbles_to_take marbles.middle -= marbles_to_take
computer_marbles += marbles_to_take marbles.computer += marbles_to_take
def play_game(): def play_game(whose_turn: PlayerType) -> None:
global marbles_in_middle marbles = MarbleCounts(middle=27, human=0, computer=0)
global human_marbles print_board(marbles)
global computer_marbles
# initialize the game state
marbles_in_middle = 27
human_marbles = 0
computer_marbles = 0
print_board()
while True: while True:
if marbles_in_middle == 0: if marbles.middle == 0:
game_over() game_over(marbles)
return return
elif whose_turn == "human": elif whose_turn == "human":
human_turn() human_turn(marbles)
print_board() print_board(marbles)
next_player() whose_turn = toggle_player(whose_turn)
elif whose_turn == "computer": elif whose_turn == "computer":
computer_turn() computer_turn(marbles)
print_board() print_board(marbles)
next_player() whose_turn = toggle_player(whose_turn)
else: else:
serious_error(f"play_game: unknown player {whose_turn}") raise Exception(f"whose_turn={whose_turn} is not 'human' or 'computer'")
def main() -> None: def main() -> None:
global whose_turn
print_intro() print_intro()
while True: while True:
choose_first_player() whose_turn = choose_first_player()
play_game() play_game(whose_turn)
# ask if the user if they want to play again
print() print()
again = input("Would you like to play again? (y/n) --> ") again = input("Would you like to play again? (y/n) --> ").lower()
if again == "y": if again == "y":
print() print("\nOk, let's play again ...\n")
print("Ok, let's play again ...")
print()
else: else:
print() print("\nOk, thanks for playing ... goodbye!\n")
print("Ok, thanks for playing ... goodbye!")
print()
return return