Compare commits

...

16 commits

Author SHA1 Message Date
Jarv
538c5b0ab6 Improved most messages in spanish (#39) 2017-09-19 17:31:53 +02:00
Karho
14bd90ea92 Update bot.py 2017-03-04 09:30:09 +08:00
Karho
9dde74381e Update bot.py
Removing Typos
2017-03-04 09:17:52 +08:00
Karho
683d203984 Update bot.py
Adding mode function
2017-03-04 09:17:20 +08:00
Karho
d8dfb438a3 Update game_manager.py
Changing the function end_game order to (self, user, chat)
2017-03-03 19:40:40 +08:00
Karho
52296e6df8 Update player.py 2017-02-24 14:16:52 +08:00
Karho
4c386218b0 Update player.py
Adding a new mode part in the function _card_playable.
2017-02-24 14:15:08 +08:00
Karho
393da434e8 Update game.py
Adding a new option "mode"
2017-02-24 13:39:14 +08:00
Karho
fae55f758d Merge pull request #30 from jh0ker/master
Update README.md
2017-02-24 13:32:34 +08:00
Karho
92c07d12ad Update bot.py
Changing the function process_result on result_id in c.COLORS such that there is a try-except statement to prevent a deadlock in choosing colors.
2017-02-18 00:06:41 +08:00
Karho
bffd7fb1c3 Update errors.py
Adding a new PlayerLeftError
2017-02-17 22:55:11 +08:00
Karho
69dc39bb56 Update player.py
Correcting the code once again as the last commit.
2017-02-17 22:47:16 +08:00
Karho
75e3076285 Update player.py
Reverting back to the original rule.
2017-02-17 22:05:24 +08:00
Karho
f2e7a14318 Update bot.py
Adding the function notify_me more texts in else part.
Adding the function new_game to make players create a new game and join the game with the same command.
Changing the function reset_waiting_time to 60 seconds.
Changing the function skip_player in the else part to include the stats part for skipped_player.
2017-02-17 19:35:56 +08:00
Karho
0114fe774d Update game.py
Using set()) instead of list in  self.joined_before
2017-02-17 11:45:53 +08:00
Karho
615bb35359 Combining with "patch" branch (#29)
* Update unobot.po

* Update test_player.py

* Update test_player.py

* Update test_player.py

* Update test_player.py

* Update player.py

* Update player.py

* Update internationalization.py

* Update internationalization.py

* Update test_player.py

* Update test_player.py

* Update test_player.py

* Update player.py

* Update player.py

* Update player.py

* Update test_player.py

* Update test_player.py

* revert play 4 then 4 rule

* Update test_player.py

* Update player.py

* Update player.py

* Update player.py

* Update game.py

* Update game_manager.py

* Update game_manager.py

* Update game_manager.py

* Update player.py

* Update test_player.py

* Update player.py

* Update bot.py

* Update credentials.py

* Update credentials.py

* Update bot.py

* Update game.py

* Update game_manager.py

* Update game_manager.py

* Update player.py
2017-02-16 18:43:44 +08:00
7 changed files with 399 additions and 96 deletions

110
bot.py
View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Telegram bot to play UNO in group chats # Telegram bot to play UNO in group chats
# Copyright (c) 2016 Jannes Höke <uno@jhoeke.de> # Copyright (c) 2016 - 2017 Jannes Höke <uno@jhoeke.de> and Karho Yau
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
@ -23,9 +23,9 @@ from datetime import datetime
from random import randint from random import randint
from telegram import ParseMode, Message, Chat, InlineKeyboardMarkup, \ from telegram import ParseMode, Message, Chat, InlineKeyboardMarkup, \
InlineKeyboardButton InlineKeyboardButton, ReplyKeyboardMarkup, Emoji
from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \ from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \
CommandHandler, MessageHandler, Filters, CallbackQueryHandler CommandHandler, MessageHandler, Filters, CallbackQueryHandler, RegexHandler
from telegram.ext.dispatcher import run_async from telegram.ext.dispatcher import run_async
from start_bot import start_bot from start_bot import start_bot
@ -36,7 +36,7 @@ from user_setting import UserSetting
from utils import display_name from utils import display_name
import card as c import card as c
from errors import (NoGameInChatError, LobbyClosedError, AlreadyJoinedError, from errors import (NoGameInChatError, LobbyClosedError, AlreadyJoinedError,
NotEnoughPlayersError, DeckEmptyError) NotEnoughPlayersError, DeckEmptyError, PlayerLeftError)
from utils import send_async, answer_async, error, TIMEOUT from utils import send_async, answer_async, error, TIMEOUT
from shared_vars import botan, gm, updater, dispatcher from shared_vars import botan, gm, updater, dispatcher
from internationalization import _, __, user_locale, game_locales from internationalization import _, __, user_locale, game_locales
@ -55,7 +55,7 @@ logger = logging.getLogger(__name__)
@user_locale @user_locale
def notify_me(bot, update): def notify_me(bot, update):
"""Handler for /notify_me command, pm people for next game""" """Handler for /notify_me command, pm people for next game"""
chat_id = update.message.chat_id chat_id = update.message.chat.id
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
send_async(bot, send_async(bot,
chat_id, chat_id,
@ -64,6 +64,11 @@ def notify_me(bot, update):
else: else:
try: try:
gm.remind_dict[chat_id].add(update.message.from_user.id) gm.remind_dict[chat_id].add(update.message.from_user.id)
send_async(bot,
chat_id,
text=_("You will be notified "
"when a new game is started in {title}.").format(
title=update.message.chat.title))
except KeyError: except KeyError:
gm.remind_dict[chat_id] = {update.message.from_user.id} gm.remind_dict[chat_id] = {update.message.from_user.id}
@ -71,7 +76,7 @@ def notify_me(bot, update):
@user_locale @user_locale
def new_game(bot, update): def new_game(bot, update):
"""Handler for the /new command""" """Handler for the /new command"""
chat_id = update.message.chat_id chat_id = update.message.chat.id
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
help(bot, update) help(bot, update)
@ -79,19 +84,20 @@ def new_game(bot, update):
else: else:
if update.message.chat_id in gm.remind_dict: if update.message.chat_id in gm.remind_dict:
for user in gm.remind_dict[update.message.chat_id]: for user in gm.remind_dict[chat_id]:
send_async(bot, send_async(bot,
user, user,
text=_("A new game has been started in {title}").format( text=_("A new game has been started in {title}.").format(
title=update.message.chat.title)) title=update.message.chat.title))
del gm.remind_dict[update.message.chat_id] del gm.remind_dict[chat_id]
game = gm.new_game(update.message.chat) game = gm.new_game(update.message.chat)
game.owner = update.message.from_user game.owner = update.message.from_user
send_async(bot, chat_id, send_async(bot, chat_id,
text=_("Created a new game! Join the game with /join " text=_("Created a new game! Wait for your friends "
"and start the game with /start")) "and start the game with /start"))
gm.join_game(update.message.from_user, update.message.chat)
if botan: if botan:
botan.track(update.message, 'New games') botan.track(update.message, 'New games')
@ -162,7 +168,7 @@ def leave_game(bot, update):
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
except NotEnoughPlayersError: except NotEnoughPlayersError:
gm.end_game(chat, user) gm.end_game(user, chat)
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate)) send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
else: else:
@ -226,7 +232,7 @@ def status_update(bot, update):
except NoGameInChatError: except NoGameInChatError:
pass pass
except NotEnoughPlayersError: except NotEnoughPlayersError:
gm.end_game(chat, user) gm.end_game(user, chat)
send_async(bot, chat.id, text=__("Game ended!", send_async(bot, chat.id, text=__("Game ended!",
multi=game.translate)) multi=game.translate))
else: else:
@ -418,7 +424,48 @@ def disable_translations(bot, update):
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
@game_locales
@user_locale
def mode(bot, update):
"""Handler for the /mode command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if not games:
send_async(bot, chat.id,
text=__("There is no running game in this chat."))
return
game = games[-1]
if chat.type == 'private':
send_async(bot, chat.id,
text=_("Please change the group mode in the public game group with "
"the bot."))
return
if game.owner.id == user.id and not games.started:
kb = [["🎻 " + _("Original"), "🚴 " + _("Progressive UNO")]]
markup = ReplyKeyboardMarkup(kb, resize_keyboard=True, one_time_keyboard=True)
choice = send_async(bot, chat.id, text=_("Choose the game mode:"), reply_markup = markup)
if choice[0] == "🎻":
game.mode = 0
send_async(bot, chat.id, text=_("Original rules will be used."))
else if choice[0] == "🚴":
game.mode = 1
send_async(bot, chat.id, text=_("Progressive UNO rules will be used."))
return
else:
send_async(bot, chat.id,
text=_("Only the game creator ({name}) can do that when the game does not start")
.format(name=game.owner.first_name),
reply_to_message_id=update.message.message_id)
return
@game_locales @game_locales
@user_locale @user_locale
def skip_player(bot, update): def skip_player(bot, update):
@ -475,7 +522,7 @@ def skip_player(bot, update):
try: try:
gm.leave_game(skipped_player.user, chat) gm.leave_game(skipped_player.user, chat)
send_async(bot, chat.id, send_async(bot, chat.id,
text=__("{name1} was skipped four times in a row " text=__("{name1} was skipped three times in a row "
"and has been removed from the game.\n" "and has been removed from the game.\n"
"Next player: {name2}", multi=game.translate) "Next player: {name2}", multi=game.translate)
.format(name1=display_name(skipped_player.user), .format(name1=display_name(skipped_player.user),
@ -483,12 +530,17 @@ def skip_player(bot, update):
except NotEnoughPlayersError: except NotEnoughPlayersError:
send_async(bot, chat.id, send_async(bot, chat.id,
text=__("{name} was skipped four times in a row " text=__("{name} was skipped three times in a row "
"and has been removed from the game.\n" "and has been removed from the game.\n"
"The game ended.", multi=game.translate) "The game ended.", multi=game.translate)
.format(name=display_name(skipped_player.user))) .format(name=display_name(skipped_player.user)))
gm.end_game(chat.id, skipped_player.user) us2 = UserSetting.get(id=skipped_player.user.id)
if us2 and us2.stats:
us2.games_played += 1
gm.end_game(skipped_player.user, chat)
@game_locales @game_locales
@ -594,7 +646,13 @@ def process_result(bot, update):
elif result_id == 'pass': elif result_id == 'pass':
game.turn() game.turn()
elif result_id in c.COLORS: elif result_id in c.COLORS:
game.choose_color(result_id) try:
game.choose_color(result_id)
except PlayerLeftError:
send_async(bot, chat.id,
text=__("There are errors in choosing color. "
"Color is now unchanged.", multi=game.translate))
game.turn()
else: else:
reset_waiting_time(bot, player) reset_waiting_time(bot, player)
do_play_card(bot, player, result_id) do_play_card(bot, player, result_id)
@ -609,10 +667,10 @@ def reset_waiting_time(bot, player):
"""Resets waiting time for a player and sends a notice to the group""" """Resets waiting time for a player and sends a notice to the group"""
chat = player.game.chat chat = player.game.chat
if player.waiting_time < 90: if player.waiting_time < 60:
player.waiting_time = 90 player.waiting_time = 60
send_async(bot, chat.id, send_async(bot, chat.id,
text=__("Waiting time for {name} has been reset to 90 " text=__("Waiting time for {name} has been reset to 60 "
"seconds", multi=player.game.translate) "seconds", multi=player.game.translate)
.format(name=display_name(player.user))) .format(name=display_name(player.user)))
@ -661,7 +719,7 @@ def do_play_card(bot, player, result_id):
if us2 and us2.stats: if us2 and us2.stats:
us2.games_played += 1 us2.games_played += 1
gm.end_game(chat, user) gm.end_game(user, chat)
if botan: if botan:
botan.track(Message(randint(1, 1000000000), user, datetime.now(), botan.track(Message(randint(1, 1000000000), user, datetime.now(),
@ -694,9 +752,10 @@ def do_call_bluff(bot, player):
if player.prev.bluffing: if player.prev.bluffing:
send_async(bot, chat.id, send_async(bot, chat.id,
text=__("Bluff called! Giving 4 cards to {name}", text=__("Bluff called! Giving {numbers} cards to {name}",
multi=game.translate) multi=game.translate)
.format(name=player.prev.user.first_name)) .format(name=player.prev.user.first_name,
numbers=game.draw_counter))
try: try:
player.prev.draw() player.prev.draw()
@ -708,10 +767,11 @@ def do_call_bluff(bot, player):
else: else:
game.draw_counter += 2 game.draw_counter += 2
send_async(bot, chat.id, send_async(bot, chat.id,
text=__("{name1} didn't bluff! Giving 6 cards to {name2}", text=__("{name1} didn't bluff! Giving {numbers} cards to {name2}",
multi=game.translate) multi=game.translate)
.format(name1=player.prev.user.first_name, .format(name1=player.prev.user.first_name,
name2=player.user.first_name)) name2=player.user.first_name,
numbers=game.draw_counter))
try: try:
player.draw() player.draw()
except DeckEmptyError: except DeckEmptyError:
@ -738,6 +798,8 @@ dispatcher.add_handler(CommandHandler('disable_translations',
disable_translations)) disable_translations))
dispatcher.add_handler(CommandHandler('skip', skip_player)) dispatcher.add_handler(CommandHandler('skip', skip_player))
dispatcher.add_handler(CommandHandler('notify_me', notify_me)) dispatcher.add_handler(CommandHandler('notify_me', notify_me))
dispatcher.add_handler(CommandHandler('mode', mode))
dispatcher.add_handler(RegexHandler('^(Original|Progressive UNO)$', mode, pass_groups=True))
simple_commands.register() simple_commands.register()
settings.register() settings.register()
dispatcher.add_handler(MessageHandler([Filters.status_update], status_update)) dispatcher.add_handler(MessageHandler([Filters.status_update], status_update))

View file

@ -36,3 +36,7 @@ class NotEnoughPlayersError(Exception):
class DeckEmptyError(Exception): class DeckEmptyError(Exception):
pass pass
class PlayerLeftError(Exception):
pass

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Telegram bot to play UNO in group chats # Telegram bot to play UNO in group chats
# Copyright (c) 2016 Jannes Höke <uno@jhoeke.de> # Copyright (c) 2016 - 2017 Jannes Höke <uno@jhoeke.de> and Karho Yau
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
@ -36,10 +36,12 @@ class Game(object):
open = True open = True
translate = False translate = False
players_won = 0 players_won = 0
mode = 0
def __init__(self, chat): def __init__(self, chat):
self.chat = chat self.chat = chat
self.last_card = None self.last_card = None
self.joined_before = set()
while not self.last_card or self.last_card.special: while not self.last_card or self.last_card.special:
self.deck = Deck() self.deck = Deck()

View file

@ -19,6 +19,7 @@
import logging import logging
import random
from game import Game from game import Game
from player import Player from player import Player
@ -77,7 +78,8 @@ class GameManager(object):
# Don not re-add a player and remove the player from previous games in # Don not re-add a player and remove the player from previous games in
# this chat, if he is in one of them # this chat, if he is in one of them
for player in players: for player in players:
if player in game.players: # Try to pervent someone win or leave then join again.
if player in game.players or user.id in game.joined_before:
raise AlreadyJoinedError() raise AlreadyJoinedError()
else: else:
try: try:
@ -93,8 +95,14 @@ class GameManager(object):
players = self.userid_players[user.id] players = self.userid_players[user.id]
player = Player(game, user) player = Player(game, user)
players.append(player) # Randomize player position.
game.joined_before.append(user.id)
if len(players) > 2:
players.insert(random.randrange(len(players)), player)
else:
players.append(player)
self.userid_current[user.id] = player self.userid_current[user.id] = player
def leave_game(self, user, chat): def leave_game(self, user, chat):
@ -135,7 +143,7 @@ class GameManager(object):
del self.userid_current[user.id] del self.userid_current[user.id]
del self.userid_players[user.id] del self.userid_players[user.id]
def end_game(self, chat, user): def end_game(self, user, chat):
""" """
End a game End a game
""" """

View file

@ -107,29 +107,28 @@ msgstr ""
msgid "" msgid ""
"Created a new game! Join the game with /join and start the game with /start" "Created a new game! Join the game with /join and start the game with /start"
msgstr "" msgstr ""
"¡Partida nueva creada! Únete a la partida con /join y comienza la partida " "¡Nueva partida creada! Únete con /join. Cuando estéis todos listos, haz /start para empezar."
"con /start "
#: bot.py:152 #: bot.py:152
msgid "The lobby is closed" msgid "The lobby is closed"
msgstr "La sala esta cerrada" msgstr "La sala está cerrada."
#: bot.py:156 #: bot.py:156
msgid "No game is running at the moment. Create a new game with /new" msgid "No game is running at the moment. Create a new game with /new"
msgstr "" msgstr ""
"No hay ninguna partida activa por el momento. Crea una partida con /new" "No hay ninguna partida en curso. Crea una partida con /new."
#: bot.py:162 #: bot.py:162
msgid "You already joined the game. Start the game with /start" msgid "You already joined the game. Start the game with /start"
msgstr "Ya te uniste a la partida. Comienza la partida con /start" msgstr "Ya te uniste a esta partida. Comienza la partida con /start."
#: bot.py:167 #: bot.py:167
msgid "Joined the game" msgid "Joined the game"
msgstr "Te uniste a la partida" msgstr "Te uniste a la partida."
#: bot.py:179 bot.py:191 #: bot.py:179 bot.py:191
msgid "You are not playing in a game in this group." msgid "You are not playing in a game in this group."
msgstr "No estas jugando una partida en este grupo." msgstr "No estas jugando a una partida en este grupo."
#: bot.py:197 bot.py:258 bot.py:595 #: bot.py:197 bot.py:258 bot.py:595
msgid "Game ended!" msgid "Game ended!"
@ -145,7 +144,7 @@ msgstr "Partida no encontrada."
#: bot.py:223 #: bot.py:223
msgid "Back to last group" msgid "Back to last group"
msgstr "De vuelta al último grupo" msgstr "De vuelta al último grupo."
#: bot.py:227 #: bot.py:227
msgid "Please switch to the group you selected!" msgid "Please switch to the group you selected!"
@ -163,20 +162,20 @@ msgstr ""
#: bot.py:260 #: bot.py:260
#, python-format #, python-format
msgid "Removing {name} from the game" msgid "Removing {name} from the game"
msgstr "Removiendo a {name} de la partida" msgstr "Eliminando a {name} de la partida."
#: bot.py:273 #: bot.py:273
msgid "There is no game running in this chat. Create a new one with /new" msgid "There is no game running in this chat. Create a new one with /new"
msgstr "No hay ninguna partida en curso en este chat. Crea una nueva con /new" msgstr "No hay ninguna partida en curso en este chat. Crea una nueva con /new."
#: bot.py:278 #: bot.py:278
msgid "The game has already started" msgid "The game has already started"
msgstr "La partida ya ha comenzado" msgstr "La partida ya ha comenzado."
#: bot.py:281 #: bot.py:281
msgid "At least two players must /join the game before you can start it" msgid "At least two players must /join the game before you can start it"
msgstr "" msgstr ""
"Antes de iniciar la partida, al menos dos jugadores deben darle al /join" "Antes de iniciar la partida, al menos dos jugadores deben unirse usando /join."
#: bot.py:297 #: bot.py:297
#, python-format #, python-format
@ -187,7 +186,7 @@ msgid ""
msgstr "" msgstr ""
"Primer Jugador: {name}\n" "Primer Jugador: {name}\n"
"Usa /close para evitar que la gente se una a la partida.\n" "Usa /close para evitar que la gente se una a la partida.\n"
"Habilita multi-traducciones con /enable_translations" "Habilita multi-traducciones con /enable_translations."
#: bot.py:321 #: bot.py:321
msgid "Please select the group you want to play in." msgid "Please select the group you want to play in."
@ -195,11 +194,11 @@ msgstr "Elige el grupo en el que deseas jugar."
#: bot.py:335 bot.py:361 #: bot.py:335 bot.py:361
msgid "There is no running game in this chat." msgid "There is no running game in this chat."
msgstr "No hay ninguna partida ejecutándose en este chat." msgstr "No hay ninguna partida en curso en este grupo."
#: bot.py:342 #: bot.py:342
msgid "Closed the lobby. No more players can join this game." msgid "Closed the lobby. No more players can join this game."
msgstr "Sala cerrada. No se pueden unir más jugadores a la partida." msgstr "Sala cerrada. No podrán unirse más jugadores a la partida."
#: bot.py:348 bot.py:373 #: bot.py:348 bot.py:373
#, python-format #, python-format
@ -210,7 +209,7 @@ msgstr "Solo el creador de la partida ({name}) puede hacer eso."
#, python-format #, python-format
msgid "Enabled multi-translations. Disable with /disable_translations" msgid "Enabled multi-translations. Disable with /disable_translations"
msgstr "" msgstr ""
"Multi-traducciones habilitadas. Deshabilítalas con /disable_translations" "Multi-traducciones habilitadas. Deshabilítalas con /disable_translations."
#: bot.py:377 #: bot.py:377
#, python-format #, python-format
@ -218,11 +217,11 @@ msgid ""
"Disabled multi-translations. Enable them again with /enable_translations" "Disabled multi-translations. Enable them again with /enable_translations"
msgstr "" msgstr ""
"Multi-traducciones deshabilitadas. Habilítalas de nuevo con /" "Multi-traducciones deshabilitadas. Habilítalas de nuevo con /"
"enable_translations" "enable_translations."
#: bot.py:368 #: bot.py:368
msgid "Opened the lobby. New players may /join the game." msgid "Opened the lobby. New players may /join the game."
msgstr "Sala abierta. Nuevos jugadores darle al /join." msgstr "Sala abierta. Ahora pueden unirse nuevos jugadores usando /join."
#: bot.py:386 #: bot.py:386
msgid "You are not playing in a game in this chat." msgid "You are not playing in a game in this chat."
@ -232,8 +231,8 @@ msgstr "No estás jugando una partida en este chat."
#, python-format #, python-format
msgid "Please wait {time} second" msgid "Please wait {time} second"
msgid_plural "Please wait {time} seconds" msgid_plural "Please wait {time} seconds"
msgstr[0] "Por favor, espere {time} segundo" msgstr[0] "Por favor, espere un segundo."
msgstr[1] "Por favor, espere {time} segundos" msgstr[1] "Por favor, espere {time} segundos."
#: bot.py:413 #: bot.py:413
#, python-format #, python-format
@ -256,7 +255,7 @@ msgid ""
"{name1} was skipped four times in a row and has been removed from the game.\n" "{name1} was skipped four times in a row and has been removed from the game.\n"
"Next player: {name2}" "Next player: {name2}"
msgstr "" msgstr ""
"{name1} fue saltado cuatro veces consecutivas y ha sido removido de la " "{name1} fue saltado cuatro veces consecutivas y ha sido eliminado de la "
"partida.\n" "partida.\n"
"Siguiente Jugador {name2}" "Siguiente Jugador {name2}"
@ -266,7 +265,7 @@ msgid ""
"{name} was skipped four times in a row and has been removed from the game.\n" "{name} was skipped four times in a row and has been removed from the game.\n"
"The game ended." "The game ended."
msgstr "" msgstr ""
"{name} fue saltado cuatro veces consecutivas y ha sido removido de la " "{name} fue saltado cuatro veces consecutivas y ha sido eliminado de la "
"partida.\n" "partida.\n"
"Partida finalizada." "Partida finalizada."
@ -282,11 +281,11 @@ msgstr "Partida actual: {group}"
#: bot.py:545 #: bot.py:545
#, python-format #, python-format
msgid "Cheat attempt by {name}" msgid "Cheat attempt by {name}"
msgstr "Intento de trampa por {name}" msgstr "{name} intentó hacer trampa."
#: bot.py:562 #: bot.py:562
msgid "Next player: {name}" msgid "Next player: {name}"
msgstr "Siguiente Jugador: {name}" msgstr "Siguiente Jugador: {name}."
#: bot.py:572 #: bot.py:572
#, python-format #, python-format
@ -309,12 +308,12 @@ msgstr "No hay más cartas en la baraja."
#: bot.py:627 #: bot.py:627
#, python-format #, python-format
msgid "Bluff called! Giving 4 cards to {name}" msgid "Bluff called! Giving 4 cards to {name}"
msgstr "¡Engaño descubierto! {name} recibe 4 cartas" msgstr "¡Era un farol! {name} recibe 4 cartas."
#: bot.py:639 #: bot.py:639
#, python-format #, python-format
msgid "{name1} didn't bluff! Giving 6 cards to {name2}" msgid "{name1} didn't bluff! Giving 6 cards to {name2}"
msgstr "¡{name1} no ha engañado!, {name2} recibe 6 cartas" msgstr "¡{name1} no se había tirado un farol!, {name2} recibe 6 cartas."
#: results.py:38 #: results.py:38
msgid "Choose Color" msgid "Choose Color"
@ -323,16 +322,16 @@ msgstr "Selecciona un color"
#: results.py:56 #: results.py:56
msgid "Last card (tap for game state):" msgid "Last card (tap for game state):"
msgid_plural "Cards (tap for game state):" msgid_plural "Cards (tap for game state):"
msgstr[0] "Última carta (presiona para el estado de la partida):" msgstr[0] "Última carta (pulsa para ver el estado de la partida):"
msgstr[1] "Cartas (presiona para el estado de la partida):" msgstr[1] "Cartas (pulsa para ver el estado de la partida):"
#: results.py:60 results.py:123 results.py:165 #: results.py:60 results.py:123 results.py:165
msgid "Current player: {name}" msgid "Current player: {name}"
msgstr "Jugador actual: {name}" msgstr "Jugador actual: {name}."
#: results.py:61 results.py:124 results.py:167 #: results.py:61 results.py:124 results.py:167
msgid "Last card: {card}" msgid "Last card: {card}"
msgstr "Última carta: {card}" msgstr "Última carta: {card}."
#: results.py:62 results.py:125 results.py:168 #: results.py:62 results.py:125 results.py:168
msgid "Player: {player_list}" msgid "Player: {player_list}"
@ -349,30 +348,30 @@ msgstr[1] "{name} ({number} cartas)"
#: results.py:81 #: results.py:81
msgid "You are not playing" msgid "You are not playing"
msgstr "No estás jugando" msgstr "No estás jugando."
#: results.py:83 #: results.py:83
msgid "" msgid ""
"Not playing right now. Use /new to start a game or /join to join the current " "Not playing right now. Use /new to start a game or /join to join the current "
"game in this group" "game in this group"
msgstr "" msgstr ""
"No estas jugando en este momento. Usa /new para comenzar una partida o /" "No estás jugando en este momento. Usa /new para comenzar una partida o /"
"join para unirte a la partida actual en este grupo" "join si hay una partida en curso en este grupo."
#: results.py:95 #: results.py:95
msgid "The game wasn't started yet" msgid "The game wasn't started yet"
msgstr "La partida no ha comenzado todavía" msgstr "La partida no ha comenzado todavía."
#: results.py:97 #: results.py:97
msgid "Start the game with /start" msgid "Start the game with /start"
msgstr "Comenzar la partida con /start" msgstr "Comienza la partida con /start."
#: results.py:108 #: results.py:108
#, python-format #, python-format
msgid "Drawing {number} card" msgid "Drawing {number} card"
msgid_plural "Drawing {number} cards" msgid_plural "Drawing {number} cards"
msgstr[0] "Robando {number} carta" msgstr[0] "Robando {number} carta."
msgstr[1] "Robando {number} cartas" msgstr[1] "Robando {number} cartas."
#: results.py:136 #: results.py:136
msgid "Pass" msgid "Pass"
@ -380,7 +379,7 @@ msgstr "Pasar"
#: results.py:148 #: results.py:148
msgid "I'm calling your bluff!" msgid "I'm calling your bluff!"
msgstr "Me estas engañando!" msgstr "¡Es un farol!"
#: settings.py:39 #: settings.py:39
msgid "Please edit your settings in a private chat with the bot." msgid "Please edit your settings in a private chat with the bot."
@ -404,45 +403,45 @@ msgstr "Ajustes"
#: settings.py:68 #: settings.py:68
msgid "Enabled statistics!" msgid "Enabled statistics!"
msgstr "Estadísticas habilitadas!" msgstr "¡Estadísticas habilitadas!"
#: settings.py:70 #: settings.py:70
msgid "Select locale" msgid "Select locale"
msgstr "Seleccionar región" msgstr "Selecciona tu idioma"
#: settings.py:81 #: settings.py:81
msgid "Deleted and disabled statistics!" msgid "Deleted and disabled statistics!"
msgstr "Estadísticas borradas y deshabilitadas! " msgstr "¡Estadísticas borradas y deshabilitadas! "
#: settings.py:94 #: settings.py:94
msgid "Set locale!" msgid "Set locale!"
msgstr "Región seleccionada!" msgstr "¡Idioma seleccionada!"
#: simple_commands.py #: simple_commands.py
msgid "" msgid ""
"You did not enable statistics. Use /settings in a private chat with the bot " "You did not enable statistics. Use /settings in a private chat with the bot "
"to enable them." "to enable them."
msgstr "" msgstr ""
"No has habilitado las estadísticas. Usa /settings en un chat privado con el " "Tus estadísticas no están habilitadas. Usa /settings en un chat privado con el "
"bot para habilitarlas." "bot para habilitarlas."
#: simple_commands.py #: simple_commands.py
msgid "{number} game played" msgid "{number} game played"
msgid_plural "{number} games played" msgid_plural "{number} games played"
msgstr[0] "{number} partida jugada" msgstr[0] "{number} partida jugada."
msgstr[1] "{number} partidas jugadas" msgstr[1] "{number} partidas jugadas."
#: simple_commands.py #: simple_commands.py
msgid "{number} first place" msgid "{number} first place"
msgid_plural "{number} first places" msgid_plural "{number} first places"
msgstr[0] "{number} primero lugare" msgstr[0] "{number} primer lugar."
msgstr[1] "{number} primeros lugares" msgstr[1] "{number} primeros lugares."
#: simple_commands.py #: simple_commands.py
msgid "{number} card played" msgid "{number} card played"
msgid_plural "{number} cards played" msgid_plural "{number} cards played"
msgstr[0] "{number} carta jugada" msgstr[0] "{number} carta jugada."
msgstr[1] "{number} cartas jugadas" msgstr[1] "{number} cartas jugadas."
#: utils.py #: utils.py
msgid "{emoji} Green" msgid "{emoji} Green"

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Telegram bot to play UNO in group chats # Telegram bot to play UNO in group chats
# Copyright (c) 2016 Jannes Höke <uno@jhoeke.de> # Copyright (c) 2016 - 2017 Jannes Höke <uno@jhoeke.de> and Karho Yau
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
@ -63,7 +63,7 @@ class Player(object):
self.drew = False self.drew = False
self.anti_cheat = 0 self.anti_cheat = 0
self.turn_started = datetime.now() self.turn_started = datetime.now()
self.waiting_time = 90 self.waiting_time = 60
def leave(self): def leave(self):
"""Removes player from the game and closes the gap in the list""" """Removes player from the game and closes the gap in the list"""
@ -160,25 +160,51 @@ class Player(object):
is_playable = True is_playable = True
last = self.game.last_card last = self.game.last_card
mode = self.game.mode
self.logger.debug("Checking card " + str(card)) self.logger.debug("Checking card " + str(card))
if (card.color != last.color and card.value != last.value and if mode == 0: # This mode is to apply the original rule
if (card.color != last.color and card.value != last.value and
not card.special): not card.special):
self.logger.debug("Card's color or value doesn't match") self.logger.debug("Card's color or value doesn't match")
is_playable = False is_playable = False
elif last.value == c.DRAW_TWO and not \ elif last.value == c.DRAW_TWO and self.game.draw_counter:
card.value == c.DRAW_TWO and self.game.draw_counter: self.logger.debug("Player has to draw and can't counter")
self.logger.debug("Player has to draw and can't counter") is_playable = False
is_playable = False elif last.special == c.DRAW_FOUR and self.game.draw_counter:
elif last.special == c.DRAW_FOUR and self.game.draw_counter: self.logger.debug("Player has to draw and can't counter")
self.logger.debug("Player has to draw and can't counter") is_playable = False
is_playable = False elif (last.special == c.CHOOSE or last.special == c.DRAW_FOUR) and \
elif (last.special == c.CHOOSE or last.special == c.DRAW_FOUR) and \
(card.special == c.CHOOSE or card.special == c.DRAW_FOUR): (card.special == c.CHOOSE or card.special == c.DRAW_FOUR):
self.logger.debug("Can't play colorchooser on another one") self.logger.debug("Can't play colorchooser on another one")
is_playable = False is_playable = False
elif not last.color: # Prevent game being locked by choosing colors.
self.logger.debug("Last card has no color") # When player is going to leave and he doesn't select a color, it causes game lock.
is_playable = False elif not last.color and (last.special != c.CHOOSE and last.special != c.DRAW_FOUR):
self.logger.debug("Last card has no color")
is_playable = False
elif mode == 1: # This mode is to apply the Progressive UNO rule.
if (card.color != last.color and card.value != last.value and
not card.special):
self.logger.debug("Card's color or value doesn't match")
is_playable = False
elif last.value == c.DRAW_TWO and self.game.draw_counter and not \
card.value == c.DRAW_TWO:
self.logger.debug("Player has to draw and can't counter")
is_playable = False
elif last.special == c.DRAW_FOUR and self.game.draw_counter and not \
card.special == c.DRAW_FOUR:
self.logger.debug("Player has to draw and can't counter")
is_playable = False
elif (last.special == c.CHOOSE and (card.special == c.CHOOSE or card.special == c.DRAW_FOUR)) or \
(last.special == c.DRAW_FOUR and card.special == c.CHOOSE):
self.logger.debug("Can't play colorchooser on another one")
is_playable = False
# Prevent game being locked by choosing colors.
# When player is going to leave and he doesn't select a color, it causes game lock.
elif not last.color and (last.special != c.CHOOSE and last.special != c.DRAW_FOUR):
self.logger.debug("Last card has no color")
is_playable = False
return is_playable return is_playable

202
test_player.py Normal file
View file

@ -0,0 +1,202 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Telegram bot to play UNO in group chats
# Copyright (c) 2016 Jannes Höke <uno@jhoeke.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import telegram
from game import Game
from player import Player
import card as c
import logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
logger = logging.getLogger(__name__)
class Test(unittest.TestCase):
game = None
def setUp(self):
self.game = Game(None)
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)
def test_leave(self):
p0 = Player(self.game, "Player 0")
p1 = Player(self.game, "Player 1")
p2 = Player(self.game, "Player 2")
p1.leave()
self.assertEqual(p0, p2.next)
self.assertEqual(p2, p0.next)
def test_draw(self):
p = Player(self.game, "Player 0")
deck_before = len(self.game.deck.cards)
top_card = self.game.deck.cards[-1]
p.draw()
self.assertEqual(top_card, p.cards[-1])
self.assertEqual(deck_before, len(self.game.deck.cards) + 1)
def test_draw_two(self):
p = Player(self.game, "Player 0")
deck_before = len(self.game.deck.cards)
self.game.draw_counter = 2
p.draw()
self.assertEqual(deck_before, len(self.game.deck.cards) + 2)
def test_playable_cards_simple(self):
p = Player(self.game, "Player 0")
self.game.last_card = c.Card(c.RED, '5')
p.cards = [c.Card(c.RED, '0'), c.Card(c.RED, '5'), c.Card(c.BLUE, '0'),
c.Card(c.GREEN, '5'), c.Card(c.GREEN, '8')]
expected = [c.Card(c.RED, '0'), c.Card(c.RED, '5'),
c.Card(c.GREEN, '5')]
self.assertListEqual(p.playable_cards(), expected)
def test_playable_cards_on_draw_two(self):
p = Player(self.game, "Player 0")
self.game.last_card = c.Card(c.RED, c.DRAW_TWO)
self.game.draw_counter = 2
p.cards = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.RED, '5'),
c.Card(c.BLUE, '0'), c.Card(c.GREEN, '5'),
c.Card(c.GREEN, c.DRAW_TWO)]
expected = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.GREEN, c.DRAW_TWO)]
self.assertListEqual(p.playable_cards(), expected)
def test_playable_cards_on_draw_two_then_four(self):
p = Player(self.game, "Player 0")
self.game.last_card = c.Card(c.RED, c.DRAW_TWO)
self.game.draw_counter = 2
p.cards = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.RED, '5'),
c.Card(c.BLUE, '0'), c.Card(c.GREEN, '5'),
c.Card(c.GREEN, c.DRAW_TWO),
c.Card(None, None, c.DRAW_FOUR)]
expected = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.GREEN, c.DRAW_TWO), c.Card(None, None, c.DRAW_FOUR)]
self.assertListEqual(p.playable_cards(), expected)
def test_playable_cards_on_draw_four(self):
p = Player(self.game, "Player 0")
self.game.last_card = c.Card(c.RED, None, c.DRAW_FOUR)
self.game.draw_counter = 4
p.cards = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.RED, '5'),
c.Card(c.BLUE, '0'), c.Card(c.GREEN, '5'),
c.Card(c.GREEN, c.DRAW_TWO),
c.Card(None, None, c.DRAW_FOUR),
c.Card(None, None, c.CHOOSE)]
expected = [c.Card(None, None, c.DRAW_FOUR)]
self.assertListEqual(p.playable_cards(), expected)
# def test_playable_cards_on_draw_four_then_four(self):
# p = Player(self.game, "Player 0")
# self.game.last_card = c.Card(c.RED, None, c.DRAW_FOUR)
# self.game.draw_counter = 4
# p.cards = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.RED, '5'),
# c.Card(c.BLUE, '0'), c.Card(c.GREEN, '5'),
# c.Card(c.GREEN, c.DRAW_TWO),
# c.Card(None, None, c.DRAW_FOUR)]
# expected = [c.Card(None, None, c.DRAW_FOUR)]
# self.assertListEqual(p.playable_cards(), expected)
def test_bluffing(self):
p = Player(self.game, "Player 0")
Player(self.game, "Player 01")
self.game.last_card = c.Card(c.RED, '1')
p.cards = [c.Card(c.RED, c.DRAW_TWO), c.Card(c.RED, '5'),
c.Card(c.BLUE, '0'), c.Card(c.GREEN, '5'),
c.Card(c.RED, '5'), c.Card(c.GREEN, c.DRAW_TWO),
c.Card(None, None, c.DRAW_FOUR),
c.Card(None, None, c.CHOOSE)]
p.playable_cards()
self.assertTrue(p.bluffing)
p.cards = [c.Card(c.BLUE, '1'), c.Card(c.GREEN, '1'),
c.Card(c.GREEN, c.DRAW_TWO),
c.Card(None, None, c.DRAW_FOUR),
c.Card(None, None, c.CHOOSE)]
p.playable_cards()
p.play(c.Card(None, None, c.DRAW_FOUR))
self.game.choose_color(c.GREEN)
self.assertFalse(self.game.current_player.prev.bluffing)
if __name__ == '__main__':
unittest.main()