diff --git a/.idea/misc.xml b/.idea/misc.xml index 3999087..d348161 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/schafkopf.iml b/.idea/schafkopf.iml index 9477bc8..e114674 100644 --- a/.idea/schafkopf.iml +++ b/.idea/schafkopf.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/src/Card.py b/src/Card.py index 341782c..a0330dc 100644 --- a/src/Card.py +++ b/src/Card.py @@ -1,41 +1,39 @@ import enum from typing import * -from collections import namedtuple +from Errors import SchafkopfError class Card: - class Suit(enum.Enum): - Eichel = 0 - Gras = 1 - Herz = 2 - Schellen = 3 + class OpError(SchafkopfError, TypeError): + def __init__(self): + self.message = "An '{}' object may only be operated on with others of the same type.".format( + Card.__class__.__name__) - SUITS_STR: Dict[Suit, str] = { - Suit.Eichel: "♣", - Suit.Gras: "♠", - Suit.Herz: "♥", - Suit.Schellen: "♦", - } + class Suit(enum.Enum): + Eichel = 3 + Gras = 2 + Herz = 1 + Schellen = 0 class Rank(enum.Enum): - Ober = 0 - Unter = 1 - Ass = 2 - Koenig = 3 - Zehn = 4 - Neun = 5 - Acht = 6 - Sieben = 7 + Ass = 7 + Koenig = 6 + Ober = 5 + Unter = 4 + Zehn = 3 + Neun = 2 + Acht = 1 + Sieben = 0 - RANKS_STR: Dict[Rank, str] = { - Rank.Ober: "O", - Rank.Unter: "U", - Rank.Ass: "A", - Rank.Koenig: "K", - Rank.Zehn: "X", - Rank.Neun: "9", - Rank.Acht: "8", - Rank.Sieben: "7", + RANK_SCORE: Dict[Rank, int] = { + Rank.Ass: 11, + Rank.Koenig: 4, + Rank.Ober: 3, + Rank.Unter: 2, + Rank.Zehn: 10, + Rank.Neun: 0, + Rank.Acht: 0, + Rank.Sieben: 0, } def __init__(self, suit: Suit, rank: Rank): @@ -43,11 +41,37 @@ class Card: self.rank: Card.Rank = rank def __str__(self): - return "\n".join(self.glyph()) + return self.suit.name + " " + self.rank.name def __repr__(self): return "Card: <" + self.suit.name + ", " + self.rank.name + ">" + def __gt__(self, other): + if type(other) != Card: + raise Card.OpError() + else: + return self.suit.value > other.suit.value or self.rank.value > other.rank.value + def __lt__(self, other): + if type(other) != Card: + raise Card.OpError() + else: + return self.suit.value < other.suit.value or self.rank.value < other.rank.value + def __add__(self, other): + if type(other) != Card: + raise Card.OpError() + else: + return Card.RANK_SCORE[self.rank] + Card.RANK_SCORE[other.rank] + def __sub__(self, other): + if type(other) != Card: + raise Card.OpError() + else: + return Card.RANK_SCORE[self.rank] - Card.RANK_SCORE[other.rank] + + def __int__(self): + return Card.RANK_SCORE[self.rank] + + def rep(self) -> Tuple[Suit, Rank]: + return self.suit, self.rank diff --git a/src/DecisionMaker.py b/src/DecisionMaker.py new file mode 100644 index 0000000..8722826 --- /dev/null +++ b/src/DecisionMaker.py @@ -0,0 +1,19 @@ +from __future__ import annotations +from Game import RoundType +from typing import Union, List +TYPECHECKING = False +if TYPECHECKING: + from Player import Hand, GameChoice + from Card import Card + from Game import TrickState + + +class DecisionMaker: + def __init__(self): + pass + + def make_decision(self, trick_state: TrickState, hand: Hand) -> int: + raise NotImplementedError("This is an abstract base class!") + + def choose_round_type(self, hand: Hand, sauspiel_choices: List[Card.Suit]) -> GameChoice: + raise NotImplementedError("This is an abstract base class!") diff --git a/src/Deck.py b/src/Deck.py index 6c0c300..1ba08ef 100644 --- a/src/Deck.py +++ b/src/Deck.py @@ -1,5 +1,7 @@ from Card import Card -from typing import List +TYPECHECKING = False +if TYPECHECKING: + from typing import List class Deck(list): diff --git a/src/Game.py b/src/Game.py index 420e1eb..be74c74 100644 --- a/src/Game.py +++ b/src/Game.py @@ -1,53 +1,168 @@ -from Player import Player -from typing import List -from Round import Round -from RufSpiel import RufSpiel -from Card import Card -from Deck import Deck -from Renderer import Renderer -from Errors import GameLevelError +from __future__ import annotations import random as rn from itertools import cycle +from enum import Enum +from typing import List, Union, NamedTuple, Dict, Tuple +from Errors import GameLevelError +from Card import Card +from Deck import Deck + +TYPECHECKING = False +if TYPECHECKING: + from Player import Player, Hand, GameChoice + from Renderer import Renderer -Stack = List[Card] +class RoundType(Enum): + Sauspiel = "Sauspiel" + Wenz = "Wenz" + Farbsolo = "Farbsolo" + Ramsch = "Ramsch" + + +class Round(NamedTuple): + type: RoundType + isFarbSolo: bool + trumpSuit: Union[Card.Suit, None] + + +class Turn(NamedTuple): + suit: Card.Suit + isTrumpTurn: bool + + +class Move(NamedTuple): + card: Card + playerId: Player.Id + + +Trick = List[Card] +TrickState = List[Move] +CardRep = Tuple[Card.Suit, Card.Rank] +CardRanking = Dict[CardRep, int] class Game: - def __init__(self, players: List[Player], renderer: Renderer): - if len(players) != 4: - raise GameLevelError("There must be exactly four players, but received " + str(len(players)) + "!") - self.players: List[Player] = players - self.game_over: bool = False - self.renderer = renderer - self.deck = Deck() - self.stack: List[Card] = [] - self.current_round: Round = RufSpiel(self.players) + PLAYER_COUNT = 4 + TRICK_SIZE = PLAYER_COUNT - def play(self): + SAUSPIEL_TRUMP_ORDER: List[CardRep] = [ + (Card.Suit.Eichel, Card.Rank.Ober), + (Card.Suit.Gras, Card.Rank.Ober), + (Card.Suit.Herz, Card.Rank.Ober), + (Card.Suit.Schellen, Card.Rank.Ober), + (Card.Suit.Eichel, Card.Rank.Unter), + (Card.Suit.Gras, Card.Rank.Unter), + (Card.Suit.Herz, Card.Rank.Unter), + (Card.Suit.Schellen, Card.Rank.Unter), + ] + + def __init__(self, players: List[Player], renderer: Renderer): + if len(players) != Game.PLAYER_COUNT: + raise GameLevelError("There must be exactly four players, but received " + str(len(players)) + "!") + + self.renderer: Renderer = renderer + + self.players: List[Player] = players + self.current_player_index: int = 0 + self.last_trick_winner_index: int = 0 + self.dealer_index: int = 0 + + self.game_over: bool = False + self.round_over: bool = False + self.round_count: int = 0 + self.trick_count: int = 0 + self.round_details: Round = Round( + type=RoundType.Sauspiel, + isFarbSolo=False, + trumpSuit=Card.Suit.Herz + ) + self.round_trump_ranking: CardRanking = {} + self.trick_details: Turn = Turn( + suit=Card.Suit.Schellen, + isTrumpTurn=False + ) + + self.deck = Deck() + self.current_trick: TrickState = [] + + def play(self) -> None: self.renderer.render_message("Game start!") while not self.game_over: - self.current_round = Round(self.players) + self.round_count += 1 + self.trick_count = 0 + self.current_player_index = self.neighbour_index(self.dealer_index, after=True) self.play_round() - self.game_over = True + self.dealer_index = self.neighbour_index(self.dealer_index, after=True) self.renderer.render_message("Game end!") - def play_round(self): + def play_round(self) -> None: + self.renderer.render_message("Round {} start!".format(self.round_count)) self.deal_cards() - while not self.current_round.is_over(): - self.stack.clear() - self.round_turn() - self.renderer.render() - self.current_round.next_turn() + self.determine_game_type() + self.update_trump_ranking() + for i in range(len(self.deck) // Game.PLAYER_COUNT): + self.trick_count += 1 + self.current_trick.clear() + self.play_trick() + self.current_player_index = self.last_trick_winner_index + self.renderer.render_message("Round Results:") + for player in self.players: + score = player.calc_score_for_tricks() + self.renderer.render_message("{}: {} points.".format(player.get_name(), score)) + self.renderer.render_message("Round end!") - def round_turn(self): - self.renderer.render_message("Turn {} start.".format(self.current_round.turns_taken() + 1)) + def determine_game_type(self) -> None: + player_choosing = self.current_player_index + player = None + for i in range(len(self.players)): + self.renderer.render_hand(self.players[player_choosing]) + sauspiel_choices = self.sauspiel_choices_for_hand(self.players[player_choosing].card_options()) + self.players[player_choosing].choose_round_type(sauspiel_choices) + player_choosing = self.neighbour_index(player_choosing, after=True) + + def sauspiel_choices_for_hand(self, hand: Hand) -> List[Card.Suit]: + has_card = { + Card.Suit.Eichel: {"ass": False, "normal": False}, + Card.Suit.Gras: {"ass": False, "normal": False}, + Card.Suit.Schellen: {"ass": False, "normal": False} + } + choices: List[Card.Suit] = [] + for card in hand: + if card.suit != Card.Suit.Herz: + if card.rank not in [Card.Rank.Ober, Card.Rank.Unter, Card.Rank.Ass]: + has_card[card.suit]["normal"] = True + elif card.rank == Card.Rank.Ass: + has_card[card.suit]["ass"] = True + for suit in has_card: + if has_card[suit]["normal"] and not has_card[suit]["ass"]: + choices.append(suit) + return choices + + def update_trump_ranking(self) -> None: + rank_val = 0 + if self.round_details.type == RoundType.Sauspiel: + for card_type in Game.SAUSPIEL_TRUMP_ORDER: + self.round_trump_ranking[card_type] = rank_val + rank_val += 1 + for rank in Card.Rank: + if rank != Card.Rank.Ober and rank != Card.Rank.Unter: + self.round_trump_ranking[(self.round_details.trumpSuit, rank)] = rank_val + rank_val += 1 + + def play_trick(self) -> None: + self.renderer.render_message("Turn {} start.".format(self.trick_count)) for i in range(len(self.players)): self.renderer.render_hand(self.current_player()) self.make_move_for_current_player() - self.renderer.render_stack(self.stack) - if self.current_round.turn_in_progress(): - self.current_round.move_turn_to_next_player() + self.renderer.render_stack(self.current_trick) + self.proceed_to_next_player() + winning_move: Move = self.determine_winning_move(self.current_trick) + self.last_trick_winner_index = winning_move.playerId + winner = self.players[self.last_trick_winner_index] + winner.add_trick([move.card for move in self.current_trick]) + self.renderer.render_message("{p} won the trick with the card: {c}".format( + p=winner.get_name(), c=winning_move.card)) self.renderer.render_message("Turn end.") def deal_cards(self) -> None: @@ -57,32 +172,54 @@ class Game: dealt_cards = self.deck[i:i + 4] next(player_iterator).deal_cards(*dealt_cards) - def current_player(self): - return self.players[self.current_round.get_current_player_index()] - def make_move_for_current_player(self) -> None: - cards_to_choose_from: List[Card] = self.current_player().card_options() - card_index_choice = None - while card_index_choice is None: - try: - attempt = int(input("Choose a card -> ")) - if len(cards_to_choose_from) >= attempt > 0: - card_index_choice = attempt - 1 - else: - print("Choice was outside card range.") - except ValueError: - print("Invalid choice.") - self.stack.append(self.current_player().play_card(card_index_choice)) + self.current_trick.append( + self.current_player().make_decision(self.current_trick.copy()) + ) + if self.last_trick_winner_index == self.current_player_index: + self.trick_details = Turn( + suit=self.current_trick[0].card.suit, + isTrumpTurn=self.current_trick[0].card.suit == self.round_details.trumpSuit) - def last_cards_played(self, num_cards: int) -> Stack: - if len(self.stack) == 0: - return [] - elif len(self.stack) == 1: - return [self.stack[0]] - else: - if num_cards > len(self.stack): - num_cards = len(self.stack) - return self.stack[-num_cards:] + def round_is_over(self) -> bool: + return self.round_over - def get_stack(self) -> Stack: - return self.stack.copy() + def proceed_to_next_player(self) -> None: + self.current_player_index = self.neighbour_index(self.current_player_index, after=True) + + def neighbour_index(self, player_index: int, after: bool = True) -> int: + neighbour = player_index + 1 if after else player_index - 1 + if neighbour >= len(self.players): + neighbour = 0 + elif neighbour < 0: + neighbour = len(self.players) - 1 + return neighbour + + def set_starting_player(self) -> None: + self.current_player_index = rn.randint(0, len(self.players) - 1) + + def determine_winning_move(self, trick: TrickState) -> Move: + best_move = trick[0] + for move in trick: + if self.round_details.type == RoundType.Sauspiel: + if move.card.rep() in self.round_trump_ranking: + if best_move.card.rep() in self.round_trump_ranking: + if self.new_move_wins_trump_trick(best_move, move): + best_move = move + else: + best_move = move + elif move.card.suit == self.trick_details.suit and move.card > best_move.card: + best_move = move + return best_move + + def new_move_wins_trump_trick(self, old_move: Move, new_move: Move) -> bool: + return self.round_trump_ranking[new_move.card.rep()] < self.round_trump_ranking[old_move.card.rep()] + + def trick_value(self, trick: TrickState) -> int: + return sum([move.card for move in trick]) + + def current_player(self) -> Player: + return self.players[self.current_player_index] + + def current_dealer(self) -> Player: + return self.players[self.dealer_index] diff --git a/src/Glyph.py b/src/Glyph.py index d3e5c01..ce4fba8 100644 --- a/src/Glyph.py +++ b/src/Glyph.py @@ -2,7 +2,22 @@ from typing import Dict, List from Card import Card from collections import namedtuple - +SUITS_STR: Dict[Card.Suit, str] = { + Card.Suit.Eichel: "♣", + Card.Suit.Gras: "♠", + Card.Suit.Herz: "♥", + Card.Suit.Schellen: "♦", +} +RANKS_STR: Dict[Card.Rank, str] = { + Card.Rank.Ober: "O", + Card.Rank.Unter: "U", + Card.Rank.Ass: "A", + Card.Rank.Koenig: "K", + Card.Rank.Zehn: "X", + Card.Rank.Neun: "9", + Card.Rank.Acht: "8", + Card.Rank.Sieben: "7", +} SUIT_COLOR: Dict[Card.Suit, str] = { Card.Suit.Eichel: "Black", Card.Suit.Gras: "Green", @@ -54,7 +69,7 @@ GLYPH_CHAR: Dict[str, str] = { def glyph_for(card: Card) -> Glyph: glyph = Glyph(CARD_GLYPH_CHARS.copy(), CARD_GLYPH_COLOR.copy()) for i, line in enumerate(glyph.chars): - glyph.chars[i] = line.replace("S", Card.SUITS_STR[card.suit]).replace("R", Card.RANKS_STR[card.rank]) + glyph.chars[i] = line.replace("S", SUITS_STR[card.suit]).replace("R", RANKS_STR[card.rank]) for i, line in enumerate(glyph.colors): glyph.colors[i] = line.replace("S", GLYPH_CHAR[SUIT_COLOR[card.suit]]) return glyph @@ -83,7 +98,7 @@ def stacked_glyphs(cards: List[Card]): result_width = len(CARD_GLYPH_CHARS[0]) + len(cards) - 1 result_height = len(CARD_GLYPH_CHARS) + len(cards) - 1 result = Glyph([" " * result_width] * result_height, [GLYPH_CHAR["Default"] * result_width] * result_height) - for i, card in enumerate(reversed(cards)): + for i, card in enumerate(cards): origin_coord = [i, i] for j, (chars_line, glyph_colors_line) in enumerate(zip(glyph_for(card).chars, glyph_for(card).colors)): chars_out = result.chars[j + origin_coord[0]] diff --git a/src/HumanDecisionInterface.py b/src/HumanDecisionInterface.py new file mode 100644 index 0000000..19d9014 --- /dev/null +++ b/src/HumanDecisionInterface.py @@ -0,0 +1,78 @@ +from __future__ import annotations +from DecisionMaker import DecisionMaker +from Player import GameChoice +from Game import RoundType +from typing import List +from Card import Card +TYPECHECKING = False +if TYPECHECKING: + from Player import Hand + from Game import TrickState + + +class HumanDecisionInterface(DecisionMaker): + def __init__(self): + super().__init__() + + def make_decision(self, trick_state: TrickState, hand: Hand) -> int: + card_index_choice = None + while card_index_choice is None: + try: + attempt = int(input("Choose a card -> ")) + if len(hand) >= attempt > 0: + card_index_choice = attempt - 1 + else: + print("Choice was outside card range.") + except ValueError: + print("Invalid choice.") + return card_index_choice + + def choose_round_type(self, hand: Hand, sauspiel_choices: List[Card.Suit]) -> GameChoice: + type_choice = self.prompt_round_type(sauspiel_choices) + suit_choice = None + if type_choice not in [RoundType.Wenz, None]: + suit_choice = self.prompt_suit(type_choice, sauspiel_choices) + playing_tout = False + if type_choice in [RoundType.Wenz, RoundType.Farbsolo]: + playing_tout = self.prompt_tout() + return GameChoice(type_choice, suit_choice, playing_tout) + + def prompt_round_type(self, sauspiel_choices: List[Card.Suit]) -> RoundType: + round_types = [None] + \ + ([RoundType.Sauspiel] if len(sauspiel_choices) > 0 else []) + \ + [RoundType.Farbsolo, RoundType.Wenz] + type_choice = None + type_was_chosen = False + while not type_was_chosen: + try: + print("Choose a game to play: ") + print(", ".join( + ["Pass (1)"] + ["{} ({})".format(round_type.name, i + 1) for i, round_type in enumerate(round_types) + if round_type is not None])) + attempt = int(input(" -> ")) + type_choice = round_types[attempt - 1] + type_was_chosen = True + except ValueError: + print("Invalid input.") + except IndexError: + print("Invalid choice.") + return type_choice + + def prompt_suit(self, type_choice: RoundType, sauspiel_choices: List[Card.Suit]) -> Card.Suit: + suit_choice = None + while suit_choice is None: + try: + suit_types = [suit for suit in Card.Suit] if type_choice != RoundType.Sauspiel else sauspiel_choices + print("Choose a suit to play with: ") + print(", ".join(["{} ({})".format(suit.name, i + 1) for i, suit in enumerate(suit_types)])) + attempt = int(input(" -> ")) + suit_choice = suit_types[attempt - 1] + except ValueError: + print("Invalid input.") + except IndexError: + print("Invalid choice.") + return suit_choice + + def prompt_tout(self) -> bool: + attempt = input("Will you play Tout? (Y/N) -> ") + return attempt == "Y" diff --git a/src/Player.py b/src/Player.py index 026aac3..0c32c35 100644 --- a/src/Player.py +++ b/src/Player.py @@ -1,10 +1,23 @@ -from typing import List, Tuple +from __future__ import annotations +from typing import List, Union, NamedTuple from Card import Card from copy import deepcopy +from Game import Move, RoundType +from enum import Enum +TYPECHECKING = False +if TYPECHECKING: + from DecisionMaker import DecisionMaker + from Game import TrickState Hand = List[Card] -Trick = Tuple[Card, Card, Card, Card] +Trick = List[Card] + + +class GameChoice(NamedTuple): + type: Union[RoundType, None] + suit: Union[Card.Suit, None] + tout: bool class Player: @@ -17,11 +30,13 @@ class Player: cls.instances += 1 return new_id - def __init__(self, name: str): + def __init__(self, name: str, decision_maker: DecisionMaker, is_human: bool): self.id = self.get_new_id() + self.human_controlled: bool = is_human self.name: str = name self.hand: Hand = [] self.tricks: List[Trick] = [] + self.decision_maker: DecisionMaker = decision_maker def __str__(self): return self.name @@ -42,11 +57,30 @@ class Player: self.tricks = [] self.hand = [] - def tricks(self) -> List[Trick]: + def get_tricks(self) -> List[Trick]: return deepcopy(self.tricks) def get_name(self): return self.name def get_id(self) -> int: - return self.id \ No newline at end of file + return self.id + + def make_decision(self, trick_state: TrickState) -> Move: + return Move( + self.play_card(self.decision_maker.make_decision(trick_state, self.hand)), + self.get_id() + ) + + def choose_round_type(self, sauspiel_choices: List[Card.Suit]) -> None: + self.decision_maker.choose_round_type(self.hand, sauspiel_choices) + + def game_choice(self) -> Union[RoundType, None]: + return self.game_choice() + + def calc_score_for_tricks(self) -> int: + return sum([int(card) for trick in self.tricks for card in trick]) + + def is_human(self): + return self.human_controlled + diff --git a/src/Renderer.py b/src/Renderer.py index 9b81b33..f922fb8 100644 --- a/src/Renderer.py +++ b/src/Renderer.py @@ -1,13 +1,15 @@ -from Player import Player -from Card import Card -from typing import List +from __future__ import annotations +TYPECHECKING = False +if TYPECHECKING: + from Player import Player + from Game import TrickState class Renderer: def __init__(self): pass - def render_stack(self, cards: List[Card]): + def render_stack(self, cards: TrickState): pass def render_hand(self, player: Player): @@ -15,6 +17,3 @@ class Renderer: def render_message(self, message: str): pass - - def render(self) -> None: - pass diff --git a/src/Round.py b/src/Round.py deleted file mode 100644 index b8f9e2b..0000000 --- a/src/Round.py +++ /dev/null @@ -1,39 +0,0 @@ -from Player import Player -from Errors import RoundLevelError -from typing import List -import random as rn - - -class Round: - def __init__(self, players: List[Player]): - self.current_player_index: int = 0 - self.round_ended: bool = False - self.turn_number: int = 1 - self.moves_left_in_turn: int = len(players) - self.num_players = len(players) - - def gen_player_list(self, players: List[Player]): - return list(map(lambda x: x.get_id(), players)) - - def turns_taken(self) -> int: - return self.turn_number - 1 - - def turn_in_progress(self) -> bool: - return self.moves_left_in_turn > 0 - - def is_over(self) -> bool: - return self.round_ended or self.turn_number > 8 - - def next_turn(self) -> None: - self.turn_number += 1 - self.current_player_index = 0 - - def move_turn_to_next_player(self) -> None: - if not self.turn_in_progress(): - raise RoundLevelError("Cannot move to the next player if all players have already made their move!") - self.current_player_index += 1 - if self.current_player_index >= self.num_players: - self.current_player_index = 0 - - def get_current_player_index(self) -> int: - return self.current_player_index diff --git a/src/RufSpiel.py b/src/RufSpiel.py deleted file mode 100644 index 40d1042..0000000 --- a/src/RufSpiel.py +++ /dev/null @@ -1,11 +0,0 @@ -from Card import Card -from Player import Player, Player.Id -from typing import List -from Round import Round - - -class RufSpiel(Round): - def __init__(self, players: List[Player], spieler_id: Player.Id, spieler): - super().__init__(players) - self.variable_trump: Card.Suit = Card.Suit.Herz - self.spieler: Player.Id = \ No newline at end of file diff --git a/src/SequentialConsoleRenderer.py b/src/SequentialConsoleRenderer.py new file mode 100644 index 0000000..3468d05 --- /dev/null +++ b/src/SequentialConsoleRenderer.py @@ -0,0 +1,21 @@ +from Renderer import Renderer +from typing import List +from Player import Player +from Card import Card +from Glyph import side_by_side_glyphs, stacked_glyphs, glyph_to_colored_string +from Game import TrickState +import os + + +class SequentialConsoleRenderer(Renderer): + def __init__(self): + super().__init__() + + def render_stack(self, trick_state: TrickState): + print(glyph_to_colored_string(stacked_glyphs([move.card for move in trick_state]))) + + def render_hand(self, player: Player): + print(player.get_name() + "'s hand:\n" + glyph_to_colored_string(side_by_side_glyphs(player.card_options()))) + + def render_message(self, message: str): + print(message) diff --git a/src/ConsoleRenderer.py b/src/SingleScreenConsoleRenderer.py similarity index 76% rename from src/ConsoleRenderer.py rename to src/SingleScreenConsoleRenderer.py index 45d453e..64cd436 100644 --- a/src/ConsoleRenderer.py +++ b/src/SingleScreenConsoleRenderer.py @@ -3,18 +3,19 @@ from typing import List from Player import Player from Card import Card from Glyph import side_by_side_glyphs, stacked_glyphs, glyph_to_colored_string +from Game import TrickState import os -class ConsoleRenderer(Renderer): +class SingleScreenConsoleRenderer(Renderer): def __init__(self): super().__init__() self.header = "" self.stack = "" self.hand = "" - def render_stack(self, cards: List[Card]): - self.stack = glyph_to_colored_string(stacked_glyphs(cards)) + def render_stack(self, trick_state: TrickState): + self.stack = glyph_to_colored_string(stacked_glyphs([move.card for move in trick_state])) self.rerender() def render_hand(self, player: Player): @@ -32,4 +33,4 @@ class ConsoleRenderer(Renderer): print() print("Current Stack: ") print(self.stack) - print(self.hand) + print(self.hand) \ No newline at end of file diff --git a/src/__pycache__/Card.cpython-38.pyc b/src/__pycache__/Card.cpython-38.pyc new file mode 100644 index 0000000..36de1d9 Binary files /dev/null and b/src/__pycache__/Card.cpython-38.pyc differ diff --git a/src/__pycache__/ConsoleRenderer.cpython-36.pyc b/src/__pycache__/ConsoleRenderer.cpython-36.pyc deleted file mode 100644 index 506a376..0000000 Binary files a/src/__pycache__/ConsoleRenderer.cpython-36.pyc and /dev/null differ diff --git a/src/__pycache__/DecisionMaker.cpython-36.pyc b/src/__pycache__/DecisionMaker.cpython-36.pyc new file mode 100644 index 0000000..5a567ea Binary files /dev/null and b/src/__pycache__/DecisionMaker.cpython-36.pyc differ diff --git a/src/__pycache__/DecisionMaker.cpython-38.pyc b/src/__pycache__/DecisionMaker.cpython-38.pyc new file mode 100644 index 0000000..6ce66ba Binary files /dev/null and b/src/__pycache__/DecisionMaker.cpython-38.pyc differ diff --git a/src/__pycache__/Deck.cpython-36.pyc b/src/__pycache__/Deck.cpython-36.pyc index 89b256c..5270993 100644 Binary files a/src/__pycache__/Deck.cpython-36.pyc and b/src/__pycache__/Deck.cpython-36.pyc differ diff --git a/src/__pycache__/Deck.cpython-38.pyc b/src/__pycache__/Deck.cpython-38.pyc new file mode 100644 index 0000000..fc82fee Binary files /dev/null and b/src/__pycache__/Deck.cpython-38.pyc differ diff --git a/src/__pycache__/Errors.cpython-38.pyc b/src/__pycache__/Errors.cpython-38.pyc new file mode 100644 index 0000000..a4bd7cc Binary files /dev/null and b/src/__pycache__/Errors.cpython-38.pyc differ diff --git a/src/__pycache__/Game.cpython-36.pyc b/src/__pycache__/Game.cpython-36.pyc index db81362..6edf2d4 100644 Binary files a/src/__pycache__/Game.cpython-36.pyc and b/src/__pycache__/Game.cpython-36.pyc differ diff --git a/src/__pycache__/Game.cpython-38.pyc b/src/__pycache__/Game.cpython-38.pyc new file mode 100644 index 0000000..f149247 Binary files /dev/null and b/src/__pycache__/Game.cpython-38.pyc differ diff --git a/src/__pycache__/Glyph.cpython-38.pyc b/src/__pycache__/Glyph.cpython-38.pyc new file mode 100644 index 0000000..bcb76a8 Binary files /dev/null and b/src/__pycache__/Glyph.cpython-38.pyc differ diff --git a/src/__pycache__/HumanDecisionInterface.cpython-36.pyc b/src/__pycache__/HumanDecisionInterface.cpython-36.pyc new file mode 100644 index 0000000..ddd3c85 Binary files /dev/null and b/src/__pycache__/HumanDecisionInterface.cpython-36.pyc differ diff --git a/src/__pycache__/HumanDecisionInterface.cpython-38.pyc b/src/__pycache__/HumanDecisionInterface.cpython-38.pyc new file mode 100644 index 0000000..1ea4409 Binary files /dev/null and b/src/__pycache__/HumanDecisionInterface.cpython-38.pyc differ diff --git a/src/__pycache__/Player.cpython-36.pyc b/src/__pycache__/Player.cpython-36.pyc index 987b7ca..15d6497 100644 Binary files a/src/__pycache__/Player.cpython-36.pyc and b/src/__pycache__/Player.cpython-36.pyc differ diff --git a/src/__pycache__/Player.cpython-38.pyc b/src/__pycache__/Player.cpython-38.pyc new file mode 100644 index 0000000..bd09d30 Binary files /dev/null and b/src/__pycache__/Player.cpython-38.pyc differ diff --git a/src/__pycache__/Renderer.cpython-36.pyc b/src/__pycache__/Renderer.cpython-36.pyc index eac8798..c726eff 100644 Binary files a/src/__pycache__/Renderer.cpython-36.pyc and b/src/__pycache__/Renderer.cpython-36.pyc differ diff --git a/src/__pycache__/Renderer.cpython-38.pyc b/src/__pycache__/Renderer.cpython-38.pyc new file mode 100644 index 0000000..1c3ebe0 Binary files /dev/null and b/src/__pycache__/Renderer.cpython-38.pyc differ diff --git a/src/__pycache__/Round.cpython-36.pyc b/src/__pycache__/Round.cpython-36.pyc deleted file mode 100644 index 0f62ddf..0000000 Binary files a/src/__pycache__/Round.cpython-36.pyc and /dev/null differ diff --git a/src/__pycache__/RufSpiel.cpython-36.pyc b/src/__pycache__/RufSpiel.cpython-36.pyc deleted file mode 100644 index 5f6055b..0000000 Binary files a/src/__pycache__/RufSpiel.cpython-36.pyc and /dev/null differ diff --git a/src/__pycache__/SequentialConsoleRenderer.cpython-38.pyc b/src/__pycache__/SequentialConsoleRenderer.cpython-38.pyc new file mode 100644 index 0000000..cc0aa43 Binary files /dev/null and b/src/__pycache__/SequentialConsoleRenderer.cpython-38.pyc differ diff --git a/src/main.py b/src/main.py index 9f30f78..350a56f 100644 --- a/src/main.py +++ b/src/main.py @@ -1,31 +1,36 @@ -from typing import List -from Player import Player +from HumanDecisionInterface import HumanDecisionInterface +from SequentialConsoleRenderer import SequentialConsoleRenderer from Game import Game -from ConsoleRenderer import ConsoleRenderer +from Player import Player +from typing import List def main() -> None: players: List[Player] = get_players() - renderer = ConsoleRenderer() + renderer = SequentialConsoleRenderer() game: Game = Game(players, renderer) game.play() -def get_players(): +def get_players() -> List[Player]: return get_default_players() def get_default_players() -> List[Player]: names = ["Andy", "Ben", "Chris", "Daniel"] - return list(map(Player, names)) + return list(map(make_new_human_player, names)) + + +def make_new_human_player(name: str) -> Player: + return Player(name, HumanDecisionInterface(), True) def get_input_players() -> List[Player]: print("Who's playing?") - p1 = Player(input("Player 1: ")) - p2 = Player(input("Player 2: ")) - p3 = Player(input("Player 3: ")) - p4 = Player(input("Player 4: ")) + p1 = make_new_human_player(input("Player 1: ")) + p2 = make_new_human_player(input("Player 2: ")) + p3 = make_new_human_player(input("Player 3: ")) + p4 = make_new_human_player(input("Player 4: ")) return [p1, p2, p3, p4] diff --git a/src/tests.py b/src/tests.py index 576cce6..f0a92d3 100644 --- a/src/tests.py +++ b/src/tests.py @@ -1,5 +1,5 @@ import unittest -from Player import Player, PlayerLoop +from Player import Player class MyTestCase(unittest.TestCase):