Ard Overkamp 5 лет назад
Родитель
Сommit
4ce7ae69c0
8 измененных файлов с 274 добавлено и 164 удалено
  1. 35 0
      cards.py
  2. 8 0
      color.py
  3. 0 40
      command.py
  4. 73 58
      game.py
  5. 45 0
      option.py
  6. 50 17
      player.py
  7. 13 12
      ui/dog.html
  8. 50 37
      webserver.py

+ 35 - 0
cards.py

@@ -0,0 +1,35 @@
+import random
+
+suits = ["harts", "spades", "clubs", "diamonds"]
+denoms = ["ace", "king", "queen", "jack", "10", "9", "8", "7", "6", "5", "4", "3", "2"]
+
+class Card(object):
+    def __init__(self, suit, denom):
+        self.suit = suit
+        self.denom = denom
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self.__dict__ == other.__dict__
+        elif isinstance(other, dict):
+            return self.__dict__ == other
+        else:
+            return False
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+def shuffle(numberOfDecks):
+    deck = [Card(x, y) for x in suits for y in denoms] + [Card(None, "joker") for _ in range(3)]
+    stock = deck * numberOfDecks
+
+    for n in range(len(stock)):
+        x = random.randint(0, len(stock) - n)
+        value = stock[x]
+        stock[x] = stock[n]
+        stock[n] = value
+
+    return stock
+
+if __name__ == "__main__":
+    print(shuffle(2))

+ 8 - 0
color.py

@@ -0,0 +1,8 @@
+from enum import Enum
+
+class Color(str, Enum):
+    RED = "red"
+    BLUE = "blue"
+    YELLOW = "yellow"
+    GREEN = "green"
+

+ 0 - 40
command.py

@@ -1,40 +0,0 @@
-from enum import Enum
-import json
-
-
-class Command(str, Enum):
-    NEWGAME = "newgame"
-    JOINGAME = "joingame"
-    PICKCOLOR = "pickcolor"
-    DEAL = "deal"
-    CHANGECARD = "changecard"
-    PLAYCARD = "playcard"
-    READY = "ready"
-    UNDOCARD = "undocard"
-    PASS = "pass"
-
-
-class Option(object):
-    def __init__(self, command, args=None, text=None):
-        self.command = command  # Command(command)
-        self.args = args
-        self.text = text
-
-    def check_arg(self, expected_arg, given_arg):
-        return expected_arg == "#" or expected_arg == given_arg
-
-    def check_args(self, other):
-        if self.args == other.args:
-            return True
-
-        if self.args == None or len(self.args) == 0 or len(self.args) != len(other.args):
-            return False
-
-        return all(self.check_arg(expected, given) for (expected, given) in zip(self.args, other.args))
-
-
-if __name__ == "__main__":
-    option = Option("newgame")
-    print(option)
-    print(option.__dict__)
-    print(json.dumps(option))

+ 73 - 58
game.py

@@ -1,11 +1,10 @@
 import random
 from typing import List, Dict
 
-from command import Command, Option
-from player import Player, Color
-
-suits = ["Harten", "Schoppen", "Klaver", "Ruiten"]
-denoms = ["Aas", "Heer", "Vrouw", "Boer", "10", "9", "8", "7", "6", "5", "4", "3", "2"]
+from color import Color
+from cards import Card, shuffle
+from option import OptionCode, Option
+from player import Player, StateCode, ErrorCode
 
 
 class Game(object):
@@ -13,6 +12,7 @@ class Game(object):
 
     players: Dict[Color, Player]
     current_player: Player
+    current_dealer: Player
     stock: List[str]
     number_of_cards: int
 
@@ -31,26 +31,18 @@ class Game(object):
         self.players = dict((player.color, player) for player in players)
 
         for player in self.players.values():
-            player.options = [Option(Command.DEAL, None, "Delen")]
+            player.options = [Option(OptionCode.DEAL, "Delen")]
             player.message = "Wie begint er met delen?"
-
-    def shuffle(self):
-        deck = [x + " " + y for x in suits for y in denoms] + ["Joker" for _ in range(3)]
-        self.stock = deck * 2
-
-        for n in range(len(self.stock)):
-            x = random.randint(0, len(self.stock) - n)
-            value = self.stock[x]
-            self.stock[x] = self.stock[n]
-            self.stock[n] = value
+            player.set_state(StateCode.FIRST_DEAL)
 
     def deal(self, player_color):
         player = self.players[player_color]
 
         self.current_player = player
+        self.current_dealer = player
 
         if self.number_of_cards <= 2:
-            self.shuffle()
+            self.stock = shuffle(2)
             self.number_of_cards = 6
         else:
             self.number_of_cards -= 1
@@ -58,10 +50,12 @@ class Game(object):
         for player in self.players.values():
             player.hand = self.stock[:self.number_of_cards]
             self.stock = self.stock[self.number_of_cards:]
-            player.options = [Option(Command.CHANGECARD, [card], card) for card in player.hand]
+            player.options = [Option(OptionCode.SWAP_CARD, card.denom, card=card) for card in player.hand]
             player.message = "Kies een kaart om te wisselen"
+            player.set_state(StateCode.SWAP_CARD)
             player.selected_card = None
             player.card_is_changed = False
+            player.passed = False
 
         return self.players
 
@@ -69,20 +63,21 @@ class Game(object):
         player = self.players[player_color]
 
         player.hand.remove(card)
-        mate = self.mate(player)
+        partner = self.partner(player)
 
-        if mate.selected_card is None:
+        if partner.selected_card is None:
             player.selected_card = card
             player.message = "Wacht op je maat"
-            player.options = [Option(Command.UNDOCARD, None, "Terug")]
+            player.set_state(StateCode.SWAP_CARD_PARTNER)
+            player.options = [Option(OptionCode.UNDO_CARD, "Terug")]
             return self.players
 
-        player.hand.append(mate.selected_card)
-        mate.selected_card = None
-        mate.hand.append(card)
+        player.hand.append(partner.selected_card)
+        partner.selected_card = None
+        partner.hand.append(card)
         player.card_is_changed = True
-        mate.card_is_changed = True
-        mate.options.clear()
+        partner.card_is_changed = True
+        partner.options.clear()
         player.options.clear()
         player.message = ""
 
@@ -90,7 +85,9 @@ class Game(object):
             self.next_turn()
         else:
             player.message = "Wacht op het andere team"
-            mate.message = "wacht op het andere team"
+            player.set_state(StateCode.SWAP_CARD_OTHERS)
+            partner.message = "wacht op het andere team"
+            partner.set_state(StateCode.SWAP_CARD_OTHERS)
 
         return self.players
 
@@ -99,15 +96,25 @@ class Game(object):
 
         player.hand.remove(card)
         player.selected_card = card
-        player.options = [Option(Command.UNDOCARD, None, "Terug"), Option(Command.READY, None, "Klaar")]
+        player.options = [Option(OptionCode.UNDO_CARD, "Terug"), Option(OptionCode.READY, "Klaar")]
         player.message = f"Je speelt {card}"
+        player.set_state(StateCode.PLAYING_CARD, card=card)
 
         for other in self.players.values():
             if other.color != player.color:
                 other.message = f"{player.name} speelt {card}"
+                other.set_state(StateCode.PLAYING_CARD_OTHER, card=card, other_player_name=player.name)
 
         return self.players
 
+    def drop_cards(self, player_color):
+        player = self.players[player_color]
+
+        player.hand = []
+        player.options = []
+        player.passed = True
+        self.next_turn()
+
     def ready(self, player_color):
         player = self.players[player_color]
 
@@ -126,24 +133,27 @@ class Game(object):
         if player.card_is_changed:
             self.turn()
         else:
-            player.options = [Option(Command.CHANGECARD, [card], card) for card in player.hand]
+            player.options = [Option(OptionCode.SWAP_CARD, card.denom, card=card) for card in player.hand]
             player.message = "Kies een kaart om te wisselen"
+            player.set_state(StateCode.SWAP_CARD)
 
         return self.players
 
     def play_option(self, player, option):
-        if option.command == Command.DEAL:
+        if option.code == OptionCode.DEAL:
             return self.deal(player.color)
-        elif option.command == Command.CHANGECARD:
-            return self.change_card(player.color, option.args[0])
-        elif option.command == Command.PLAYCARD:
-            return self.play_card(player.color, option.args[0])
-        elif option.command == Command.READY:
+        elif option.code == OptionCode.SWAP_CARD:
+            return self.change_card(player.color, option.card)
+        elif option.code == OptionCode.PLAY_CARD:
+            return self.play_card(player.color, option.card)
+        elif option.code == OptionCode.READY:
             return self.ready(player.color)
-        elif option.command == Command.UNDOCARD:
+        elif option.code == OptionCode.UNDO_CARD:
             return self.undo_card(player.color)
+        elif option.code == OptionCode.PASS:
+            return self.drop_cards(player.color)
         else:
-            raise Exception(f"Unknown command {option.command}")
+            raise Exception(f"Unknown option {option.code}")
 
     def next_player(self, player):
         if player.color == Color.RED:
@@ -158,24 +168,37 @@ class Game(object):
             raise Exception(f"Unknown color {player.Color}")
 
     def next_turn(self):
-        self.current_player = self.next_player(self.current_player)
-        self.turn()
+        if all(len(player.hand) == 0 for player in self.players.values()):
+            self.next_deal()
+        else:
+            self.current_player = self.next_player(self.current_player)
+            while len(self.current_player.hand) == 0:
+                self.current_player = self.next_player(self.current_player)
+            self.turn()
+
+    def next_deal(self):
+        self.current_player = self.next_player(self.current_dealer)
+        self.current_player.options = [Option(OptionCode.DEAL, "Delen")]
+        self.current_player.message = f"Jij bent aan de beurt om te delen."
+        self.current_player.set_state(StateCode.DEAL)
+        
+        for player in self.players.values():
+            if player.color != self.current_player.color:
+                player.message = f"{self.current_player.name} is aan de beurt om te delen"
+                player.set_state(StateCode.DEAL_OTHER, other_player_name=self.current_player.name) 
 
     def turn(self):
-        if len(self.current_player.hand) > 0:
-            self.current_player.options = [Option(Command.PLAYCARD, [card], card) for card in self.current_player.hand]
-            self.current_player.message = "Kies een kaart om te spelen"
-            other_message = ""
-        else:
-            self.current_player.options = [Option(Command.DEAL, None, "Delen")]
-            self.current_player.message = f"Jij bent aan de beurt om te delen."
-            other_message = " om te delen"
+        self.current_player.options = [Option(OptionCode.PLAY_CARD, card.denom, card=card) for card in self.current_player.hand]
+        self.current_player.options.append(Option(OptionCode.PASS, "Pas"))
+        self.current_player.message = "Kies een kaart om te spelen"
+        self.current_player.set_state(StateCode.PLAY_CARD)
 
         for player in self.players.values():
             if player.color != self.current_player.color:
-                player.message = f"{self.current_player.name} is aan de beurt" + other_message
+                player.message = f"{self.current_player.name} is aan de beurt"
+                player.set_state(StateCode.PLAY_CARD_OTHER, other_player_name=self.current_player.name) 
 
-    def mate(self, player):
+    def partner(self, player):
         if player.color == Color.RED:
             return self.players[Color.YELLOW]
         elif player.color == Color.BLUE:
@@ -191,14 +214,6 @@ class Game(object):
 if __name__ == "__main__":
     game = Game()
 
-    # game.deal(Color.BLUE)
-    # game.changeCard(Color.YELLOW, game.players[Color.YELLOW].hand[4])
-    # game.changeCard(Color.RED, game.players[Color.RED].hand[2])
-    # game.changeCard(Color.GREEN, game.players[Color.GREEN].hand[0])
-    # game.changeCard(Color.BLUE, game.players[Color.BLUE].hand[5])
-    # game.playCard(Color.YELLOW, game.players[Color.YELLOW].hand[1])
-    # game.undoCard(Color.YELLOW)
-
     colors = [Color.BLUE, Color.YELLOW, Color.GREEN, Color.RED]
 
     for round in range(2):
@@ -221,4 +236,4 @@ if __name__ == "__main__":
     for player in game.players.values():
         print(player.__dict__, end="\n\n")
 
-    print(game.stock, end="\n\n")
+    print([card.__dict__ for card in game.stock], end="\n\n")

+ 45 - 0
option.py

@@ -0,0 +1,45 @@
+from enum import Enum
+from color import Color
+from cards import Card
+import json
+
+
+class OptionCode(str, Enum):
+    NEW_GAME = "new_game"
+    JOIN_GAME = "join_game"
+    PICK_COLOR = "pick_color"
+    DEAL = "deal"
+    SWAP_CARD = "swap_card"
+    PLAY_CARD = "play_card"
+    READY = "ready"
+    UNDO_CARD = "undo_card"
+    PASS = "pass"
+
+
+class Option(object):
+    def __init__(self, code, text, game_code=None, color=None, card=None):
+        self.code = OptionCode(code)
+        self.text = text
+        self.game_code = int(game_code) if game_code != None else None
+        self.color = Color(color) if color != None else None
+        self.card = card if isinstance(card, Card) else Card(card['suit'], card['denom']) if isinstance(card, dict) else None
+
+    def isOption(self, other):
+        # ignore text and game_code 
+    
+        return \
+            self.code == other.code and \
+            self.color == other.color and \
+            self.card == other.card
+
+
+if __name__ == "__main__":
+    option = Option(OptionCode.NEW_GAME, "Nieuw spel", game_code=3)
+    print(option)
+    print(option.__dict__)
+    print(json.dumps(option.__dict__))
+    other = Option(OptionCode.NEW_GAME, "Start nog een spel", game_code=4)
+    print(option.isOption(other))
+    other = Option(OptionCode.NEW_GAME, "Doe maar gek", color='red')
+    print(option.isOption(other))
+    other = Option(OptionCode.SWAP_CARD, "Wissel kaart", card=Card("spades", "10"))

+ 50 - 17
player.py

@@ -1,22 +1,43 @@
 from enum import Enum
-from command import Option
+from color import Color
+from option import Option, OptionCode
 import json
-import jsonpickle
 
 
-class Color(str, Enum):
-    RED = "red"
-    BLUE = "blue"
-    YELLOW = "yellow"
-    GREEN = "green"
+class StateCode(str, Enum):
+    START = "start"
+    JOIN_OTHERS = "join_others"
+    PICK_COLOR = "pick_color"
+    PICK_COLOR_OTHERS = "pick_color_others"
+    FIRST_DEAL = "first_deal"
+    DEAL = "deal"
+    DEAL_OTHER = "deal_other"
+    SWAP_CARD = "swap_card"
+    SWAP_CARD_PARTNER = "swap_card_partner"
+    SWAP_CARD_OTHERS = "swap_card_others"
+    PLAY_CARD = "play_card"
+    PLAY_CARD_OTHER = "play_card_other"
+    PLAYING_CARD = "playing_card"
+    PLAYING_CARD_OTHER = "playing_card_other"
 
-    def __getstate__(self):
-        return "hallo"
 
-    def __setstate__(state):
-        return Color(state)
+class ErrorCode(str, Enum):
+    UNKNOWN_CODE = "unknown_code"
+    COLOR_ALREADY_CHOSEN = "color_already_chosen"
+    OPTION_NOT_ALLOWED = "option_not_allowed"
 
 
+class PlayerState(object):
+    def __init__(self, code, **kwargs):
+        self.code = StateCode(code)
+        self.args = kwargs
+
+
+class PlayerError(object):
+    def __init__(self, code, **kwargs):
+        self.code = ErrorCode(code)
+        self.args = kwargs
+
 class Player(object):
     def __init__(self, color = None, name = None):
         self.color = color
@@ -24,17 +45,30 @@ class Player(object):
         self.hand = []
         self.options = []
         self.message = "Even wachten"
+        self.set_state(StateCode.START)
+        self.error = None
         self.selected_card = None
         self.card_is_changed = False
+        self.passed = False
 
     def check_option(self, option):
-        found_option = any(option.command == o.command and o.check_args(option) for o in self.options)
+        found_option = any(o.isOption(option) for o in self.options)
 
         if not found_option:
             self.message = f"{option.text} is niet toegestaan. " + self.message
+            self.set_error(ErrorCode.OPTION_NOT_ALLOWED, option=option)
             
         return found_option
 
+    def set_state(self, state_code, **kwargs):
+        self.state = PlayerState(state_code, **kwargs)
+
+    def set_error(self, error_code, **kwargs):
+        if error_code != None:
+            self.error = PlayerError(error_code, **kwargs)
+        else:
+            self.error = None
+
 
 class DogEncoder(json.JSONEncoder):
     def default(self, obj):
@@ -45,9 +79,8 @@ class DogEncoder(json.JSONEncoder):
 
 if __name__ == "__main__":
     player = Player(Color.BLUE, "Groen")
-    player.options = [Option("newgame", None, "text")]
-    print(player)
+    player.options = [Option(OptionCode.NEW_GAME, "Nieuw spel", color='green'), Option(OptionCode.JOIN_GAME, "doe mee met een spel")]
+    player.set_state(StateCode.PICK_COLOR, other_player_name="Rood")
     print(player.__dict__)
-    print(json.dumps(player, cls=DogEncoder))
-    # print(json.dumps(player))
-    print(jsonpickle.encode(player))
+    print(player.options[0].__dict__)
+    print(json.dumps(player.__dict__, cls=DogEncoder))

+ 13 - 12
ui/dog.html

@@ -9,22 +9,22 @@
                 background-color: lightgray;
             }
             .buttons {
-                font-size: 3em;
+                font-size: 1em;
                 display: flex;
                 justify-content: center;
             }
             .button {
                 line-height: 1;
-                padding: 2rem;
-                margin: 2rem;
-                border: medium solid;
+                padding: 1rem;
+                margin: 1rem;
+                border: thin solid;
                 min-height: 1em;
                 min-width: 1em;
                 cursor: pointer;
                 user-select: none;
             }
             .state, .name {
-                font-size: 3em;
+                font-size: 1em;
                 padding: 1rem;
             }
             .name {
@@ -44,19 +44,20 @@
             <div class="button" id="b3" onclick="doOption(3)">4</div>
             <div class="button" id="b4" onclick="doOption(4)">5</div>
             <div class="button" id="b5" onclick="doOption(5)">6</div>
+            <div class="button" id="b6" onclick="doOption(6)">7</div>
         </div>
         <script>
             var player = { options: [
               {
-                command: 'newgame',
+                code: 'newgame',
                 args: null,
                 text: 'Nieuw spel'
-              },{
-                command: 'joingame',
-                args: [3936],
-                text: 'Doe mee'
+                }, {
+                code: 'joingame',
+                args: { code: 3936 },
+                text: 'Doe mee met een spel'
               }],
-              message: "Hallo"};
+              message: "Even wachten op de server"};
 
             function doOption(i) {
               if (player.options.length > i) {
@@ -66,7 +67,7 @@
 
             function show() {
               var i;
-              for (i = 0; i < 6; i++) {
+              for (i = 0; i < 7; i++) {
                 document.getElementById("b" + i.toString()).textContent = player.options.length > i ? player.options[i].text : '';
               }
               document.getElementById("msg").textContent = player.message;

+ 50 - 37
webserver.py

@@ -9,9 +9,10 @@ import logging
 import os
 import websockets
 
-from command import (Command, Option)
+from color import Color
+from option import OptionCode, Option
 from game import Game
-from player import (Color, Player)
+from player import ErrorCode, StateCode, Player
 
 logging.basicConfig()
 
@@ -77,12 +78,16 @@ async def notify(player_sockets):
 
 
 async def handler(websocket, path):
-    player = None
+    player = Player()
+    player.options = [
+        Option(OptionCode.NEW_GAME, "Nieuw spel"), 
+        Option(OptionCode.JOIN_GAME, "Doe mee met een spel", game_code='3936')]
+    player.message = "Wat wil je doen?"
+    player.set_state(StateCode.START)
+    game_code = 0
     try:
-        player = Player()
-        player.options = [
-            Option(Command.NEWGAME, None, "Nieuw spel"), 
-            Option(Command.JOINGAME, ["#"], "Doe mee met een spel")]
+        await notify([(player, websocket)])
+
         async for message in websocket:
             option = Option(**json.loads(message))
 
@@ -90,67 +95,75 @@ async def handler(websocket, path):
                 await notify([(player, websocket)])
                 continue
 
-            if option.command == Command.NEWGAME:
-                code = 3936  # random.randint(1000, 9999);
-                sockets[code] = [(player, websocket)]
-                player.message = f"De andere spelers kunnen meedoen door code {code} in te voeren"
+            player.set_error(None)
+
+            if option.code == OptionCode.NEW_GAME:
+                game_code = 3936  # random.randint(1000, 9999);
+                sockets[game_code] = [(player, websocket)]
+                player.message = f"De andere spelers kunnen meedoen door code {game_code} in te voeren"
+                player.set_state(StateCode.JOIN_OTHERS, game_code=game_code)
                 player.options = []
-                await notify(sockets[code])
+                await notify(sockets[game_code])
 
-            elif option.command == Command.JOINGAME:
-                code = int(option.args[0])
-                socketlist = sockets.get(code)
+            elif option.code == OptionCode.JOIN_GAME:
+                game_code = option.game_code
+                socketlist = sockets.get(game_code) if game_code > 0 else None
                 if socketlist == None:
-                    player.message = f"onbekande code {code}"
+                    player.message = f"onbekande code {game_code}"
+                    player.set_error(ErrorCode.UNKNOWN_CODE, game_code=game_code)
                     await notify([(player, websocket)])
                     continue
 
-                sockets[code].append((player, websocket))
-                if len(sockets[code]) < 4:
+                sockets[game_code].append((player, websocket))
+                if len(sockets[game_code]) < 4:
                     player.message = "wacht op de andere spelers"
+                    player.set_state(StateCode.JOIN_OTHERS, game_code=game_code)
                     player.options = []
                     await notify([(player, websocket)])
 
                 else:
-                    for (player, _) in sockets[code]:
+                    for (player, _) in sockets[game_code]:
                         player.message = "Kies een kleur om mee te spelen"
+                        player.set_state(StateCode.PICK_COLOR)
                         player.options = [
-                            Option(Command.PICKCOLOR, [Color.RED], "Rood"),
-                            Option(Command.PICKCOLOR, [Color.BLUE], "Blauw"),
-                            Option(Command.PICKCOLOR, [Color.YELLOW], "Geel"),
-                            Option(Command.PICKCOLOR, [Color.GREEN], "Groen")
+                            Option(OptionCode.PICK_COLOR, "Rood", color=Color.RED),
+                            Option(OptionCode.PICK_COLOR, "Blauw", color=Color.BLUE),
+                            Option(OptionCode.PICK_COLOR, "Geel", color=Color.YELLOW),
+                            Option(OptionCode.PICK_COLOR, "Groen", color=Color.GREEN)
                             ]
                    
-                    await notify(sockets[code])
+                    await notify(sockets[game_code])
 
-            elif option.command == Command.PICKCOLOR:
-                color = option.args[0]
-                if any(other.color == color for (other, _) in sockets[code]):
+            elif option.code == OptionCode.PICK_COLOR:
+                color = option.color
+                if any(other.color == color for (other, _) in sockets[game_code]):
                     player.message = f"Kleur {option.text} is al gekozen door een andere speler. " + player.message
+                    player.set_error(ErrorCode.COLOR_ALREADY_CHOSEN, color=color) 
                     await notify([(player, websocket)])
                     continue
 
                 player.color = color
                 player.name = option.text
                 player.message = "Wacht op de andere spelers"
+                player.set_state(StateCode.PICK_COLOR_OTHERS)
                 player.options = []
 
-                for (other, _) in sockets[code]:
-                    other.options = [option for option in other.options if option.args[0] != color]
+                for (other, _) in sockets[game_code]:
+                    other.options = [option for option in other.options if option.color != color]
 
-                if all( not any(p.options) for (p, _) in sockets[code]):
-                    game = Game(p for (p, _) in sockets[code])
-                    games[code] = game
+                if all( not any(p.options) for (p, _) in sockets[game_code]):
+                    game = Game(p for (p, _) in sockets[game_code])
+                    games[game_code] = game
 
-                await notify(sockets[code])
+                await notify(sockets[game_code])
 
             else:
-                game = games[code]
+                game = games[game_code]
                 game.play_option(player, option)
-                await notify(sockets[code])
+                await notify(sockets[game_code])
     finally:
-        if player is not None:
-            sockets[code].remove((player, websocket))
+        if player is not None and game_code > 0:
+            sockets[game_code].remove((player, websocket))
         pass
 
 if __name__ == "__main__":