From 493b116f73ec3912ca7af3d6ece3434279cbcfb5 Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Fri, 9 Mar 2018 13:38:22 +0100 Subject: renamed the project SwiftStory Signed-off-by: Olivier Gayot --- swiftstory/Board.py | 55 +++++++++++ swiftstory/Cards.py | 13 +++ swiftstory/Client.py | 73 ++++++++++++++ swiftstory/Game.py | 235 ++++++++++++++++++++++++++++++++++++++++++++++ swiftstory/GameManager.py | 34 +++++++ swiftstory/Player.py | 43 +++++++++ swiftstory/Status.py | 7 ++ swiftstory/__init__.py | 0 8 files changed, 460 insertions(+) create mode 100644 swiftstory/Board.py create mode 100644 swiftstory/Cards.py create mode 100644 swiftstory/Client.py create mode 100644 swiftstory/Game.py create mode 100644 swiftstory/GameManager.py create mode 100644 swiftstory/Player.py create mode 100644 swiftstory/Status.py create mode 100644 swiftstory/__init__.py (limited to 'swiftstory') diff --git a/swiftstory/Board.py b/swiftstory/Board.py new file mode 100644 index 0000000..4f4e7c6 --- /dev/null +++ b/swiftstory/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/swiftstory/Cards.py b/swiftstory/Cards.py new file mode 100644 index 0000000..78dd64a --- /dev/null +++ b/swiftstory/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('usr/share/swiftstory/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('usr/share/swiftstory/lang/' + lang + '/cards/black') as fd: + return [line.strip() for line in fd] diff --git a/swiftstory/Client.py b/swiftstory/Client.py new file mode 100644 index 0000000..33b8e4b --- /dev/null +++ b/swiftstory/Client.py @@ -0,0 +1,73 @@ +from swiftstory.Status import error +from swiftstory.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 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 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 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 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: + 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 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 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 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 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.game.disconnect(self.player) diff --git a/swiftstory/Game.py b/swiftstory/Game.py new file mode 100644 index 0000000..5e0961c --- /dev/null +++ b/swiftstory/Game.py @@ -0,0 +1,235 @@ +from swiftstory.Player import Player +from swiftstory.Board import Board + +from swiftstory.Status import error, 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 error('too many players in this game') + + cards = [] + + try: + for i in range(10): + cards.append(self.board.pick_white_card()) + except IndexError: + return 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 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 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 error('Who asked you to play now ?!') + + if self.judge is player: + return error('You\'re the judge, you silly') + elif player.get_has_played(): + return error('You already played, you dumb ass') + + try: + card = player.pop_card(card_id) + except IndexError: + return error('Invalid card id') + + player.set_has_played() + + self.board.play_card(player, card) + + self.judge.send_notification({'op': 'card_played'}) + + return success({'card_id': card_id}) + + + def try_collect_cards(self, player): + if self.state is not self.WAITING_COLLECTION: + return error('Do you think it\'s the moment for colletion !?') + + if self.judge is not player: + return 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 error('Not now, moron !') + + if self.judge is not player: + return error('Who do you think you are !?') + + if card_id is None and len(self.board.played_cards) > 0: + return 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 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 + + for p in self.players: + if p is not player: + p.send_notification({'op': 'judge_needed'}) + + self.state = self.WAITING_NEW_JUDGE + + return 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 success(self.__view_player_cards(player)) + + def try_view_played_cards(self, player): + if self.state is not self.WAITING_DESIGNATION: + return error('Not now, moron !') + + cards = [] + + for card, unused in self.board.played_cards: + cards.append(self.white_desc[card]) + + return success(cards) + + def try_view_black_card(self, player): + card = self.board.current_black_card + + if card is not None: + return success(self.black_desc[card]) + + return error('The black card has not been revealed yet') + + def disconnect(self, player): + player.client = None + + if self.judge is player: + self.board.recycle_black_card() + self.judge = None + + for p in self.players: + p.send_notification({'op': 'judge_needed'}) + + for card, p in self.board.played_cards: + idx = p.receive_card(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.played_cards = [] + self.state = self.WAITING_NEW_JUDGE diff --git a/swiftstory/GameManager.py b/swiftstory/GameManager.py new file mode 100644 index 0000000..7cdb5da --- /dev/null +++ b/swiftstory/GameManager.py @@ -0,0 +1,34 @@ +from swiftstory.Game import Game +from swiftstory.Cards import Cards + +import os + +class GameManager(): + def __init__(self): + self.langs = {} + + for filename in next(os.walk('usr/share/swiftstory/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/swiftstory/Player.py b/swiftstory/Player.py new file mode 100644 index 0000000..cc64d17 --- /dev/null +++ b/swiftstory/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/swiftstory/Status.py b/swiftstory/Status.py new file mode 100644 index 0000000..4e8c84d --- /dev/null +++ b/swiftstory/Status.py @@ -0,0 +1,7 @@ +import json + +def error(msg, code=255): + return json.dumps({'type': 'response', 'content': {'status': code, 'info': msg}}) + +def success(obj): + return json.dumps({'type': 'response', 'content': {'status': 0, 'result': obj}}) diff --git a/swiftstory/__init__.py b/swiftstory/__init__.py new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3