Jelajahi Sumber

Initial commit

Adds first backend version and simple frontend
niels 5 tahun lalu
melakukan
31a1234078
5 mengubah file dengan 576 tambahan dan 0 penghapusan
  1. 37 0
      command.py
  2. 228 0
      game.py
  3. 52 0
      player.py
  4. 90 0
      ui/dog.html
  5. 169 0
      webserver.py

+ 37 - 0
command.py

@@ -0,0 +1,37 @@
+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 checkArg(self, expectedArg, givenArg):
+        return expectedArg == "#" or expectedArg == givenArg
+
+    def checkArgs(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.checkArg(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))

+ 228 - 0
game.py

@@ -0,0 +1,228 @@
+import json;
+import random;
+from player import Player, Color;
+from command import Command, Option
+
+suits = ["Harten", "Schoppen", "Klaver", "Ruiten"]
+denoms = ["Aas", "Heer", "Vrouw", "Boer", "10", "9", "8", "7", "6", "5", "4", "3", "2"]
+
+
+class Game(object):
+    """description of class"""
+
+    def __init__(self):
+        self.__init([ 
+            Player(Color.RED, "Rood"), 
+            Player(Color.BLUE, "Blauw"), 
+            Player(Color.YELLOW, "Geel"), 
+            Player(Color.GREEN, "Groen")])
+
+    def __init__(self, players):
+        self.stock = []
+        random.seed()
+        self.numberOfCards = 0
+
+        self.players = dict((player.color, player) for player in players)
+
+        for player in self.players.values():
+            player.options = [Option(Command.DEAL, None, "Delen")]
+            player.message = "Wie begint er met delen?"
+
+
+    def shuffle(self):
+        deck = [x + " " + y for x in suits for y in denoms] + ["Joker" for i 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
+
+
+    def deal(self, playerColor):
+        player = self.players[playerColor]
+
+        self.currentPlayer = player
+
+        if self.numberOfCards <= 2:
+            self.shuffle()
+            self.numberOfCards = 6
+        else:
+            self.numberOfCards -= 1
+
+        for player in self.players.values():
+            player.hand = self.stock[:self.numberOfCards]
+            self.stock = self.stock[self.numberOfCards:]
+            player.options = [Option(Command.CHANGECARD, [card], card) for card in player.hand]
+            player.message = "Kies een kaart om te wisselen"
+            player.selectedCard = None
+            player.cardIsChanged = False
+
+        return self.players
+
+
+    def changeCard(self, playerColor, card):
+        player = self.players[playerColor]
+
+        player.hand.remove(card)
+        mate = self.mate(player)
+
+        if mate.selectedCard is None:
+            player.selectedCard = card
+            player.message = "Wacht op je maat"
+            player.options = [Option(Command.UNDOCARD, None, "Terug")]
+            return self.players
+
+        player.hand.append(mate.selectedCard)
+        mate.selectedCard = None
+        mate.hand.append(card)
+        player.cardIsChanged = True
+        mate.cardIsChanged = True
+        mate.options.clear()
+        player.options.clear()
+        player.message = ""
+
+        if all(player.cardIsChanged for player in self.players.values()):
+            self.nextTurn()
+        else:
+            player.message = "Wacht op het andere team"
+            mate.message = "wacht op het andere team"
+
+        return self.players
+
+
+    def playCard(self, playerColor, card):
+        player = self.players[playerColor]
+
+        player.hand.remove(card)
+        player.selectedCard = card
+        player.options = [Option(Command.UNDOCARD, None, "Terug"), Option(Command.READY, None, "Klaar")]
+        player.message = f"Je speelt {card}"
+
+        for other in self.players.values():
+            if other.color != player.color:
+                other.message = f"{player.name} speelt {card}"
+
+        return self.players
+
+
+    def ready(self, playerColor):
+        player = self.players[playerColor]
+
+        player.options.clear()
+        player.selectedCard = None
+        self.nextTurn()
+
+        return self.players
+
+
+    def undoCard(self, playerColor):
+        player = self.players[playerColor]
+
+        player.hand.append(player.selectedCard)
+        player.selectedCard = None
+
+        if player.cardIsChanged:
+            self.turn()
+        else:
+            player.options = [Option(Command.CHANGECARD, [card], card) for card in player.hand]
+            player.message = "Kies een kaart om te wisselen"
+
+        return self.players
+
+    def playOption(self, player, option):
+        if option.command == Command.DEAL:
+            return self.deal(player.color)
+        elif option.command == Command.CHANGECARD:
+            return self.changeCard(player.color, option.args[0])
+        elif option.command == Command.PLAYCARD:
+            return self.playCard(player.color, option.args[0])
+        elif option.command == Command.READY:
+            return self.ready(player.color)
+        elif option.command == Command.UNDOCARD:
+            return self.undoCard(player.color)
+        else:
+            raise Exception(f"Unknown command {option.command}")
+
+
+    def nextPlayer(self, player):
+        if player.color == Color.RED:
+            return self.players[Color.BLUE]
+        elif player.color == Color.BLUE:
+            return self.players[Color.YELLOW]
+        elif player.color == Color.YELLOW:
+            return self.players[Color.GREEN]
+        elif player.color == Color.GREEN:
+            return self.players[Color.RED]
+        else:
+            raise Exception(f"Unknown color {player.Color}")
+
+
+    def nextTurn(self):
+        self.currentPlayer = self.nextPlayer(self.currentPlayer)
+        self.turn()
+
+
+    def turn(self):
+        if len(self.currentPlayer.hand) > 0:
+            self.currentPlayer.options = [Option(Command.PLAYCARD, [card], card) for card in self.currentPlayer.hand]
+            self.currentPlayer.message = "Kies een kaart om te spelen"
+            otherMessage = ""
+        else:
+            self.currentPlayer.options = [Option(Command.DEAL, None, "Delen")]
+            self.currentPlayer.message = f"Jij bent aan de beurt om te delen."
+            otherMessage = " om te delen"
+
+        for player in self.players.values():
+            if player.color != self.currentPlayer.color:
+                player.message = f"{self.currentPlayer.name} is aan de beurt" + otherMessage
+
+
+    def mate(self, player):
+        if player.color == Color.RED:
+            return self.players[Color.YELLOW]
+        elif player.color == Color.BLUE:
+            return self.players[Color.GREEN]
+        elif player.color == Color.YELLOW:
+            return self.players[Color.RED]
+        elif player.color == Color.GREEN:
+            return self.players[Color.BLUE]
+        else:
+            raise Exception(f"Unknown color {player.Color}")
+
+
+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):
+        for deal in [6, 5, 4, 3, 2]:
+            color = colors[0]
+            print(color)
+            game.deal(color)
+            game.changeCard(Color.YELLOW, game.players[Color.YELLOW].hand[deal - 1])
+            game.changeCard(Color.RED, game.players[Color.RED].hand[1])
+            game.changeCard(Color.GREEN, game.players[Color.GREEN].hand[0])
+            game.changeCard(Color.BLUE, game.players[Color.BLUE].hand[-1])
+
+            colors = colors[1:] + [colors[0]]
+
+            for turn in range(deal):
+                for color in colors:
+                    game.playCard(color, game.players[color].hand[0])
+                    game.ready(color)
+
+    for player in game.players.values():
+        print(player.__dict__, end="\n\n")
+
+    print(game.stock, end="\n\n")

+ 52 - 0
player.py

@@ -0,0 +1,52 @@
+from enum import Enum
+from command import Option
+import json
+import jsonpickle
+
+class Color(str, Enum):
+    RED = "red"
+    BLUE = "blue"
+    YELLOW = "yellow"
+    GREEN = "green"
+
+    def __getstate__(self):
+        return "hallo"
+
+    def __setstate__(state):
+        return Color(state)
+
+
+class Player(object):
+    def __init__(self, color = None, name = None):
+        self.color = color
+        self.name = name
+        self.hand = []
+        self.options = []
+        self.message = "Even wachten"
+        self.selectedCard = None
+        self.cardIsChanged = False
+
+    def checkOption(self, option):
+        foundOption = any(option.command == o.command and o.checkArgs(option) for o in self.options)
+
+        if not foundOption:
+            self.message = f"{option.text} is niet toegestaan. " + self.message
+            
+        return foundOption
+
+
+class DogEncoder(json.JSONEncoder):
+    def default(self, obj):
+        if hasattr(obj, '__dict__'):
+            return obj.__dict__
+        return json.JSONEncoder.default(self, obj)
+
+
+if __name__ == "__main__":
+   player = Player(Color.BLUE, "Groen")
+   player.options = [Option("newgame", None, "text")]
+   print(player)
+   print(player.__dict__)
+   print(json.dumps(player, cls=DogEncoder))
+   #print(json.dumps(player))
+   print(jsonpickle.encode(player))

+ 90 - 0
ui/dog.html

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Dog</title>
+        <style type="text/css">
+            body {
+                font-family: "Courier New", sans-serif;
+                text-align: center;
+                background-color: lightgray;
+            }
+            .buttons {
+                font-size: 3em;
+                display: flex;
+                justify-content: center;
+            }
+            .button {
+                line-height: 1;
+                padding: 2rem;
+                margin: 2rem;
+                border: medium solid;
+                min-height: 1em;
+                min-width: 1em;
+                cursor: pointer;
+                user-select: none;
+            }
+            .state, .name {
+                font-size: 3em;
+                padding: 1rem;
+            }
+            .name {
+                color: white;
+             }
+        </style>
+    </head>
+    <body>
+        <div class="name" id="name">You</div>
+        <div class="state">
+            <span class="message" id="msg">?</span>
+        </div>
+        <div class="buttons">
+            <div class="button" id="b0" onclick="doOption(0)">1</div>
+            <div class="button" id="b1" onclick="doOption(1)">2</div>
+            <div class="button" id="b2" onclick="doOption(2)">3</div>
+            <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>
+        <script>
+            var player = { options: [
+              {
+                command: 'newgame',
+                args: null,
+                text: 'Nieuw spel'
+              },{
+                command: 'joingame',
+                args: [3936],
+                text: 'Doe mee'
+              }],
+              message: "Hallo"};
+
+            function doOption(i) {
+              if (player.options.length > i) {
+                websocket.send(JSON.stringify(player.options[i]));
+              }
+            }
+
+            function show() {
+              var i;
+              for (i = 0; i < 6; i++) {
+                document.getElementById("b" + i.toString()).textContent = player.options.length > i ? player.options[i].text : '';
+              }
+              document.getElementById("msg").textContent = player.message;
+              var name = document.getElementById("name");
+              name.textContent = player.name ?? 'You';
+              if (player.color) {
+                name.style.color = player.color; 
+              }
+            }
+            
+            websocket = new WebSocket("ws://127.0.0.1:6789/");
+
+            websocket.onmessage = function (event) {
+                player = JSON.parse(event.data);
+                show();
+            }
+            
+            show();
+        </script>
+    </body>
+</html>

+ 169 - 0
webserver.py

@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+
+# WS server example that synchronizes state across clients
+
+import asyncio
+import functools
+import json
+import logging
+import os
+import random
+import websockets
+
+from enum import Enum
+
+from command import (Command, Option)
+from game import Game
+from player import (Color, Player)
+
+logging.basicConfig()
+
+games = dict()   # code -> game
+
+sockets = dict()   # code -> [(player, websocket), ...]
+
+
+class DogEncoder(json.JSONEncoder):
+    def default(self, obj):
+        if hasattr(obj, '__dict__'):
+            return obj.__dict__
+        return json.JSONEncoder.default(self, obj)
+
+
+from http import HTTPStatus
+
+
+MIME_TYPES = {
+    "html": "text/html",
+    "js": "text/javascript",
+    "css": "text/css"
+}
+
+
+async def process_request(sever_root, path, request_headers):
+    """Serves a file when doing a GET request with a valid path."""
+
+    if "Upgrade" in request_headers:
+        return  # Probably a WebSocket connection
+
+    path = '/dog.html'
+
+    response_headers = [
+        ('Server', 'asyncio websocket server'),
+        ('Connection', 'close'),
+    ]
+
+    # Derive full system path
+    full_path = os.path.realpath(os.path.join(sever_root, path[1:]))
+
+    # Validate the path
+    if os.path.commonpath((sever_root, full_path)) != sever_root or \
+            not os.path.exists(full_path) or not os.path.isfile(full_path):
+        print("HTTP GET {} 404 NOT FOUND".format(path))
+        return HTTPStatus.NOT_FOUND, [], b'404 NOT FOUND'
+
+    # Guess file content type
+    extension = full_path.split(".")[-1]
+    mime_type = MIME_TYPES.get(extension, "application/octet-stream")
+    response_headers.append(('Content-Type', mime_type))
+
+    # Read the whole file into memory and send it out
+    body = open(full_path, 'rb').read()
+    response_headers.append(('Content-Length', str(len(body))))
+    print("HTTP GET {} 200 OK".format(path))
+    return HTTPStatus.OK, response_headers, body
+
+
+async def notify(playerSockets):
+    if playerSockets:
+        await asyncio.wait([websocket.send(json.dumps(player, cls=DogEncoder)) for (player, websocket) in playerSockets])
+
+
+async def handler(websocket, path):
+    try:
+        player = Player()
+        player.options = [
+            Option(Command.NEWGAME, None, "Nieuw spel"), 
+            Option(Command.JOINGAME, ["#"], "Doe mee met een spel")]
+        async for message in websocket:
+            option = Option(**json.loads(message))
+
+            if not player.checkOption(option):
+                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.options = []
+                await notify(sockets[code])
+
+            elif option.command == Command.JOINGAME:
+                code = int(option.args[0])
+                socketlist = sockets.get(code)
+                if socketlist == None:
+                    player.message = f"onbekande code {code}"
+                    await notify([(player, websocket)])
+                    continue
+
+                sockets[code].append((player, websocket))
+                if len(sockets[code]) < 4:
+                    player.message = "wacht op de andere spelers"
+                    player.options = []
+                    await notify([(player, websocket)])
+
+                else:
+                    for (player, _) in sockets[code]:
+                        player.message = "Kies een kleur om mee te spelen"
+                        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")
+                            ]
+                   
+                    await notify(sockets[code])
+
+            elif option.command == Command.PICKCOLOR:
+                color = option.args[0]
+                if any(other.color == color for (other, _) in sockets[code]):
+                    player.message = f"Kleur {option.text} is al gekozen door een andere speler. " + player.message
+                    await notify([(player, websocket)])
+                    continue
+
+                player.color = color
+                player.name = option.text
+                player.message = "Wacht op de andere spelers"
+                player.options = []
+
+                for (other, _) in sockets[code]:
+                    other.options = [option for option in other.options if option.args[0] != color]
+
+                if all( not any(p.options) for (p, _) in sockets[code]):
+                    game = Game(p for (p, _) in sockets[code])
+                    games[code] = game
+
+                await notify(sockets[code])
+
+            else:
+                game = games[code]
+                game.playOption(player, option)
+                await notify(sockets[code])
+
+
+    finally:
+        sockets[code].remove((player, websocket))
+        pass
+
+if __name__ == "__main__":
+
+    staticHandler = functools.partial(process_request, os.getcwd() + '\\ui')
+
+
+    start_server = websockets.serve(handler, "localhost", 6789, process_request=staticHandler)
+
+    print("Running server at http://127.0.0.1:6789/")
+
+    asyncio.get_event_loop().run_until_complete(start_server)
+    asyncio.get_event_loop().run_forever()