mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-21 23:00:43 -08:00
Remove tests and alternative Python implementations
Ad discussed here: https://github.com/coding-horror/basic-computer-games/issues/548#issuecomment-1081008471
This commit is contained in:
3
.github/workflows/check-python.yml
vendored
3
.github/workflows/check-python.yml
vendored
@@ -20,9 +20,6 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r 00_Utilities/python/ci-requirements.txt
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest -m "not slow"
|
||||
- name: Test with mypy
|
||||
run: |
|
||||
mypy . --exclude 79_Slalom --exclude 27_Civil_War --exclude 38_Fur_Trader --exclude 81_Splat --exclude 09_Battle --exclude 40_Gomoko --exclude 36_Flip_Flop --exclude 43_Hammurabi --exclude 04_Awari --exclude 78_Sine_Wave --exclude 77_Salvo --exclude 34_Digits --exclude 17_Bullfight --exclude 16_Bug
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pytest
|
||||
flake8
|
||||
flake8-bugbear
|
||||
flake8-comprehensions
|
||||
|
||||
@@ -8,7 +8,6 @@ attrs==20.3.0
|
||||
# via
|
||||
# flake8-bugbear
|
||||
# flake8-implicit-str-concat
|
||||
# pytest
|
||||
flake8==4.0.1
|
||||
# via
|
||||
# -r ci-requirements.in
|
||||
@@ -20,8 +19,6 @@ flake8-comprehensions==3.8.0
|
||||
# via -r ci-requirements.in
|
||||
flake8-implicit-str-concat==0.2.0
|
||||
# via -r ci-requirements.in
|
||||
iniconfig==1.1.1
|
||||
# via pytest
|
||||
mccabe==0.6.1
|
||||
# via flake8
|
||||
more-itertools==8.12.0
|
||||
@@ -30,23 +27,11 @@ mypy==0.931
|
||||
# via -r ci-requirements.in
|
||||
mypy-extensions==0.4.3
|
||||
# via mypy
|
||||
packaging==21.3
|
||||
# via pytest
|
||||
pluggy==1.0.0
|
||||
# via pytest
|
||||
py==1.11.0
|
||||
# via pytest
|
||||
pycodestyle==2.8.0
|
||||
# via flake8
|
||||
pyflakes==2.4.0
|
||||
# via flake8
|
||||
pyparsing==3.0.7
|
||||
# via packaging
|
||||
pytest==7.0.1
|
||||
# via -r ci-requirements.in
|
||||
tomli==2.0.1
|
||||
# via
|
||||
# mypy
|
||||
# pytest
|
||||
# via mypy
|
||||
typing-extensions==4.1.1
|
||||
# via mypy
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
"""
|
||||
AceyDuchy
|
||||
|
||||
From: BASIC Computer Games (1978)
|
||||
Edited by David Ahl
|
||||
|
||||
"The original BASIC program author was Bill Palmby
|
||||
of Prairie View, Illinois."
|
||||
|
||||
Python port by Aviyam Fischer, 2022
|
||||
"""
|
||||
|
||||
from typing import List, Literal, TypeAlias, get_args
|
||||
|
||||
Suit: TypeAlias = Literal["\u2665", "\u2666", "\u2663", "\u2660"]
|
||||
Rank: TypeAlias = Literal[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
|
||||
|
||||
class Card:
|
||||
def __init__(self, suit: Suit, rank: Rank) -> None:
|
||||
self.suit = suit
|
||||
self.rank = rank
|
||||
|
||||
def __str__(self) -> str:
|
||||
r = str(self.rank)
|
||||
r = {"11": "J", "12": "Q", "13": "K", "14": "A"}.get(r, r)
|
||||
return f"{r}{self.suit}"
|
||||
|
||||
|
||||
class Deck:
|
||||
def __init__(self) -> None:
|
||||
self.cards: List[Card] = []
|
||||
self.build()
|
||||
|
||||
def build(self) -> None:
|
||||
for suit in get_args(Suit):
|
||||
for rank in get_args(Rank):
|
||||
self.cards.append(Card(suit, rank))
|
||||
|
||||
def shuffle(self) -> None:
|
||||
import random
|
||||
|
||||
random.shuffle(self.cards)
|
||||
|
||||
def deal(self) -> Card:
|
||||
return self.cards.pop()
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(self) -> None:
|
||||
self.deck = Deck()
|
||||
self.deck.shuffle()
|
||||
self.card_a = self.deck.deal()
|
||||
self.card_b = self.deck.deal()
|
||||
self.money = 100
|
||||
self.not_done = True
|
||||
|
||||
def play(self) -> None:
|
||||
while self.not_done:
|
||||
while self.money > 0:
|
||||
card_a = self.card_a
|
||||
card_b = self.card_b
|
||||
|
||||
if card_a.rank > card_b.rank:
|
||||
card_a, card_b = card_b, card_a
|
||||
|
||||
if card_a.rank == card_b.rank:
|
||||
self.card_b = self.deck.deal()
|
||||
card_b = self.card_b
|
||||
|
||||
print(f"You have:\t ${self.money} ")
|
||||
print(f"Your cards:\t {card_a} {card_b}")
|
||||
|
||||
bet = int(input("What is your bet? "))
|
||||
player_card = self.deck.deal()
|
||||
if 0 < bet <= self.money:
|
||||
|
||||
print(f"Your deal:\t {player_card}")
|
||||
if card_a.rank < player_card.rank < card_b.rank:
|
||||
print("You Win!")
|
||||
self.money += bet
|
||||
else:
|
||||
print("You Lose!")
|
||||
self.money -= bet
|
||||
self.not_done = False
|
||||
else:
|
||||
print("Chicken!")
|
||||
print(f"Your deal should have been: {player_card}")
|
||||
if card_a.rank < player_card.rank < card_b.rank:
|
||||
print("You could have won!")
|
||||
else:
|
||||
print("You would lose, so it was wise of you to chicken out!")
|
||||
|
||||
self.not_done = False
|
||||
break
|
||||
|
||||
if len(self.deck.cards) <= 3:
|
||||
print("You ran out of cards. Game over.")
|
||||
self.not_done = False
|
||||
break
|
||||
|
||||
self.card_a = self.deck.deal()
|
||||
self.card_b = self.deck.deal()
|
||||
|
||||
if self.money == 0:
|
||||
self.not_done = False
|
||||
|
||||
|
||||
def game_loop() -> None:
|
||||
game_over = False
|
||||
|
||||
while not game_over:
|
||||
game = Game()
|
||||
game.play()
|
||||
print(f"You have ${game.money} left")
|
||||
print("Would you like to play again? (y/n)")
|
||||
if input() == "n":
|
||||
game_over = True
|
||||
|
||||
|
||||
def main():
|
||||
print(
|
||||
"""
|
||||
Acey Ducey is a card game where you play against the computer.
|
||||
The Dealer(computer) will deal two cards facing up.
|
||||
You have an option to bet or not bet depending on whether or not you
|
||||
feel the card will have a value between the first two.
|
||||
If you do not want to bet input a 0
|
||||
"""
|
||||
)
|
||||
game_loop()
|
||||
print("\nThanks for playing!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,212 +0,0 @@
|
||||
"""aceyducey.py contains game code"""
|
||||
########################################################
|
||||
#
|
||||
# Acey Ducey
|
||||
#
|
||||
# From: BASIC Computer Games (1978)
|
||||
# Edited by David Ahl
|
||||
#
|
||||
# "This is a simulation of the Acey Ducey card game.
|
||||
# In the game, the dealer (the computer) deals two
|
||||
# cards face up. You have an option to bet or not to
|
||||
# bet depending on whether or not you feel the next
|
||||
# card dealt will have a value between the first two.
|
||||
#
|
||||
# "Your initial money is set to $100. The game keeps
|
||||
# going on until you lose all your money or interrupt
|
||||
# the program.
|
||||
#
|
||||
# "The original BASIC program author was Bill Palmby
|
||||
# of Prairie View, Illinois."
|
||||
#
|
||||
# Python port by Jeff Jetton, 2019
|
||||
#
|
||||
########################################################
|
||||
|
||||
|
||||
import random
|
||||
|
||||
# "You may alter [the following statement] if you want
|
||||
# to start with more or less than $100."
|
||||
DEFAULT_BANKROLL = 100
|
||||
|
||||
|
||||
def deal_card_num() -> int:
|
||||
"""Get card number"""
|
||||
return random.randint(0, 12)
|
||||
|
||||
|
||||
def get_card_name(number: int) -> str:
|
||||
"""Get card name"""
|
||||
card_names = (
|
||||
" 2",
|
||||
" 3",
|
||||
" 4",
|
||||
" 5",
|
||||
" 6",
|
||||
" 7",
|
||||
" 8",
|
||||
" 9",
|
||||
" 10",
|
||||
"Jack",
|
||||
"Queen",
|
||||
"King",
|
||||
"Ace",
|
||||
)
|
||||
return card_names[number]
|
||||
|
||||
|
||||
def display_bankroll(bank_roll: int) -> None:
|
||||
"""Print current bankroll"""
|
||||
if bank_roll > 0:
|
||||
print(f"You now have {bank_roll} dollars\n")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Display initial title and instructions."""
|
||||
print("\n Acey Ducey Card Game")
|
||||
print("Creative Computing Morristown, New Jersey")
|
||||
print("\n\n")
|
||||
print("Acey-Ducey is played in the following manner")
|
||||
print("The dealer (computer) deals two cards face up")
|
||||
print("You have an option to bet or not bet depending")
|
||||
print("on whether or not you feel the card will have")
|
||||
print("a value between the first two.")
|
||||
print("If you do not want to bet, input a 0")
|
||||
|
||||
multiple_game_loop()
|
||||
|
||||
print("OK Hope you had fun\n")
|
||||
|
||||
|
||||
def multiple_game_loop() -> None:
|
||||
"""Loop for series of multiple games."""
|
||||
keep_playing = True
|
||||
while keep_playing:
|
||||
# Initialize bankroll at start of each game
|
||||
BANK_ROLL = DEFAULT_BANKROLL
|
||||
display_bankroll(BANK_ROLL)
|
||||
|
||||
single_round(BANK_ROLL)
|
||||
|
||||
print("\n\nSorry, friend but you blew your wad")
|
||||
player_response = input("Try again (yes or no) ")
|
||||
if player_response.lower() == "yes":
|
||||
print()
|
||||
else:
|
||||
keep_playing = False
|
||||
|
||||
|
||||
def single_round(BANK_ROLL: int) -> None:
|
||||
"""Loop for a single round. Repeat until out of money."""
|
||||
while BANK_ROLL > 0:
|
||||
# Deal out dealer cards
|
||||
print("Here are your next two cards")
|
||||
dealer1 = deal_card_num()
|
||||
# If the cards match, we redeal 2nd card until they don't
|
||||
dealer2 = dealer1
|
||||
while dealer1 == dealer2:
|
||||
dealer2 = deal_card_num()
|
||||
# Organize the cards in order if they're not already
|
||||
if dealer1 >= dealer2:
|
||||
(dealer1, dealer2) = (dealer2, dealer1) # Ya gotta love Python!
|
||||
# Show dealer cards to the player
|
||||
# (use card name rather than internal number)
|
||||
print(get_card_name(dealer1))
|
||||
print(get_card_name(dealer2) + "\n")
|
||||
|
||||
# Get and handle player bet choice
|
||||
BET_IS_VALID = False
|
||||
while not BET_IS_VALID:
|
||||
curr_bet_str = input("What is your bet? ")
|
||||
try:
|
||||
curr_bet = int(curr_bet_str)
|
||||
except ValueError:
|
||||
# Bad input? Just loop back up and ask again...
|
||||
pass
|
||||
else:
|
||||
if curr_bet == 0:
|
||||
BET_IS_VALID = True
|
||||
print("Chicken!!\n")
|
||||
elif curr_bet > BANK_ROLL:
|
||||
print("Sorry, my friend but you bet too much")
|
||||
print(f"You have only {BANK_ROLL} dollars to bet\n")
|
||||
else:
|
||||
# Deal player card
|
||||
BET_IS_VALID = True
|
||||
player = deal_card_num()
|
||||
print(get_card_name(player))
|
||||
|
||||
# Did we win?
|
||||
if dealer1 < player < dealer2:
|
||||
print("You win!!!")
|
||||
BANK_ROLL += curr_bet
|
||||
else:
|
||||
print("Sorry, you lose")
|
||||
BANK_ROLL -= curr_bet
|
||||
|
||||
# Update player on new bankroll level
|
||||
display_bankroll(BANK_ROLL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
########################################################
|
||||
#
|
||||
# Porting notes:
|
||||
#
|
||||
# The original BASIC version had a variable named N
|
||||
# that was initialized to 100 and then never used.
|
||||
# Maybe it did something in feature that was edited
|
||||
# out of the final version used in the book?
|
||||
#
|
||||
# The original program simply generated random numbers
|
||||
# for each card. It did not simulate a true card deck,
|
||||
# where the dealing of a card eliminates it from the
|
||||
# deck and reduces the chances of the same value
|
||||
# being drawn. This "infinite deck" logic (or "deal,
|
||||
# with replacement after") has NOT been changed.
|
||||
#
|
||||
# In the interests of historical fidelity, the bug
|
||||
# in the original BASIC listing that let you input a
|
||||
# negative bet value has been faithfully reproduced.
|
||||
# This lets the player lose money when they win and
|
||||
# earn money when they lose! :-)
|
||||
#
|
||||
#
|
||||
# Ideas for Modifications
|
||||
#
|
||||
# Give the user the ability to quit the game, perhaps
|
||||
# by typing "quit" instead of making a bet. Provide a
|
||||
# final assessment based on how much of the original
|
||||
# bankroll they have left.
|
||||
#
|
||||
# Or have the game run for a set number of rounds or
|
||||
# until a certain bankroll goal is attained.
|
||||
#
|
||||
# Implement an "ante"--a set amount the player has to
|
||||
# bet each time rather than having the option to lay
|
||||
# out entirely.
|
||||
#
|
||||
# See "porting notes" above about negative bet values.
|
||||
# How would you fix this?
|
||||
#
|
||||
# When the player "chickens out", show them what the
|
||||
# next card would've been and point out whether they
|
||||
# made a good or bad decision.
|
||||
#
|
||||
# In what situations are the odds of winning high
|
||||
# enough to justify making a bet? Create a cheat mode
|
||||
# where the program identifies these situations and
|
||||
# lets the player know.
|
||||
#
|
||||
# Change the card dealing to simulate deals from a
|
||||
# single deck (or a user-selectable number of decks).
|
||||
#
|
||||
# Implement a two-player mode where players take turns
|
||||
# betting (or both bet on the same dealer cards and
|
||||
# get their own player card dealt).
|
||||
#
|
||||
########################################################
|
||||
@@ -1,29 +0,0 @@
|
||||
import io
|
||||
from unittest import mock
|
||||
from typing import TypeVar
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from acey_ducey import play_game
|
||||
|
||||
|
||||
@mock.patch("random.shuffle")
|
||||
def test_play_game_lose(
|
||||
mock_random_shuffle, monkeypatch: MonkeyPatch, capsys: CaptureFixture
|
||||
) -> None:
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO("100\n100"))
|
||||
T = TypeVar("T")
|
||||
|
||||
def identity(x: T) -> T:
|
||||
return x
|
||||
|
||||
mock_random_shuffle = identity # noqa: F841
|
||||
play_game()
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == (
|
||||
"You now have 100 dollars\n\n"
|
||||
"Here are you next two cards\n King\n Ace\n\n"
|
||||
"What is your bet? Queen\n"
|
||||
"Sorry, you lose\n"
|
||||
"Sorry, friend, but you blew your wad\n"
|
||||
)
|
||||
@@ -1,36 +0,0 @@
|
||||
from acey_ducey_oo import Card, Deck, Game
|
||||
|
||||
|
||||
def test_card_init() -> None:
|
||||
card = Card("\u2665", 2)
|
||||
assert card.suit == "\u2665"
|
||||
assert card.rank == 2
|
||||
|
||||
|
||||
def test_card_str() -> None:
|
||||
card = Card("\u2665", 2)
|
||||
assert str(card) == "2\u2665"
|
||||
|
||||
|
||||
def test_deck_init() -> None:
|
||||
deck = Deck()
|
||||
assert len(deck.cards) == 52
|
||||
assert deck.cards[0].suit == "\u2665"
|
||||
assert deck.cards[0].rank == 2
|
||||
|
||||
|
||||
def test_deck_shuffle() -> None:
|
||||
deck = Deck()
|
||||
deck.shuffle()
|
||||
|
||||
|
||||
def test_deck_deal() -> None:
|
||||
deck = Deck()
|
||||
card = deck.deal()
|
||||
assert card.rank == 14
|
||||
assert card.suit == "\u2660"
|
||||
|
||||
|
||||
def test_game_init() -> None:
|
||||
game = Game()
|
||||
assert len(game.deck.cards) == 50 # two are already dealt
|
||||
@@ -1,58 +0,0 @@
|
||||
import io
|
||||
import pytest
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from amazing import build_maze, welcome_header, main
|
||||
|
||||
|
||||
def test_welcome_header(capsys: CaptureFixture[str]) -> None:
|
||||
capsys.readouterr()
|
||||
welcome_header()
|
||||
out, err = capsys.readouterr()
|
||||
assert out == (
|
||||
" AMAZING PROGRAM\n"
|
||||
" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n\n"
|
||||
)
|
||||
assert err == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("width", "length"),
|
||||
[
|
||||
(1, 1),
|
||||
(1, 0),
|
||||
(1, -1),
|
||||
(1, 2),
|
||||
(2, 1),
|
||||
],
|
||||
)
|
||||
def test_build_maze(width: int, length: int) -> None:
|
||||
with pytest.raises(AssertionError):
|
||||
build_maze(width, length)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("width", "length"),
|
||||
[
|
||||
(3, 3),
|
||||
(10, 10),
|
||||
],
|
||||
)
|
||||
def test_main(monkeypatch: MonkeyPatch, width: int, length: int) -> None:
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{width},{length}"),
|
||||
)
|
||||
main()
|
||||
|
||||
|
||||
def test_main_error(monkeypatch: MonkeyPatch) -> None:
|
||||
width = 1
|
||||
length = 2
|
||||
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{width},{length}\n3,3"),
|
||||
)
|
||||
main()
|
||||
@@ -1,5 +0,0 @@
|
||||
from animal import initial_message
|
||||
|
||||
|
||||
def test_initial_message() -> None:
|
||||
initial_message()
|
||||
@@ -1,15 +0,0 @@
|
||||
import io
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
import pytest
|
||||
|
||||
from awari import print_with_tab, main
|
||||
|
||||
|
||||
def test_print_with_tab() -> None:
|
||||
print_with_tab(3, "Hello")
|
||||
|
||||
|
||||
def test_main(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO("1\n2\n3\n4\n5\n6"))
|
||||
with pytest.raises(EOFError):
|
||||
main()
|
||||
@@ -1,35 +0,0 @@
|
||||
import io
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bagels import build_result_string, main, pick_number
|
||||
|
||||
|
||||
def test_build_result_string() -> None:
|
||||
build_result_string(["a", "b", "c"], "abc")
|
||||
|
||||
|
||||
def test_pick_number() -> None:
|
||||
picked = pick_number()
|
||||
assert len(picked) == 3
|
||||
for el in picked:
|
||||
assert el in "0123456789"
|
||||
|
||||
|
||||
def test_main(monkeypatch: MonkeyPatch, capsys: CaptureFixture) -> None:
|
||||
# Succeed
|
||||
round_1 = "Y\n4444\nabc\n444\n456\n145\n321\n123"
|
||||
|
||||
# Fail after 20 guesses
|
||||
round_2 = (
|
||||
"666\n132\n321\n312\n132\n213\n678\n678\n678\n678\n678\n"
|
||||
"678\n678\n678\n678\n678\n678\n678\n678\n678\n678\nNo"
|
||||
)
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO(f"{round_1}\nYES\n{round_2}"))
|
||||
monkeypatch.setattr("bagels.pick_number", lambda: ["1", "2", "3"])
|
||||
main()
|
||||
captured = capsys.readouterr()
|
||||
assert "Would you like the rules" in captured.out
|
||||
assert "I have a number in mind" in captured.out
|
||||
assert "My number was" in captured.out
|
||||
assert "Hope you had fun." in captured.out
|
||||
@@ -1,109 +0,0 @@
|
||||
import io
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.capture import CaptureFixture
|
||||
from banner import print_banner
|
||||
|
||||
|
||||
def test_print_banner(monkeypatch: MonkeyPatch) -> None:
|
||||
horizontal = "1"
|
||||
vertical = "1"
|
||||
centered = "1"
|
||||
char = "*"
|
||||
statement = "O" # only capital letters
|
||||
set_page = "2"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{horizontal}\n{vertical}\n{centered}\n{char}\n{statement}\n{set_page}"
|
||||
),
|
||||
)
|
||||
print_banner()
|
||||
|
||||
|
||||
def test_print_banner_horizontal_0(
|
||||
monkeypatch: MonkeyPatch, capsys: CaptureFixture
|
||||
) -> None:
|
||||
horizontal = "1"
|
||||
vertical = "1"
|
||||
centered = "1"
|
||||
char = "*"
|
||||
statement = "O" # only capital letters
|
||||
set_page = "2"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"0\n{horizontal}\n{vertical}\n{centered}\n{char}\n{statement}\n{set_page}"
|
||||
),
|
||||
)
|
||||
print_banner()
|
||||
captured = capsys.readouterr()
|
||||
assert "Please enter a number greater than zero" in captured.out
|
||||
|
||||
|
||||
def test_print_banner_vertical_0(
|
||||
monkeypatch: MonkeyPatch, capsys: CaptureFixture
|
||||
) -> None:
|
||||
horizontal = "1"
|
||||
vertical = "1"
|
||||
centered = "1"
|
||||
char = "*"
|
||||
statement = "O" # only capital letters
|
||||
set_page = "2"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{horizontal}\n0\n{vertical}\n{centered}\n{char}\n{statement}\n{set_page}"
|
||||
),
|
||||
)
|
||||
print_banner()
|
||||
captured = capsys.readouterr()
|
||||
assert "Please enter a number greater than zero" in captured.out
|
||||
|
||||
|
||||
def test_print_banner_centered(
|
||||
monkeypatch: MonkeyPatch, capsys: CaptureFixture
|
||||
) -> None:
|
||||
horizontal = "1"
|
||||
vertical = "1"
|
||||
centered = "Y"
|
||||
char = "*"
|
||||
statement = "O" # only capital letters
|
||||
set_page = "2"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{horizontal}\n{vertical}\n{centered}\n{char}\n{statement}\n{set_page}"
|
||||
),
|
||||
)
|
||||
print_banner()
|
||||
captured = capsys.readouterr()
|
||||
expected = (
|
||||
"Horizontal Vertical Centered Character "
|
||||
"(type 'ALL' if you want character being printed) Statement Set page "
|
||||
" *****\n"
|
||||
" * *\n"
|
||||
" * *\n"
|
||||
" * *\n"
|
||||
" * *\n"
|
||||
" * *\n"
|
||||
" *****\n\n\n"
|
||||
)
|
||||
assert captured.out.split("\n") == expected.split("\n")
|
||||
|
||||
|
||||
def test_print_banner_all_statement(
|
||||
monkeypatch: MonkeyPatch, capsys: CaptureFixture
|
||||
) -> None:
|
||||
horizontal = "1"
|
||||
vertical = "1"
|
||||
centered = "1"
|
||||
char = "UNIT TESTING"
|
||||
statement = "ALL" # only capital letters
|
||||
set_page = "2"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{horizontal}\n{vertical}\n{centered}\n{char}\n{statement}\n{set_page}"
|
||||
),
|
||||
)
|
||||
print_banner()
|
||||
@@ -1,16 +0,0 @@
|
||||
import io
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.capture import CaptureFixture
|
||||
|
||||
from basketball import Basketball
|
||||
|
||||
|
||||
def test_basketball(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO("\n1\n6\n1\n2\n1\n2\n1\n2\n1\n2\n3\n4\n5\n4"),
|
||||
)
|
||||
with pytest.raises(EOFError):
|
||||
Basketball()
|
||||
@@ -1,21 +0,0 @@
|
||||
import io
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from batnum import main
|
||||
|
||||
|
||||
def test_main_win(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
pile_size = 1
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO(f"{pile_size}\n1\n1 2\n2\n1\n-1\n"))
|
||||
main()
|
||||
captured = capsys.readouterr()
|
||||
assert "CONGRATULATIONS, YOU WIN" in captured.out
|
||||
|
||||
|
||||
def test_main_lose(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
pile_size = 3
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO(f"{pile_size}\n2\n1 2\n2\n1\n1\n-1\n"))
|
||||
main()
|
||||
captured = capsys.readouterr()
|
||||
assert "TOUGH LUCK, YOU LOSE" in captured.out
|
||||
@@ -1,218 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from dataclasses import dataclass
|
||||
from random import randrange
|
||||
|
||||
DESTROYER_LENGTH = 2
|
||||
CRUISER_LENGTH = 3
|
||||
AIRCRAFT_CARRIER_LENGTH = 4
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
@classmethod
|
||||
def random(cls, start: int, stop: int) -> "Point":
|
||||
return Point(randrange(start, stop), randrange(start, stop))
|
||||
|
||||
def __add__(self, vector: "Vector") -> "Point":
|
||||
return Point(self.x + vector.x, self.y + vector.y)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Vector:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
@staticmethod
|
||||
def random() -> "Vector":
|
||||
return Vector(randrange(-1, 2, 2), randrange(-1, 2, 2))
|
||||
|
||||
def __mul__(self, factor: int) -> "Vector":
|
||||
return Vector(self.x * factor, self.y * factor)
|
||||
|
||||
|
||||
class Sea:
|
||||
WIDTH = 6
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._graph = tuple([0 for _ in range(self.WIDTH)] for _ in range(self.WIDTH))
|
||||
|
||||
def _validate_item_indices(self, point: Point) -> None:
|
||||
if not isinstance(point, Point):
|
||||
raise ValueError(f"Sea indices must be Points, not {type(point).__name__}")
|
||||
|
||||
if not ((1 <= point.x <= self.WIDTH) and (1 <= point.y <= self.WIDTH)):
|
||||
raise IndexError("Sea index out of range")
|
||||
|
||||
# Allows us to get the value using a point as a key, for example, `sea[Point(3,2)]`
|
||||
def __getitem__(self, point: Point) -> int:
|
||||
self._validate_item_indices(point)
|
||||
|
||||
return self._graph[point.y - 1][point.x - 1]
|
||||
|
||||
# Allows us to get the value using a point as a key, for example, `sea[Point(3,2)] = 3`
|
||||
def __setitem__(self, point: Point, value: int) -> None:
|
||||
self._validate_item_indices(point)
|
||||
self._graph[point.y - 1][point.x - 1] = value
|
||||
|
||||
# Allows us to check if a point exists in the sea for example, `if Point(3,2) in sea:`
|
||||
def __contains__(self, point: Point) -> bool:
|
||||
try:
|
||||
self._validate_item_indices(point)
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Redefines how python will render this object when asked as a str
|
||||
def __str__(self) -> str:
|
||||
# Display it encoded
|
||||
return "\n".join(
|
||||
[
|
||||
" ".join(
|
||||
[str(self._graph[y][x]) for y in range(self.WIDTH - 1, -1, -1)]
|
||||
)
|
||||
for x in range(self.WIDTH)
|
||||
]
|
||||
)
|
||||
|
||||
def has_ship(self, ship_code: int) -> bool:
|
||||
return any(ship_code in row for row in self._graph)
|
||||
|
||||
def count_sunk(self, *ship_codes: int) -> int:
|
||||
return sum(not self.has_ship(ship_code) for ship_code in ship_codes)
|
||||
|
||||
|
||||
class Battle:
|
||||
def __init__(self) -> None:
|
||||
self.sea = Sea()
|
||||
self.place_ship(DESTROYER_LENGTH, 1)
|
||||
self.place_ship(DESTROYER_LENGTH, 2)
|
||||
self.place_ship(CRUISER_LENGTH, 3)
|
||||
self.place_ship(CRUISER_LENGTH, 4)
|
||||
self.place_ship(AIRCRAFT_CARRIER_LENGTH, 5)
|
||||
self.place_ship(AIRCRAFT_CARRIER_LENGTH, 6)
|
||||
self.splashes = 0
|
||||
self.hits = 0
|
||||
|
||||
def _next_target(self) -> Point:
|
||||
while True:
|
||||
try:
|
||||
guess = input("? ")
|
||||
coordinates = guess.split(",")
|
||||
|
||||
if len(coordinates) != 2:
|
||||
raise ValueError()
|
||||
|
||||
point = Point(int(coordinates[0]), int(coordinates[1]))
|
||||
|
||||
if point not in self.sea:
|
||||
raise ValueError()
|
||||
|
||||
return point
|
||||
except ValueError:
|
||||
print(
|
||||
f"INVALID. SPECIFY TWO NUMBERS FROM 1 TO {Sea.WIDTH}, SEPARATED BY A COMMA."
|
||||
)
|
||||
|
||||
@property
|
||||
def splash_hit_ratio(self) -> str:
|
||||
return f"{self.splashes}/{self.hits}"
|
||||
|
||||
@property
|
||||
def _is_finished(self) -> bool:
|
||||
return self.sea.count_sunk(*(i for i in range(1, 7))) == 6
|
||||
|
||||
def place_ship(self, size: int, ship_code: int) -> None:
|
||||
while True:
|
||||
start = Point.random(1, self.sea.WIDTH + 1)
|
||||
vector = Vector.random()
|
||||
# Get potential ship points
|
||||
points = [start + vector * i for i in range(size)]
|
||||
|
||||
if not (
|
||||
all([point in self.sea for point in points])
|
||||
and not any([self.sea[point] for point in points])
|
||||
):
|
||||
# ship out of bounds or crosses other ship, trying again
|
||||
continue
|
||||
|
||||
# We found a valid spot, so actually place it now
|
||||
for point in points:
|
||||
self.sea[point] = ship_code
|
||||
|
||||
break
|
||||
|
||||
def loop(self) -> None:
|
||||
while True:
|
||||
target = self._next_target()
|
||||
target_value = self.sea[target]
|
||||
|
||||
if target_value < 0:
|
||||
print(
|
||||
f"YOU ALREADY PUT A HOLE IN SHIP NUMBER {abs(target_value)} AT THAT POINT."
|
||||
)
|
||||
|
||||
if target_value <= 0:
|
||||
print("SPLASH! TRY AGAIN.")
|
||||
self.splashes += 1
|
||||
continue
|
||||
|
||||
print(f"A DIRECT HIT ON SHIP NUMBER {target_value}")
|
||||
self.hits += 1
|
||||
self.sea[target] = -target_value
|
||||
|
||||
if not self.sea.has_ship(target_value):
|
||||
print("AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS.")
|
||||
self._display_sunk_report()
|
||||
|
||||
if self._is_finished:
|
||||
self._display_game_end()
|
||||
break
|
||||
|
||||
print(f"YOUR CURRENT SPLASH/HIT RATIO IS {self.splash_hit_ratio}")
|
||||
|
||||
def _display_sunk_report(self) -> None:
|
||||
print(
|
||||
"SO FAR, THE BAD GUYS HAVE LOST",
|
||||
f"{self.sea.count_sunk(1, 2)} DESTROYER(S),",
|
||||
f"{self.sea.count_sunk(3, 4)} CRUISER(S),",
|
||||
f"AND {self.sea.count_sunk(5, 6)} AIRCRAFT CARRIER(S).",
|
||||
)
|
||||
|
||||
def _display_game_end(self) -> None:
|
||||
print(
|
||||
"YOU HAVE TOTALLY WIPED OUT THE BAD GUYS' FLEET "
|
||||
f"WITH A FINAL SPLASH/HIT RATIO OF {self.splash_hit_ratio}"
|
||||
)
|
||||
|
||||
if not self.splashes:
|
||||
print("CONGRATULATIONS -- A DIRECT HIT EVERY TIME.")
|
||||
|
||||
print("\n****************************")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
game = Battle()
|
||||
print(
|
||||
f"""
|
||||
BATTLE
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
THE FOLLOWING CODE OF THE BAD GUYS' FLEET DISPOSITION
|
||||
HAS BEEN CAPTURED BUT NOT DECODED:
|
||||
|
||||
{game.sea}
|
||||
|
||||
DE-CODE IT AND USE IT IF YOU CAN
|
||||
BUT KEEP THE DE-CODING METHOD A SECRET.
|
||||
|
||||
START GAME"""
|
||||
)
|
||||
game.loop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,27 +0,0 @@
|
||||
import io
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from battle import main as main_one
|
||||
from battle_oo import main as main_oo
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"main",
|
||||
[main_one, main_oo],
|
||||
)
|
||||
def test_main(monkeypatch: MonkeyPatch, main: Callable[[], None]) -> None:
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
"1,1\n1,2\n1,3\n1,4\n1,5\n1,6\n"
|
||||
"2,1\n2,2\n2,3\n2,4\n2,5\n2,6\n"
|
||||
"3,1\n3,2\n3,3\n3,4\n3,5\n3,6\n"
|
||||
"4,1\n4,2\n4,3\n4,4\n4,5\n4,6\n"
|
||||
"5,1\n5,2\n5,3\n5,4\n5,5\n5,6\n"
|
||||
"6,1\n6,2\n6,3\n6,4\n6,5\n6,6\n"
|
||||
),
|
||||
)
|
||||
main()
|
||||
@@ -1,17 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.capture import CaptureFixture
|
||||
|
||||
from blackjack import main
|
||||
|
||||
|
||||
def test_blackjack(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
nb_players = 1
|
||||
instructions = "y"
|
||||
bet = 100
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{nb_players}\n{instructions}\n\n{bet}\ns\nn\n"),
|
||||
)
|
||||
main()
|
||||
@@ -1,17 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bombardment import play
|
||||
|
||||
|
||||
def test_bombardment(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
"\n1 2 3 4\n6\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
|
||||
"\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
|
||||
"\n21\n22\n23\n24\n25"
|
||||
),
|
||||
)
|
||||
play()
|
||||
@@ -1,16 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bombs_away import play_game
|
||||
|
||||
|
||||
def test_bombs_away(monkeypatch: MonkeyPatch) -> None:
|
||||
side = 1
|
||||
target = 1
|
||||
missions = 1
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{side}\n{target}\n{missions}\n3\n50"),
|
||||
)
|
||||
play_game()
|
||||
@@ -1,72 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.capture import CaptureFixture
|
||||
|
||||
from bounce import main
|
||||
|
||||
|
||||
def test_bounce(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
time_increment = 0.1
|
||||
velocity = 30
|
||||
coefficient = 1
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{time_increment:0.1f}\n{velocity}\n{coefficient}\n"),
|
||||
)
|
||||
main()
|
||||
actual = capsys.readouterr().out
|
||||
expected = """ BOUNCE
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
|
||||
THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY
|
||||
OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF
|
||||
ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION
|
||||
COEFFICIENCY (LESS THAN 1).
|
||||
|
||||
YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN
|
||||
'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).
|
||||
|
||||
TIME INCREMENT (SEC)?
|
||||
VELOCITY (FPS)?
|
||||
COEFFICIENT?
|
||||
FEET
|
||||
|
||||
14 000 000 000
|
||||
0 0 0
|
||||
13 0 0 0 0 0 0
|
||||
|
||||
12 0 0 0 0 0 0
|
||||
|
||||
11 0 0 0
|
||||
0 0 0
|
||||
10
|
||||
0 0 0
|
||||
9 0 0 0
|
||||
|
||||
8
|
||||
0 0 0
|
||||
7 0 0 0
|
||||
|
||||
6
|
||||
0 0 0
|
||||
5 0 0 0
|
||||
|
||||
4
|
||||
|
||||
3 0 0 0
|
||||
|
||||
2 0 0 0
|
||||
|
||||
1
|
||||
|
||||
00 0 0
|
||||
...................................................................
|
||||
0 1 2 3 4 5 6
|
||||
|
||||
SECONDS
|
||||
|
||||
""" # noqa: W291
|
||||
assert actual.split("\n") == expected.split("\n")
|
||||
@@ -1,273 +0,0 @@
|
||||
import io
|
||||
from typing import List
|
||||
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bowling import main
|
||||
|
||||
|
||||
def test_bowling_strikes(monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None:
|
||||
def perfect_roll(pins: List[int]) -> None:
|
||||
for i in range(20):
|
||||
x = i
|
||||
if x < len(pins):
|
||||
pins[x] = 1
|
||||
|
||||
monkeypatch.setattr("bowling.simulate_roll", perfect_roll)
|
||||
|
||||
instructions1 = "Y"
|
||||
players1 = 1
|
||||
name1 = "Martin"
|
||||
another_game1 = "Y"
|
||||
|
||||
instructions2 = "N"
|
||||
players2 = 2
|
||||
name21 = "Anna"
|
||||
name22 = "Bob"
|
||||
another_game2 = "N"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{instructions1}\n{players1}\n{name1}\n{another_game1}\n"
|
||||
f"{instructions2}\n{players2}\n{name21}\n{name22}\n{another_game2}"
|
||||
),
|
||||
)
|
||||
main()
|
||||
actual = capsys.readouterr().out
|
||||
expected = """ Bowl
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
WELCOME TO THE ALLEY.
|
||||
BRING YOUR FRIENDS.
|
||||
OKAY LET'S FIRST GET ACQUAINTED.
|
||||
|
||||
THE INSTRUCTIONS (Y/N)? THE GAME OF BOWLING TAKES MIND AND SKILL. DURING THE GAME
|
||||
THE COMPUTER WILL KEEP SCORE. YOU MAY COMPETE WITH
|
||||
OTHER PLAYERS[UP TO FOUR]. YOU WILL BE PLAYING TEN FRAMES.
|
||||
ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE
|
||||
PIN IS STANDING. AFTER THE GAME THE COMPUTER WILL SHOW YOUR
|
||||
SCORES.
|
||||
FIRST OF ALL...HOW MANY ARE PLAYING?
|
||||
VERY GOOD...
|
||||
Enter name for player 1:
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Martin
|
||||
STRIKE!!!
|
||||
Extra rolls for Martin
|
||||
Martin: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], total:300
|
||||
DO YOU WANT ANOTHER GAME?
|
||||
THE INSTRUCTIONS (Y/N)? FIRST OF ALL...HOW MANY ARE PLAYING?
|
||||
VERY GOOD...
|
||||
Enter name for player 1: Enter name for player 2:
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Anna
|
||||
STRIKE!!!
|
||||
Extra rolls for Anna
|
||||
|
||||
O O O O
|
||||
O O O
|
||||
O O
|
||||
O
|
||||
10 for Bob
|
||||
STRIKE!!!
|
||||
Extra rolls for Bob
|
||||
Anna: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], total:300
|
||||
Bob: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], total:300
|
||||
DO YOU WANT ANOTHER GAME? """ # noqa: W291
|
||||
assert actual.split("\n") == expected.split("\n")
|
||||
@@ -1,61 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.capture import CaptureFixture
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from boxing import play
|
||||
|
||||
|
||||
def test_boxing_bad_opponent(
|
||||
monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]
|
||||
) -> None:
|
||||
monkeypatch.setattr("boxing.Player.get_punch_choice", lambda self: 1)
|
||||
monkeypatch.setattr("boxing.get_opponent_stats", lambda: (2, 1))
|
||||
monkeypatch.setattr("boxing.is_opponents_turn", lambda: False)
|
||||
|
||||
opponent = "Anna"
|
||||
my_man = "Bob"
|
||||
strength = "1"
|
||||
weakness = "2"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{opponent}\n{my_man}\n{strength}\n{weakness}\n1\n1\n1"),
|
||||
)
|
||||
play()
|
||||
actual = capsys.readouterr().out
|
||||
expected = """BOXING
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
|
||||
BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)
|
||||
WHAT IS YOUR OPPONENT'S NAME? WHAT IS YOUR MAN'S NAME? DIFFERENT PUNCHES ARE 1 FULL SWING 2 HOOK 3 UPPERCUT 4 JAB
|
||||
WHAT IS YOUR MAN'S BEST? WHAT IS HIS VULNERABILITY? Anna'S ADVANTAGE is 1 AND VULNERABILITY IS SECRET.
|
||||
ROUND 1 BEGINS...
|
||||
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob WINS ROUND 1
|
||||
ROUND 2 BEGINS...
|
||||
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob SWINGS AND HE CONNECTS!
|
||||
Bob WINS ROUND 2
|
||||
ROUND 3 BEGINS...
|
||||
|
||||
Bob AMAZINGLY WINS
|
||||
|
||||
|
||||
AND NOW GOODBYE FROM THE OLYMPIC ARENA.
|
||||
"""
|
||||
assert actual.split("\n") == expected.split("\n")
|
||||
@@ -1,350 +0,0 @@
|
||||
"""
|
||||
BUG (overengineered)
|
||||
|
||||
Overengineered version of bug game
|
||||
Demonstrates function-based Model View Controller pattern
|
||||
|
||||
Ported by Peter Sharp
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from random import randint
|
||||
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, TypedDict, Union
|
||||
|
||||
PAGE_WIDTH = 64
|
||||
|
||||
OneParamFunc = Callable[[Any], Any]
|
||||
TwoParamFunc = Callable[[Any, Any], Any]
|
||||
StateFunctions = Tuple[OneParamFunc, OneParamFunc, TwoParamFunc]
|
||||
Action = Literal["instructions", "game", "pictures", "won", "start", "exit"]
|
||||
|
||||
Bodypart = namedtuple("Bodypart", ["name", "count", "depends"])
|
||||
|
||||
# body part types used by the game to work out whether a player's body part can be added
|
||||
part_types = (
|
||||
Bodypart(name="BODY", count=1, depends=None),
|
||||
Bodypart(name="NECK", count=1, depends=0),
|
||||
Bodypart(name="HEAD", count=1, depends=1),
|
||||
Bodypart(name="FEELERS", count=2, depends=2),
|
||||
Bodypart(name="TAIL", count=1, depends=0),
|
||||
Bodypart(name="LEGS", count=6, depends=0),
|
||||
)
|
||||
|
||||
|
||||
class DataDict(TypedDict):
|
||||
state: Action
|
||||
partNo: Optional[Any]
|
||||
players: Dict[str, List[int]]
|
||||
partTypes: Tuple[Bodypart, ...]
|
||||
finished: List[Any]
|
||||
logs: List[Any]
|
||||
|
||||
|
||||
def game_loop(states: Dict[Action, StateFunctions], data: DataDict) -> None:
|
||||
"""
|
||||
Starts the game loop using given states and data
|
||||
|
||||
Uses a modified version of the MVC (Model View Controller) pattern that uses functions instead of objects
|
||||
|
||||
each state in the game has one of each of the following:
|
||||
View, displays data
|
||||
Control, converts raw command from user into something the model understands
|
||||
Model, updates game data based on action received from controller
|
||||
"""
|
||||
|
||||
while True:
|
||||
if data["state"] == "exit":
|
||||
break
|
||||
view, control, model = states[data["state"]]
|
||||
cmd = view(data)
|
||||
action = control(cmd)
|
||||
data = model(data, action)
|
||||
|
||||
|
||||
def print_start(_: Any) -> str:
|
||||
"""
|
||||
Prints start message
|
||||
"""
|
||||
print_centered("BUG")
|
||||
print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
print("THE GAME BUG")
|
||||
print("I HOPE YOU ENJOY THIS GAME.")
|
||||
print()
|
||||
return input("DO YOU WANT INSTRUCTIONS? ")
|
||||
|
||||
|
||||
def control_start(cmd: str) -> str:
|
||||
"""
|
||||
Controls the start state
|
||||
"""
|
||||
if cmd.lower() in ("y", "yes"):
|
||||
action = "instructions"
|
||||
else:
|
||||
action = "game"
|
||||
return action
|
||||
|
||||
|
||||
def print_instructions(data: DataDict) -> str:
|
||||
"""
|
||||
Prints game instructions
|
||||
"""
|
||||
print("THE OBJECT OF BUG IS TO FINISH YOUR BUG BEFORE I FINISH")
|
||||
print("MINE. EACH NUMBER STANDS FOR A PART OF THE BUG BODY.")
|
||||
print("I WILL ROLL THE DIE FOR YOU, TELL YOU WHAT I ROLLED FOR YOU")
|
||||
print("WHAT THE NUMBER STANDS FOR, AND IF YOU CAN GET THE PART.")
|
||||
print("IF YOU CAN GET THE PART I WILL GIVE IT TO YOU.")
|
||||
print("THE SAME WILL HAPPEN ON MY TURN.")
|
||||
print("IF THERE IS A CHANGE IN EITHER BUG I WILL GIVE YOU THE")
|
||||
print("OPTION OF SEEING THE PICTURES OF THE BUGS.")
|
||||
print("THE NUMBERS STAND FOR PARTS AS FOLLOWS:")
|
||||
|
||||
print_table(
|
||||
[
|
||||
("NUMBER", "PART", "NUMBER OF PART NEEDED"),
|
||||
*[
|
||||
(i + 1, part.name, part.count)
|
||||
for i, part in enumerate(data["partTypes"])
|
||||
],
|
||||
]
|
||||
)
|
||||
print()
|
||||
print()
|
||||
return ""
|
||||
|
||||
|
||||
def goto_game(_: Any) -> Literal["game"]:
|
||||
return "game"
|
||||
|
||||
|
||||
def update_state(data: DataDict, action: Action) -> DataDict:
|
||||
"""
|
||||
sets game state to given player value
|
||||
"""
|
||||
return {**data, "state": action} # type: ignore
|
||||
|
||||
|
||||
def update_game(data: DataDict, action: Action) -> DataDict:
|
||||
"""
|
||||
Updates game data for player turns until one player successfully gets a body part
|
||||
"""
|
||||
# stores logs of what happened during a particular round
|
||||
Log1 = Tuple[str, int, Any]
|
||||
Log2 = Tuple[str, int, Any, Any]
|
||||
logs: List[Union[Log1, Log2]] = []
|
||||
|
||||
if action == "pictures":
|
||||
data["state"] = "pictures"
|
||||
else:
|
||||
part_added = False
|
||||
while not part_added:
|
||||
for player, parts in data["players"].items():
|
||||
# rolls the dice for a part
|
||||
new_part_idx = randint(1, 6) - 1
|
||||
|
||||
# gets information about the picked part
|
||||
part_type = data["partTypes"][new_part_idx]
|
||||
|
||||
# gets the number of existing parts of that type the player has
|
||||
part_count = parts[new_part_idx]
|
||||
|
||||
logs.append(("rolled", new_part_idx, player))
|
||||
|
||||
# a new part can only be added if the player has the parts
|
||||
# the new part depends on and doesn't have enough of the part already
|
||||
overMaxParts = part_type.count < part_count + 1
|
||||
missing_part_dep = (
|
||||
part_type.depends is not None and parts[part_type.depends] == 0
|
||||
)
|
||||
|
||||
if not overMaxParts and not missing_part_dep:
|
||||
# adds a new part
|
||||
part_count += 1
|
||||
logs.append(("added", new_part_idx, player))
|
||||
part_added = True
|
||||
elif missing_part_dep:
|
||||
logs.append(("missingDep", new_part_idx, player, part_type.depends))
|
||||
if overMaxParts:
|
||||
logs.append(("overMax", new_part_idx, player, part_count))
|
||||
|
||||
data["players"][player][new_part_idx] = part_count
|
||||
data["logs"] = logs
|
||||
|
||||
# checks if any players have finished their bug
|
||||
finished = get_finished(data)
|
||||
if len(finished) > 0:
|
||||
# and sets the state to 'won' if that's the case
|
||||
data["finished"] = finished
|
||||
data["state"] = "won"
|
||||
return data
|
||||
|
||||
|
||||
def get_finished(data: DataDict) -> List[str]:
|
||||
"""
|
||||
Gets players who have finished their bugs
|
||||
"""
|
||||
total_parts = sum(part_type.count for part_type in data["partTypes"])
|
||||
finished = []
|
||||
for player, parts in data["players"].items():
|
||||
if sum(parts) == total_parts:
|
||||
finished.append(player)
|
||||
return finished
|
||||
|
||||
|
||||
def print_game(data: DataDict) -> str:
|
||||
"""
|
||||
Displays the results of the game turn
|
||||
"""
|
||||
for log in data["logs"]:
|
||||
code, part_idx, player, *logdata = log
|
||||
part_type = data["partTypes"][part_idx]
|
||||
|
||||
if code == "rolled":
|
||||
print()
|
||||
print(f"{player} ROLLED A {part_idx + 1}")
|
||||
print(f"{part_idx + 1}={part_type.name}")
|
||||
|
||||
elif code == "added":
|
||||
if player == "YOU":
|
||||
if part_type.name in ["FEELERS", "LEGS", "TAIL"]:
|
||||
print(f"I NOW GIVE YOU A {part_type.name.replace('s', '')}.")
|
||||
else:
|
||||
print(f"YOU NOW HAVE A {part_type.name}.")
|
||||
elif player == "I":
|
||||
if part_type.name in ["BODY", "NECK", "TAIL"]:
|
||||
print(f"I NOW HAVE A {part_type.name}.")
|
||||
elif part_type.name == "FEELERS":
|
||||
print("I GET A FEELER.")
|
||||
|
||||
if part_type.count > 2:
|
||||
print(
|
||||
f"{player} NOW HAVE {data['players'][player][part_idx]} {part_type.name}"
|
||||
)
|
||||
|
||||
elif code == "missingDep":
|
||||
(dep_idx,) = logdata
|
||||
dep = data["partTypes"][dep_idx]
|
||||
print(
|
||||
f"YOU DO NOT HAVE A {dep.name}"
|
||||
if player == "YOU"
|
||||
else f"I NEEDED A {dep.name}"
|
||||
)
|
||||
|
||||
elif code == "overMax":
|
||||
(part_count,) = logdata
|
||||
if part_count > 1:
|
||||
num = "TWO" if part_count == 2 else part_count
|
||||
maxMsg = f"HAVE {num} {part_type.name}S ALREADY"
|
||||
else:
|
||||
maxMsg = f"ALREADY HAVE A {part_type.name}"
|
||||
print(f"{player} {maxMsg}")
|
||||
|
||||
return input("DO YOU WANT THE PICTURES? ") if len(data["logs"]) else "n"
|
||||
|
||||
|
||||
def print_pictures(data: DataDict) -> None:
|
||||
"""
|
||||
Displays what the bugs look like for each player
|
||||
"""
|
||||
typeIxs = {part_type.name: idx for idx, part_type in enumerate(data["partTypes"])}
|
||||
PIC_WIDTH = 22
|
||||
for player, parts in data["players"].items():
|
||||
print(f"*****{'YOUR' if player == 'YOU' else 'MY'} BUG*****")
|
||||
print()
|
||||
print()
|
||||
if parts[typeIxs["BODY"]] > 0:
|
||||
if parts[typeIxs["FEELERS"]] > 0:
|
||||
F = " ".join(["F"] * parts[typeIxs["FEELERS"]])
|
||||
for _ in range(4):
|
||||
print(" " * 9 + F)
|
||||
if parts[typeIxs["HEAD"]] > 0:
|
||||
print_centered("HHHHHHH", PIC_WIDTH)
|
||||
print_centered("H H", PIC_WIDTH)
|
||||
print_centered("H O O H", PIC_WIDTH)
|
||||
print_centered("H H", PIC_WIDTH)
|
||||
print_centered("H V H", PIC_WIDTH)
|
||||
print_centered("HHHHHHH", PIC_WIDTH)
|
||||
if parts[typeIxs["NECK"]] > 0:
|
||||
for _ in range(2):
|
||||
print_centered("N N", PIC_WIDTH)
|
||||
print_centered("BBBBBBBBBBBB", PIC_WIDTH)
|
||||
for _ in range(2):
|
||||
print_centered("B B", PIC_WIDTH)
|
||||
|
||||
if parts[typeIxs["TAIL"]] > 0:
|
||||
print("TTTTTB B")
|
||||
print_centered("BBBBBBBBBBBB", PIC_WIDTH)
|
||||
if parts[typeIxs["LEGS"]] > 0:
|
||||
L = "L" * parts[typeIxs["LEGS"]]
|
||||
for _ in range(2):
|
||||
print(" " * 5 + L)
|
||||
print()
|
||||
|
||||
|
||||
def control_game(cmd: str) -> Literal["pictures", "game"]:
|
||||
"""
|
||||
returns state based on command
|
||||
"""
|
||||
if cmd.lower() in ("y", "yes"):
|
||||
action = "pictures"
|
||||
else:
|
||||
action = "game"
|
||||
return action # type: ignore
|
||||
|
||||
|
||||
def print_winner(data: DataDict) -> None:
|
||||
"""
|
||||
Displays the winning message
|
||||
"""
|
||||
for player in data["finished"]:
|
||||
print(f"{'YOUR' if player == 'YOU' else 'MY'} BUG IS FINISHED.")
|
||||
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
|
||||
|
||||
|
||||
def exit_game(_: Any) -> Literal["exit"]:
|
||||
"""Exist the game regardless of input"""
|
||||
return "exit"
|
||||
|
||||
|
||||
def print_centered(msg: str, width: int = PAGE_WIDTH) -> None:
|
||||
"""Print given message centered to given width."""
|
||||
spaces = " " * ((width - len(msg)) // 2)
|
||||
print(spaces + msg)
|
||||
|
||||
|
||||
def print_table(rows: List[Any]) -> None:
|
||||
for row in rows:
|
||||
print(*row, sep="\t")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# The main states in the game
|
||||
states: Dict[Action, StateFunctions] = {
|
||||
# Initial state of the game
|
||||
"start": (print_start, control_start, update_state),
|
||||
# displays game instructions
|
||||
"instructions": (print_instructions, goto_game, update_state),
|
||||
# the main game state
|
||||
"game": (print_game, control_game, update_game),
|
||||
# displays pictures before returning to game
|
||||
"pictures": (print_pictures, goto_game, update_state),
|
||||
# Displays the winning players and message
|
||||
"won": (print_winner, exit_game, update_state),
|
||||
}
|
||||
|
||||
# all the data used by the game
|
||||
data = DataDict(
|
||||
state="start",
|
||||
partNo=None,
|
||||
players={"YOU": [0] * len(part_types), "I": [0] * len(part_types)},
|
||||
partTypes=part_types,
|
||||
finished=[],
|
||||
logs=[],
|
||||
)
|
||||
game_loop(states, data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,25 +0,0 @@
|
||||
import io
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bug import main
|
||||
from bug_overengineered import main as overengineered_main
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"main",
|
||||
[main, overengineered_main],
|
||||
)
|
||||
def test_main(monkeypatch: MonkeyPatch, main: Callable[[], None]) -> None:
|
||||
monkeypatch.setattr("time.sleep", lambda n: n)
|
||||
instructions = "Y"
|
||||
pictures = "Y"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{instructions}\n{pictures}\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\n"
|
||||
),
|
||||
)
|
||||
main()
|
||||
@@ -1,16 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from bullfight import main
|
||||
|
||||
|
||||
def test_main(monkeypatch: MonkeyPatch) -> None:
|
||||
instructions = "Y"
|
||||
kill_bull = "YES"
|
||||
kill_method = "0"
|
||||
run_from_ring = "YES"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{instructions}\n{kill_bull}\n{kill_method}\n{run_from_ring}\n"),
|
||||
)
|
||||
main()
|
||||
@@ -1,13 +0,0 @@
|
||||
import io
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from bullseye import main
|
||||
|
||||
|
||||
def test_main(monkeypatch: MonkeyPatch) -> None:
|
||||
nb_players = 1
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"{nb_players}\nMartin\n3\n2\n1" + ("\n2" * 21)),
|
||||
)
|
||||
main()
|
||||
@@ -1,88 +0,0 @@
|
||||
import io
|
||||
import math
|
||||
|
||||
import pytest
|
||||
from golf import (
|
||||
CircleGameObj,
|
||||
GameObjType,
|
||||
Golf,
|
||||
Point,
|
||||
RectGameObj,
|
||||
get_distance,
|
||||
is_in_rectangle,
|
||||
odds,
|
||||
to_degrees_360,
|
||||
to_radians,
|
||||
)
|
||||
|
||||
|
||||
def test_odds() -> None:
|
||||
n = 1000
|
||||
p = sum(odds(50) for i in range(n)) / n
|
||||
assert abs(p - 0.5) < 0.1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("p1", "p2", "expected"),
|
||||
[
|
||||
((0, 0), (0, 0), 0),
|
||||
((0, 0), (1, 0), 1),
|
||||
((0, 0), (0, 1), 1),
|
||||
((0, 1), (0, 0), 1),
|
||||
((1, 0), (0, 0), 1),
|
||||
((0, 0), (2, 0), 2),
|
||||
((0, 0), (0, 2), 2),
|
||||
((0, 2), (0, 0), 2),
|
||||
((2, 0), (0, 0), 2),
|
||||
((0, 0), (1, 1), 2**0.5),
|
||||
((2, 3), (4, 5), (2**2 + 2**2) ** 0.5),
|
||||
],
|
||||
)
|
||||
def test_get_distance(p1, p2, expected):
|
||||
assert get_distance(Point(*p1), Point(*p2)) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pt", "rect", "expected"),
|
||||
[
|
||||
(
|
||||
CircleGameObj(1, 1, 1, GameObjType.BALL),
|
||||
RectGameObj(0, 0, 2, 2, GameObjType.GREEN),
|
||||
True,
|
||||
),
|
||||
(
|
||||
CircleGameObj(1, 1, 1, GameObjType.BALL),
|
||||
RectGameObj(0, 0, 1, 1, GameObjType.GREEN),
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_is_in_rectangle(pt, rect, expected):
|
||||
assert is_in_rectangle(pt, rect) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("angle", "radians"),
|
||||
[
|
||||
(0, 0),
|
||||
(180, math.pi),
|
||||
(360, 2 * math.pi),
|
||||
],
|
||||
)
|
||||
def test_to_radians(angle, radians):
|
||||
assert to_radians(angle) == radians
|
||||
assert to_degrees_360(radians) == angle
|
||||
|
||||
|
||||
def test_golf(monkeypatch, capsys):
|
||||
handycap = 10
|
||||
difficulty = 4
|
||||
club = 10
|
||||
swing = 10
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(f"10\na\n{handycap}\n{difficulty}\n{club}\n{swing}\nQUIT"),
|
||||
)
|
||||
Golf()
|
||||
out, err = capsys.readouterr()
|
||||
assert err == ""
|
||||
@@ -1,30 +0,0 @@
|
||||
import io
|
||||
|
||||
import hamurabi
|
||||
|
||||
|
||||
def test_main(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO("100\n100\n100"))
|
||||
hamurabi.main()
|
||||
captured = capsys.readouterr()
|
||||
actual_lines = captured.out.splitlines()
|
||||
expected_lines = [
|
||||
"HAMURABI", # 0
|
||||
"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", # 1
|
||||
"", # 2
|
||||
"", # 3
|
||||
"", # 4
|
||||
"", # 5
|
||||
"TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA", # 6
|
||||
"FOR A TEN-YEAR TERM OF OFFICE.", # 7
|
||||
"", # 8
|
||||
"", # 9
|
||||
"", # 10
|
||||
"", # 11
|
||||
"HAMURABI: I BEG TO REPORT TO YOU\n", # 12
|
||||
"IN YEAR 1 , 0 PEOPLE STARVED, 5 CAME TO THE CITY,\n", # 13
|
||||
"POPULATION IS NOW 100\n", # 14
|
||||
"THE CITY NOW OWNS 1000.0 ACRES.", # 15
|
||||
]
|
||||
for i, (actual, expected) in enumerate(zip(actual_lines, expected_lines)):
|
||||
assert actual.strip() == expected.strip(), f"Line {i} is wrong"
|
||||
@@ -1,328 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.3-final"
|
||||
},
|
||||
"orig_nbformat": 2,
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3",
|
||||
"language": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 727,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 728,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BOARD_WIDTH = 10\n",
|
||||
"BOARD_HEIGHT = 10\n",
|
||||
"\n",
|
||||
"SHIPS = [(\"BATTLESHIP\", 5), (\"CRUISER\", 3), (\"DESTROYER<A>\", 2), (\"DESTROYER<B>\", 2)]\n",
|
||||
"\n",
|
||||
"VALID_MOVES = [[-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1]]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 729,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# random number functions\n",
|
||||
"#\n",
|
||||
"# seed the random number generator\n",
|
||||
"random.seed()\n",
|
||||
"\n",
|
||||
"# random_x_y\n",
|
||||
"#\n",
|
||||
"# generate a valid x,y coordinate on the board\n",
|
||||
"# returns: x,y\n",
|
||||
"# x: integer between 1 and BOARD_HEIGHT\n",
|
||||
"# y: integer between 1 and BOARD WIDTH\n",
|
||||
"def random_x_y():\n",
|
||||
" x = random.randrange(1, BOARD_WIDTH + 1)\n",
|
||||
" y = random.randrange(1, BOARD_HEIGHT + 1)\n",
|
||||
" return (x, y)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 730,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"5 3\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"x, y = random_x_y()\n",
|
||||
"print(x, y)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 731,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# TODO: add an optional starting coordinate for testing\n",
|
||||
"# purposes\n",
|
||||
"def generate_ship_coordinates(ship):\n",
|
||||
"\n",
|
||||
" # randomly generate starting x,y coordinates\n",
|
||||
" start_x, start_y = random_x_y()\n",
|
||||
"\n",
|
||||
" # using starting coordinates and the ship type,\n",
|
||||
" # generate a vector of possible directions the ship\n",
|
||||
" # could be placed. directions are numbered 0-7 along\n",
|
||||
" # points of the compass (N, NE, E, SE, S, SW, W, NW)\n",
|
||||
" # clockwise. a vector of valid directions where the\n",
|
||||
" # ship does not go off the board is determined\n",
|
||||
" ship_len = SHIPS[ship][1] - 1\n",
|
||||
" dirs = [False for x in range(8)]\n",
|
||||
" dirs[0] = (start_x - ship_len) >= 1\n",
|
||||
" dirs[2] = (start_y + ship_len) <= BOARD_WIDTH\n",
|
||||
" dirs[1] = dirs[0] and dirs[2]\n",
|
||||
" dirs[4] = (start_x + ship_len) <= BOARD_HEIGHT\n",
|
||||
" dirs[3] = dirs[2] and dirs[4]\n",
|
||||
" dirs[6] = (start_y - ship_len) >= 1\n",
|
||||
" dirs[5] = dirs[4] and dirs[6]\n",
|
||||
" dirs[7] = dirs[6] and dirs[0]\n",
|
||||
" directions = [p for p in range(len(dirs)) if dirs[p]]\n",
|
||||
"\n",
|
||||
" # using the vector of valid directions, pick a\n",
|
||||
" # random direction to place the ship\n",
|
||||
" dir_idx = random.randrange(len(directions))\n",
|
||||
" direction = directions[dir_idx]\n",
|
||||
"\n",
|
||||
" # using the starting x,y, direction and ship\n",
|
||||
" # type, return the coordinates of each point\n",
|
||||
" # of the ship. VALID_MOVES is a staic array\n",
|
||||
" # of coordinate offsets to walk from starting\n",
|
||||
" # coordinate to the end coordinate in the\n",
|
||||
" # chosen direction\n",
|
||||
" ship_len = SHIPS[ship][1] - 1\n",
|
||||
" d_x = VALID_MOVES[direction][0]\n",
|
||||
" d_y = VALID_MOVES[direction][1]\n",
|
||||
"\n",
|
||||
" coords = [(start_x, start_y)]\n",
|
||||
" x_coord = start_x\n",
|
||||
" y_coord = start_y\n",
|
||||
" for i in range(ship_len):\n",
|
||||
" x_coord = x_coord + d_x\n",
|
||||
" y_coord = y_coord + d_y\n",
|
||||
" coords.append((x_coord, y_coord))\n",
|
||||
" return coords"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 732,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"BATTLESHIP 5 [(10, 2), (10, 3), (10, 4), (10, 5), (10, 6)]\nCRUISER 3 [(9, 4), (8, 4), (7, 4)]\nDESTROYER<A> 2 [(2, 5), (1, 5)]\nDESTROYER<B> 2 [(7, 8), (7, 9)]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for ship in range(len(SHIPS)):\n",
|
||||
" coords = generate_ship_coordinates(ship)\n",
|
||||
" print(f\"{SHIPS[ship][0]:15}\", f\"{SHIPS[ship][1]:2}\", coords)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 733,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def create_blank_board():\n",
|
||||
" return [[None for y in range(BOARD_WIDTH)] for x in range(BOARD_HEIGHT)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 734,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
" 1 2 3 4 5 6 7 8 9 10\n 1 \n 2 \n 3 \n 4 \n 5 \n 6 \n 7 \n 8 \n 9 \n10 \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def print_board(board):\n",
|
||||
"\n",
|
||||
" # print board header (column numbers)\n",
|
||||
" print(\" \", end=\"\")\n",
|
||||
" for z in range(BOARD_WIDTH):\n",
|
||||
" print(f\"{z+1:3}\", end=\"\")\n",
|
||||
" print(\"\")\n",
|
||||
"\n",
|
||||
" for x in range(len(board)):\n",
|
||||
" print(f\"{x+1:2}\", end=\"\")\n",
|
||||
" for y in range(len(board[x])):\n",
|
||||
" if board[x][y] is None:\n",
|
||||
" print(f\"{' ':3}\", end=\"\")\n",
|
||||
" else:\n",
|
||||
" print(f\"{board[x][y]:3}\", end=\"\")\n",
|
||||
" print(\"\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"computer_board = create_blank_board()\n",
|
||||
"print_board(computer_board)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 735,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def place_ship(board, coords, ship):\n",
|
||||
" for coord in coords:\n",
|
||||
" board[coord[0] - 1][coord[1] - 1] = ship"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 736,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"DESTROYER<B> 2 [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]\n 1 2 3 4 5 6 7 8 9 10\n 1 0 \n 2 0 \n 3 0 \n 4 0 \n 5 0 \n 6 \n 7 \n 8 \n 9 \n10 \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# test place_ship\n",
|
||||
"board = create_blank_board()\n",
|
||||
"coords = generate_ship_coordinates(0)\n",
|
||||
"print(f\"{SHIPS[ship][0]:15}\", f\"{SHIPS[ship][1]:2}\", coords)\n",
|
||||
"place_ship(board, coords, 0)\n",
|
||||
"print_board(board)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 737,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
" 1 2 3 4 5 6 7 8 9 10\n",
|
||||
" 1 \n",
|
||||
" 2 1 \n",
|
||||
" 3 1 2 \n",
|
||||
" 4 1 2 \n",
|
||||
" 5 0 \n",
|
||||
" 6 0 \n",
|
||||
" 7 0 \n",
|
||||
" 8 0 \n",
|
||||
" 9 0 3 \n",
|
||||
"10 3 \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# NOTE: A little quirk that exists here and in the orginal\n",
|
||||
"# game: Ships are allowed to cross each other!\n",
|
||||
"# For example: 2 destroyers, length 2, one at\n",
|
||||
"# [(1,1),(2,2)] and other at [(2,1),(1,2)]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_board():\n",
|
||||
" board = create_blank_board()\n",
|
||||
"\n",
|
||||
" for ship in range(len(SHIPS)):\n",
|
||||
" placed = False\n",
|
||||
" coords = []\n",
|
||||
" while not placed:\n",
|
||||
" coords = generate_ship_coordinates(ship)\n",
|
||||
" clear = True\n",
|
||||
" for coord in coords:\n",
|
||||
" if board[coord[0] - 1][coord[1] - 1] is not None:\n",
|
||||
" clear = False\n",
|
||||
" break\n",
|
||||
" if clear:\n",
|
||||
" placed = True\n",
|
||||
" place_ship(board, coords, ship)\n",
|
||||
" return board\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print_board(generate_board())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 738,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"[(4, 1), (3, 6), (6, 10), (10, 6), (4, 5)]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_shots(number):\n",
|
||||
" shots = []\n",
|
||||
" for i in range(number):\n",
|
||||
" shots.append(random_x_y())\n",
|
||||
" return shots\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"shots = generate_shots(5)\n",
|
||||
"print(shots)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import io
|
||||
|
||||
import pytest
|
||||
from superstartrek import main
|
||||
|
||||
|
||||
def test_main(monkeypatch, capsys):
|
||||
monkeypatch.setattr("sys.stdin", io.StringIO("NAV\n1\n1\nSRS\nXXX\nXXX\n"))
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
# captured = capsys.readouterr()
|
||||
@@ -1,49 +0,0 @@
|
||||
import unittest
|
||||
|
||||
import tower
|
||||
|
||||
|
||||
class TowerTestCase(unittest.TestCase):
|
||||
def test_something(self):
|
||||
t = tower.Tower()
|
||||
self.assertTrue(t.empty())
|
||||
|
||||
d = tower.Disk(3)
|
||||
t.add(d)
|
||||
self.assertFalse(t.empty())
|
||||
|
||||
d5 = tower.Disk(5)
|
||||
self.assertRaises(Exception, t.add, d5)
|
||||
self.assertFalse(t.empty())
|
||||
|
||||
def test_oksize(self):
|
||||
t = tower.Tower()
|
||||
self.assertTrue(t.empty())
|
||||
|
||||
d5 = tower.Disk(5)
|
||||
t.add(d5)
|
||||
self.assertFalse(t.empty())
|
||||
|
||||
d3 = tower.Disk(3)
|
||||
t.add(d3)
|
||||
self.assertFalse(t.empty())
|
||||
|
||||
self.assertEqual(t.top(), d3)
|
||||
self.assertEqual(t.pop(), d3)
|
||||
self.assertEqual(t.pop(), d5)
|
||||
|
||||
def test_game(self):
|
||||
g = tower.Game()
|
||||
self.assertEqual(g.moves(), 0)
|
||||
self.assertFalse(g.winner())
|
||||
|
||||
def test_format(self):
|
||||
t = tower.Tower()
|
||||
d3 = tower.Disk(3)
|
||||
d5 = tower.Disk(5)
|
||||
t.add(d5)
|
||||
t.add(d3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,49 +0,0 @@
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
from weekday import calculate_day_of_week
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("year", "month", "day"),
|
||||
[
|
||||
(yr, m, d)
|
||||
for yr in range(1600, 2021)
|
||||
for m in range(1, 12)
|
||||
for d in range(1, 28)
|
||||
],
|
||||
)
|
||||
@pytest.mark.slow # Those are 125,037 tests!
|
||||
def test_weekday_calc(year, month, day):
|
||||
dt = datetime.date(year, month, day)
|
||||
python_weekday = dt.weekday() # Monday = 0, Sunday = 6
|
||||
|
||||
basic_weekday = calculate_day_of_week(year, month, day) # Sunday = 1, Saturday = 7
|
||||
|
||||
if ((python_weekday + 2) % 7) != (basic_weekday % 7):
|
||||
print(f"testing yr {year} month {month} day {day}")
|
||||
print(f"python says {python_weekday}")
|
||||
print(f"BASIC says {basic_weekday}")
|
||||
assert False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("year", "month", "day"),
|
||||
[
|
||||
(yr, m, d)
|
||||
for yr in range(2016, 2021)
|
||||
for m in range(1, 12)
|
||||
for d in range(1, 28)
|
||||
],
|
||||
)
|
||||
def test_weekday_calc_4_years(year, month, day):
|
||||
dt = datetime.date(year, month, day)
|
||||
python_weekday = dt.weekday() # Monday = 0, Sunday = 6
|
||||
|
||||
basic_weekday = calculate_day_of_week(year, month, day) # Sunday = 1, Saturday = 7
|
||||
|
||||
if ((python_weekday + 2) % 7) != (basic_weekday % 7):
|
||||
print(f"testing yr {year} month {month} day {day}")
|
||||
print(f"python says {python_weekday}")
|
||||
print(f"BASIC says {basic_weekday}")
|
||||
assert False
|
||||
Reference in New Issue
Block a user