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):