Compare commits

...

27 Commits

Author SHA1 Message Date
JerryXiao b9fd7efbcb
draw accumulation 2023-10-04 15:35:11 +08:00
JerryXiao fc51c39101
allow only one game perchat 2023-10-04 15:35:10 +08:00
Jannes Höke 7af19fae31 🐛 Add missing comma 2023-08-06 22:32:52 +02:00
Pradeep 09086f986a
🔀 Added Hindi Language (#118)
* Update available.py

* Update available.py

* Update compile.sh

* Add files via upload

* Update available.py

* Update compile.sh
2023-08-06 22:22:38 +02:00
Jannes Höke bda53c1b71
Merge pull request #116 from JuniorJPDJ/juniorjpdj/actions
feat(ci): build docker images on push and on the schedule
2023-06-11 12:23:42 +02:00
JuniorJPDJ f482af3330 feat(ci): build docker images on push and on the schedule 2023-06-10 18:12:59 +02:00
Jannes Höke 79c16e3920
Merge pull request #115 from JuniorJPDJ/fix-syntaxwarning-is
fix: SyntaxWarning: "is" with a literal. Did you mean "=="?
2023-06-07 14:01:39 +02:00
JuniorJPDJ 1e532ca6fc
fix: SyntaxWarning: "is" with a literal. Did you mean "=="? 2023-06-07 13:24:22 +02:00
Jannes Höke e25879cead 💄 Use new colorblind-accessable card stickers 2023-04-09 15:55:28 +02:00
Jannes Höke 0d936c961d 🍱 Add all colorblind images and helper scripts
also reorganize somes stuff
2023-04-09 15:30:05 +02:00
Jannes Höke 82471f5120 🔥 Delete JPG version of cards 2023-04-08 17:41:59 +02:00
Jannes Höke 68c04a9bd9 🚚 Move classic deck into subdirectory 2023-04-08 17:41:33 +02:00
Jannes Höke 034dd5ab34 📝 Credit Lê Minh Sơn for vi_VN translation 2023-03-05 15:18:20 +01:00
Jannes Höke 43e0479c6f 🌐 Fix plural form 2023-03-05 14:43:28 +01:00
Jannes Höke 6a467a9d0c 🌐 Fix translation 2023-03-05 14:39:59 +01:00
Jannes Höke 1785da37b5
🔀 Merge pull request #112 from leminhson06/master
Locale Việt Nam
2023-03-05 14:36:32 +01:00
leminhson06 56f623e321 Locale Việt Nam 2023-03-05 16:32:01 +07:00
Jannes Höke f16bf317b5 🐛 Use v13 signature for error handler 2022-12-07 14:06:58 +01:00
Jannes Höke 961a9ced76
🔀 Merge pull request #109 from Tackyou/master
Latest python in Dockerfile & add docker compose
2022-11-29 16:19:16 +01:00
tackyou ece86473a2 latest python in Dockerfile & add docker compose 2022-11-29 13:40:03 +01:00
Jannes Höke cb4d9bd5e3
🔀 Merge pull request #107 from tehcneko/master
Update python-telegram-bot to 13
2022-11-14 14:51:33 +01:00
NekoInverter c9e52174e1 Apply fixes from @jh0ker 2022-03-03 16:18:53 +08:00
tehcneko f11df72b0b Fix 2022-02-13 22:44:58 +08:00
tehcneko 1b7aea4320 Update python-telegram-bot to 13 2022-02-10 15:00:51 +08:00
Jannes Höke 4021c1483e
🔀 Merge pull request #106 from nikitastykov/master
Dockerfile changes :Changed the python version + removed extra requirements.txt
2021-12-10 15:46:39 +01:00
Nikita a8d5132dc5
Updated Dockerfile
Added the working and tested Python version, Previous imported the last version of python, which is not compatible
Removed extra install requirements.txt step
2021-12-09 05:46:44 +01:00
nikitastykov 6109c90d06 Changed the python version to the supported and tested one.
Removed extra requirements.txt installation.
2021-12-08 18:55:58 +00:00
644 changed files with 2430 additions and 330 deletions

60
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: Docker build
on:
schedule:
- cron: '25 15 * * *'
push:
branches:
- master
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

6
.gitignore vendored
View File

@ -27,6 +27,8 @@ var/
.installed.cfg
*.egg
venv/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
@ -69,3 +71,7 @@ target/
# Database file
uno.sqlite3
images/api_auth.json
images/sticker_config.json
images/sticker_uploader.session

View File

@ -10,7 +10,6 @@ COPY . .
RUN cd locales && find . -maxdepth 2 -type d -name 'LC_MESSAGES' -exec ash -c 'msgfmt {}/unobot.po -o {}/unobot.mo' \;
RUN pip install -r requirements.txt
VOLUME /app/data
ENV UNO_DB /app/data/uno.sqlite3

View File

@ -4,10 +4,11 @@ url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
telethon = "*"
[packages]
python-telegram-bot = "==8.1.1"
python-telegram-bot = "==13.11"
pony = "*"
[requires]
python_version = "3.7"
python_version = "3.11"

150
Pipfile.lock generated
View File

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "de56c4d5f516205e99d141cd7d372f67b602b6f981306971c01ffe25a5abf5c6"
"sha256": "87f82f4abefdefd3b212fa99f5cbf6e222d6855aa7574d7a94fbf51b33cc342f"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
"python_version": "3.11"
},
"sources": [
{
@ -16,34 +16,150 @@
]
},
"default": {
"apscheduler": {
"hashes": [
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
"sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
],
"version": "==3.6.3"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
],
"version": "==2019.6.16"
},
"future": {
"hashes": [
"sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"
],
"version": "==0.17.1"
"markers": "python_version >= '3.6'",
"version": "==2022.12.7"
},
"pony": {
"hashes": [
"sha256:55bb9d4d12029d8c2bbbc7a284970e72225035db7e6370c0a15ec93d1886fe88"
"sha256:5f45fc67587f4520c560a57148cc573b097d42f82f5cb200d72c957b5708198d",
"sha256:608a1c1d662983bad2590e650f2bbc1cd6ed48558894ad8f50da4739ff98f614"
],
"index": "pypi",
"version": "==0.7.10"
"version": "==0.7.16"
},
"python-telegram-bot": {
"hashes": [
"sha256:238c4a88b09d93c52d413bcf7e7fe14dfeb02f5f9222ffe4cafd4bd3d55489a3",
"sha256:997983e5082dc6aa811bce3a6014731201fc64b0a9c02fdb26beac686029d94b"
"sha256:534f5bb0ff4ca34c9252e97e0b3bcdab81d97be0eb4821682a361cb426c00e55",
"sha256:baeff704baa2ac3dc17a944c02da888228ad258e89be2e5bcbd13a8a5102d573"
],
"index": "pypi",
"version": "==8.1.1"
"version": "==13.11"
},
"pytz": {
"hashes": [
"sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
"sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
],
"version": "==2023.3"
},
"pytz-deprecation-shim": {
"hashes": [
"sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
"sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==0.1.0.post0"
},
"setuptools": {
"hashes": [
"sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a",
"sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"
],
"markers": "python_version >= '3.7'",
"version": "==67.6.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"tornado": {
"hashes": [
"sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca",
"sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72",
"sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23",
"sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8",
"sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b",
"sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9",
"sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13",
"sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75",
"sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac",
"sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e",
"sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"
],
"markers": "python_version >= '3.7'",
"version": "==6.2"
},
"tzdata": {
"hashes": [
"sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a",
"sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"
],
"markers": "platform_system == 'Windows'",
"version": "==2023.3"
},
"tzlocal": {
"hashes": [
"sha256:3f21d09e1b2aa9f2dacca12da240ca37de3ba5237a93addfd6d593afe9073355",
"sha256:b44c4388f3d34f25862cfbb387578a4d70fec417649da694a132f628a23367e2"
],
"markers": "python_version >= '3.7'",
"version": "==4.3"
}
},
"develop": {}
"develop": {
"pyaes": {
"hashes": [
"sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"
],
"version": "==1.6.1"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"rsa": {
"hashes": [
"sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7",
"sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"version": "==4.9"
},
"telethon": {
"hashes": [
"sha256:613bae42acb5f2eeb1a0b92614e323021c66f374db62adf9826ea0c2c9120bb1",
"sha256:893c10f133974fba4c53eb1736b6514d596d1cd94c83436a711f3345df945199"
],
"index": "pypi",
"version": "==1.28.2"
}
}
}

View File

@ -2,17 +2,18 @@
The following awesome people contributed to this project by translating it:
| Locale | Translators |
|--------|-------------|
| ca_CA | [retiolus](https://github.com/retiolus) |
| de_DE | [Jannes Höke](https://github.com/jh0ker) |
| es_ES | [José.A Rojo](https://github.com/J4RV), [Ricardo Valverde Hernández](https://telegram.me/rivh1), Victor, Yuga |
| id_ID | [Erwin Guo](https://www.facebook.com/erwinfransiscus) |
| it_IT | Carola Mariano, ɳick |
| ml_IN | [Adhith T](https://github.com/adhitht123) |
| pt_BR | [Iuri Guilherme](https://github.com/iuriguilherme), [João Rodrigo Couto de Oliveira](http://twitter.com/JoaoRodrigoJR) |
| zh_CN | [imlonghao](https://github.com/imlonghao), [XhyEax](https://github.com/XhyEax) |
| zh_HK | [Jed Cheng](https://www.facebook.com/profile.php?id=100002258388821) |
| zh_TW | [Eugene Lam](https://www.facebook.com/eugenelam1118), [jimchen5209](https://www.youtube.com/user/jimchen5209), [pan93412](https://www.github.com/pan93412) |
| Locale | Translators |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ca_CA | [retiolus](https://github.com/retiolus) |
| de_DE | [Jannes Höke](https://github.com/jh0ker) |
| es_ES | [José.A Rojo](https://github.com/J4RV), [Ricardo Valverde Hernández](https://telegram.me/rivh1), Victor, Yuga |
| id_ID | [Erwin Guo](https://www.facebook.com/erwinfransiscus) |
| it_IT | Carola Mariano, ɳick |
| ml_IN | [Adhith T](https://github.com/adhitht123) |
| pt_BR | [Iuri Guilherme](https://github.com/iuriguilherme), [João Rodrigo Couto de Oliveira](http://twitter.com/JoaoRodrigoJR) |
| vi_VN | [Lê Minh Sơn](https://github.com/leminhson06) |
| zh_CN | [imlonghao](https://github.com/imlonghao), [XhyEax](https://github.com/XhyEax) |
| zh_HK | [Jed Cheng](https://www.facebook.com/profile.php?id=100002258388821) |
| zh_TW | [Eugene Lam](https://www.facebook.com/eugenelam1118), [jimchen5209](https://www.youtube.com/user/jimchen5209), [pan93412](https://www.github.com/pan93412) |
Please add yourself here alphabetically when you submit your first translation.

View File

@ -6,6 +6,8 @@ import card as c
from datetime import datetime
from telegram import Message, Chat
from telegram.ext import CallbackContext
from apscheduler.jobstores.base import JobLookupError
from config import TIME_REMOVAL_AFTER_SKIP, MIN_FAST_TURN_TIME
from errors import DeckEmptyError, NotEnoughPlayersError
@ -111,7 +113,7 @@ def do_play_card(bot, player, result_id):
if us.stats:
us.games_played += 1
if game.players_won is 0:
if game.players_won == 0:
us.first_places += 1
game.players_won += 1
@ -153,11 +155,15 @@ def do_call_bluff(bot, player):
chat = game.chat
if player.prev.bluffing:
draw_prev = 4
draw_next = game.draw_counter - draw_prev
draw_next_text = ". " + __("Giving {count} cards to {name}").format(count=draw_next, name=player.user.first_name) if draw_next > 0 else ""
send_async(bot, chat.id,
text=__("Bluff called! Giving 4 cards to {name}",
text=__("Bluff called! Giving {count} cards to {name}" + draw_next_text,
multi=game.translate)
.format(name=player.prev.user.first_name))
.format(name=player.prev.user.first_name, count=draw_prev))
game.draw_counter = draw_prev
try:
player.prev.draw()
except DeckEmptyError:
@ -165,12 +171,21 @@ def do_call_bluff(bot, player):
text=__("There are no more cards in the deck.",
multi=game.translate))
game.draw_counter = draw_next
try:
player.draw()
except DeckEmptyError:
send_async(bot, player.game.chat.id,
text=__("There are no more cards in the deck.",
multi=game.translate))
else:
game.draw_counter += 2
send_async(bot, chat.id,
text=__("{name1} didn't bluff! Giving 6 cards to {name2}",
text=__("{name1} didn't bluff! Giving {count} cards to {name2}",
multi=game.translate)
.format(name1=player.prev.user.first_name,
count=game.draw_counter,
name2=player.user.first_name))
try:
player.draw()
@ -191,7 +206,10 @@ def start_player_countdown(bot, game, job_queue):
if game.mode == 'fast':
if game.job:
game.job.schedule_removal()
try:
game.job.schedule_removal()
except JobLookupError:
pass
job = job_queue.run_once(
#lambda x,y: do_skip(bot, player),
@ -205,9 +223,9 @@ def start_player_countdown(bot, game, job_queue):
player.game.job = job
def skip_job(bot, job):
player = job.context.player
def skip_job(context: CallbackContext):
player = context.job.context.player
game = player.game
if game_is_running(game):
job_queue = job.context.job_queue
do_skip(bot, player, job_queue)
job_queue = context.job.context.job_queue
do_skip(context.bot, player, job_queue)

203
bot.py
View File

@ -21,9 +21,9 @@ import logging
from datetime import datetime
from telegram import ParseMode, InlineKeyboardMarkup, \
InlineKeyboardButton
InlineKeyboardButton, Update
from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \
CommandHandler, MessageHandler, Filters, CallbackQueryHandler
CommandHandler, MessageHandler, Filters, CallbackQueryHandler, CallbackContext
from telegram.ext.dispatcher import run_async
import card as c
@ -49,9 +49,10 @@ logging.basicConfig(
level=logging.INFO
)
logger = logging.getLogger(__name__)
logging.getLogger('apscheduler').setLevel(logging.WARNING)
@user_locale
def notify_me(bot, update):
def notify_me(update: Update, context: CallbackContext):
"""Handler for /notify_me command, pm people for next game"""
chat_id = update.message.chat_id
if update.message.chat.type == 'private':
@ -67,15 +68,25 @@ def notify_me(bot, update):
@user_locale
def new_game(bot, update):
def new_game(update: Update, context: CallbackContext):
"""Handler for the /new command"""
chat_id = update.message.chat_id
if update.message.chat.type == 'private':
help_handler(bot, update)
help_handler(update, context)
else:
try:
_game = gm.chatid_games[chat_id][-1]
except (KeyError, IndexError):
pass
else:
send_async(bot, chat_id,
text=_("There is already a game running in this chat. Join the "
"game with /join"))
return
if update.message.chat_id in gm.remind_dict:
for user in gm.remind_dict[update.message.chat_id]:
send_async(bot,
@ -89,88 +100,88 @@ def new_game(bot, update):
game.starter = update.message.from_user
game.owner.append(update.message.from_user.id)
game.mode = DEFAULT_GAMEMODE
send_async(bot, chat_id,
send_async(context.bot, chat_id,
text=_("Created a new game! Join the game with /join "
"and start the game with /start"))
@user_locale
def kill_game(bot, update):
def kill_game(update: Update, context: CallbackContext):
"""Handler for the /kill command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if update.message.chat.type == 'private':
help_handler(bot, update)
help_handler(update, context)
return
if not games:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("There is no running game in this chat."))
return
game = games[-1]
if user_is_creator_or_admin(user, game, bot, chat):
if user_is_creator_or_admin(user, game, context.bot, chat):
try:
gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
except NoGameInChatError:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id)
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@user_locale
def join_game(bot, update):
def join_game(update: Update, context: CallbackContext):
"""Handler for the /join command"""
chat = update.message.chat
if update.message.chat.type == 'private':
help_handler(bot, update)
help_handler(update, context)
return
try:
gm.join_game(update.message.from_user, chat)
except LobbyClosedError:
send_async(bot, chat.id, text=_("The lobby is closed"))
send_async(context.bot, chat.id, text=_("The lobby is closed"))
except NoGameInChatError:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("No game is running at the moment. "
"Create a new game with /new"),
reply_to_message_id=update.message.message_id)
except AlreadyJoinedError:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("You already joined the game. Start the game "
"with /start"),
reply_to_message_id=update.message.message_id)
except DeckEmptyError:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("There are not enough cards left in the deck for "
"new players to join."),
reply_to_message_id=update.message.message_id)
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Joined the game"),
reply_to_message_id=update.message.message_id)
@user_locale
def leave_game(bot, update):
def leave_game(update: Update, context: CallbackContext):
"""Handler for the /leave command"""
chat = update.message.chat
user = update.message.from_user
@ -178,7 +189,7 @@ def leave_game(bot, update):
player = gm.player_for_user_in_chat(user, chat)
if player is None:
send_async(bot, chat.id, text=_("You are not playing in a game in "
send_async(context.bot, chat.id, text=_("You are not playing in a game in "
"this group."),
reply_to_message_id=update.message.message_id)
return
@ -190,23 +201,23 @@ def leave_game(bot, update):
gm.leave_game(user, chat)
except NoGameInChatError:
send_async(bot, chat.id, text=_("You are not playing in a game in "
send_async(context.bot, chat.id, text=_("You are not playing in a game in "
"this group."),
reply_to_message_id=update.message.message_id)
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
else:
if game.started:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=__("Okay. Next Player: {name}",
multi=game.translate).format(
name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id)
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=__("{name} left the game before it started.",
multi=game.translate).format(
name=display_name(user)),
@ -214,11 +225,11 @@ def leave_game(bot, update):
@user_locale
def kick_player(bot, update):
def kick_player(update: Update, context: CallbackContext):
"""Handler for the /kick command"""
if update.message.chat.type == 'private':
help_handler(bot, update)
help_handler(update, context)
return
chat = update.message.chat
@ -228,20 +239,20 @@ def kick_player(bot, update):
game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError):
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("No game is running at the moment. "
"Create a new game with /new"),
reply_to_message_id=update.message.message_id)
return
if not game.started:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id)
return
if user_is_creator_or_admin(user, game, bot, chat):
if user_is_creator_or_admin(user, game, context.bot, chat):
if update.message.reply_to_message:
kicked = update.message.reply_to_message.from_user
@ -250,40 +261,40 @@ def kick_player(bot, update):
gm.leave_game(kicked, chat)
except NoGameInChatError:
send_async(bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))),
send_async(context.bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))),
reply_to_message_id=update.message.message_id)
return
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
return
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Please reply to the person you want to kick and type /kick again."),
reply_to_message_id=update.message.message_id)
return
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=__("Okay. Next Player: {name}",
multi=game.translate).format(
name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id)
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
def select_game(bot, update):
def select_game(update: Update, context: CallbackContext):
"""Handler for callback queries to select the current game"""
chat_id = int(update.callback_query.data)
@ -299,16 +310,15 @@ def select_game(bot, update):
text=_("Game not found."))
return
@run_async
def selected(bot):
def selected():
back = [[InlineKeyboardButton(text=_("Back to last group"),
switch_inline_query='')]]
bot.answerCallbackQuery(update.callback_query.id,
context.bot.answerCallbackQuery(update.callback_query.id,
text=_("Please switch to the group you selected!"),
show_alert=False,
timeout=TIMEOUT)
bot.editMessageText(chat_id=update.callback_query.message.chat_id,
context.bot.editMessageText(chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id,
text=_("Selected group: {group}\n"
"<b>Make sure that you switch to the correct "
@ -318,11 +328,11 @@ def select_game(bot, update):
parse_mode=ParseMode.HTML,
timeout=TIMEOUT)
selected(bot)
dispatcher.run_async(selected)
@game_locales
def status_update(bot, update):
def status_update(update: Update, context: CallbackContext):
"""Remove player from game if user leaves the group"""
chat = update.message.chat
@ -337,17 +347,17 @@ def status_update(bot, update):
pass
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!",
send_async(context.bot, chat.id, text=__("Game ended!",
multi=game.translate))
else:
send_async(bot, chat.id, text=__("Removing {name} from the game",
send_async(context.bot, chat.id, text=__("Removing {name} from the game",
multi=game.translate)
.format(name=display_name(user)))
@game_locales
@user_locale
def start_game(bot, update, args, job_queue):
def start_game(update: Update, context: CallbackContext):
"""Handler for the /start command"""
if update.message.chat.type != 'private':
@ -356,16 +366,16 @@ def start_game(bot, update, args, job_queue):
try:
game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError):
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("There is no game running in this chat. Create "
"a new one with /new"))
return
if game.started:
send_async(bot, chat.id, text=_("The game has already started"))
send_async(context.bot, chat.id, text=_("The game has already started"))
elif len(game.players) < MIN_PLAYERS:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=__("At least {minplayers} players must /join the game "
"before you can start it").format(minplayers=MIN_PLAYERS))
@ -383,30 +393,29 @@ def start_game(bot, update, args, job_queue):
multi=game.translate)
.format(name=display_name(game.current_player.user)))
@run_async
def send_first():
"""Send the first card and player"""
bot.sendSticker(chat.id,
context.bot.sendSticker(chat.id,
sticker=c.STICKERS[str(game.last_card)],
timeout=TIMEOUT)
bot.sendMessage(chat.id,
context.bot.sendMessage(chat.id,
text=first_message,
reply_markup=InlineKeyboardMarkup(choice),
timeout=TIMEOUT)
send_first()
start_player_countdown(bot, game, job_queue)
dispatcher.run_async(send_first)
start_player_countdown(context.bot, game, context.job_queue)
elif len(args) and args[0] == 'select':
elif len(context.args) and context.args[0] == 'select':
players = gm.userid_players[update.message.from_user.id]
groups = list()
for player in players:
title = player.game.chat.title
if player is gm.userid_current[update.message.from_user.id]:
if player == gm.userid_current[update.message.from_user.id]:
title = '- %s -' % player.game.chat.title
groups.append(
@ -414,23 +423,23 @@ def start_game(bot, update, args, job_queue):
callback_data=str(player.game.chat.id))]
)
send_async(bot, update.message.chat_id,
send_async(context.bot, update.message.chat_id,
text=_('Please select the group you want to play in.'),
reply_markup=InlineKeyboardMarkup(groups))
else:
help_handler(bot, update)
help_handler(update, context)
@user_locale
def close_game(bot, update):
def close_game(update: Update, context: CallbackContext):
"""Handler for the /close 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,
send_async(context.bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -438,12 +447,12 @@ def close_game(bot, update):
if user.id in game.owner:
game.open = False
send_async(bot, chat.id, text=_("Closed the lobby. "
send_async(context.bot, chat.id, text=_("Closed the lobby. "
"No more players can join this game."))
return
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -451,14 +460,14 @@ def close_game(bot, update):
@user_locale
def open_game(bot, update):
def open_game(update: Update, context: CallbackContext):
"""Handler for the /open 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,
send_async(context.bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -466,11 +475,11 @@ def open_game(bot, update):
if user.id in game.owner:
game.open = True
send_async(bot, chat.id, text=_("Opened the lobby. "
send_async(context.bot, chat.id, text=_("Opened the lobby. "
"New players may /join the game."))
return
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -478,14 +487,14 @@ def open_game(bot, update):
@user_locale
def enable_translations(bot, update):
def enable_translations(update: Update, context: CallbackContext):
"""Handler for the /enable_translations 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,
send_async(context.bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -493,12 +502,12 @@ def enable_translations(bot, update):
if user.id in game.owner:
game.translate = True
send_async(bot, chat.id, text=_("Enabled multi-translations. "
send_async(context.bot, chat.id, text=_("Enabled multi-translations. "
"Disable with /disable_translations"))
return
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -506,14 +515,14 @@ def enable_translations(bot, update):
@user_locale
def disable_translations(bot, update):
def disable_translations(update: Update, context: CallbackContext):
"""Handler for the /disable_translations 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,
send_async(context.bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -521,13 +530,13 @@ def disable_translations(bot, update):
if user.id in game.owner:
game.translate = False
send_async(bot, chat.id, text=_("Disabled multi-translations. "
send_async(context.bot, chat.id, text=_("Disabled multi-translations. "
"Enable them again with "
"/enable_translations"))
return
else:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -536,14 +545,14 @@ def disable_translations(bot, update):
@game_locales
@user_locale
def skip_player(bot, update):
def skip_player(update: Update, context: CallbackContext):
"""Handler for the /skip command"""
chat = update.message.chat
user = update.message.from_user
player = gm.player_for_user_in_chat(user, chat)
if not player:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("You are not playing in a game in this chat."))
return
@ -558,19 +567,19 @@ def skip_player(bot, update):
# You can skip yourself even if you have time left (you'll still draw)
if delta < skipped_player.waiting_time and player != skipped_player:
n = skipped_player.waiting_time - delta
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=_("Please wait {time} second",
"Please wait {time} seconds",
n)
.format(time=n),
reply_to_message_id=update.message.message_id)
else:
do_skip(bot, player)
do_skip(context.bot, player)
@game_locales
@user_locale
def reply_to_query(bot, update):
def reply_to_query(update: Update, context: CallbackContext):
"""
Handler for inline queries.
Builds the result list for inline queries and answers to the client.
@ -638,13 +647,13 @@ def reply_to_query(bot, update):
if players and game and len(players) > 1:
switch = _('Current game: {game}').format(game=game.chat.title)
answer_async(bot, update.inline_query.id, results, cache_time=0,
answer_async(context.bot, update.inline_query.id, results, cache_time=0,
switch_pm_text=switch, switch_pm_parameter='select')
@game_locales
@user_locale
def process_result(bot, update, job_queue):
def process_result(update: Update, context: CallbackContext):
"""
Handler for chosen inline results.
Checks the players actions and acts accordingly.
@ -671,38 +680,46 @@ def process_result(bot, update, job_queue):
mode = result_id[5:]
game.set_mode(mode)
logger.info("Gamemode changed to {mode}".format(mode = mode))
send_async(bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode)))
send_async(context.bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode)))
return
elif len(result_id) == 36: # UUID result
return
elif int(anti_cheat) != last_anti_cheat:
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=__("Cheat attempt by {name}", multi=game.translate)
.format(name=display_name(player.user)))
return
elif result_id == 'call_bluff':
reset_waiting_time(bot, player)
do_call_bluff(bot, player)
reset_waiting_time(context.bot, player)
do_call_bluff(context.bot, player)
elif result_id == 'draw':
reset_waiting_time(bot, player)
do_draw(bot, player)
reset_waiting_time(context.bot, player)
do_draw(context.bot, player)
elif result_id == 'pass':
game.turn()
elif result_id in c.COLORS:
game.choose_color(result_id)
else:
reset_waiting_time(bot, player)
do_play_card(bot, player, result_id)
reset_waiting_time(context.bot, player)
if game.mode == "text":
sticker_id = c.STICKERS.get(result_id)
if sticker_id:
context.bot.sendSticker(chat.id,
sticker=sticker_id,
timeout=TIMEOUT)
else:
logger.warning(f"no sticker found for {result_id=}")
do_play_card(context.bot, player, result_id)
if game_is_running(game):
nextplayer_message = (
__("Next player: {name}", multi=game.translate)
.format(name=display_name(game.current_player.user)))
choice = [[InlineKeyboardButton(text=_("Make your choice!"), switch_inline_query_current_chat='')]]
send_async(bot, chat.id,
send_async(context.bot, chat.id,
text=nextplayer_message,
reply_markup=InlineKeyboardMarkup(choice))
start_player_countdown(bot, game, job_queue)
start_player_countdown(context.bot, game, context.job_queue)
def reset_waiting_time(bot, player):

354
card.py
View File

@ -60,122 +60,252 @@ DRAW_FOUR = 'draw_four'
SPECIALS = (CHOOSE, DRAW_FOUR)
CARDS_CLASSIC = {
"normal": {
"b_0": "BQADBAAD2QEAAl9XmQAB--inQsYcLTsC",
"b_1": "BQADBAAD2wEAAl9XmQABBzh4U-rFicEC",
"b_2": "BQADBAAD3QEAAl9XmQABo3l6TT0MzKwC",
"b_3": "BQADBAAD3wEAAl9XmQAB2y-3TSapRtIC",
"b_4": "BQADBAAD4QEAAl9XmQABT6nhOuolqKYC",
"b_5": "BQADBAAD4wEAAl9XmQABwRfmekGnpn0C",
"b_6": "BQADBAAD5QEAAl9XmQABQITgUsEsqxsC",
"b_7": "BQADBAAD5wEAAl9XmQABVhPF6EcfWjEC",
"b_8": "BQADBAAD6QEAAl9XmQABP6baig0pIvYC",
"b_9": "BQADBAAD6wEAAl9XmQAB0CQdsQs_pXIC",
"b_draw": "BQADBAAD7QEAAl9XmQAB00Wii7R3gDUC",
"b_skip": "BQADBAAD8QEAAl9XmQAB_RJHYKqlc-wC",
"b_reverse": "BQADBAAD7wEAAl9XmQABo7D0B9NUPmYC",
"g_0": "BQADBAAD9wEAAl9XmQABb8CaxxsQ-Y8C",
"g_1": "BQADBAAD-QEAAl9XmQAB9B6ti_j6UB0C",
"g_2": "BQADBAAD-wEAAl9XmQABYpLjOzbRz8EC",
"g_3": "BQADBAAD_QEAAl9XmQABKvc2ZCiY-D8C",
"g_4": "BQADBAAD_wEAAl9XmQABJB52wzPdHssC",
"g_5": "BQADBAADAQIAAl9XmQABp_Ep1I4GA2cC",
"g_6": "BQADBAADAwIAAl9XmQABaaMxxa4MihwC",
"g_7": "BQADBAADBQIAAl9XmQABv5Q264Crz8gC",
"g_8": "BQADBAADBwIAAl9XmQABjMH-X9UHh8sC",
"g_9": "BQADBAADCQIAAl9XmQAB26fZ2fW7vM0C",
"g_draw": "BQADBAADCwIAAl9XmQAB64jIZrgXrQUC",
"g_skip": "BQADBAADDwIAAl9XmQAB17yhhnh46VQC",
"g_reverse": "BQADBAADDQIAAl9XmQAB_xcaab0DkegC",
"r_0": "BQADBAADEQIAAl9XmQABiUfr1hz-zT8C",
"r_1": "BQADBAADEwIAAl9XmQAB5bWfwJGs6Q0C",
"r_2": "BQADBAADFQIAAl9XmQABHR4mg9Ifjw0C",
"r_3": "BQADBAADFwIAAl9XmQABYBx5O_PG2QIC",
"r_4": "BQADBAADGQIAAl9XmQABTQpGrlvet3cC",
"r_5": "BQADBAADGwIAAl9XmQABbdLt4gdntBQC",
"r_6": "BQADBAADHQIAAl9XmQABqEI274p3lSoC",
"r_7": "BQADBAADHwIAAl9XmQABCw8u67Q4EK4C",
"r_8": "BQADBAADIQIAAl9XmQAB8iDJmLxp8ogC",
"r_9": "BQADBAADIwIAAl9XmQAB_HCAww1kNGYC",
"r_draw": "BQADBAADJQIAAl9XmQABuz0OZ4l3k6MC",
"r_skip": "BQADBAADKQIAAl9XmQAC2AL5Ok_ULwI",
"r_reverse": "BQADBAADJwIAAl9XmQABu2tIeQTpDvUC",
"y_0": "BQADBAADKwIAAl9XmQAB_nWoNKe8DOQC",
"y_1": "BQADBAADLQIAAl9XmQABVprAGUDKgOQC",
"y_2": "BQADBAADLwIAAl9XmQABqyT4_YTm54EC",
"y_3": "BQADBAADMQIAAl9XmQABGC-Xxg_N6fIC",
"y_4": "BQADBAADMwIAAl9XmQABbc-ZGL8kApAC",
"y_5": "BQADBAADNQIAAl9XmQAB67QJZIF6XAcC",
"y_6": "BQADBAADNwIAAl9XmQABJg_7XXoITsoC",
"y_7": "BQADBAADOQIAAl9XmQABVrd7OcS2k34C",
"y_8": "BQADBAADOwIAAl9XmQABRpJSahBWk3EC",
"y_9": "BQADBAADPQIAAl9XmQAB9MwJWKLJogYC",
"y_draw": "BQADBAADPwIAAl9XmQABaPYK8oYg84cC",
"y_skip": "BQADBAADQwIAAl9XmQABO_AZKtxY6IMC",
"y_reverse": "BQADBAADQQIAAl9XmQABZdQFahGG6UQC",
"draw_four": "BQADBAAD9QEAAl9XmQABVlkSNfhn76cC",
"colorchooser": "BQADBAAD8wEAAl9XmQABl9rUOPqx4E4C",
},
"not_playable": {
"b_0": "BQADBAADRQIAAl9XmQAB1IfkQ5xAiK4C",
"b_1": "BQADBAADRwIAAl9XmQABbWvhTeKBii4C",
"b_2": "BQADBAADSQIAAl9XmQABS1djHgyQokMC",
"b_3": "BQADBAADSwIAAl9XmQABwQ6VTbgY-MIC",
"b_4": "BQADBAADTQIAAl9XmQABAlKUYha8YccC",
"b_5": "BQADBAADTwIAAl9XmQABMvx8xVDnhUEC",
"b_6": "BQADBAADUQIAAl9XmQABDEbhP1Zd31kC",
"b_7": "BQADBAADUwIAAl9XmQABXb5XQBBaAnIC",
"b_8": "BQADBAADVQIAAl9XmQABgL5HRDLvrjgC",
"b_9": "BQADBAADVwIAAl9XmQABtO3XDQWZLtYC",
"b_draw": "BQADBAADWQIAAl9XmQAB2kk__6_2IhMC",
"b_skip": "BQADBAADXQIAAl9XmQABEGJI6CaH3vcC",
"b_reverse": "BQADBAADWwIAAl9XmQAB_kZA6UdHXU8C",
"g_0": "BQADBAADYwIAAl9XmQABGD5a9oG7Yg4C",
"g_1": "BQADBAADZQIAAl9XmQABqwABZHAXZIg0Ag",
"g_2": "BQADBAADZwIAAl9XmQABTI3mrEhojRkC",
"g_3": "BQADBAADaQIAAl9XmQABVi3rUyzWS3YC",
"g_4": "BQADBAADawIAAl9XmQABZIf5ThaXnpUC",
"g_5": "BQADBAADbQIAAl9XmQABNndVJSQCenIC",
"g_6": "BQADBAADbwIAAl9XmQABpoy1c4ZkrvwC",
"g_7": "BQADBAADcQIAAl9XmQABDeaT5fzxwREC",
"g_8": "BQADBAADcwIAAl9XmQABLIQ06ZM5NnAC",
"g_9": "BQADBAADdQIAAl9XmQABel-mC7eXGsMC",
"g_draw": "BQADBAADdwIAAl9XmQABOHEpxSztCf8C",
"g_skip": "BQADBAADewIAAl9XmQABDaQdMxjjPsoC",
"g_reverse": "BQADBAADeQIAAl9XmQABek1lGz7SJNAC",
"r_0": "BQADBAADfQIAAl9XmQABWrxoiXcsg0EC",
"r_1": "BQADBAADfwIAAl9XmQABlav-bkgSgRcC",
"r_2": "BQADBAADgQIAAl9XmQABDjZkqfJ4AdAC",
"r_3": "BQADBAADgwIAAl9XmQABT7lH7VVcy3MC",
"r_4": "BQADBAADhQIAAl9XmQAB1arPC5x0LrwC",
"r_5": "BQADBAADhwIAAl9XmQABWvs7xkCDldkC",
"r_6": "BQADBAADiQIAAl9XmQABjwABH5ZonWn8Ag",
"r_7": "BQADBAADiwIAAl9XmQABjekJfm4fBDIC",
"r_8": "BQADBAADjQIAAl9XmQABqFjchpsJeEkC",
"r_9": "BQADBAADjwIAAl9XmQAB-sKdcgABdNKDAg",
"r_draw": "BQADBAADkQIAAl9XmQABtw9RPVDHZOQC",
"r_skip": "BQADBAADlQIAAl9XmQABtG2GixCxtX4C",
"r_reverse": "BQADBAADkwIAAl9XmQABz2qyEbabnVsC",
"y_0": "BQADBAADlwIAAl9XmQABAb3ZwTGS1lMC",
"y_1": "BQADBAADmQIAAl9XmQAB9v5qJk9R0x8C",
"y_2": "BQADBAADmwIAAl9XmQABCsgpRHC2g-cC",
"y_3": "BQADBAADnQIAAl9XmQAB3kLLXCv-qY0C",
"y_4": "BQADBAADnwIAAl9XmQAB7R_y-NexNLIC",
"y_5": "BQADBAADoQIAAl9XmQABl-7mwsjD-cMC",
"y_6": "BQADBAADowIAAl9XmQABwbVsyv2MfPkC",
"y_7": "BQADBAADpQIAAl9XmQABoBqC0JsemVwC",
"y_8": "BQADBAADpwIAAl9XmQABpkwAAeh9ldlHAg",
"y_9": "BQADBAADqQIAAl9XmQABpSBEUfd4IM8C",
"y_draw": "BQADBAADqwIAAl9XmQABMt-2zW0VYb4C",
"y_skip": "BQADBAADrwIAAl9XmQABIDf-_TuuxtEC",
"y_reverse": "BQADBAADrQIAAl9XmQABm9M0Zh-_UwkC",
"draw_four": "BQADBAADYQIAAl9XmQAB_HWlvZIscDEC",
"colorchooser": "BQADBAADXwIAAl9XmQABY_ksDdMex-wC",
},
}
CARDS_CLASSIC_COLORBLIND = {
"normal": {
"colorchooser": "CAADBAADrg4AAvX2mVEpx_BiDIE5nQI",
"draw_four": "CAADBAADYRAAArnkmVGmqXHhjWEBxAI",
"r_0": "CAADBAAD6A8AAn_ckVHPWHqiBR_3jAI",
"r_1": "CAADBAAD5Q0AAg-ImVEx-blQI88RrQI",
"r_2": "CAADBAAD1g0AAuMjmVEkQsVhN49DMAI",
"r_3": "CAADBAADlhAAAqy4mVHWovoaWfQG_gI",
"r_4": "CAADBAADCRoAAqf_kVFnl8ACL1rjpwI",
"r_5": "CAADBAADVw8AAjmamVEEv2TVeL9cpQI",
"r_6": "CAADBAADHQ4AAuuUkVH2I-yn6nRBVAI",
"r_7": "CAADBAADNQ8AArP1kVF5rqHtk0pQ-AI",
"r_8": "CAADBAAD1BAAAuQDkVEPiIodUi6WvwI",
"r_9": "CAADBAAD2Q4AAq1nkFHM6z5C0Kff2QI",
"r_draw": "CAADBAADvQ8AAqZukFGEmkRSoSZQEwI",
"r_reverse": "CAADBAAD5RAAAg89mVE8-EY_2DifcAI",
"r_skip": "CAADBAADRg4AAp8bmVFOC6xdEZZRwwI",
"g_0": "CAADBAADTg4AAoQxmFF07jR_vfB4xgI",
"g_1": "CAADBAADQg4AAhkgmFGlsif9nNtXwgI",
"g_2": "CAADBAAD2BUAAue_mFGENiPSjZxbiQI",
"g_3": "CAADBAADpw4AAjO9mFHAOz8KD2n7BwI",
"g_4": "CAADBAADRhAAAqF7kFEcwLalLfDfaAI",
"g_5": "CAADBAADAg8AAqXLmFHJyg2F_ybbvwI",
"g_6": "CAADBAADVhYAAtK7mVGigRq_EkCuVgI",
"g_7": "CAADBAAD2RIAArccmFEj-8LIVNAbsgI",
"g_8": "CAADBAAD6AwAAuvmmFHBRarMimOWawI",
"g_9": "CAADBAADExEAAsNkmVFr8DaHGOwsggI",
"g_draw": "CAADBAADhA8AArxYmVH9ch5Jp00AAboC",
"g_reverse": "CAADBAADMhAAAvVOmFGH284LIY7cegI",
"g_skip": "CAADBAADbBcAAqinkVEOwkJtDRfk2gI",
"b_0": "CAADBAAD-BAAAkj8kFG61GJdw29QOAI",
"b_1": "CAADBAADcRMAAu-EmFFT1i4LcqO4OQI",
"b_2": "CAADBAAD0xQAAqVhmVHyrFSAbxtfjwI",
"b_3": "CAADBAADNg0AAn-xmFHev8IdF_ie0wI",
"b_4": "CAADBAADlQ4AAjZamVFcIL_pVB5cFwI",
"b_5": "CAADBAADrgwAAuL5mVHvEBZ8CG5p5QI",
"b_6": "CAADBAADDhUAAuGRmVGQYvmEOxczBAI",
"b_7": "CAADBAADIxEAAv_dmFEuVt39kkgZgwI",
"b_8": "CAADBAAD2w0AAoE6kVHG7WscV4F2hwI",
"b_9": "CAADBAADvQ0AArRMmVErWaSRP_giKQI",
"b_draw": "CAADBAADlw4AAjF_kFHPWSoYKBwtwQI",
"b_reverse": "CAADBAADog8AAqDJmVEJQp5WocnUnQI",
"b_skip": "CAADBAAD-QwAAgbZmFGltUlnslDNUQI",
"y_0": "CAADBAADrQ4AAr5WmVHNf69eBn2YOAI",
"y_1": "CAADBAADcg8AAmqKmVHfVeUI3u_i7AI",
"y_2": "CAADBAADkA4AAuDImFEQ8qjFlcKplQI",
"y_3": "CAADBAAD-QwAAmromFGAqVn-Y8N72wI",
"y_4": "CAADBAADjQ4AAmNLmFG80k7kfgx1NAI",
"y_5": "CAADBAADqQ8AAmgYmFH1_ey_bMQNYwI",
"y_6": "CAADBAADdQ0AAuWcmFEbG_gm1wGYCQI",
"y_7": "CAADBAAD6QwAApQAAZhRI8OfRvLX3vkC",
"y_8": "CAADBAADARAAAi-2kVEifJ-O9WVilgI",
"y_9": "CAADBAADxA0AAhQ8mFHjnl9tUCHSLAI",
"y_draw": "CAADBAADzw4AAncZmVEhLhX17eqX8AI",
"y_reverse": "CAADBAADTxAAAqgFmVEJRBw4eWgnDwI",
"y_skip": "CAADBAADPhYAAiGbkFG9hptFPLgj7wI",
},
"not_playable": {
"colorchooser": "CAADBAADpQ4AAlfDmFFHGkwyGFeCFQI",
"draw_four": "CAADBAADMRMAAv7amFHvKGLoNyFbNQI",
"r_0": "CAADBAADsBMAAuGdkFHTZ-jl4eNn-gI",
"r_1": "CAADBAADVA4AAhpfkFEKt19qveGSPgI",
"r_2": "CAADBAADrw0AAoWsmVHguULNoYJwUwI",
"r_3": "CAADBAADzxMAAjvkkFFdtKJu5WGwUgI",
"r_4": "CAADBAAD1Q8AAoHZkFFvyQnFHzfwiQI",
"r_5": "CAADBAADWxEAAvkHkFGUo86qxKV0kwI",
"r_6": "CAADBAAD_hIAAjx0mVGmlm-b_FHQBAI",
"r_7": "CAADBAADmhEAAslomFHOv7bqcDJkDAI",
"r_8": "CAADBAADtw0AAgqVmVG2HdSbcJYxZgI",
"r_9": "CAADBAADNxEAAuF6mVE3WzTMJkSVAgI",
"r_draw": "CAADBAADVxAAAiNukFE1K2xORNnfMwI",
"r_reverse": "CAADBAADQxMAAvH0mVHKznpt-uu9ngI",
"r_skip": "CAADBAADZA4AApbPkFFB9E2Px-HFpgI",
"g_0": "CAADBAAD8w4AAjDEmFG7DwKggUEj9QI",
"g_1": "CAADBAAD2g0AAo_DmVHIPG84WdIo1wI",
"g_2": "CAADBAADEhEAAoRXmVGIG2nuN45P6AI",
"g_3": "CAADBAADug8AAsSRmFFzk0TcRuG8VAI",
"g_4": "CAADBAADrQ8AAvgmkFESfo9BjF7-3gI",
"g_5": "CAADBAADVhAAAnPqkFFtxtFX9HlT-AI",
"g_6": "CAADBAADMg8AAiSBmFHIQw1jFjv6UwI",
"g_7": "CAADBAADvREAAv0BkVGDq3H1DCq_DQI",
"g_8": "CAADBAADWQ4AAhOEkVG96JDgCtFrEwI",
"g_9": "CAADBAAD2xYAAruDmFFAUMFryEwjoAI",
"g_draw": "CAADBAADLA4AAu9tkVGTzBbeeYydIQI",
"g_reverse": "CAADBAADVAwAAhYYmFExJS0ozE8-rAI",
"g_skip": "CAADBAADYg4AAulsmFHxOkaz9OsTiwI",
"b_0": "CAADBAADVxUAAtnOkFEIAAGw5CZEIxgC",
"b_1": "CAADBAAD1RAAAnQqkFF9kDqD0wp3ngI",
"b_2": "CAADBAADZg4AAvcUmVHTXwldirf1hAI",
"b_3": "CAADBAADfBAAAkX1mVHw0CWX0h31iQI",
"b_4": "CAADBAADPBAAAuTCmFFDpvXzes4qjwI",
"b_5": "CAADBAADTQ4AAsWQmVHcrxDQUWOB4AI",
"b_6": "CAADBAAD_hAAAoUhmVG8kjd65J8EngI",
"b_7": "CAADBAADlRAAArtjkFGko5TuFNnncwI",
"b_8": "CAADBAADZQ8AAltEmFE_fDYIXBrV3QI",
"b_9": "CAADBAADrhAAAtM-mVGwhrWTD9IaYgI",
"b_draw": "CAADBAADtQ0AAnVbmFGC1hI60JaOQQI",
"b_reverse": "CAADBAADShEAAlcOmFHStPeFzfVIEwI",
"b_skip": "CAADBAAD_xEAAgZFmVFMRA1J8Y1gxAI",
"y_0": "CAADBAAD7xAAAqjjmFHnCu7eKJvSBgI",
"y_1": "CAADBAADJQwAAp6tmFE2zDPVMieQ2QI",
"y_2": "CAADBAADNA4AAl2mmVFpQOxJ41gk_gI",
"y_3": "CAADBAAD3A4AAsxPmFGyZFv42UlxAQI",
"y_4": "CAADBAADwg8AAm88kVEc9HZpl2gmzQI",
"y_5": "CAADBAAD5hIAAkQ6mFHS-aGVuYZAnAI",
"y_6": "CAADBAADvQ8AAs3RmVHVkVBfEF7eIwI",
"y_7": "CAADBAAD1gwAAjlbmFGGH6rBdqP8QQI",
"y_8": "CAADBAADbg8AAqvXkVH1ESeZFcGVrgI",
"y_9": "CAADBAADOQ8AAnjokVG96pmCP7aZ3AI",
"y_draw": "CAADBAAD6w4AAgsJmVETUteFwqTVJgI",
"y_reverse": "CAADBAADtg8AAqiFmFFwothyN9TrXwI",
"y_skip": "CAADBAADSxEAAhcSmFGu_F5LffmsZgI",
},
}
STICKERS_OPTIONS = {
"option_draw": "BQADBAAD-AIAAl9XmQABxEjEcFM-VHIC",
"option_pass": "BQADBAAD-gIAAl9XmQABcEkAAbaZ4SicAg",
"option_bluff": "BQADBAADygIAAl9XmQABJoLfB9ntI2UC",
"option_info": "BQADBAADxAIAAl9XmQABC5v3Z77VLfEC",
}
# TODO: Support multiple card packs
# For now, just use classic colorblind
STICKERS = {
'b_0': 'BQADBAAD2QEAAl9XmQAB--inQsYcLTsC',
'b_1': 'BQADBAAD2wEAAl9XmQABBzh4U-rFicEC',
'b_2': 'BQADBAAD3QEAAl9XmQABo3l6TT0MzKwC',
'b_3': 'BQADBAAD3wEAAl9XmQAB2y-3TSapRtIC',
'b_4': 'BQADBAAD4QEAAl9XmQABT6nhOuolqKYC',
'b_5': 'BQADBAAD4wEAAl9XmQABwRfmekGnpn0C',
'b_6': 'BQADBAAD5QEAAl9XmQABQITgUsEsqxsC',
'b_7': 'BQADBAAD5wEAAl9XmQABVhPF6EcfWjEC',
'b_8': 'BQADBAAD6QEAAl9XmQABP6baig0pIvYC',
'b_9': 'BQADBAAD6wEAAl9XmQAB0CQdsQs_pXIC',
'b_draw': 'BQADBAAD7QEAAl9XmQAB00Wii7R3gDUC',
'b_skip': 'BQADBAAD8QEAAl9XmQAB_RJHYKqlc-wC',
'b_reverse': 'BQADBAAD7wEAAl9XmQABo7D0B9NUPmYC',
'g_0': 'BQADBAAD9wEAAl9XmQABb8CaxxsQ-Y8C',
'g_1': 'BQADBAAD-QEAAl9XmQAB9B6ti_j6UB0C',
'g_2': 'BQADBAAD-wEAAl9XmQABYpLjOzbRz8EC',
'g_3': 'BQADBAAD_QEAAl9XmQABKvc2ZCiY-D8C',
'g_4': 'BQADBAAD_wEAAl9XmQABJB52wzPdHssC',
'g_5': 'BQADBAADAQIAAl9XmQABp_Ep1I4GA2cC',
'g_6': 'BQADBAADAwIAAl9XmQABaaMxxa4MihwC',
'g_7': 'BQADBAADBQIAAl9XmQABv5Q264Crz8gC',
'g_8': 'BQADBAADBwIAAl9XmQABjMH-X9UHh8sC',
'g_9': 'BQADBAADCQIAAl9XmQAB26fZ2fW7vM0C',
'g_draw': 'BQADBAADCwIAAl9XmQAB64jIZrgXrQUC',
'g_skip': 'BQADBAADDwIAAl9XmQAB17yhhnh46VQC',
'g_reverse': 'BQADBAADDQIAAl9XmQAB_xcaab0DkegC',
'r_0': 'BQADBAADEQIAAl9XmQABiUfr1hz-zT8C',
'r_1': 'BQADBAADEwIAAl9XmQAB5bWfwJGs6Q0C',
'r_2': 'BQADBAADFQIAAl9XmQABHR4mg9Ifjw0C',
'r_3': 'BQADBAADFwIAAl9XmQABYBx5O_PG2QIC',
'r_4': 'BQADBAADGQIAAl9XmQABTQpGrlvet3cC',
'r_5': 'BQADBAADGwIAAl9XmQABbdLt4gdntBQC',
'r_6': 'BQADBAADHQIAAl9XmQABqEI274p3lSoC',
'r_7': 'BQADBAADHwIAAl9XmQABCw8u67Q4EK4C',
'r_8': 'BQADBAADIQIAAl9XmQAB8iDJmLxp8ogC',
'r_9': 'BQADBAADIwIAAl9XmQAB_HCAww1kNGYC',
'r_draw': 'BQADBAADJQIAAl9XmQABuz0OZ4l3k6MC',
'r_skip': 'BQADBAADKQIAAl9XmQAC2AL5Ok_ULwI',
'r_reverse': 'BQADBAADJwIAAl9XmQABu2tIeQTpDvUC',
'y_0': 'BQADBAADKwIAAl9XmQAB_nWoNKe8DOQC',
'y_1': 'BQADBAADLQIAAl9XmQABVprAGUDKgOQC',
'y_2': 'BQADBAADLwIAAl9XmQABqyT4_YTm54EC',
'y_3': 'BQADBAADMQIAAl9XmQABGC-Xxg_N6fIC',
'y_4': 'BQADBAADMwIAAl9XmQABbc-ZGL8kApAC',
'y_5': 'BQADBAADNQIAAl9XmQAB67QJZIF6XAcC',
'y_6': 'BQADBAADNwIAAl9XmQABJg_7XXoITsoC',
'y_7': 'BQADBAADOQIAAl9XmQABVrd7OcS2k34C',
'y_8': 'BQADBAADOwIAAl9XmQABRpJSahBWk3EC',
'y_9': 'BQADBAADPQIAAl9XmQAB9MwJWKLJogYC',
'y_draw': 'BQADBAADPwIAAl9XmQABaPYK8oYg84cC',
'y_skip': 'BQADBAADQwIAAl9XmQABO_AZKtxY6IMC',
'y_reverse': 'BQADBAADQQIAAl9XmQABZdQFahGG6UQC',
'draw_four': 'BQADBAAD9QEAAl9XmQABVlkSNfhn76cC',
'colorchooser': 'BQADBAAD8wEAAl9XmQABl9rUOPqx4E4C',
'option_draw': 'BQADBAAD-AIAAl9XmQABxEjEcFM-VHIC',
'option_pass': 'BQADBAAD-gIAAl9XmQABcEkAAbaZ4SicAg',
'option_bluff': 'BQADBAADygIAAl9XmQABJoLfB9ntI2UC',
'option_info': 'BQADBAADxAIAAl9XmQABC5v3Z77VLfEC'
**CARDS_CLASSIC_COLORBLIND["normal"],
**STICKERS_OPTIONS,
}
STICKERS_GREY = {
'b_0': 'BQADBAADRQIAAl9XmQAB1IfkQ5xAiK4C',
'b_1': 'BQADBAADRwIAAl9XmQABbWvhTeKBii4C',
'b_2': 'BQADBAADSQIAAl9XmQABS1djHgyQokMC',
'b_3': 'BQADBAADSwIAAl9XmQABwQ6VTbgY-MIC',
'b_4': 'BQADBAADTQIAAl9XmQABAlKUYha8YccC',
'b_5': 'BQADBAADTwIAAl9XmQABMvx8xVDnhUEC',
'b_6': 'BQADBAADUQIAAl9XmQABDEbhP1Zd31kC',
'b_7': 'BQADBAADUwIAAl9XmQABXb5XQBBaAnIC',
'b_8': 'BQADBAADVQIAAl9XmQABgL5HRDLvrjgC',
'b_9': 'BQADBAADVwIAAl9XmQABtO3XDQWZLtYC',
'b_draw': 'BQADBAADWQIAAl9XmQAB2kk__6_2IhMC',
'b_skip': 'BQADBAADXQIAAl9XmQABEGJI6CaH3vcC',
'b_reverse': 'BQADBAADWwIAAl9XmQAB_kZA6UdHXU8C',
'g_0': 'BQADBAADYwIAAl9XmQABGD5a9oG7Yg4C',
'g_1': 'BQADBAADZQIAAl9XmQABqwABZHAXZIg0Ag',
'g_2': 'BQADBAADZwIAAl9XmQABTI3mrEhojRkC',
'g_3': 'BQADBAADaQIAAl9XmQABVi3rUyzWS3YC',
'g_4': 'BQADBAADawIAAl9XmQABZIf5ThaXnpUC',
'g_5': 'BQADBAADbQIAAl9XmQABNndVJSQCenIC',
'g_6': 'BQADBAADbwIAAl9XmQABpoy1c4ZkrvwC',
'g_7': 'BQADBAADcQIAAl9XmQABDeaT5fzxwREC',
'g_8': 'BQADBAADcwIAAl9XmQABLIQ06ZM5NnAC',
'g_9': 'BQADBAADdQIAAl9XmQABel-mC7eXGsMC',
'g_draw': 'BQADBAADdwIAAl9XmQABOHEpxSztCf8C',
'g_skip': 'BQADBAADewIAAl9XmQABDaQdMxjjPsoC',
'g_reverse': 'BQADBAADeQIAAl9XmQABek1lGz7SJNAC',
'r_0': 'BQADBAADfQIAAl9XmQABWrxoiXcsg0EC',
'r_1': 'BQADBAADfwIAAl9XmQABlav-bkgSgRcC',
'r_2': 'BQADBAADgQIAAl9XmQABDjZkqfJ4AdAC',
'r_3': 'BQADBAADgwIAAl9XmQABT7lH7VVcy3MC',
'r_4': 'BQADBAADhQIAAl9XmQAB1arPC5x0LrwC',
'r_5': 'BQADBAADhwIAAl9XmQABWvs7xkCDldkC',
'r_6': 'BQADBAADiQIAAl9XmQABjwABH5ZonWn8Ag',
'r_7': 'BQADBAADiwIAAl9XmQABjekJfm4fBDIC',
'r_8': 'BQADBAADjQIAAl9XmQABqFjchpsJeEkC',
'r_9': 'BQADBAADjwIAAl9XmQAB-sKdcgABdNKDAg',
'r_draw': 'BQADBAADkQIAAl9XmQABtw9RPVDHZOQC',
'r_skip': 'BQADBAADlQIAAl9XmQABtG2GixCxtX4C',
'r_reverse': 'BQADBAADkwIAAl9XmQABz2qyEbabnVsC',
'y_0': 'BQADBAADlwIAAl9XmQABAb3ZwTGS1lMC',
'y_1': 'BQADBAADmQIAAl9XmQAB9v5qJk9R0x8C',
'y_2': 'BQADBAADmwIAAl9XmQABCsgpRHC2g-cC',
'y_3': 'BQADBAADnQIAAl9XmQAB3kLLXCv-qY0C',
'y_4': 'BQADBAADnwIAAl9XmQAB7R_y-NexNLIC',
'y_5': 'BQADBAADoQIAAl9XmQABl-7mwsjD-cMC',
'y_6': 'BQADBAADowIAAl9XmQABwbVsyv2MfPkC',
'y_7': 'BQADBAADpQIAAl9XmQABoBqC0JsemVwC',
'y_8': 'BQADBAADpwIAAl9XmQABpkwAAeh9ldlHAg',
'y_9': 'BQADBAADqQIAAl9XmQABpSBEUfd4IM8C',
'y_draw': 'BQADBAADqwIAAl9XmQABMt-2zW0VYb4C',
'y_skip': 'BQADBAADrwIAAl9XmQABIDf-_TuuxtEC',
'y_reverse': 'BQADBAADrQIAAl9XmQABm9M0Zh-_UwkC',
'draw_four': 'BQADBAADYQIAAl9XmQAB_HWlvZIscDEC',
'colorchooser': 'BQADBAADXwIAAl9XmQABY_ksDdMex-wC'
**CARDS_CLASSIC_COLORBLIND["not_playable"],
}

8
docker-compose.yml Normal file
View File

@ -0,0 +1,8 @@
version: "3.9"
services:
unobot:
container_name: unobot
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped

View File

@ -58,7 +58,7 @@ class Game(object):
current_player = self.current_player
itplayer = current_player.next
players.append(current_player)
while itplayer and itplayer is not current_player:
while itplayer and itplayer != current_player:
players.append(itplayer)
itplayer = itplayer.next
return players
@ -121,7 +121,7 @@ class Game(object):
self.logger.debug("Draw counter increased by 2")
elif card.value == c.REVERSE:
# Special rule for two players
if self.current_player is self.current_player.next.next:
if self.current_player == self.current_player.next.next:
self.turn()
else:
self.reverse()

View File

@ -110,7 +110,7 @@ class GameManager(object):
for g in games:
for p in g.players:
if p.user.id == user.id:
if p is g.current_player:
if p == g.current_player:
g.turn()
p.leave()

View File

@ -0,0 +1,4 @@
{
"api_id": 0,
"api_hash": ""
}

View File

@ -0,0 +1,92 @@
"""
Script to build the classic colorblind deck from the classic deck.
Requires imagemagick to be installed and in the path.
"""
from pathlib import Path
from shutil import copyfile
from subprocess import run
IMAGES_DIR = Path(__file__).resolve().parent
CLASSIC_DIR = IMAGES_DIR / "classic"
COLORBLIND_DIR = IMAGES_DIR / "classic_colorblind"
COLORBLIND_OVERLAY_DIR = IMAGES_DIR / "colorblind_overlay"
COLORS = ["r", "g", "b", "y"]
NUMBERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "draw", "reverse", "skip"]
SPECIALS = ["colorchooser", "draw_four"]
def overlay_image(color, number):
base = CLASSIC_DIR / "png" / f"{color}_{number}.png"
overlay = COLORBLIND_OVERLAY_DIR / f"{color}.png"
out = COLORBLIND_DIR / "png" / f"{color}_{number}.png"
run(["magick", "convert", str(base), str(overlay), "-composite", str(out)])
def create_not_playable(card):
base = COLORBLIND_DIR / "png" / f"{card}.png"
overlay = COLORBLIND_OVERLAY_DIR / "not_playable.png"
out = COLORBLIND_DIR / "png_not_playable" / f"{card}.png"
run(
[
"magick",
"convert",
str(base),
"-modulate",
"75,20",
"-brightness-contrast",
"0x10",
str(overlay),
"-composite",
str(out),
]
)
def convert_png_to_webp(suffix):
for color in COLORS:
for number in NUMBERS:
card = f"{color}_{number}"
png = COLORBLIND_DIR / f"png{suffix}" / f"{card}.png"
webp = COLORBLIND_DIR / f"webp{suffix}" / f"{card}.webp"
run(["magick", "convert", str(png), "-define", "webp:lossless=true", str(webp)])
for special in SPECIALS:
png = COLORBLIND_DIR / f"png{suffix}" / f"{special}.png"
webp = COLORBLIND_DIR / f"webp{suffix}" / f"{special}.webp"
run(["magick", "convert", str(png), "-define", "webp:lossless=true", str(webp)])
def main():
(COLORBLIND_DIR / "png").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "png_not_playable").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "webp").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "webp_not_playable").mkdir(parents=True, exist_ok=True)
for color in COLORS:
for number in NUMBERS:
overlay_image(color, number)
for special in SPECIALS:
copyfile(
CLASSIC_DIR / "png" / f"{special}.png",
COLORBLIND_DIR / "png" / f"{special}.png",
)
for color in COLORS:
for number in NUMBERS:
create_not_playable(f"{color}_{number}")
for special in SPECIALS:
create_not_playable(special)
convert_png_to_webp("")
convert_png_to_webp("_not_playable")
if __name__ == "__main__":
main()

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Some files were not shown because too many files have changed in this diff Show More