From 0fb08748e5285e5d4adc6135eec8889887a63299 Mon Sep 17 00:00:00 2001
From: Olivier Gayot <duskcoder@gmail.com>
Date: Sun, 14 Jun 2015 22:31:48 +0100
Subject: use a python package instead of just modules

The package is contained in the CAO/ folder.
server.py is still at the root of the repository though.

Signed-off-by: Olivier Gayot <duskcoder@gmail.com>
---
 CAO/Board.py       |  55 ++++++++++++++
 CAO/Cards.py       |  13 ++++
 CAO/Client.py      |  73 +++++++++++++++++++
 CAO/Game.py        | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 CAO/GameManager.py |  34 +++++++++
 CAO/Player.py      |  43 +++++++++++
 CAO/Status.py      |   7 ++
 CAO/__init__.py    |   0
 8 files changed, 431 insertions(+)
 create mode 100644 CAO/Board.py
 create mode 100644 CAO/Cards.py
 create mode 100644 CAO/Client.py
 create mode 100644 CAO/Game.py
 create mode 100644 CAO/GameManager.py
 create mode 100644 CAO/Player.py
 create mode 100644 CAO/Status.py
 create mode 100644 CAO/__init__.py

(limited to 'CAO')

diff --git a/CAO/Board.py b/CAO/Board.py
new file mode 100644
index 0000000..4f4e7c6
--- /dev/null
+++ b/CAO/Board.py
@@ -0,0 +1,55 @@
+import random
+
+class Board():
+    def __init__(self, white_cards, black_cards):
+        self.white_pick = white_cards
+        self.black_pick = black_cards
+
+        self.white_recycled = []
+        self.black_recycled = []
+
+        self.current_black_card = None
+
+        # tupple of cards / player currently being played
+        self.played_cards = []
+
+        random.shuffle(self.white_pick)
+        random.shuffle(self.black_pick)
+
+    def reveal_black_card(self):
+        if not self.black_pick:
+            self.black_pick = self.black_recycle
+
+            random.shuffle(self.black_pick)
+
+            self.black_recycled = []
+
+        card = self.black_pick.pop()
+
+        self.current_black_card = card
+
+    def recycle_black_card(self):
+        self.black_recycled.append(self.current_black_card)
+
+    def pick_white_card(self):
+        if not self.white_pick:
+            self.white_pick = self.white_recycled
+
+            random.shuffle(self.white_pick)
+
+            self.white_recycled = []
+
+        card = self.white_pick.pop()
+
+        return card
+
+    def play_card(self, player, card):
+        self.played_cards.append((card, player))
+
+    def shuffle_played_cards(self):
+        random.shuffle(self.played_cards)
+
+    def recycle_played_cards(self):
+        self.white_recycled += [i[0] for i in self.played_cards]
+
+        self.played_cards = []
diff --git a/CAO/Cards.py b/CAO/Cards.py
new file mode 100644
index 0000000..b796b97
--- /dev/null
+++ b/CAO/Cards.py
@@ -0,0 +1,13 @@
+class Cards():
+    @staticmethod
+    def get_white_cards(lang):
+        ''' Read the file containing the white cards and return a list of cards '''
+        with open('lang/' + lang + '/cards/white') as fd:
+            return [line.strip() for line in fd]
+
+    @staticmethod
+    def get_black_cards(lang):
+        ''' Read the file containing the black cards and return a list of cards '''
+
+        with open('lang/' + lang + '/cards/black') as fd:
+            return [line.strip() for line in fd]
diff --git a/CAO/Client.py b/CAO/Client.py
new file mode 100644
index 0000000..03bb68d
--- /dev/null
+++ b/CAO/Client.py
@@ -0,0 +1,73 @@
+from CAO.Status import cao_error
+from CAO.Game import Game
+
+class Client():
+    def __init__(self, socket, handler, game_manager):
+        self.game = None
+        self.game_manager = game_manager
+
+        self.handler = handler
+        self.socket = socket
+        self.player = None
+
+    def join_game(self, game_name, lang):
+        if self.game is not None:
+            return cao_error('You are already in a game')
+
+        if lang is None:
+            lang = 'en'
+
+        game = self.game_manager.join_game(game_name, lang)
+        # XXX self.game will be assigned by game.try_join()
+
+        if game is None:
+            return cao_error('Invalid language')
+
+        return game.try_join(self)
+
+    def set_game(self, game):
+        self.game = game
+    def set_player(self, player):
+        self.player = player
+
+    def play_white_card(self, card_id):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_play_card(self.player, card_id)
+
+    def pick_black_card(self):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_become_judge(self.player)
+
+    def collect_cards(self):
+        if self.game is None:
+            cao_error('You have to join a game first')
+        return self.game.try_collect_cards(self.player)
+
+    def designate_card(self, card_id):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_designate_card(self.player, card_id)
+
+    def view_player_cards(self):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_view_player_cards(self.player)
+
+    def view_played_cards(self):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_view_played_cards(self.player)
+
+    def view_black_card(self):
+        if self.game is None:
+            return cao_error('You have to join a game first')
+        return self.game.try_view_black_card(self.player)
+
+    def send_notification(self, message):
+        self.socket.send_message(self.handler, message)
+
+    def disconnect(self):
+        if self.player is not None:
+            self.player.client = None
diff --git a/CAO/Game.py b/CAO/Game.py
new file mode 100644
index 0000000..f4123d4
--- /dev/null
+++ b/CAO/Game.py
@@ -0,0 +1,206 @@
+from CAO.Player import Player
+from CAO.Board import Board
+
+from CAO.Status import cao_error, cao_success
+
+import json
+
+class Game():
+    WAITING_NEW_JUDGE = 0,
+    WAITING_COLLECTION = 1,
+    WAITING_DESIGNATION = 2,
+
+
+    def __init__(self, white_desc, black_desc):
+        self.white_desc = white_desc
+        self.black_desc = black_desc
+
+        white_pick = [i for i in range(len(self.white_desc))]
+        black_pick = [i for i in range(len(self.black_desc))]
+
+        self.state = self.WAITING_NEW_JUDGE
+
+        self.players = []
+
+        self.judge = None
+
+        self.board = Board(white_pick, black_pick)
+
+    def try_join(self, client):
+        if len(self.players) >= 10:
+            return cao_error('too many players in this game')
+
+        cards = []
+
+        try:
+            for i in range(10):
+                cards.append(self.board.pick_white_card())
+        except IndexError:
+            return cao_error('no enough white cards for player')
+
+        player = Player(client)
+
+        for card in cards:
+            player.receive_card(card)
+
+        client.set_player(player)
+        client.set_game(self)
+
+        self.players.append(player)
+
+        for p in self.players:
+            if p is not player:
+                p.send_notification({'op': 'player_joined_game'})
+
+        cards = self.__view_player_cards(player)
+
+        if self.state is self.WAITING_NEW_JUDGE:
+            state = 'waiting_judge'
+        elif self.state is self.WAITING_COLLECTION:
+            state = 'waiting_collection'
+        else:
+            state = 'waiting_designation'
+
+        return cao_success({'cards': cards, 'game_state': state})
+
+
+    def try_become_judge(self, player):
+        if self.state is not self.WAITING_NEW_JUDGE:
+            # TODO what if the judge has quit ?
+            return cao_error('Someone is judge already')
+
+        self.judge = player
+        self.board.reveal_black_card()
+
+        self.state = self.WAITING_COLLECTION
+
+        for p in self.players:
+            if p is not player:
+                p.send_notification({'op': 'judge_designed'})
+
+        return self.try_view_black_card(player)
+
+
+    def try_play_card(self, player, card_id):
+        if self.state is not self.WAITING_COLLECTION:
+            return cao_error('Who asked you to play now ?!')
+
+        if self.judge is player:
+            return cao_error('You\'re the judge, you silly')
+        elif player.get_has_played():
+            return cao_error('You already played, you dumb ass')
+
+        try:
+            card = player.pop_card(card_id)
+        except IndexError:
+            return cao_error('Invalid card id')
+
+        player.set_has_played()
+
+        self.board.play_card(player, card)
+
+        self.judge.send_notification({'op': 'card_played'})
+
+        return cao_success({'card_id': card_id})
+
+
+    def try_collect_cards(self, player):
+        if self.state is not self.WAITING_COLLECTION:
+            return cao_error('Do you think it\'s the moment for colletion !?')
+
+        if self.judge is not player:
+            return cao_error('You\'re not the judge, you fool!')
+
+        self.board.shuffle_played_cards()
+
+        # we prevent the others to play
+        self.state = self.WAITING_DESIGNATION
+
+        for p in self.players:
+            if p is not player:
+                p.send_notification({'op': 'cards_collected'})
+
+        return self.try_view_played_cards(player)
+
+
+    def try_designate_card(self, player, card_id):
+        if self.state is not self.WAITING_DESIGNATION:
+            return cao_error('Not now, moron !')
+
+        if self.judge is not player:
+            return cao_error('Who do you think you are !?')
+
+        if card_id is None and len(self.board.played_cards) > 0:
+            return cao_error('There are cards on the board, pick one !')
+
+        if card_id is not None or len(self.board.played_cards) > 0:
+            # if there are cards on the board
+            # TODO check exception
+            try:
+                card, winner = self.board.played_cards[card_id]
+            except IndexError:
+                return cao_error('Invalid card')
+
+            winner.inc_score()
+
+            # put the cards back in the deck
+            self.board.recycle_played_cards()
+
+            # reset the state of the players
+            for p in self.players:
+                if p.get_has_played:
+                    idx = p.receive_card(self.board.pick_white_card())
+                    card_idx = p.cards[idx]
+                    card_desc = self.white_desc[card_idx]
+
+                    p.send_notification({
+                        'op': 'received_card',
+                        'content': {
+                            'card': {
+                                'id': idx,
+                                'desc': card_desc,
+                                },
+                            },
+                        })
+                    p.set_has_played(False)
+
+        self.board.recycle_black_card()
+        self.judge = None # useful or not ...
+
+        for p in self.players:
+            if p is not player:
+                p.send_notification({'op': 'judge_needed'})
+
+        self.state = self.WAITING_NEW_JUDGE
+
+        return cao_success(None)
+
+    def __view_player_cards(self, player):
+        cards = []
+
+        for card in player.cards:
+            cards.append((card, self.white_desc[player.cards[card]]))
+
+        return cards
+
+    def try_view_player_cards(self, player):
+        return cao_success(self.__view_player_cards(player))
+
+    def try_view_played_cards(self, player):
+        if self.state is not self.WAITING_DESIGNATION:
+            return cao_error('Not now, moron !')
+
+        cards = []
+
+        for card, unused in self.board.played_cards:
+            cards.append(self.white_desc[card])
+
+        return cao_success(cards)
+
+    def try_view_black_card(self, player):
+        card = self.board.current_black_card
+
+        if card is not None:
+            return cao_success(self.black_desc[card])
+
+        return cao_error('The black card has not been revealed yet')
diff --git a/CAO/GameManager.py b/CAO/GameManager.py
new file mode 100644
index 0000000..5600f1e
--- /dev/null
+++ b/CAO/GameManager.py
@@ -0,0 +1,34 @@
+from CAO.Game import Game
+from CAO.Cards import Cards
+
+import os
+
+class GameManager():
+    def __init__(self):
+        self.langs = {}
+
+        for filename in next(os.walk('lang'))[1]:
+            self.langs[filename] = {}
+
+        for lang in self.langs:
+            self.langs[lang]['black_cards'] = Cards.get_black_cards(lang)
+            self.langs[lang]['white_cards'] = Cards.get_white_cards(lang)
+
+            self.langs[lang]['games'] = {}
+
+    def join_game(self, game_name, lang):
+        if self.langs.get(lang) is None:
+            return None
+
+        games = self.langs[lang]['games']
+        black_cards = self.langs[lang]['black_cards']
+        white_cards = self.langs[lang]['white_cards']
+
+        game = games.get(game_name)
+
+        if game is None:
+            print('Starting new game')
+
+            game = games[game_name] = Game(white_cards, black_cards)
+
+        return game
diff --git a/CAO/Player.py b/CAO/Player.py
new file mode 100644
index 0000000..cc64d17
--- /dev/null
+++ b/CAO/Player.py
@@ -0,0 +1,43 @@
+import json
+
+class Player():
+    def __init__(self, client):
+        self.cards = {}
+        self.next_idx = 0
+
+        self.client = client
+
+        self.score = 0
+
+        self.has_played = False
+
+        self.name = 'default'
+
+    def pop_card(self, card_id):
+        return self.cards.pop(card_id)
+
+    def get_has_played(self):
+        return self.has_played
+
+    def set_has_played(self, has=True):
+        self.has_played = has
+
+    def inc_score(self):
+        self.score += 1
+        self.send_notification({
+            'op': 'updated_score',
+            'content': self.score,
+            })
+
+    def receive_card(self, card):
+        self.cards[self.next_idx] = card
+        self.next_idx += 1
+        return self.next_idx - 1
+
+    def send_notification(self, obj):
+        if self.client is None:
+            return
+
+        message = json.dumps({'type': 'notification', 'content': obj})
+
+        self.client.send_notification(message)
diff --git a/CAO/Status.py b/CAO/Status.py
new file mode 100644
index 0000000..782a0b4
--- /dev/null
+++ b/CAO/Status.py
@@ -0,0 +1,7 @@
+import json
+
+def cao_error(msg, code=255):
+    return json.dumps({'type': 'response', 'content': {'status': code, 'info': msg}})
+
+def cao_success(obj):
+    return json.dumps({'type': 'response', 'content': {'status': 0, 'result': obj}})
diff --git a/CAO/__init__.py b/CAO/__init__.py
new file mode 100644
index 0000000..e69de29
-- 
cgit v1.2.3