first commit

This commit is contained in:
Daniel Ledda
2020-06-08 16:56:30 +02:00
commit b0c37aae52
30 changed files with 506 additions and 0 deletions

2
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/schafkopf.iml" filepath="$PROJECT_DIR$/.idea/schafkopf.iml" />
</modules>
</component>
</project>

10
.idea/schafkopf.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/untitled.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

53
src/Card.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

33
src/main.py Normal file
View 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
View File

@@ -0,0 +1,10 @@
import unittest
from Player import Player, PlayerLoop
class MyTestCase(unittest.TestCase):
pass
if __name__ == '__main__':
unittest.main()