Massive update
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,5 +3,5 @@
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (schafkopf)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
2
.idea/schafkopf.iml
generated
2
.idea/schafkopf.iml
generated
@@ -4,7 +4,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.8 (schafkopf)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
84
src/Card.py
84
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
|
||||
|
||||
19
src/DecisionMaker.py
Normal file
19
src/DecisionMaker.py
Normal file
@@ -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!")
|
||||
@@ -1,4 +1,6 @@
|
||||
from Card import Card
|
||||
TYPECHECKING = False
|
||||
if TYPECHECKING:
|
||||
from typing import List
|
||||
|
||||
|
||||
|
||||
253
src/Game.py
253
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]
|
||||
|
||||
21
src/Glyph.py
21
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]]
|
||||
|
||||
78
src/HumanDecisionInterface.py
Normal file
78
src/HumanDecisionInterface.py
Normal file
@@ -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"
|
||||
@@ -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,7 +57,7 @@ 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):
|
||||
@@ -50,3 +65,22 @@ class Player:
|
||||
|
||||
def get_id(self) -> int:
|
||||
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
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from __future__ import annotations
|
||||
TYPECHECKING = False
|
||||
if TYPECHECKING:
|
||||
from Player import Player
|
||||
from Card import Card
|
||||
from typing import List
|
||||
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
|
||||
|
||||
39
src/Round.py
39
src/Round.py
@@ -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
|
||||
@@ -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 =
|
||||
21
src/SequentialConsoleRenderer.py
Normal file
21
src/SequentialConsoleRenderer.py
Normal file
@@ -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)
|
||||
@@ -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):
|
||||
BIN
src/__pycache__/Card.cpython-38.pyc
Normal file
BIN
src/__pycache__/Card.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/DecisionMaker.cpython-36.pyc
Normal file
BIN
src/__pycache__/DecisionMaker.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/DecisionMaker.cpython-38.pyc
Normal file
BIN
src/__pycache__/DecisionMaker.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/Deck.cpython-38.pyc
Normal file
BIN
src/__pycache__/Deck.cpython-38.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Errors.cpython-38.pyc
Normal file
BIN
src/__pycache__/Errors.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/Game.cpython-38.pyc
Normal file
BIN
src/__pycache__/Game.cpython-38.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Glyph.cpython-38.pyc
Normal file
BIN
src/__pycache__/Glyph.cpython-38.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/HumanDecisionInterface.cpython-36.pyc
Normal file
BIN
src/__pycache__/HumanDecisionInterface.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/HumanDecisionInterface.cpython-38.pyc
Normal file
BIN
src/__pycache__/HumanDecisionInterface.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/Player.cpython-38.pyc
Normal file
BIN
src/__pycache__/Player.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/Renderer.cpython-38.pyc
Normal file
BIN
src/__pycache__/Renderer.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/SequentialConsoleRenderer.cpython-38.pyc
Normal file
BIN
src/__pycache__/SequentialConsoleRenderer.cpython-38.pyc
Normal file
Binary file not shown.
25
src/main.py
25
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]
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import unittest
|
||||
from Player import Player, PlayerLoop
|
||||
from Player import Player
|
||||
|
||||
|
||||
class MyTestCase(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user