commit b0c37aae52ec2d6841aeaf8c6bafcc742b2577d4 Author: Daniel Ledda Date: Mon Jun 8 16:56:30 2020 +0200 first commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3999087 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..948b2d9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/schafkopf.iml b/.idea/schafkopf.iml new file mode 100644 index 0000000..9477bc8 --- /dev/null +++ b/.idea/schafkopf.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/untitled.iml b/.idea/untitled.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/untitled.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Card.py b/src/Card.py new file mode 100644 index 0000000..341782c --- /dev/null +++ b/src/Card.py @@ -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 + ">" + + + + diff --git a/src/ConsoleRenderer.py b/src/ConsoleRenderer.py new file mode 100644 index 0000000..45d453e --- /dev/null +++ b/src/ConsoleRenderer.py @@ -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) diff --git a/src/Deck.py b/src/Deck.py new file mode 100644 index 0000000..6c0c300 --- /dev/null +++ b/src/Deck.py @@ -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) diff --git a/src/Errors.py b/src/Errors.py new file mode 100644 index 0000000..ad85ffb --- /dev/null +++ b/src/Errors.py @@ -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 \ No newline at end of file diff --git a/src/Game.py b/src/Game.py new file mode 100644 index 0000000..420e1eb --- /dev/null +++ b/src/Game.py @@ -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() diff --git a/src/Glyph.py b/src/Glyph.py new file mode 100644 index 0000000..d3e5c01 --- /dev/null +++ b/src/Glyph.py @@ -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 \ No newline at end of file diff --git a/src/Player.py b/src/Player.py new file mode 100644 index 0000000..026aac3 --- /dev/null +++ b/src/Player.py @@ -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 \ No newline at end of file diff --git a/src/Renderer.py b/src/Renderer.py new file mode 100644 index 0000000..9b81b33 --- /dev/null +++ b/src/Renderer.py @@ -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 diff --git a/src/Round.py b/src/Round.py new file mode 100644 index 0000000..b8f9e2b --- /dev/null +++ b/src/Round.py @@ -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 diff --git a/src/RufSpiel.py b/src/RufSpiel.py new file mode 100644 index 0000000..40d1042 --- /dev/null +++ b/src/RufSpiel.py @@ -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 = \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/__pycache__/Card.cpython-36.pyc b/src/__pycache__/Card.cpython-36.pyc new file mode 100644 index 0000000..005ec41 Binary files /dev/null and b/src/__pycache__/Card.cpython-36.pyc differ diff --git a/src/__pycache__/ConsoleRenderer.cpython-36.pyc b/src/__pycache__/ConsoleRenderer.cpython-36.pyc new file mode 100644 index 0000000..506a376 Binary files /dev/null and b/src/__pycache__/ConsoleRenderer.cpython-36.pyc differ diff --git a/src/__pycache__/Deck.cpython-36.pyc b/src/__pycache__/Deck.cpython-36.pyc new file mode 100644 index 0000000..89b256c Binary files /dev/null and b/src/__pycache__/Deck.cpython-36.pyc differ diff --git a/src/__pycache__/Errors.cpython-36.pyc b/src/__pycache__/Errors.cpython-36.pyc new file mode 100644 index 0000000..7809b0d Binary files /dev/null and b/src/__pycache__/Errors.cpython-36.pyc differ diff --git a/src/__pycache__/Game.cpython-36.pyc b/src/__pycache__/Game.cpython-36.pyc new file mode 100644 index 0000000..db81362 Binary files /dev/null and b/src/__pycache__/Game.cpython-36.pyc differ diff --git a/src/__pycache__/Glyph.cpython-36.pyc b/src/__pycache__/Glyph.cpython-36.pyc new file mode 100644 index 0000000..2026e30 Binary files /dev/null and b/src/__pycache__/Glyph.cpython-36.pyc differ diff --git a/src/__pycache__/Player.cpython-36.pyc b/src/__pycache__/Player.cpython-36.pyc new file mode 100644 index 0000000..987b7ca Binary files /dev/null and b/src/__pycache__/Player.cpython-36.pyc differ diff --git a/src/__pycache__/Renderer.cpython-36.pyc b/src/__pycache__/Renderer.cpython-36.pyc new file mode 100644 index 0000000..eac8798 Binary files /dev/null and b/src/__pycache__/Renderer.cpython-36.pyc differ diff --git a/src/__pycache__/Round.cpython-36.pyc b/src/__pycache__/Round.cpython-36.pyc new file mode 100644 index 0000000..0f62ddf Binary files /dev/null and b/src/__pycache__/Round.cpython-36.pyc differ diff --git a/src/__pycache__/RufSpiel.cpython-36.pyc b/src/__pycache__/RufSpiel.cpython-36.pyc new file mode 100644 index 0000000..5f6055b Binary files /dev/null and b/src/__pycache__/RufSpiel.cpython-36.pyc differ diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..9f30f78 --- /dev/null +++ b/src/main.py @@ -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() diff --git a/src/tests.py b/src/tests.py new file mode 100644 index 0000000..576cce6 --- /dev/null +++ b/src/tests.py @@ -0,0 +1,10 @@ +import unittest +from Player import Player, PlayerLoop + + +class MyTestCase(unittest.TestCase): + pass + + +if __name__ == '__main__': + unittest.main()