first commit
This commit is contained in:
53
src/Card.py
Normal file
53
src/Card.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import enum
|
||||
from typing import *
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class Card:
|
||||
class Suit(enum.Enum):
|
||||
Eichel = 0
|
||||
Gras = 1
|
||||
Herz = 2
|
||||
Schellen = 3
|
||||
|
||||
SUITS_STR: Dict[Suit, str] = {
|
||||
Suit.Eichel: "♣",
|
||||
Suit.Gras: "♠",
|
||||
Suit.Herz: "♥",
|
||||
Suit.Schellen: "♦",
|
||||
}
|
||||
|
||||
class Rank(enum.Enum):
|
||||
Ober = 0
|
||||
Unter = 1
|
||||
Ass = 2
|
||||
Koenig = 3
|
||||
Zehn = 4
|
||||
Neun = 5
|
||||
Acht = 6
|
||||
Sieben = 7
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
def __init__(self, suit: Suit, rank: Rank):
|
||||
self.suit: Card.Suit = suit
|
||||
self.rank: Card.Rank = rank
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.glyph())
|
||||
|
||||
def __repr__(self):
|
||||
return "Card: <" + self.suit.name + ", " + self.rank.name + ">"
|
||||
|
||||
|
||||
|
||||
|
||||
35
src/ConsoleRenderer.py
Normal file
35
src/ConsoleRenderer.py
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
||||
import os
|
||||
|
||||
|
||||
class ConsoleRenderer(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))
|
||||
self.rerender()
|
||||
|
||||
def render_hand(self, player: Player):
|
||||
self.hand = player.get_name() + "'s hand:\n" + \
|
||||
glyph_to_colored_string(side_by_side_glyphs(player.card_options()))
|
||||
self.rerender()
|
||||
|
||||
def render_message(self, message: str):
|
||||
self.header = message
|
||||
self.rerender()
|
||||
|
||||
def rerender(self):
|
||||
os.system("clear")
|
||||
print(self.header)
|
||||
print()
|
||||
print("Current Stack: ")
|
||||
print(self.stack)
|
||||
print(self.hand)
|
||||
11
src/Deck.py
Normal file
11
src/Deck.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from Card import Card
|
||||
from typing import List
|
||||
|
||||
|
||||
class Deck(list):
|
||||
def __init__(self):
|
||||
deck = []
|
||||
for rank in Card.Rank:
|
||||
for suit in Card.Suit:
|
||||
deck.append(Card(suit, rank))
|
||||
super().__init__(deck)
|
||||
12
src/Errors.py
Normal file
12
src/Errors.py
Normal file
@@ -0,0 +1,12 @@
|
||||
class SchafkopfError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RoundLevelError(SchafkopfError):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class GameLevelError(SchafkopfError):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
88
src/Game.py
Normal file
88
src/Game.py
Normal file
@@ -0,0 +1,88 @@
|
||||
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
|
||||
import random as rn
|
||||
from itertools import cycle
|
||||
|
||||
|
||||
Stack = List[Card]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def play(self):
|
||||
self.renderer.render_message("Game start!")
|
||||
while not self.game_over:
|
||||
self.current_round = Round(self.players)
|
||||
self.play_round()
|
||||
self.game_over = True
|
||||
self.renderer.render_message("Game end!")
|
||||
|
||||
def play_round(self):
|
||||
self.deal_cards()
|
||||
while not self.current_round.is_over():
|
||||
self.stack.clear()
|
||||
self.round_turn()
|
||||
self.renderer.render()
|
||||
self.current_round.next_turn()
|
||||
|
||||
def round_turn(self):
|
||||
self.renderer.render_message("Turn {} start.".format(self.current_round.turns_taken() + 1))
|
||||
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_message("Turn end.")
|
||||
|
||||
def deal_cards(self) -> None:
|
||||
rn.shuffle(self.deck)
|
||||
player_iterator = cycle(self.players)
|
||||
for i in range(0, len(self.deck), 4):
|
||||
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))
|
||||
|
||||
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 get_stack(self) -> Stack:
|
||||
return self.stack.copy()
|
||||
95
src/Glyph.py
Normal file
95
src/Glyph.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from typing import Dict, List
|
||||
from Card import Card
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
SUIT_COLOR: Dict[Card.Suit, str] = {
|
||||
Card.Suit.Eichel: "Black",
|
||||
Card.Suit.Gras: "Green",
|
||||
Card.Suit.Herz: "Red",
|
||||
Card.Suit.Schellen: "Yellow",
|
||||
}
|
||||
CARD_GLYPH_CHARS: List[str] = [
|
||||
"R S",
|
||||
" ",
|
||||
" S ",
|
||||
" ",
|
||||
"S R",
|
||||
]
|
||||
CARD_GLYPH_COLOR: List[str] = [
|
||||
"QWWWWWS",
|
||||
"WWWWWWW",
|
||||
"WWWSWWW",
|
||||
"WWWWWWW",
|
||||
"SWWWWWQ",
|
||||
]
|
||||
C_COLOR: Dict[str, str] = {
|
||||
"Green": "\033[32;107m",
|
||||
"Red": "\033[31;107m",
|
||||
"Yellow": "\033[33;107m",
|
||||
"Black": "\033[30;107m",
|
||||
"ResetAll": "\033[0m",
|
||||
"TextReset": "\033[0m",
|
||||
"BgReset": "\033[1;30m",
|
||||
}
|
||||
Glyph = namedtuple("Glyph", "chars colors")
|
||||
C_COLOR_FROM_GLYPH_CHAR: Dict[str, str] = {
|
||||
"Q": C_COLOR["Black"],
|
||||
"R": C_COLOR["Red"],
|
||||
"G": C_COLOR["Green"],
|
||||
"Y": C_COLOR["Yellow"],
|
||||
"D": C_COLOR["ResetAll"],
|
||||
"W": C_COLOR["Black"],
|
||||
}
|
||||
GLYPH_CHAR: Dict[str, str] = {
|
||||
"Black": "Q",
|
||||
"Red": "R",
|
||||
"Green": "G",
|
||||
"Yellow": "Y",
|
||||
"Default": "D",
|
||||
"WhiteBg": "W",
|
||||
}
|
||||
|
||||
|
||||
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])
|
||||
for i, line in enumerate(glyph.colors):
|
||||
glyph.colors[i] = line.replace("S", GLYPH_CHAR[SUIT_COLOR[card.suit]])
|
||||
return glyph
|
||||
|
||||
|
||||
def glyph_to_colored_string(glyph):
|
||||
out = ""
|
||||
for chars_line, colors_line in zip(glyph.chars, glyph.colors):
|
||||
for char, color_code in zip(chars_line, colors_line):
|
||||
out += C_COLOR_FROM_GLYPH_CHAR[color_code] + char + C_COLOR["ResetAll"]
|
||||
out += "\n"
|
||||
return out
|
||||
|
||||
|
||||
def side_by_side_glyphs(cards: List[Card]) -> Glyph:
|
||||
glyph = Glyph([""] * len(CARD_GLYPH_CHARS), [""] * len(CARD_GLYPH_CHARS))
|
||||
for card in cards:
|
||||
for i, glyph_char_line in enumerate(glyph_for(card).chars):
|
||||
glyph.chars[i] += glyph_char_line + " "
|
||||
for i, glyph_color_line in enumerate(glyph_for(card).colors):
|
||||
glyph.colors[i] += glyph_color_line + GLYPH_CHAR["Default"]
|
||||
return glyph
|
||||
|
||||
|
||||
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)):
|
||||
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]]
|
||||
colors_out = result.colors[j + origin_coord[0]]
|
||||
chars_out = chars_out[:origin_coord[1]] + chars_line
|
||||
colors_out = colors_out[:origin_coord[1]] + glyph_colors_line
|
||||
result.chars[j + origin_coord[0]] = chars_out
|
||||
result.colors[j + origin_coord[0]] = colors_out
|
||||
return result
|
||||
52
src/Player.py
Normal file
52
src/Player.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from typing import List, Tuple
|
||||
from Card import Card
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
Hand = List[Card]
|
||||
Trick = Tuple[Card, Card, Card, Card]
|
||||
|
||||
|
||||
class Player:
|
||||
Id = int
|
||||
instances = 0
|
||||
|
||||
@classmethod
|
||||
def get_new_id(cls):
|
||||
new_id = cls.instances
|
||||
cls.instances += 1
|
||||
return new_id
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.id = self.get_new_id()
|
||||
self.name: str = name
|
||||
self.hand: Hand = []
|
||||
self.tricks: List[Trick] = []
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def deal_cards(self, *cards: List[Card]) -> None:
|
||||
self.hand += cards
|
||||
|
||||
def card_options(self) -> List[Card]:
|
||||
return self.hand.copy()
|
||||
|
||||
def add_trick(self, trick: Trick) -> None:
|
||||
self.tricks.append(trick)
|
||||
|
||||
def play_card(self, card_index) -> Card:
|
||||
return self.hand.pop(card_index)
|
||||
|
||||
def reset(self) -> None:
|
||||
self.tricks = []
|
||||
self.hand = []
|
||||
|
||||
def tricks(self) -> List[Trick]:
|
||||
return deepcopy(self.tricks)
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_id(self) -> int:
|
||||
return self.id
|
||||
20
src/Renderer.py
Normal file
20
src/Renderer.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from Player import Player
|
||||
from Card import Card
|
||||
from typing import List
|
||||
|
||||
|
||||
class Renderer:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def render_stack(self, cards: List[Card]):
|
||||
pass
|
||||
|
||||
def render_hand(self, player: Player):
|
||||
pass
|
||||
|
||||
def render_message(self, message: str):
|
||||
pass
|
||||
|
||||
def render(self) -> None:
|
||||
pass
|
||||
39
src/Round.py
Normal file
39
src/Round.py
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
11
src/RufSpiel.py
Normal file
11
src/RufSpiel.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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 =
|
||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
BIN
src/__pycache__/Card.cpython-36.pyc
Normal file
BIN
src/__pycache__/Card.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/ConsoleRenderer.cpython-36.pyc
Normal file
BIN
src/__pycache__/ConsoleRenderer.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Deck.cpython-36.pyc
Normal file
BIN
src/__pycache__/Deck.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Errors.cpython-36.pyc
Normal file
BIN
src/__pycache__/Errors.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Game.cpython-36.pyc
Normal file
BIN
src/__pycache__/Game.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Glyph.cpython-36.pyc
Normal file
BIN
src/__pycache__/Glyph.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Player.cpython-36.pyc
Normal file
BIN
src/__pycache__/Player.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Renderer.cpython-36.pyc
Normal file
BIN
src/__pycache__/Renderer.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/Round.cpython-36.pyc
Normal file
BIN
src/__pycache__/Round.cpython-36.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/RufSpiel.cpython-36.pyc
Normal file
BIN
src/__pycache__/RufSpiel.cpython-36.pyc
Normal file
Binary file not shown.
33
src/main.py
Normal file
33
src/main.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import List
|
||||
from Player import Player
|
||||
from Game import Game
|
||||
from ConsoleRenderer import ConsoleRenderer
|
||||
|
||||
|
||||
def main() -> None:
|
||||
players: List[Player] = get_players()
|
||||
renderer = ConsoleRenderer()
|
||||
game: Game = Game(players, renderer)
|
||||
game.play()
|
||||
|
||||
|
||||
def get_players():
|
||||
return get_default_players()
|
||||
|
||||
|
||||
def get_default_players() -> List[Player]:
|
||||
names = ["Andy", "Ben", "Chris", "Daniel"]
|
||||
return list(map(Player, names))
|
||||
|
||||
|
||||
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: "))
|
||||
return [p1, p2, p3, p4]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
src/tests.py
Normal file
10
src/tests.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import unittest
|
||||
from Player import Player, PlayerLoop
|
||||
|
||||
|
||||
class MyTestCase(unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user