Massive update
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,5 +3,5 @@
|
|||||||
<component name="JavaScriptSettings">
|
<component name="JavaScriptSettings">
|
||||||
<option name="languageLevel" value="ES6" />
|
<option name="languageLevel" value="ES6" />
|
||||||
</component>
|
</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>
|
</project>
|
||||||
2
.idea/schafkopf.iml
generated
2
.idea/schafkopf.iml
generated
@@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="Python 3.8 (schafkopf)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
84
src/Card.py
84
src/Card.py
@@ -1,41 +1,39 @@
|
|||||||
import enum
|
import enum
|
||||||
from typing import *
|
from typing import *
|
||||||
from collections import namedtuple
|
from Errors import SchafkopfError
|
||||||
|
|
||||||
|
|
||||||
class Card:
|
class Card:
|
||||||
class Suit(enum.Enum):
|
class OpError(SchafkopfError, TypeError):
|
||||||
Eichel = 0
|
def __init__(self):
|
||||||
Gras = 1
|
self.message = "An '{}' object may only be operated on with others of the same type.".format(
|
||||||
Herz = 2
|
Card.__class__.__name__)
|
||||||
Schellen = 3
|
|
||||||
|
|
||||||
SUITS_STR: Dict[Suit, str] = {
|
class Suit(enum.Enum):
|
||||||
Suit.Eichel: "♣",
|
Eichel = 3
|
||||||
Suit.Gras: "♠",
|
Gras = 2
|
||||||
Suit.Herz: "♥",
|
Herz = 1
|
||||||
Suit.Schellen: "♦",
|
Schellen = 0
|
||||||
}
|
|
||||||
|
|
||||||
class Rank(enum.Enum):
|
class Rank(enum.Enum):
|
||||||
Ober = 0
|
Ass = 7
|
||||||
Unter = 1
|
Koenig = 6
|
||||||
Ass = 2
|
Ober = 5
|
||||||
Koenig = 3
|
Unter = 4
|
||||||
Zehn = 4
|
Zehn = 3
|
||||||
Neun = 5
|
Neun = 2
|
||||||
Acht = 6
|
Acht = 1
|
||||||
Sieben = 7
|
Sieben = 0
|
||||||
|
|
||||||
RANKS_STR: Dict[Rank, str] = {
|
RANK_SCORE: Dict[Rank, int] = {
|
||||||
Rank.Ober: "O",
|
Rank.Ass: 11,
|
||||||
Rank.Unter: "U",
|
Rank.Koenig: 4,
|
||||||
Rank.Ass: "A",
|
Rank.Ober: 3,
|
||||||
Rank.Koenig: "K",
|
Rank.Unter: 2,
|
||||||
Rank.Zehn: "X",
|
Rank.Zehn: 10,
|
||||||
Rank.Neun: "9",
|
Rank.Neun: 0,
|
||||||
Rank.Acht: "8",
|
Rank.Acht: 0,
|
||||||
Rank.Sieben: "7",
|
Rank.Sieben: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, suit: Suit, rank: Rank):
|
def __init__(self, suit: Suit, rank: Rank):
|
||||||
@@ -43,11 +41,37 @@ class Card:
|
|||||||
self.rank: Card.Rank = rank
|
self.rank: Card.Rank = rank
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "\n".join(self.glyph())
|
return self.suit.name + " " + self.rank.name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Card: <" + self.suit.name + ", " + self.rank.name + ">"
|
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
|
from Card import Card
|
||||||
|
TYPECHECKING = False
|
||||||
|
if TYPECHECKING:
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
253
src/Game.py
253
src/Game.py
@@ -1,53 +1,168 @@
|
|||||||
from Player import Player
|
from __future__ import annotations
|
||||||
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
|
|
||||||
import random as rn
|
import random as rn
|
||||||
from itertools import cycle
|
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:
|
class Game:
|
||||||
def __init__(self, players: List[Player], renderer: Renderer):
|
PLAYER_COUNT = 4
|
||||||
if len(players) != 4:
|
TRICK_SIZE = PLAYER_COUNT
|
||||||
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)
|
|
||||||
|
|
||||||
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!")
|
self.renderer.render_message("Game start!")
|
||||||
while not self.game_over:
|
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.play_round()
|
||||||
self.game_over = True
|
self.dealer_index = self.neighbour_index(self.dealer_index, after=True)
|
||||||
self.renderer.render_message("Game end!")
|
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()
|
self.deal_cards()
|
||||||
while not self.current_round.is_over():
|
self.determine_game_type()
|
||||||
self.stack.clear()
|
self.update_trump_ranking()
|
||||||
self.round_turn()
|
for i in range(len(self.deck) // Game.PLAYER_COUNT):
|
||||||
self.renderer.render()
|
self.trick_count += 1
|
||||||
self.current_round.next_turn()
|
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):
|
def determine_game_type(self) -> None:
|
||||||
self.renderer.render_message("Turn {} start.".format(self.current_round.turns_taken() + 1))
|
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)):
|
for i in range(len(self.players)):
|
||||||
self.renderer.render_hand(self.current_player())
|
self.renderer.render_hand(self.current_player())
|
||||||
self.make_move_for_current_player()
|
self.make_move_for_current_player()
|
||||||
self.renderer.render_stack(self.stack)
|
self.renderer.render_stack(self.current_trick)
|
||||||
if self.current_round.turn_in_progress():
|
self.proceed_to_next_player()
|
||||||
self.current_round.move_turn_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.")
|
self.renderer.render_message("Turn end.")
|
||||||
|
|
||||||
def deal_cards(self) -> None:
|
def deal_cards(self) -> None:
|
||||||
@@ -57,32 +172,54 @@ class Game:
|
|||||||
dealt_cards = self.deck[i:i + 4]
|
dealt_cards = self.deck[i:i + 4]
|
||||||
next(player_iterator).deal_cards(*dealt_cards)
|
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:
|
def make_move_for_current_player(self) -> None:
|
||||||
cards_to_choose_from: List[Card] = self.current_player().card_options()
|
self.current_trick.append(
|
||||||
card_index_choice = None
|
self.current_player().make_decision(self.current_trick.copy())
|
||||||
while card_index_choice is None:
|
)
|
||||||
try:
|
if self.last_trick_winner_index == self.current_player_index:
|
||||||
attempt = int(input("Choose a card -> "))
|
self.trick_details = Turn(
|
||||||
if len(cards_to_choose_from) >= attempt > 0:
|
suit=self.current_trick[0].card.suit,
|
||||||
card_index_choice = attempt - 1
|
isTrumpTurn=self.current_trick[0].card.suit == self.round_details.trumpSuit)
|
||||||
else:
|
|
||||||
print("Choice was outside card range.")
|
|
||||||
except ValueError:
|
|
||||||
print("Invalid choice.")
|
|
||||||
self.stack.append(self.current_player().play_card(card_index_choice))
|
|
||||||
|
|
||||||
def last_cards_played(self, num_cards: int) -> Stack:
|
def round_is_over(self) -> bool:
|
||||||
if len(self.stack) == 0:
|
return self.round_over
|
||||||
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 get_stack(self) -> Stack:
|
def proceed_to_next_player(self) -> None:
|
||||||
return self.stack.copy()
|
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 Card import Card
|
||||||
from collections import namedtuple
|
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] = {
|
SUIT_COLOR: Dict[Card.Suit, str] = {
|
||||||
Card.Suit.Eichel: "Black",
|
Card.Suit.Eichel: "Black",
|
||||||
Card.Suit.Gras: "Green",
|
Card.Suit.Gras: "Green",
|
||||||
@@ -54,7 +69,7 @@ GLYPH_CHAR: Dict[str, str] = {
|
|||||||
def glyph_for(card: Card) -> Glyph:
|
def glyph_for(card: Card) -> Glyph:
|
||||||
glyph = Glyph(CARD_GLYPH_CHARS.copy(), CARD_GLYPH_COLOR.copy())
|
glyph = Glyph(CARD_GLYPH_CHARS.copy(), CARD_GLYPH_COLOR.copy())
|
||||||
for i, line in enumerate(glyph.chars):
|
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):
|
for i, line in enumerate(glyph.colors):
|
||||||
glyph.colors[i] = line.replace("S", GLYPH_CHAR[SUIT_COLOR[card.suit]])
|
glyph.colors[i] = line.replace("S", GLYPH_CHAR[SUIT_COLOR[card.suit]])
|
||||||
return glyph
|
return glyph
|
||||||
@@ -83,7 +98,7 @@ def stacked_glyphs(cards: List[Card]):
|
|||||||
result_width = len(CARD_GLYPH_CHARS[0]) + len(cards) - 1
|
result_width = len(CARD_GLYPH_CHARS[0]) + len(cards) - 1
|
||||||
result_height = len(CARD_GLYPH_CHARS) + 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)
|
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]
|
origin_coord = [i, i]
|
||||||
for j, (chars_line, glyph_colors_line) in enumerate(zip(glyph_for(card).chars, glyph_for(card).colors)):
|
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]]
|
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 Card import Card
|
||||||
from copy import deepcopy
|
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]
|
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:
|
class Player:
|
||||||
@@ -17,11 +30,13 @@ class Player:
|
|||||||
cls.instances += 1
|
cls.instances += 1
|
||||||
return new_id
|
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.id = self.get_new_id()
|
||||||
|
self.human_controlled: bool = is_human
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.hand: Hand = []
|
self.hand: Hand = []
|
||||||
self.tricks: List[Trick] = []
|
self.tricks: List[Trick] = []
|
||||||
|
self.decision_maker: DecisionMaker = decision_maker
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -42,7 +57,7 @@ class Player:
|
|||||||
self.tricks = []
|
self.tricks = []
|
||||||
self.hand = []
|
self.hand = []
|
||||||
|
|
||||||
def tricks(self) -> List[Trick]:
|
def get_tricks(self) -> List[Trick]:
|
||||||
return deepcopy(self.tricks)
|
return deepcopy(self.tricks)
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
@@ -50,3 +65,22 @@ class Player:
|
|||||||
|
|
||||||
def get_id(self) -> int:
|
def get_id(self) -> int:
|
||||||
return self.id
|
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 Player import Player
|
||||||
from Card import Card
|
from Game import TrickState
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class Renderer:
|
class Renderer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def render_stack(self, cards: List[Card]):
|
def render_stack(self, cards: TrickState):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def render_hand(self, player: Player):
|
def render_hand(self, player: Player):
|
||||||
@@ -15,6 +17,3 @@ class Renderer:
|
|||||||
|
|
||||||
def render_message(self, message: str):
|
def render_message(self, message: str):
|
||||||
pass
|
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 Player import Player
|
||||||
from Card import Card
|
from Card import Card
|
||||||
from Glyph import side_by_side_glyphs, stacked_glyphs, glyph_to_colored_string
|
from Glyph import side_by_side_glyphs, stacked_glyphs, glyph_to_colored_string
|
||||||
|
from Game import TrickState
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class ConsoleRenderer(Renderer):
|
class SingleScreenConsoleRenderer(Renderer):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.header = ""
|
self.header = ""
|
||||||
self.stack = ""
|
self.stack = ""
|
||||||
self.hand = ""
|
self.hand = ""
|
||||||
|
|
||||||
def render_stack(self, cards: List[Card]):
|
def render_stack(self, trick_state: TrickState):
|
||||||
self.stack = glyph_to_colored_string(stacked_glyphs(cards))
|
self.stack = glyph_to_colored_string(stacked_glyphs([move.card for move in trick_state]))
|
||||||
self.rerender()
|
self.rerender()
|
||||||
|
|
||||||
def render_hand(self, player: Player):
|
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 HumanDecisionInterface import HumanDecisionInterface
|
||||||
from Player import Player
|
from SequentialConsoleRenderer import SequentialConsoleRenderer
|
||||||
from Game import Game
|
from Game import Game
|
||||||
from ConsoleRenderer import ConsoleRenderer
|
from Player import Player
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
players: List[Player] = get_players()
|
players: List[Player] = get_players()
|
||||||
renderer = ConsoleRenderer()
|
renderer = SequentialConsoleRenderer()
|
||||||
game: Game = Game(players, renderer)
|
game: Game = Game(players, renderer)
|
||||||
game.play()
|
game.play()
|
||||||
|
|
||||||
|
|
||||||
def get_players():
|
def get_players() -> List[Player]:
|
||||||
return get_default_players()
|
return get_default_players()
|
||||||
|
|
||||||
|
|
||||||
def get_default_players() -> List[Player]:
|
def get_default_players() -> List[Player]:
|
||||||
names = ["Andy", "Ben", "Chris", "Daniel"]
|
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]:
|
def get_input_players() -> List[Player]:
|
||||||
print("Who's playing?")
|
print("Who's playing?")
|
||||||
p1 = Player(input("Player 1: "))
|
p1 = make_new_human_player(input("Player 1: "))
|
||||||
p2 = Player(input("Player 2: "))
|
p2 = make_new_human_player(input("Player 2: "))
|
||||||
p3 = Player(input("Player 3: "))
|
p3 = make_new_human_player(input("Player 3: "))
|
||||||
p4 = Player(input("Player 4: "))
|
p4 = make_new_human_player(input("Player 4: "))
|
||||||
return [p1, p2, p3, p4]
|
return [p1, p2, p3, p4]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from Player import Player, PlayerLoop
|
from Player import Player
|
||||||
|
|
||||||
|
|
||||||
class MyTestCase(unittest.TestCase):
|
class MyTestCase(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user