Add comments everywhere

This commit is contained in:
Jannes Höke 2016-03-08 02:50:24 +01:00
parent 16cfbb611c
commit 1f7466cba5
6 changed files with 65 additions and 33 deletions

22
bot.py
View file

@ -35,6 +35,7 @@ help_text = "Follow these steps:\n\n" \
def list_subtract(list1, list2): def list_subtract(list1, list2):
""" Helper function to subtract two lists and return the sorted result """
list1 = list1.copy() list1 = list1.copy()
for x in list2: for x in list2:
@ -44,6 +45,7 @@ def list_subtract(list1, list2):
def display_name(game): def display_name(game):
""" Get the current players name including their username, if possible """
user = game.current_player.user user = game.current_player.user
user_name = user.first_name user_name = user.first_name
if user.username: if user.username:
@ -52,6 +54,7 @@ def display_name(game):
def display_color(color): def display_color(color):
""" Convert a color code to actual color name """
if color == "r": if color == "r":
return "Red" return "Red"
if color == "b": if color == "b":
@ -63,10 +66,12 @@ def display_color(color):
def error(bot, update, error): def error(bot, update, error):
""" Simple error handler """
logger.exception(error) logger.exception(error)
def new_game(bot, update): def new_game(bot, update):
""" Handler for the /new command """
chat_id = update.message.chat_id chat_id = update.message.chat_id
link = gm.generate_invite_link(u.bot.getMe().username, chat_id) link = gm.generate_invite_link(u.bot.getMe().username, chat_id)
bot.sendMessage(chat_id, bot.sendMessage(chat_id,
@ -74,10 +79,12 @@ def new_game(bot, update):
def leave_game(bot, update): def leave_game(bot, update):
""" Handler for the /leave command """
chat_id = update.message.chat_id chat_id = update.message.chat_id
game_id = gm.chatid_gameid[chat_id] game_id = gm.chatid_gameid[chat_id]
game = gm.gameid_game[game_id] game = gm.gameid_game[game_id]
user = update.message.from_user user = update.message.from_user
if game.current_player.user.id == user.id: if game.current_player.user.id == user.id:
bot.sendMessage(chat_id, bot.sendMessage(chat_id,
text="You can't leave the game if it's your turn") text="You can't leave the game if it's your turn")
@ -87,8 +94,9 @@ def leave_game(bot, update):
def start(bot, update, args): def start(bot, update, args):
""" Handler for the /start command """
if args: if args:
game_id = args[0] game_id = args[0] # Contains the game id
gm.join_game(game_id, update.message.from_user) gm.join_game(game_id, update.message.from_user)
game = gm.gameid_game[game_id] game = gm.gameid_game[game_id]
groupchat = gm.chatid_gameid[game_id] groupchat = gm.chatid_gameid[game_id]
@ -99,6 +107,7 @@ def start(bot, update, args):
text=update.message.from_user.first_name + text=update.message.from_user.first_name +
" joined the game!") " joined the game!")
# Check if user is the first player to join and if, show the first card
if game.current_player is game.current_player.next: if game.current_player is game.current_player.next:
game.play_card(game.last_card) game.play_card(game.last_card)
bot.sendPhoto(groupchat, bot.sendPhoto(groupchat,
@ -111,6 +120,7 @@ def start(bot, update, args):
def inline(bot, update): def inline(bot, update):
""" Handler for all inline stuff """
if update.inline_query: if update.inline_query:
reply_to_query(bot, update) reply_to_query(bot, update)
else: else:
@ -118,12 +128,14 @@ def inline(bot, update):
def help(bot, update): def help(bot, update):
""" Handler for the /help command """
bot.sendMessage(update.message.chat_id, bot.sendMessage(update.message.chat_id,
text=help_text, text=help_text,
parse_mode=ParseMode.HTML) parse_mode=ParseMode.HTML)
def reply_to_query(bot, update): def reply_to_query(bot, update):
""" Builds the result list for inline queries and answers to the client """
user_id = update.inline_query.from_user.id user_id = update.inline_query.from_user.id
player = gm.userid_player[user_id] player = gm.userid_player[user_id]
game = gm.userid_game[user_id] game = gm.userid_game[user_id]
@ -241,6 +253,7 @@ def add_gameinfo(game, results):
current_player = game.current_player current_player = game.current_player
itplayer = current_player.next itplayer = current_player.next
add_player(current_player, players) add_player(current_player, players)
while itplayer is not current_player: while itplayer is not current_player:
add_player(itplayer, players) add_player(itplayer, players)
itplayer = itplayer.next itplayer = itplayer.next
@ -264,12 +277,13 @@ def add_player(itplayer, players):
def process_result(bot, update): def process_result(bot, update):
""" Check the players actions and act accordingly """
user = update.chosen_inline_result.from_user user = update.chosen_inline_result.from_user
game = gm.userid_game[user.id] game = gm.userid_game[user.id]
player = gm.userid_player[user.id] player = gm.userid_player[user.id]
result_id = update.chosen_inline_result.result_id result_id = update.chosen_inline_result.result_id
chat_id = gm.chatid_gameid[game] chat_id = gm.chatid_gameid[game]
logger.info("Selected result: " + result_id) logger.debug("Selected result: " + result_id)
if result_id in ('hand', 'gameinfo'): if result_id in ('hand', 'gameinfo'):
return return
@ -284,8 +298,7 @@ def process_result(bot, update):
else: else:
do_play_card(bot, chat_id, game, player, result_id, user) do_play_card(bot, chat_id, game, player, result_id, user)
user_name = display_name(game) bot.sendMessage(chat_id, text="Next player: " + display_name(game))
bot.sendMessage(chat_id, text="Next player: " + user_name)
def do_play_card(bot, chat_id, game, player, result_id, user): def do_play_card(bot, chat_id, game, player, result_id, user):
@ -330,6 +343,7 @@ def do_call_bluff(bot, chat_id, game, player):
game.turn() game.turn()
# Add all handlers to the dispatcher and run the bot
dp.addTelegramInlineHandler(inline) dp.addTelegramInlineHandler(inline)
dp.addTelegramCommandHandler('start', start) dp.addTelegramCommandHandler('start', start)
dp.addTelegramCommandHandler('new', new_game) dp.addTelegramCommandHandler('new', new_game)

View file

@ -38,6 +38,9 @@ THUMB_PATTERN = 'https://raw.githubusercontent.com/jh0ker/mau_mau_bot/' \
class Card(object): class Card(object):
"""
This class represents a card.
"""
def __init__(self, color, value, special=None): def __init__(self, color, value, special=None):
self.color = color self.color = color
@ -54,19 +57,24 @@ class Card(object):
return ' '.join([s.capitalize() for s in str(self).split('_')]) return ' '.join([s.capitalize() for s in str(self).split('_')])
def __eq__(self, other): def __eq__(self, other):
""" Needed for sorting the cards """
return str(self) == str(other) return str(self) == str(other)
def __lt__(self, other): def __lt__(self, other):
""" Needed for sorting the cards """
return str(self) < str(other) return str(self) < str(other)
def get_image_link(self): def get_image_link(self):
""" Returns a link to the image of this card """
return IMAGE_PATTERN % str(self) return IMAGE_PATTERN % str(self)
def get_thumb_link(self): def get_thumb_link(self):
""" Returns a link to the thumbnail-image of this card """
return THUMB_PATTERN % str(self) return THUMB_PATTERN % str(self)
def from_str(string): def from_str(string):
""" Decode a Card object from a string """
if string not in SPECIALS: if string not in SPECIALS:
color, value = string.split('_') color, value = string.split('_')
return Card(color, value) return Card(color, value)

15
deck.py
View file

@ -1,33 +1,37 @@
from random import shuffle from random import shuffle
import card import card as c
from card import Card from card import Card
import logging import logging
class Deck(object): class Deck(object):
""" This class represents a deck of cards """
def __init__(self): def __init__(self):
self.cards = list() self.cards = list()
self.graveyard = list() self.graveyard = list()
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
for color in card.COLORS: # Fill deck
for value in card.VALUES: for color in c.COLORS:
for value in c.VALUES:
self.cards.append(Card(color, value)) self.cards.append(Card(color, value))
if not value == card.ZERO: if not value == c.ZERO:
self.cards.append(Card(color, value)) self.cards.append(Card(color, value))
for special in card.SPECIALS * 4: for special in c.SPECIALS * 4:
self.cards.append(Card(None, None, special=special)) self.cards.append(Card(None, None, special=special))
self.logger.debug(self.cards) self.logger.debug(self.cards)
self.shuffle() self.shuffle()
def shuffle(self): def shuffle(self):
""" Shuffle the deck """
self.logger.debug("Shuffling Deck") self.logger.debug("Shuffling Deck")
shuffle(self.cards) shuffle(self.cards)
def draw(self): def draw(self):
""" Draw a card from this deck """
try: try:
card = self.cards.pop() card = self.cards.pop()
self.logger.debug("Drawing card " + str(card)) self.logger.debug("Drawing card " + str(card))
@ -39,4 +43,5 @@ class Deck(object):
return self.draw() return self.draw()
def dismiss(self, card): def dismiss(self, card):
""" All played cards should be returned into the deck """
self.graveyard.append(card) self.graveyard.append(card)

19
game.py
View file

@ -1,16 +1,11 @@
import logging import logging
from deck import Deck from deck import Deck
from card import Card
import card as c import card as c
from player import Player
class Game(object): class Game(object):
""" This class represents a game of mau mau """ This class represents a game of UNO """
:type current_player: Player
"""
current_player = None current_player = None
reversed = False reversed = False
draw_counter = 0 draw_counter = 0
@ -22,20 +17,17 @@ class Game(object):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
def reverse(self): def reverse(self):
""" Reverse the direction of play """
self.reversed = not self.reversed self.reversed = not self.reversed
def turn(self): def turn(self):
""" Mark the turn as over and change the current player """
self.logger.debug("Next Player") self.logger.debug("Next Player")
self.current_player = self.current_player.next self.current_player = self.current_player.next
self.current_player.drew = False self.current_player.drew = False
def play_card(self, card): def play_card(self, card):
""" """ Play a card and trigger its effects """
:param card:
:type card: Card
:return:
"""
self.deck.dismiss(self.last_card) self.deck.dismiss(self.last_card)
self.last_card = card self.last_card = card
@ -49,11 +41,13 @@ class Game(object):
self.draw_counter += 2 self.draw_counter += 2
self.logger.debug("Draw counter increased by 2") self.logger.debug("Draw counter increased by 2")
elif card.value == c.REVERSE: elif card.value == c.REVERSE:
# Special rule for two players
if self.current_player is self.current_player.next.next: if self.current_player is self.current_player.next.next:
self.turn() self.turn()
else: else:
self.reverse() self.reverse()
# Don't turn if the current player has to choose a color
if card.special not in (c.CHOOSE, c.DRAW_FOUR): if card.special not in (c.CHOOSE, c.DRAW_FOUR):
self.turn() self.turn()
else: else:
@ -61,6 +55,7 @@ class Game(object):
self.choosing_color = True self.choosing_color = True
def choose_color(self, color): def choose_color(self, color):
""" Carries out the color choosing and turns the game """
self.last_card.color = color self.last_card.color = color
self.turn() self.turn()
self.choosing_color = False self.choosing_color = False

View file

@ -8,15 +8,20 @@ LINK_PATTERN = 'https://telegram.me/%s?start=%s'
class GameManager(object): class GameManager(object):
""" Manages all running games by using a confusing amount of dicts """
def __init__(self): def __init__(self):
self.gameid_game = dict() self.gameid_game = dict()
self.userid_game = dict() self.userid_game = dict()
self.chatid_gameid = dict() self.chatid_gameid = dict() # Goes both ways
self.userid_player = dict() self.userid_player = dict()
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
def generate_invite_link(self, bot_name, chat_id): def generate_invite_link(self, bot_name, chat_id):
"""
Generate a game join link with a unique ID and connect the game to the
group chat
"""
game_id = str(uuid4()) game_id = str(uuid4())
game = Game() game = Game()
@ -29,6 +34,7 @@ class GameManager(object):
return LINK_PATTERN % (bot_name, game_id) return LINK_PATTERN % (bot_name, game_id)
def join_game(self, game_id, user): def join_game(self, game_id, user):
""" Create a player from the Telegram user and add it to the game """
self.logger.info("Joining game with id " + game_id) self.logger.info("Joining game with id " + game_id)
game = self.gameid_game[game_id] game = self.gameid_game[game_id]
player = Player(game, user) player = Player(game, user)
@ -36,6 +42,7 @@ class GameManager(object):
self.userid_game[user.id] = game self.userid_game[user.id] = game
def leave_game(self, user): def leave_game(self, user):
""" Remove a player from its current game """
player = self.userid_player[user.id] player = self.userid_player[user.id]
player.leave() player.leave()

View file

@ -4,19 +4,20 @@ import card as c
class Player(object): class Player(object):
"""
This class represents a player.
It is basically a doubly-linked ring list with the option to reverse the
direction. On initialization, it will connect itself to a game and its
other players by placing itself behind the current player.
"""
def __init__(self, game, user): def __init__(self, game, user):
"""
:param game:
:type game Game
:return:
"""
self.cards = list() self.cards = list()
self.game = game self.game = game
self.user = user self.user = user
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
# Check if this player is the first player in this game.
if game.current_player: if game.current_player:
self.next = game.current_player self.next = game.current_player
self.prev = game.current_player.prev self.prev = game.current_player.prev
@ -34,6 +35,7 @@ class Player(object):
self.drew = False self.drew = False
def leave(self): def leave(self):
""" Leave the current game """
self.next.prev = self.prev self.next.prev = self.prev
self.prev.next = self.next self.prev.next = self.next
self.next = None self.next = None
@ -68,10 +70,7 @@ class Player(object):
self._next = player self._next = player
def playable_cards(self): def playable_cards(self):
""" Returns a list of the cards this player can play right now """
if self.game.current_player.user.id is not self.user.id:
self.logger.debug("Player is not current player")
return False
playable = list() playable = list()
last = self.game.last_card last = self.game.last_card
@ -83,14 +82,18 @@ class Player(object):
self.logger.debug("Matching!") self.logger.debug("Matching!")
playable.append(card) playable.append(card)
# You may only play a +4 if it's the only card you can play
self.bluffing = bool(len(playable) - 1) self.bluffing = bool(len(playable) - 1)
# You may not play a +4 as your last card
if len(self.cards) == 1 and self.cards[0].special == c.DRAW_FOUR: if len(self.cards) == 1 and self.cards[0].special == c.DRAW_FOUR:
return list() return list()
return playable return playable
def card_playable(self, card, playable): def card_playable(self, card, playable):
""" Check a single card if it can be played """
is_playable = True is_playable = True
last = self.game.last_card last = self.game.last_card
self.logger.debug("Checking card " + str(card)) self.logger.debug("Checking card " + str(card))