diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..441ef09 --- /dev/null +++ b/bot.py @@ -0,0 +1,47 @@ + +from telegram import Updater, InlineQueryResultPhoto + +from game_manager import GameManager +import card as c +from credentials import TOKEN + +gm = GameManager() +u = Updater(TOKEN) +dp = u.dispatcher + + +def new_game(bot, update): + chat_id = update.message.chat_id + link = gm.generate_invite_link(u.bot.getMe().username, chat_id) + bot.sendMessage(chat_id, + text="Click this link to join the game: %s" % link) + + +def start(bot, update, args): + if args: + gm.join_game(args[0], update.message.from_user) + else: + bot.sendMessage(update.message.chat_id, + text="Please invite me to a group and " + "issue the /start command there.") + + +def inline(bot, update): + if update.inline_query: + user_id = update.inline_query.from_user.id + player = gm.userid_player[user_id] + + playable = list() + for card in player.playable_cards(): + playable.append( + InlineQueryResultPhoto(str(card), + card.get_image_link(), + card.get_thumb_link()) + ) + + bot.answerInlineQuery(update.inline_query.id, playable) + + else: + user_id = update.chosen_inline_result.from_user.id + game = gm.userid_game[user_id] + game.play_card(c.from_str(update.chosen_inline_result.id)) diff --git a/card.py b/card.py new file mode 100644 index 0000000..4e0f125 --- /dev/null +++ b/card.py @@ -0,0 +1,68 @@ + +# Colors +RED = 'r' +BLUE = 'b' +GREEN = 'g' +YELLOW = 'y' + +COLORS = (RED, BLUE, GREEN, YELLOW) + +# Values +ZERO = '0' +ONE = '1' +TWO = '2' +THREE = '3' +FOUR = '4' +FIVE = '5' +SIX = '6' +SEVEN = '7' +EIGHT = '8' +NINE = '9' +DRAW_TWO = 'draw' +REVERSE = 'reverse' +SKIP = 'skip' + +VALUES = (ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, DRAW_TWO, + REVERSE, SKIP) + +# Special cards +CHOOSE = 'colorchooser' +DRAW_FOUR = 'draw_four' + +SPECIALS = (CHOOSE, DRAW_FOUR) + +IMAGE_PATTERN = 'https://raw.githubusercontent.com/jh0ker/mau_mau_bot/' \ + 'master/images/jpg/%s.jpg' +THUMB_PATTERN = 'https://raw.githubusercontent.com/jh0ker/mau_mau_bot/' \ + 'master/images/thumb/%s.jpg' + + +class Card(object): + + def __init__(self, color, value, special=None): + self.color = color + self.value = value + self.special = special + + def __str__(self): + if self.special: + return self.special + else: + return '%s_%s' % (self.color, self.value) + + def __repr__(self): + return str(self) + + def get_image_link(self): + return IMAGE_PATTERN % str(self) + + def get_thumb_link(self): + return THUMB_PATTERN % str(self) + + +def from_str(string): + if '_' in string: + color, value = string.split('_') + return Card(color, value) + else: + return Card(None, None, string) \ No newline at end of file diff --git a/credentials.py b/credentials.py new file mode 100644 index 0000000..885b6ca --- /dev/null +++ b/credentials.py @@ -0,0 +1 @@ +TOKEN = 'TOKEN' \ No newline at end of file diff --git a/deck.py b/deck.py new file mode 100644 index 0000000..9ae5107 --- /dev/null +++ b/deck.py @@ -0,0 +1,39 @@ +from random import shuffle +import card +from card import Card +import logging + + +class Deck(object): + + def __init__(self): + self.cards = list() + self.graveyard = list() + self.logger = logging.getLogger(__name__) + + for color in card.COLORS: + for value in card.VALUES: + self.cards.append(Card(color, value)) + if not value == card.ZERO: + self.cards.append(Card(color, value)) + + for special in card.SPECIALS * 4: + self.cards.append(Card(None, None, special=special)) + + self.logger.debug(self.cards) + self.shuffle() + + def shuffle(self): + self.cards = shuffle(self.cards) + + def draw(self): + try: + return self.cards.pop() + except IndexError: + while len(self.graveyard): + self.cards.append(self.graveyard.pop()) + self.shuffle() + return self.draw() + + def dismiss(self, card): + self.graveyard.append(card) diff --git a/game.py b/game.py new file mode 100644 index 0000000..52e813a --- /dev/null +++ b/game.py @@ -0,0 +1,53 @@ +from deck import Deck +from card import Card +import card as c +from player import Player + + +class Game(object): + """ This class represents a game of mau mau + + :type current_player: Player + """ + current_player = None + reversed = False + draw_counter = 0 + choosing_color = False + + def __init__(self): + self.deck = Deck() + self.last_card = self.deck.draw() + + def reverse(self): + self.reversed = not self.reversed + + def turn(self): + self.current_player = self.current_player.next + + def play_card(self, card): + """ + + :param card: + :type card: Card + :return: + """ + self.deck.dismiss(self.last_card) + self.last_card = card + if card.value is c.SKIP: + self.current_player = self.current_player.next.next + elif card.special is c.DRAW_FOUR: + self.draw_counter += 4 + elif card.value is c.DRAW_TWO: + self.draw_counter += 2 + elif card.value is c.REVERSE: + self.reverse() + + if card.special not in (c.CHOOSE, c.DRAW_FOUR): + self.current_player = self.current_player.next + else: + self.choosing_color = True + + def choose_color(self, color): + self.last_card.color = color + self.current_player = self.current_player.next + self.choosing_color = False diff --git a/game_manager.py b/game_manager.py new file mode 100644 index 0000000..819a68e --- /dev/null +++ b/game_manager.py @@ -0,0 +1,29 @@ +from uuid import uuid4 +from game import Game +from player import Player + +LINK_PATTERN = 'https://telegram.me/%s?start=%s' + + +class GameManager(object): + + def __init__(self): + self.gameid_game = dict() + self.userid_game = dict() + self.chatid_gameid = dict() + self.userid_user = dict() + self.userid_player = dict() + + def generate_invite_link(self, bot_name, chat_id): + game_id = uuid4() + game = Game() + self.gameid_game[game_id] = game + self.chatid_gameid[chat_id] = game_id + + return LINK_PATTERN % (bot_name, game_id) + + def join_game(self, game_id, user): + game = self.gameid_game[game_id] + player = Player(game, user) + self.userid_player[user.id] = player + self.userid_game[user.id] = game diff --git a/player.py b/player.py new file mode 100644 index 0000000..17d7fb2 --- /dev/null +++ b/player.py @@ -0,0 +1,70 @@ +import card as c + + +class Player(object): + + def __init__(self, game, user): + """ + + :param game: + :type game Game + :return: + """ + self.cards = list() + self.game = game + self.user = user + if game.current_player: + self.next = game.current_player + self.prev = game.current_player.prev + game.current_player.prev.next = self + game.current_player.prev = self + else: + self._next = self + self._prev = self + game.current_player = self + + for i in range(6): + self.cards.append(self.game.deck.draw()) + + def __repr__(self): + return repr(self.user) + + def __str__(self): + return str(self.user) + + @property + def next(self): + return self._next if not self.game.reversed else self._prev + + @next.setter + def next(self, player): + if not self.game.reversed: + self._next = player + else: + self._prev = player + + @property + def prev(self): + return self._prev if not self.game.reversed else self._next + + @prev.setter + def prev(self, player): + if not self.game.reversed: + self._prev = player + else: + self._next = player + + def playable_cards(self): + + if self.game.current_player is not self: + return False + + playable = list() + last = self.game.last_card + + for card in self.cards: + if (card.color is last.color or card.value is last.value) and \ + not last.special: + playable.append(card) + + return playable diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..05c157e --- /dev/null +++ b/test/test.py @@ -0,0 +1,41 @@ +import unittest +from game import Game +from player import Player + + +class Test(unittest.TestCase): + + game = None + + def setUp(self): + self.game = Game(0) + + def test_insert(self): + p0 = Player(self.game, "Player 0") + p1 = Player(self.game, "Player 1") + p2 = Player(self.game, "Player 2") + + self.assertEqual(p0, p2.next) + self.assertEqual(p1, p0.next) + self.assertEqual(p2, p1.next) + + self.assertEqual(p0.prev, p2) + self.assertEqual(p1.prev, p0) + self.assertEqual(p2.prev, p1) + + def test_reverse(self): + p0 = Player(self.game, "Player 0") + p1 = Player(self.game, "Player 1") + p2 = Player(self.game, "Player 2") + self.game.reverse() + p3 = Player(self.game, "Player 3") + + self.assertEqual(p0, p3.next) + self.assertEqual(p1, p2.next) + self.assertEqual(p2, p0.next) + self.assertEqual(p3, p1.next) + + self.assertEqual(p0, p2.prev) + self.assertEqual(p1, p3.prev) + self.assertEqual(p2, p1.prev) + self.assertEqual(p3, p0.prev)