commit f43bda37ec64f8dad41471fb010354c34ae0f30b Author: Jonáš Havelka Date: Mon Oct 16 07:38:57 2023 +0200 První funkční verze diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..228b148 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# Z kódu webu M&M +# Univerzální popis našich konvencí pro nastavení editorů. +# Vizte https://editorconfig.org pro detaily + +root = true + +[*] +charset = utf-8 +# Unixové řádky +end_of_line = lf +insert_final_newline = true + +[*.{py,css}] +indent_style = tab +# Nenařizujeme konkrétní šířku tabulátoru +indent_size = unset + +# Automaticky generované migrace dodržují PEP-8, nemá smysl s tím moc bojovat. +[*/migrations/*.py] +indent_style = space +indent_size = 4 + +[*.html] +indent_style = space +indent_size = 2 + +[*.json] +indent_style = tab +indent_size = unset diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04c1055 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Inspirováno MaMwebem +# virtual environment +/venv/ +/env/ + +# databáze +/*.sqlite3 + +# složky djanga +/static +/media + +# aux files +*.pyc +*.sw[mnop] + +# vim tmp files +*~ + +# pro lidi, co programují v nástrojích od JetBrains +.idea diff --git a/.requirements b/.requirements new file mode 100644 index 0000000..8837bc3 --- /dev/null +++ b/.requirements @@ -0,0 +1,2 @@ +django +qrcode diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d32319 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Byrokracie +Hra byrokracie v elektrizované verzi + +***Aktuálně je kód velmi fujky!!!*** + + +## Jak hrát +Při prvním spuštění + +- `./manage.py migrate` +- `./manage.py loaddata data/users.json` +- `./manage.py loaddata data/stanoviste.json` +- `./manage.py loaddata data/tymy.json` + +Rozběhnutí hry (hesla a uživatelská jména viz 'Hesla' níže, lze je upravit v adminu) +- `./manage.py runserver` +- v adminu (<127.0.0.1:8000/admin/>) přidat týmy / resetovat jejich start na aktuální datum a čas (nebojte, inventář se tam nezobrazuje) +- následně + - (pokud je `DEBUG = True`), pak stanoviště jsou na + - , + - , + - , + - , + - , + - + - jinak se musí na každém zařízení přihlásit (na ) jako stanoviště (viz 'Hesla' níže) +- tým po přihlášení (na ) vidí svoji domovskou stránku a po načtení qr kódu stanoviště příslušné stanoviště + +Skvělou zábavu :) + + +## Vyšetřování ztráty třídní knihy specific věci +***Neprocházet tyto části kódu, pokud chcete hru testovat!!!*** +- `data/stanoviste.json` (aktuálně pouze názvy stanovišť a viditelný popis stanovišť + název templatu) +- `hra/templates/hra/stanoviste/*` (kromě `**/base`, logika za zobrazováním textů) +- `hra/logika.py` (logika za tlačítky, tedy kdy mají být která tlačítka a jak se má změnit inventář po kliknutí na ně) +- `hra/templates/hra/inventar.html` (itemy, které může tým vlastnit) +- `hra/models.py` (od komentáře `# Inventář`, databázový záznam inventáře) +- `hra/migrations/*` (vyplývá z předchozího) + + +## Hesla +(uzivatelske_jmeno: heslo) +- `admin`: `modraplaneta` +- stanoviste + - `s1, s2, ..., sn`: `asd` +- tymy: + - `t1`: `azurova` + - `t2`: `okrova` + - `t3`: `tyrkysova` + - `t4`: `kastanova` + - `t5`: `oriskova` + - `t6`: `zulova` + - `t7`: `matova` + - `t8`: `smaragdova` + - `t9`: `cihlova` diff --git a/autentizace/__init__.py b/autentizace/__init__.py new file mode 100644 index 0000000..133e988 --- /dev/null +++ b/autentizace/__init__.py @@ -0,0 +1 @@ +# Inspirováno kódem webu M&M diff --git a/autentizace/templates/autentizace/login.html b/autentizace/templates/autentizace/login.html new file mode 100644 index 0000000..cf550a2 --- /dev/null +++ b/autentizace/templates/autentizace/login.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +

{{ settings.NAZEV }}

+

{% block nadpis %}Přihlášení{% endblock %}

+ + {# Obšlehnuto z Admina :-) #} + {% if user.is_authenticated %} {# Tedy když byl uživatel na stránku přesměrován z pro něj nepřístupné části webu. FIXME? Nedává smysl, pokud uživatel otevřel mam.mff.cuni.cz/prihlasit například z historie prohlížeče = nebyl na ni přesměrován. #} +

K této stránce nejspíš nemáte přístup. Můžete se zkusit přihlásit jako uživatel, který přístup má.

+ {% endif %} + +
+ {% csrf_token %} + + {{ form.as_table }} +
+
+ {# Django si posílá jméno další stránky jako obsah formuláře a výchozí hodnota (mi přišlo, že) nejde změnit... #} + + +
+ + {#

#} + {#
#} + {# #} + {#
#} +{% endblock %} diff --git a/autentizace/templates/autentizace/logout.html b/autentizace/templates/autentizace/logout.html new file mode 100644 index 0000000..a1b76c0 --- /dev/null +++ b/autentizace/templates/autentizace/logout.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +

{% block nadpis %}Odhlášení{% endblock %}

+

Byl jsi úspěšně odhlášen

+{% endblock %} + diff --git a/autentizace/urls.py b/autentizace/urls.py new file mode 100644 index 0000000..d5dce89 --- /dev/null +++ b/autentizace/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views +from django.views.generic.base import RedirectView + +urlpatterns = [ + path("prihlasit/", views.LoginView.as_view(), name="login"), + path("login/", RedirectView.as_view(pattern_name="login", permanent=True, query_string=True)), + path("odhlasit/", views.LogoutView.as_view(), name="logout"), + path("logout/", RedirectView.as_view(pattern_name="logout", permanent=True, query_string=True)), +] diff --git a/autentizace/views.py b/autentizace/views.py new file mode 100644 index 0000000..34eef7e --- /dev/null +++ b/autentizace/views.py @@ -0,0 +1,14 @@ +from django.urls import reverse_lazy +from django.contrib.auth import views as auth_views + + +class LoginView(auth_views.LoginView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = "autentizace/login.html" + + +class LogoutView(auth_views.LogoutView): + # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL + template_name = "autentizace/logout.html" + # Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité. + next_page = reverse_lazy("login") diff --git a/byrokracie/__init__.py b/byrokracie/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/byrokracie/context_processor.py b/byrokracie/context_processor.py new file mode 100644 index 0000000..26251f1 --- /dev/null +++ b/byrokracie/context_processor.py @@ -0,0 +1,6 @@ +from django.conf import settings + + +def add_settings(_): + """ Přidá `settings` do templatů. """ + return {"settings": settings} diff --git a/byrokracie/settings.py b/byrokracie/settings.py new file mode 100644 index 0000000..603b6c2 --- /dev/null +++ b/byrokracie/settings.py @@ -0,0 +1,99 @@ +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / "subdir". +BASE_DIR = Path(__file__).resolve().parent.parent + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-opcz6rfrp-osm7wnd@3_5yb56^4=!@7@-(38j^!6^ej%88r2ud" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +# ALLOWED_HOSTS = ['192.168.137.1' ] + +WSGI_APPLICATION = 'byrokracie.wsgi.application' + +STATIC_ROOT = 'static/' +STATIC_URL = 'static/' + +# Jednotlivé části djanga +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "byrokracie", + "autentizace", + "hra", +] +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'byrokracie.context_processor.add_settings' + ], + }, + }, +] + + +# Databáze +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Lokalizace +LANGUAGE_CODE = 'cs' +TIME_ZONE = 'Europe/Prague' +USE_L10N = True # S přechodem k djangu>=4 lze smazat (localized formatting) +USE_TZ = True # S přechodem k djangu>=5 lze smazat (timezone aware datetimes) + +# URL pro přihlášení (default je account/login) +LOGIN_URL = 'login' +LOGIN_REDIRECT_URL = '/hra/' + +# Konstantny +NAZEV: str = "Vyšetřování ztráty třídní knihy" +NEJVYSSI_INDEX: int = 6 +PRVNI_PONDELI: int = 2 +PRVNI_NOV: int = 13 +TIMEOUT_STANOVISTE: int = 60 # v sekundách +TYM_AUTO_RELOAD: int = 30 # v sekundách +TIME_SPEED: float = 1.0 # 1 = hodina za minutu +DEN: tuple[float, float] = (8.0, 18.0) +POCET_BANANU: int = 2 + +POSTOVNA_DEN_V_TYDNU = 3 + +STASTNA_CISLA: set[int] = {1, 7, 10, 13, 19, 23, 28, 31, 32, 44, 49, 68, 70, 79, 82, 86, 91, 94, 97, 100, 103, 109, 129, 130, 133, 139, 167, 176, 188, 190, 192, 193, 203, 208, 219, 226, 230, 236, 239, 262, 263, 280, 291, 293, 301, 302, 310, 313, 319, 320, 326, 329, 331, 338, 356, 362, 365, 367, 368, 376, 379, 383, 386, 391, 392, 397, 404, 409, 440, 446, 464, 469, 478, 487, 490, 496, 536, 556, 563, 565, 566, 608, 617, 622, 623, 632, 635, 637, 638, 644, 649, 653, 655, 656, 665, 671, 673, 680, 683, 694, 700, 709, 716, 736, 739, 748, 761, 763, 784, 790, 793, 802, 806, 818, 820, 833, 836, 847, 860, 863, 874, 881, 888, 899, 901, 904, 907, 910, 912, 913, 921, 923, 931, 932, 937, 940, 946, 964, 970, 973, 989, 998, 1000} +# nejasno = [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0] +# [nejasno[i] * (random.choice(list(range(3))) + 1) for i in range(len(nejasno))] +POCASI: list[int] = [3, 1, 3, 2, 2, 2, 0, 3, 2, 1, 0, 2, 1, 0, 3, 2, 3, 0, 2, 3, 3, 1, 2, 0] + + +# Django musí mít nastaveno Autofield +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# A musí mít nastavené počáteční urls +ROOT_URLCONF = 'byrokracie.urls' diff --git a/byrokracie/static/css/base.css b/byrokracie/static/css/base.css new file mode 100644 index 0000000..69d0e6f --- /dev/null +++ b/byrokracie/static/css/base.css @@ -0,0 +1,39 @@ +table { + margin-left: auto; + margin-right: auto; +} + +th { + text-align: right; +} + +.content { + text-align: center; +} + +.ds { /* Zobrazení datové schránky na stránce týmu */ + margin-top: -1em; +} + +.inventar { + width: fit-content; + margin-left: auto; + margin-right: auto; +} + +.polozka_inventare { + float: left; + min-width: 10vw; + min-height: 15vw; + border: orange 1pt solid; + + margin: 1vh; + + display: flex; + align-items: center; + justify-content: center; +} + +li { + text-align: left; +} diff --git a/byrokracie/static/images/icon.png b/byrokracie/static/images/icon.png new file mode 100644 index 0000000..42ff7dd Binary files /dev/null and b/byrokracie/static/images/icon.png differ diff --git a/byrokracie/templates/base.html b/byrokracie/templates/base.html new file mode 100644 index 0000000..b89d1c4 --- /dev/null +++ b/byrokracie/templates/base.html @@ -0,0 +1,26 @@ +{% load static %} + + + + {% block nadpis %}{% endblock %} + + + + + + {# (Autoreload:) = Refresh every 30 second #} + {% block prenacitani %}{% endblock %} + + {# script specifický pro stránku #} + {% block script %}{% endblock %} + + + +
+ {% block content %} + {% endblock content %} +
+ + + + diff --git a/byrokracie/urls.py b/byrokracie/urls.py new file mode 100644 index 0000000..344c093 --- /dev/null +++ b/byrokracie/urls.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from django.shortcuts import redirect +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('autentizace.urls')), + path('hra/', include('hra.urls')), + path('', lambda _request: redirect('hra/', permanent=True)), +] diff --git a/byrokracie/wsgi.py b/byrokracie/wsgi.py new file mode 100644 index 0000000..5a9d72b --- /dev/null +++ b/byrokracie/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for byrokracie project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'byrokracie.settings') + +application = get_wsgi_application() diff --git a/data/stanoviste.json b/data/stanoviste.json new file mode 100644 index 0000000..3953af5 --- /dev/null +++ b/data/stanoviste.json @@ -0,0 +1,68 @@ +[ + { + "fields": { + "nazev": "Časový výr 🦉", + "poradi": 1, + "user": 2, + "verejny_text": "

Posun ve čtvrté dimenzi.

", + "template": "casovy_vyr.html" + }, + "model": "hra.stanoviste", + "pk": 1 + }, + { + "fields": { + "nazev": "Internát", + "poradi": 2, + "user": 3, + "verejny_text": "

Domov, sladký domov.

", + "template": "domov.html" + }, + "model": "hra.stanoviste", + "pk": 2 + }, + { + "fields": { + "nazev": "Potrubní pošta akademie ✉️🪠", + "poradi": 3, + "user": 4, + "verejny_text": "

Podobnost s Českou poštou je čistě náhodná.

Otevřeno: nonstop

", + "template": "posta.html" + }, + "model": "hra.stanoviste", + "pk": 3 + }, + { + "fields": { + "nazev": "Studijní oddělení", + "poradi": 4, + "user": 5, + "verejny_text": "

Žádosti všeho druhu.

Otevřeno: Po–Čt: 8:00 – 16:00, Pá: 8:00 – 14:00

", + "template": "studijni.html" + }, + "model": "hra.stanoviste", + "pk": 4 + }, + { + "fields": { + "nazev": "Jídelna", + "poradi": 5, + "user": 6, + "verejny_text": "

\uD83E\uDD6C\uD83E\uDD57\uD83C\uDF55\uD83E\uDD53\uD83C\uDF69\uD83C\uDF68\uD83C\uDF6A\uD83C\uDF82\uD83C\uDF4C\uD83C\uDF70\uD83C\uDF6B\uD83C\uDF67\uD83C\uDF66\uD83C\uDF61\uD83C\uDF60\uD83E\uDED1

Vaříme denně

Snídaně: 8:00 – 11:00

Obědy: 11:00 – 17:00

Večeře: 17:00 – 22:00

", + "template": "menza.html" + }, + "model": "hra.stanoviste", + "pk": 5 + }, + { + "fields": { + "nazev": "Knihovna & Archiv", + "poradi": 6, + "user": 7, + "verejny_text": "

Otevřeno: Po–Čt: 8:00 – 16:00, Pá: 8:00 – 14:00

Ook!?

", + "template": "archiv.html" + }, + "model": "hra.stanoviste", + "pk": 6 + } +] diff --git a/data/tymy.json b/data/tymy.json new file mode 100644 index 0000000..af00729 --- /dev/null +++ b/data/tymy.json @@ -0,0 +1,83 @@ +[ + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 8 + }, + "model": "hra.tym", + "pk": 1 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 9 + }, + "model": "hra.tym", + "pk": 2 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 10 + }, + "model": "hra.tym", + "pk": 3 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 11 + }, + "model": "hra.tym", + "pk": 4 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 12 + }, + "model": "hra.tym", + "pk": 5 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 13 + }, + "model": "hra.tym", + "pk": 6 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 14 + }, + "model": "hra.tym", + "pk": 7 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 15 + }, + "model": "hra.tym", + "pk": 8 + }, + { + "fields": { + "hraci": "", + "nazev": "Nepojmenovaní", + "user": 16 + }, + "model": "hra.tym", + "pk": 9 + } +] diff --git a/data/users.json b/data/users.json new file mode 100644 index 0000000..06f2d1c --- /dev/null +++ b/data/users.json @@ -0,0 +1,178 @@ +[ + { + "fields": { + "is_active": true, + "is_staff": true, + "is_superuser": true, + "password": "pbkdf2_sha256$600000$yylg8H03nCOuMEmo7X4V6A$Za9qMeX44zvjYtIwyAlw5QLJQCEy+vDxlXDRAZqhZ2c=", + "username": "admin" + }, + "model": "auth.user", + "pk": 1 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s1" + }, + "model": "auth.user", + "pk": 2 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s2" + }, + "model": "auth.user", + "pk": 3 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s3" + }, + "model": "auth.user", + "pk": 4 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s4" + }, + "model": "auth.user", + "pk": 5 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s5" + }, + "model": "auth.user", + "pk": 6 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$BdSaaScmKfh6Qu9xoEqp3f$96RlyOuK8lHlD5hVpfDEYJVmSxaby3O8wIT0/1MQM1k=", + "username": "s6" + }, + "model": "auth.user", + "pk": 7 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$y2SzlaUhvzQx5MAaAPg8eR$x99VQuFAPQymfie3rNJQPIXJlmJZZ7GaX2Q8CoS+HLo=", + "username": "t1" + }, + "model": "auth.user", + "pk": 8 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$Dzb2XnXVY2FClbyYXXOD8V$MhI+tOVRXZBfY8OPaaWG1WMk0vMrIDpZguMp/X0udv8=", + "username": "t2" + }, + "model": "auth.user", + "pk": 9 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$uePowUck1EhZwiWMK3TJ2c$zGAiYICEZ46pSxtO8MF/cAawcT+UnazBcWjAiYFbTFk=", + "username": "t3" + }, + "model": "auth.user", + "pk": 10 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$UiEFSvRON6lWpzpp3eqlsS$CKvQO8L2Aelucmjp1Aoz5qTe8T18jMWGE4qH72Cihpw=", + "username": "t4" + }, + "model": "auth.user", + "pk": 11 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$L01lowUNtthy22eTVLaUrz$dp0SKguUiN5wM7gHhpW6WDhGofdIHdRj3aOQfrlih2o=", + "username": "t5" + }, + "model": "auth.user", + "pk": 12 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$sOCpNhTjfrLwoA9p1KlxAf$GA6njrzD0qj6Sxa/6wWVm83CdDhZw4gjg5SMKYPtKPo=", + "username": "t6" + }, + "model": "auth.user", + "pk": 13 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$cUKVLoph1a4lo8vWsexHV0$9bhzUjWezsPhZFmNG7gjg4WCR2Yrou68yDG+4joUXF0=", + "username": "t7" + }, + "model": "auth.user", + "pk": 14 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$zoKRalZSKeVYAe9yKq0Jk2$1sS7fJsiUUweoFL8nb7WLv0ZIEpnkpSjYLL2HLev/Gg=", + "username": "t8" + }, + "model": "auth.user", + "pk": 15 + }, + { + "fields": { + "is_active": true, + "is_staff": false, + "is_superuser": false, + "password": "pbkdf2_sha256$600000$N27QVcoRhkcto7JA3ygQtG$1QSCWn8Lyk+UiJyJDYr3WWYbcPP8YqGxQj4JoeyAxoc=", + "username": "t9" + }, + "model": "auth.user", + "pk": 16 + } +] diff --git a/fix_json.py b/fix_json.py new file mode 100644 index 0000000..411349f --- /dev/null +++ b/fix_json.py @@ -0,0 +1,12 @@ +#!/usr/bin/python3 + +import json +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("input", type=argparse.FileType('r', encoding='utf-8')) +parser.add_argument('output', type=argparse.FileType('w', encoding='utf-8')) +args = parser.parse_args() + +data = json.load(args.input) +json.dump(data, args.output, ensure_ascii=False, sort_keys=True, indent='\t') diff --git a/hra/__init__.py b/hra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hra/admin.py b/hra/admin.py new file mode 100644 index 0000000..8ad7851 --- /dev/null +++ b/hra/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from .models import Stanoviste, Tym, Zadost + +admin.site.register(Stanoviste) +admin.site.register(Zadost) + + +class TymAdmin(admin.ModelAdmin): + # fields = ["user", "nazev", "hraci", "start"] + pass + + +admin.site.register(Tym, TymAdmin) diff --git a/hra/logika.py b/hra/logika.py new file mode 100644 index 0000000..b365e63 --- /dev/null +++ b/hra/logika.py @@ -0,0 +1,260 @@ +from random import random +from typing import Callable, Iterable + +from django.conf import settings + +from .models import Tym, Zadost +from .utils.doba import Doba + +# Velká část logiky je přímo na `.models.Tym`! + + +def zadosti(tym: Tym, akce: str, choices: list, zadost: str = "Žádost", suffix: str = ""): + choices.append(f"{akce} {zadost}{suffix}") + + if tym.zadosti.filter(typ=Zadost.TYP_BANAN, vyzvednuta=True).count() == 0: + choices.append(f"{akce} {zadost} o banán{suffix}") + + if tym.vi_o_mase and tym.zadosti.filter(typ=Zadost.TYP_VEGE, vyzvednuta=True).count() == 0: + choices.append(f"{akce} {zadost} o více vegetariánskou stravu v jídelně{suffix}") + + if tym.vi_o_zadosti and tym.zadosti.filter(typ=Zadost.TYP_TK, vyzvednuta=True, plati=True).count() == 0: + choices.append(f"{akce} {zadost} o přístup k záznamům o třídní knize{suffix}") + + +def get_posible_choices_casovy_vyr(doba: Doba, tym: Tym) -> list[str]: + choices = ["Dopředu o hodinu", "Dopředu o 3 hodiny"] + + if doba > 3*doba.DEN: + choices.append("Dopředu o den") + + if doba > doba.TYDEN: + choices.append("Dopředu o týden") + + if tym.tk_nalezena is not None: + choices += ["Dozadu o hodinu", "Dozadu o 3 hodiny", "Dozadu o den", "Dozadu o týden"] + + return choices + + +def get_posible_choices_domov(doba: Doba, tym: Tym) -> list[str]: + choices = [] + + if tym.nevyzvednute_zadosti.count() > 0: + choices.append("Vyzvednout poštu") + + if doba.je_noc: + choices.append("Spát") + + if not tym.ma_penize: + choices.append("Vzít si chechtáky") + + zadosti(tym, "Tisk", choices, zadost="Žádosti") + + if tym.ma_ds: + zadosti(tym, "Podat", choices, suffix=" datovou schránkou") + + return choices + + +def get_posible_choices_posta(doba: Doba, tym: Tym) -> list[str]: + if tym.ds_bad is not None and doba - tym.ds_bad < 5: + return [] + + choices = [] + + if tym.zadosti.filter(vytistena=True).count() > 0: + choices.append("Poslat vytištěné žádosti") + + if tym.nevyzvednute_zadosti.count() > 0: + choices.append("Zeptat se, zda pro nás mají dopis") + + if not tym.ma_ds: + choices.append("Zažádat o datovou schránku") + + if tym.ds_bad is not None: + choices.append("Zažádat o datovou schránku (šeptem)") + + return choices + + +def get_posible_choices_studijni(doba: Doba, tym: Tym) -> list[str]: + if ( + doba.cas < 8 or + doba.cas > 15 or + doba.den_v_tydnu >= 6 or + (doba.den_v_tydnu == 5 and doba.cas > 13) + ): + return [] + + choices = [] + + zadosti(tym, "Podat", choices) + + if tym.nevyzvednute_zadosti.count(): + choices.append("Zeptat se na poslané žádosti") + + return choices + + +def get_posible_choices_jidelna(doba: Doba, tym: Tym) -> list[str]: + if doba.cas < 8 or doba.cas > 22: + return [] + + choices = ["Klábosit"] + + if tym.pocet_bananu < settings.POCET_BANANU: + choices.append("Poprosit o banán") + + return choices + + +def get_posible_choices_archiv(doba: Doba, tym: Tym) -> list[str]: + choices = [] + + if not ( + doba.cas < 8 or + doba.cas > 16 or + doba.den_v_tydnu >= 6 or + (doba.den_v_tydnu == 5 and doba.cas > 14) + ): + if tym.ma_penize: + choices.append("Uplatit knihovníka penězi") + + if tym.pocet_bananu > 0 and not tym.vi_o_zadosti: + choices.append("Dát banán knihovníkovi") + + if tym.ma_platnou_zadost(Zadost.TYP_TK) and tym.tk_nalezena is None: + choices.append("Ukázat knihovníkovi povolení k náhledu do záznamů o třídní knize") + + choices.append("Přelézt pult a sami si vzít záznamy o třídní knize") + choices.append("Pokusit se sami projít kartotéku") + + return choices + + +def banan(_: Doba, tym: Tym) -> None: + if (tym.pocet_bananu == 0 and tym.ma_platnou_zadost(Zadost.TYP_BANAN)) or tym.ma_platnou_zadost(Zadost.TYP_VEGE): + tym.pocet_bananu += 1 + tym.save() + if tym.pocet_bananu == 1: + print("ALERT: BANÁN!!!") + elif tym.pocet_bananu == 1: + tym.vi_o_mase = True + tym.save() + elif tym.pocet_bananu == 0: + tym.ovesna_kase += 1 + tym.save() + + +get_posible_choices: list[Callable[[Doba, Tym], list[str]]] = [ + # 0: + None, # poradi začíná od 1 + # 1: + get_posible_choices_casovy_vyr, + # 2: + get_posible_choices_domov, + # 3: + get_posible_choices_posta, + # 4: + get_posible_choices_studijni, + # 5: + get_posible_choices_jidelna, + # 6: + get_posible_choices_archiv, +] + + +apply_choice: list[dict[str, Callable[[Doba, Tym], Iterable[Zadost] | None]]] = [ + # 0: + {}, # poradi začíná od 1 + # 1: + { + "Dopředu o hodinu": + lambda _, tym: tym.pricti_cas_a_uloz(1), + "Dopředu o 3 hodiny": + lambda _, tym: tym.pricti_cas_a_uloz(3), + "Dopředu o den": + lambda _, tym: tym.pricti_cas_a_uloz(Doba.DEN), + "Dopředu o týden": + lambda _, tym: tym.pricti_cas_a_uloz(Doba.TYDEN), + "Dozadu o hodinu": + lambda _, tym: tym.pricti_cas_a_uloz(-1), + "Dozadu o 3 hodiny": + lambda _, tym: tym.pricti_cas_a_uloz(-3), + "Dozadu o den": + lambda _, tym: tym.pricti_cas_a_uloz(-Doba.DEN), + "Dozadu o týden": + lambda _, tym: tym.pricti_cas_a_uloz(-Doba.TYDEN), + }, + # 2: + { + "Vyzvednout poštu": + lambda doba, tym: tym.vyzvedni_internat(doba), + "Spát": + lambda doba, tym: tym.pricti_cas_a_uloz((settings.DEN[0] - doba.cas + 24) % 24), + "Vzít si chechtáky": + lambda _, tym: tym.nastav_a_uloz("ma_penize", True), + "Tisk Žádosti": + lambda _, tym: tym.vytiskni_zadost(Zadost.TYP_ZADOST), + "Tisk Žádosti o banán": + lambda _, tym: tym.vytiskni_zadost(Zadost.TYP_BANAN), + "Tisk Žádosti o více vegetariánskou stravu v jídelně": + lambda _, tym: tym.vytiskni_zadost(Zadost.TYP_VEGE), + "Tisk Žádosti o přístup k záznamům o třídní knize": + lambda _, tym: tym.vytiskni_zadost(Zadost.TYP_TK), + "Podat Žádost datovou schránkou": + lambda doba, tym: tym.posli_zadost_ds(doba, Zadost.TYP_ZADOST), + "Podat Žádost o banán datovou schránkou": + lambda doba, tym: tym.posli_zadost_ds(doba, Zadost.TYP_BANAN), + "Podat Žádost o více vegetariánskou stravu v jídelně datovou schránkou": + lambda doba, tym: tym.posli_zadost_ds(doba, Zadost.TYP_VEGE), + "Podat Žádost o přístup k záznamům o třídní knize datovou schránkou": + lambda doba, tym: tym.posli_zadost_ds(doba, Zadost.TYP_TK), + }, + # 3: + { + "Poslat vytištěné žádosti": + lambda doba, tym: tym.poslat_vytistene_zadosti(doba), + "Zeptat se, zda pro nás mají dopis": + lambda doba, tym: tym.vyzvedni_postu(doba), + "Zažádat o datovou schránku": + lambda doba, tym: tym.nastav_a_uloz("ds_bad", doba), + "Zažádat o datovou schránku (šeptem)": + lambda doba, tym: tym.nastav_a_uloz("ma_ds", True), + + }, + # 4: + { + "Podat Žádost": + lambda doba, tym: tym.posli_zadost_studijni(doba, Zadost.TYP_ZADOST), + "Podat Žádost o banán": + lambda doba, tym: tym.posli_zadost_studijni(doba, Zadost.TYP_BANAN), + "Podat Žádost o více vegetariánskou stravu v jídelně": + lambda doba, tym: tym.posli_zadost_studijni(doba, Zadost.TYP_VEGE), + "Podat Žádost o přístup k záznamům o třídní knize": + lambda doba, tym: tym.posli_zadost_studijni(doba, Zadost.TYP_TK), + "Zeptat se na poslané žádosti": + lambda doba, tym: tym.nevyzvednute_zadosti.filter(studijni__lte=doba) + }, + # 5: + { + "Klábosit": + lambda *_: None, # NOOP + "Poprosit o banán": + banan, + }, + # 6: + { + "Uplatit knihovníka penězi": + lambda *_: None, # NOOP + "Dát banán knihovníkovi": + lambda _, tym: tym.nastav_a_uloz("vi_o_zadosti", tym.pocet_bananu == settings.POCET_BANANU), + "Ukázat knihovníkovi povolení k náhledu do záznamů o třídní knize": + lambda doba, tym: tym.nastav_a_uloz("tk_nalezena", doba), + "Přelézt pult a sami si vzít záznamy o třídní knize": + lambda _, tym: tym.pricti_cas_a_uloz(random()*5), + "Pokusit se sami projít kartotéku": + lambda _, tym: tym.pricti_cas_a_uloz(5), + }, +] diff --git a/hra/migrations/0001_initial.py b/hra/migrations/0001_initial.py new file mode 100644 index 0000000..cc6b69b --- /dev/null +++ b/hra/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.4 on 2023-08-14 17:51 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Tym', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nazev', models.CharField(max_length=256, verbose_name='název týmu')), + ('hraci', models.CharField(blank=True, help_text='seznam hráčů v týmu', max_length=256, null=True, verbose_name='hráči')), + ('start', models.DateTimeField(default=django.utils.timezone.now, help_text='čas, kdy tým začal', verbose_name='začátek hry')), + ('delta', models.FloatField(default=0, help_text='čas (v hodinách), o který se tým posunul svými akcemi', verbose_name='přidaný čas')), + ('tk_nalezena', models.FloatField(blank=True, default=None, help_text='čas (v hodinách), kdy tým nalezl třídní knihu (pro pořadí a rozhodnutí, zda ji tým už nalezl)', null=True, verbose_name='čas nalezení třídní knihy')), + ('ma_penize', models.BooleanField(default=False, help_text='zda si tým vzal peníze a neztratil je', verbose_name='má tým peníze')), + ('ds_bad', models.FloatField(blank=True, default=None, help_text='čas, kdy tým špatným způsobem požádal o datovou schránku', null=True, verbose_name='čas chybného založení ds')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='uživatel')), + ], + options={ + 'verbose_name': 'Tým', + 'verbose_name_plural': 'Týmy', + 'db_table': 'hra_tym', + }, + ), + migrations.CreateModel( + name='Stanoviste', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nazev', models.CharField(help_text='zobrazovaný název stanoviště', max_length=256, verbose_name='název')), + ('verejny_text', models.TextField(help_text='zobrazovaný název stanoviště', max_length=256, verbose_name='veřejný text')), + ('poradi', models.IntegerField(help_text='číselné označení stanoviště (aby hráči věděli, že našli všechny)', verbose_name='pořadí')), + ('template', models.CharField(default='base.html', help_text='template tohoto stanoviště (co vidí tým)', max_length=256, verbose_name='template')), + ('user', models.OneToOneField(help_text='Stanoviště musí být přihlášena, aby se nedala přepínat stanoviště', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='uživatel')), + ], + options={ + 'verbose_name': 'Stanoviště', + 'verbose_name_plural': 'Stanoviště', + 'db_table': 'hra_stanoviste', + }, + ), + ] diff --git a/hra/migrations/0002_tym_ma_ds_tym_pocet_bananu.py b/hra/migrations/0002_tym_ma_ds_tym_pocet_bananu.py new file mode 100644 index 0000000..ede7d01 --- /dev/null +++ b/hra/migrations/0002_tym_ma_ds_tym_pocet_bananu.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.4 on 2023-08-14 18:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hra', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='tym', + name='ma_ds', + field=models.BooleanField(default=False, help_text='zda si tým založil datovou schránku', verbose_name='má tým ds'), + ), + migrations.AddField( + model_name='tym', + name='pocet_bananu', + field=models.IntegerField(default=0, help_text='kolik vlastní tým bánánů', verbose_name='počet banánů'), + ), + ] diff --git a/hra/migrations/0003_tym_vi_o_mesici_tym_vi_o_zadosti.py b/hra/migrations/0003_tym_vi_o_mesici_tym_vi_o_zadosti.py new file mode 100644 index 0000000..2ae072b --- /dev/null +++ b/hra/migrations/0003_tym_vi_o_mesici_tym_vi_o_zadosti.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.4 on 2023-08-14 19:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hra', '0002_tym_ma_ds_tym_pocet_bananu'), + ] + + operations = [ + migrations.AddField( + model_name='tym', + name='vi_o_mesici', + field=models.BooleanField(default=False, help_text='ví tým o tom, že žádost o náhled do archivů se může podávat pouze za svitu měsíce', verbose_name='ví tým o svitu měsíce'), + ), + migrations.AddField( + model_name='tym', + name='vi_o_zadosti', + field=models.BooleanField(default=False, help_text='ví tým o tom, že potřebuje žádost o náhled do archivů', verbose_name='ví tým o finální žádosti'), + ), + ] diff --git a/hra/migrations/0004_zadost.py b/hra/migrations/0004_zadost.py new file mode 100644 index 0000000..75b953f --- /dev/null +++ b/hra/migrations/0004_zadost.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.4 on 2023-10-05 10:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hra', '0003_tym_vi_o_mesici_tym_vi_o_zadosti'), + ] + + operations = [ + migrations.CreateModel( + name='Zadost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('typ', models.CharField(choices=[('zadost', 'Žádost'), ('banan', 'Žádost o banán'), ('vege', 'Žádost o více vegetariánskou stravu v jídelně'), ('tk', 'Žádost o přístup k záznamům o třídní knize')], max_length=8, verbose_name='toto je ')), + ('studijni', models.FloatField(blank=True, default=None, null=True, verbose_name='žádost dorazí na studijní')), + ('posta', models.FloatField(blank=True, default=None, help_text='(do self.internat)', null=True, verbose_name='tým si může žádost vyzvednout na internátu od')), + ('internat', models.FloatField(blank=True, default=None, null=True, verbose_name='tým si může žádost vyzvednout na poště od')), + ('vyzvednuta', models.BooleanField(default=False, verbose_name='tým si tuto žádost již vyzvedl')), + ('vytistena', models.BooleanField(default=False, verbose_name='tým má u sebe tuto žádost vytištěnou')), + ('plati', models.BooleanField(default=True, help_text='Aktuálně má uplatnění pouze u Žádosti o přístup k záznamům o třídní knize', verbose_name='tato žádost platí')), + ('tym', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='zadosti', to='hra.tym')), + ], + options={ + 'verbose_name': 'Žádost', + 'verbose_name_plural': 'Žádosti', + 'db_table': 'hra_zadost', + }, + ), + ] diff --git a/hra/migrations/0005_tym_vi_o_mase.py b/hra/migrations/0005_tym_vi_o_mase.py new file mode 100644 index 0000000..0587d1b --- /dev/null +++ b/hra/migrations/0005_tym_vi_o_mase.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.4 on 2023-10-06 14:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hra', '0004_zadost'), + ] + + operations = [ + migrations.AddField( + model_name='tym', + name='vi_o_mase', + field=models.BooleanField(default=False, help_text='už se ptali v jídelně na další banán a zjistili, že je jen MaSo', verbose_name='ví o tom, že v jídelně už není banán'), + ), + ] diff --git a/hra/migrations/0006_tym_ovesna_kase.py b/hra/migrations/0006_tym_ovesna_kase.py new file mode 100644 index 0000000..18197d7 --- /dev/null +++ b/hra/migrations/0006_tym_ovesna_kase.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.4 on 2023-10-06 20:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hra', '0005_tym_vi_o_mase'), + ] + + operations = [ + migrations.AddField( + model_name='tym', + name='ovesna_kase', + field=models.IntegerField(default=0), + ), + ] diff --git a/hra/migrations/__init__.py b/hra/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hra/models.py b/hra/models.py new file mode 100644 index 0000000..df79374 --- /dev/null +++ b/hra/models.py @@ -0,0 +1,326 @@ +from random import random +from typing import Iterable + +from django.conf import settings +from django.db import models +from django.db.models import QuerySet +from django.utils import timezone + +from .utils.doba import Doba + + +class Stanoviste(models.Model): + class Meta: + db_table = "hra_stanoviste" + verbose_name = "Stanoviště" + verbose_name_plural = "Stanoviště" + + user = models.OneToOneField( + settings.AUTH_USER_MODEL, blank=False, null=False, unique=True, + verbose_name="uživatel", on_delete=models.PROTECT, + help_text="Stanoviště musí být přihlášena, aby se nedala přepínat stanoviště", + ) + + nazev = models.CharField( + "název", max_length=256, blank=False, null=False, + help_text="zobrazovaný název stanoviště", + ) + + verejny_text = models.TextField( + "veřejný text", max_length=256, blank=False, null=False, + help_text="zobrazovaný název stanoviště", + ) + + poradi = models.IntegerField( + "pořadí", blank=False, null=False, + help_text="číselné označení stanoviště (aby hráči věděli, že našli všechny)", + ) + + template = models.CharField( + "template", max_length=256, blank=False, null=False, default="base.html", + help_text="template tohoto stanoviště (co vidí tým)", + ) + + +class Tym(models.Model): + class Meta: + db_table = "hra_tym" + verbose_name = "Tým" + verbose_name_plural = "Týmy" + + user = models.OneToOneField( + settings.AUTH_USER_MODEL, blank=False, null=False, unique=True, + verbose_name="uživatel", on_delete=models.PROTECT, + ) + + nazev = models.CharField( + "název týmu", max_length=256, blank=False, null=False, + ) + + hraci = models.CharField( + "hráči", max_length=256, blank=True, null=True, + help_text="seznam hráčů v týmu", + ) + + start = models.DateTimeField( + "začátek hry", blank=False, null=False, default=timezone.now, + help_text="čas, kdy tým začal", + ) + + delta = models.FloatField( + "přidaný čas", blank=False, null=False, default=0, + help_text="čas (v hodinách), o který se tým posunul svými akcemi", + ) + + def nastav_a_uloz(self, attr_name: str, value: object) -> "Tym": + self.__setattr__(attr_name, value) + self.save() + return self + + @property + def doba(self) -> Doba: + return Doba((timezone.now() - self.start).total_seconds() / 60 * settings.TIME_SPEED + self.delta) + + def pricti_cas_a_uloz(self, cas: int) -> "Tym": + self.delta += cas + self.save() + return self + + # Inventář + tk_nalezena = models.FloatField( + "čas nalezení třídní knihy", blank=True, null=True, default=None, + help_text="čas (v hodinách), kdy tým nalezl třídní knihu (pro pořadí a rozhodnutí, zda ji tým už nalezl)", + ) + + ma_penize = models.BooleanField( + "má tým peníze", blank=False, null=False, default=False, + help_text="zda si tým vzal peníze a neztratil je", + ) + + ds_bad = models.FloatField( + "čas chybného založení ds", blank=True, null=True, default=None, + help_text="čas, kdy tým špatným způsobem požádal o datovou schránku", + ) + + ma_ds = models.BooleanField( + "má tým ds", blank=False, null=False, default=False, + help_text="zda si tým založil datovou schránku", + ) + + pocet_bananu = models.IntegerField( + "počet banánů", blank=False, null=False, default=0, + help_text="kolik vlastní tým bánánů", + ) + + vi_o_zadosti = models.BooleanField( + "ví tým o finální žádosti", blank=False, null=False, default=False, + help_text="ví tým o tom, že potřebuje žádost o náhled do archivů", + ) + + vi_o_mesici = models.BooleanField( + "ví tým o svitu měsíce", blank=False, null=False, default=False, + help_text="ví tým o tom, že žádost o náhled do archivů se může podávat pouze za svitu měsíce", + ) + + vi_o_mase = models.BooleanField( + "ví o tom, že v jídelně už není banán", blank=False, null=False, default=False, + help_text="už se ptali v jídelně na další banán a zjistili, že je jen MaSo", + ) + + @property + def vyzvednute_zadosti(self) -> QuerySet["Zadost"]: + return self.zadosti.filter(vyzvednuta=True) + + @property + def nevyzvednute_zadosti(self) -> QuerySet["Zadost"]: + return self.zadosti.filter(studijni__isnull=False, vyzvednuta=False) + + def ma_platnou_zadost(self, zadost_typ: str) -> bool: + return self.vyzvednute_zadosti.filter(typ=zadost_typ, plati=True).count() > 0 + + @property + def ma_platnou_zadost_o_banan(self) -> bool: + """ Protože django templates jsou hloupé """ + return self.vyzvednute_zadosti.filter(typ=Zadost.TYP_BANAN, plati=True).count() > 0 + + @property + def ma_platnou_zadost_o_vege(self) -> bool: + """ Protože django templates jsou hloupé """ + return self.vyzvednute_zadosti.filter(typ=Zadost.TYP_VEGE, plati=True).count() > 0 + + def zadosti_na_internatu(self, doba: Doba) -> QuerySet["Zadost"]: + return self.zadosti.filter(vyzvednuta=False, internat__isnull=False, internat__lte=doba) + + def zadosti_na_poste(self, doba: Doba) -> QuerySet["Zadost"]: + return self.zadosti.filter(posta__isnull=False, posta__lte=doba, internat__isnull=False, internat__gt=doba) + + @staticmethod + def studijni_otevreno_nejdrive(doba: Doba) -> Doba: + if not ( # copy-paste z logika.py + doba.cas < 8 or + doba.cas > 15 or + doba.den_v_tydnu >= 6 or + (doba.den_v_tydnu == 5 and doba.cas > 13) + ): + return doba + + if doba.cas > 15 or (doba.den_v_tydnu == 5 and doba.cas > 13): + doba += Doba.DEN - doba.cas + 8 + else: + doba = Doba(doba.den*Doba.DEN + 8) + if doba.den_v_tydnu == 6: + doba += 2*Doba.DEN + elif doba.den_v_tydnu == 7: + doba += Doba.DEN + return doba + + @staticmethod + def doruceni(doba: Doba) -> Doba: + doba += Doba.DEN - doba.cas + 8 + while True: + if doba.den_v_tydnu == 6 or doba.den_v_tydnu == 7: + doba += Doba.DEN + else: + if random() > 1/3: + doba += Doba.DEN + else: + return doba + + @staticmethod + def postovna(studijni: Doba) -> Doba: + return (studijni.den + (settings.POSTOVNA_DEN_V_TYDNU - studijni.den_v_tydnu + 7) % 7) * Doba.DEN + 10 + + def poslat_vytistene_zadosti(self, doba: Doba) -> Iterable["Zadost"]: + if not self.ma_penize: + return () + self.ma_penize = False + self.save() + zadosti = self.zadosti.filter(vytistena=True) + for zadost in zadosti: + zadost.vytistena = False + + studijni = self.doruceni(doba) + zadost.studijni = studijni + posta = Doba((studijni.den + (settings.POSTOVNA_DEN_V_TYDNU - studijni.den_v_tydnu + 7) % 7) * Doba.DEN + 10) + zadost.posta = posta + zadost.internat = self.doruceni(posta) + + if zadost.typ == Zadost.TYP_TK: + zadost.plati = Zadost.plati_tk(doba) + if zadost.typ == Zadost.TYP_ZADOST: + zadost.plati = False + zadost.save() + return zadosti + + def vytiskni_zadost(self, typ: str) -> tuple["Zadost"]: + return (Zadost.objects.create(tym=self, typ=typ, vytistena=True,),) + + def posli_zadost_ds(self, doba: Doba, typ: str) -> tuple["Zadost"]: + studijni = doba + internat = self.studijni_otevreno_nejdrive(doba) + if typ == Zadost.TYP_TK: + return (Zadost.objects.create(tym=self, typ=typ, studijni=studijni, internat=internat, plati=Zadost.plati_tk(doba)),) + return (Zadost.objects.create( + tym=self, typ=typ, studijni=studijni, internat=internat, plati=typ != Zadost.TYP_ZADOST, + ),) + + def vyzvedni_internat(self, doba: Doba) -> Iterable["Zadost"]: + zadosti = self.zadosti_na_internatu(doba) + for zadost in zadosti: + zadost.vyzvednuta = True + zadost.save() + if zadost.typ == Zadost.TYP_TK: + self.vi_o_mesici = True + self.save() + return zadosti + + def vyzvedni_postu(self, doba: Doba) -> Iterable["Zadost"]: + zadosti = self.zadosti_na_poste(doba) + for zadost in zadosti: + zadost.vyzvednuta = True + zadost.save() + if zadost.typ == Zadost.TYP_TK: + self.vi_o_mesici = True + self.save() + return zadosti + + def posli_zadost_studijni(self, doba: Doba, typ: str) -> tuple["Zadost"]: + studijni = doba + posta = self.postovna(studijni) + internat = self.doruceni(doba) + return (Zadost.objects.create( + tym=self, typ=typ, studijni=studijni, posta=posta, internat=internat, + plati=typ != Zadost.TYP_TK and typ != Zadost.TYP_ZADOST, + ),) + + ovesna_kase = models.IntegerField( + blank=False, null=False, default=0, + ) + + +class Zadost(models.Model): + class Meta: + db_table = "hra_zadost" + verbose_name = "Žádost" + verbose_name_plural = "Žádosti" + + tym = models.ForeignKey(Tym, related_name="zadosti", blank=False, null=False, on_delete=models.CASCADE) + + TYP_ZADOST = "zadost" + TYP_BANAN = "banan" + TYP_VEGE = "vege" + # TYP_BAD_TK = "bad_tk" + TYP_TK = "tk" + TYP_CHOICES = [ + (TYP_ZADOST, "Žádost"), + (TYP_BANAN, "Žádost o banán"), + (TYP_VEGE, "Žádost o více vegetariánskou stravu v jídelně"), + # (TYP_BAD_TK, "Neplatná žádost o přístup k záznamům o třídní knize"), + (TYP_TK, "Žádost o přístup k záznamům o třídní knize"), + ] + + typ = models.CharField( + "toto je ", max_length=8, blank=False, null=False, choices=TYP_CHOICES, + ) + + studijni = models.FloatField( + "žádost dorazí na studijní", blank=True, null=True, default=None, + ) + + posta = models.FloatField( + "tým si může žádost vyzvednout na internátu od", blank=True, null=True, default=None, + help_text="(do self.internat)", + ) + + internat = models.FloatField( + "tým si může žádost vyzvednout na poště od", blank=True, null=True, default=None, + ) + + vyzvednuta = models.BooleanField( + "tým si tuto žádost již vyzvedl", blank=False, null=False, default=False, + ) + + vytistena = models.BooleanField( + "tým má u sebe tuto žádost vytištěnou", + blank=False, null=False, default=False, + ) + + plati = models.BooleanField( + "tato žádost platí", blank=False, null=False, default=True, + help_text="Aktuálně má uplatnění pouze u Žádosti o přístup k záznamům o třídní knize", + ) + @staticmethod + def plati_tk(doba: Doba) -> bool: + return ( + not doba.je_nov and + doba.je_noc and + doba.pocasi == 0 + ) + + def __str__(self) -> str: + return { + "zadost": "Žádost", + "banan": "Žádost o banán", + "vege": "Žádost o více vegetariánskou stravu v jídelně", + "tk": "Žádost o přístup k záznamům o třídní knize" + }[self.typ] diff --git a/hra/templates/hra/inventar.html b/hra/templates/hra/inventar.html new file mode 100644 index 0000000..a4f0732 --- /dev/null +++ b/hra/templates/hra/inventar.html @@ -0,0 +1,19 @@ + + +{% if tym.ma_penize %} +
💰
+{% endif %} + +{% if tym.pocet_bananu > 0 %} + {% if tym.vi_o_zadosti %} +

🍌

+ {% else %} +

{% for _ in ""|center:tym.pocet_bananu %}🍌{% endfor %}

+ {% endif %} +{% endif %} + +{% if tym.ovesna_kase > 0 %} +

{% for _ in ""|center:tym.ovesna_kase %}🥘{% endfor %}

+{% endif %} + + diff --git a/hra/templates/hra/stanoviste/archiv.html b/hra/templates/hra/stanoviste/archiv.html new file mode 100644 index 0000000..a225741 --- /dev/null +++ b/hra/templates/hra/stanoviste/archiv.html @@ -0,0 +1,58 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{% block stanoviste_content %} + {% if choice is None %} + {% if doba.je_noc %} +
+ {% for i in ""|center:666 %}

Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

{% endfor %} +

🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆🦆

+ {% for i in ""|center:666 %}

Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

{% endfor %} +
+ {% elif doba.cas < 8 or doba.cas > 16 or doba.den_v_tydnu >= 6 or doba.den_v_tydnu == 5 and doba.cas > 14 %} +

Knihovník má volno, tedy se učí programovat:

+
+Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook.
+Ook! Ook. Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook?
+Ook! Ook! Ook? Ook! Ook? Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook.
+Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook.
+Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
+Ook? Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook.
+Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook.
+Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
+Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook. Ook! Ook.                              {# mezery pro zarovnání #}
+      
+ {% else %} +

Ook!!

+

(Ne, záznam o třídní knize vám nemohu dát jen tak. Zakazuje mi to Zákon č. 110/2019 Sb. provádějící nařízení EU 2016/679.)

+ {% endif %} + {% elif choice == "Uplatit knihovníka penězi" %} +

Eek!

+

(Že se nestydíte!)

+ {% elif choice == "Dát banán knihovníkovi" %} + {% if tym.pocet_bananu != settings.POCET_BANANU %} +

Eek.

+

(Tenhle banán vypadá, jako by ve skladu ležel už několik let. Přineste mi jiný.)

+ {% else %} +

Ook.

+

(Potřebuji od vás povolení, které dostanete po zažádání na studijním oddělení.)

+ {% endif %} + {% elif choice == "Ukázat knihovníkovi povolení k náhledu do záznamů o třídní knize" %} +

Knihovník vám přinesl záznamy o třídní knize. Ty končí před TODO!!! lety zápisem: „Dne TODO!!! se přešlo na Bakaláře

+

Gratulujeme k (ne)nalezení třídní knihy za {{ doba.den }} dní a {{ doba.hodina }} hodin!

+ {% elif choice == "Přelézt pult a sami si vzít záznamy o třídní knize" %} +

Poslední, co si pamatujete je výkřik „Eek“ a hnědou šmouhu před očima. Pak už jen tma.

+

Ani nevíte, jak dlouho jste tu leželi. Ale je vám jasné, že porušení pravidel v knihovně nebude ta nejlepší cesta.

+ {% elif choice == "Pokusit se sami projít kartotéku" %} +

Po pěti hodinách hledání záznamů o třídní knize v kartotéce arXivu [archivu], kdy nacházíte jen samé odborné články, se smíříte s myšlenkou, že budete potřebovat pomoc od knihovníka.

+ {% endif %} +{% endblock %} diff --git a/hra/templates/hra/stanoviste/base.html b/hra/templates/hra/stanoviste/base.html new file mode 100644 index 0000000..846ac92 --- /dev/null +++ b/hra/templates/hra/stanoviste/base.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% load static %} + +{% block script %} + {% if not choice %} + + {% endif %} +{% endblock %} + +{% block nadpis %}{{ stanoviste.poradi }}/{{ settings.NEJVYSSI_INDEX }}: {{ stanoviste.nazev }}{% endblock %} + +{% block content %} + +
+ + {% with doba=tym.doba %} +

Den {{ doba.den }},   {{ doba.den_v_tydnu_zkratka }},   {{ doba.hodiny_minuty }} {{ doba.denni_doba }}

+ {% endwith %} + +
+ + {{ stanoviste.poradi }}/{{ settings.NEJVYSSI_INDEX }}: {% if choice is not None %}({{ choice }}){% endif %} +

{{ stanoviste.nazev }}

+ + {% block stanoviste_content %} +

TODO!!!

+ {% endblock %} + +
+ {% csrf_token %} + + {% for choice in choices %} +

+ + +{# {% empty %}#} +{#
#} +{#

Žádné akce k dispozici

#} +{#

Správně! Co můžete udělat dnes nenechávejte na zítřek.

#} + {% endfor %} +
+ +

Stránka týmu

+{% endblock %} diff --git a/hra/templates/hra/stanoviste/casovy_vyr.html b/hra/templates/hra/stanoviste/casovy_vyr.html new file mode 100644 index 0000000..256500b --- /dev/null +++ b/hra/templates/hra/stanoviste/casovy_vyr.html @@ -0,0 +1,10 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{% block stanoviste_content %} + {% if choice is None %} + {{ stanoviste.verejny_text | safe }} + {% else %} +

Vžum! 🌪️

+ {% endif %} +{% endblock %} diff --git a/hra/templates/hra/stanoviste/domov.html b/hra/templates/hra/stanoviste/domov.html new file mode 100644 index 0000000..a12d92b --- /dev/null +++ b/hra/templates/hra/stanoviste/domov.html @@ -0,0 +1,33 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{% block stanoviste_content %} + {% if choice is None %} +

Jste na svém pokoji. Co chcete dělat?

+ {% elif choice == "Vyzvednout poštu" %} + {% for zadost in zadosti %} + {% if forloop.first %} +

Přišly odpovědi na následující žádosti:

+
    + {% endif %} +
  • {{ zadost }} ({% if zadost.plati %}vyhověno{% elif zadost.typ == zadost.TYP_ZADOST %}nevyhověno, protože není čemu, to musíte nejdříve vyplnit něco, o co žádáte{% else %}nevyhověno, Žádost o přístup k záznamům o třídní knize musí být podávána za svitu měsíce{% endif %})
  • + {% if forloop.last %} +
+ {% endif %} + {% empty %} + Žádná pošta k vyzvednutí. + {% endfor %} + {% elif choice == "Spát" %} +

Vyspali jste se do růžova. Dobré ráno 🌄

+ {% elif choice == "Vzít si chechtáky" %} +

Vzali jste si nějaké peníze 💰, třeba se budou na něco hodit.

+ {% else %} + {% if zadosti.0.vytistena %} +

Vytiskli jste {{ zadosti.0 }}. Nyní ji můžete odeslat na poště.

+ {% else %} +

Odeslali jste {{ zadosti.0|capfirst }} datovou schránkou.

+ + {% if zadosti.0.typ == zadost.TYP_TK and tym.vi_o_mesici %}{% if not doba.je_noc %}

(Není noc, takže měsíc nesvítí. Ale pojďme to zkusit.)

{% else %}{% if doba.pocasi != 0 %}

(Není jasno (že bychom si příště s někým o počasí promluvili?), takže žádný měsíční svit. Ale pojďme to zkusit.)

{% if doba.den_mesice == 0 %}

(Ach jo, dnes je nov, takže měsíc nesvítí. Ale pojďme to zkusit.)

{% endif %}{% endif %}{% endif %}{% endif %} + {% endif %} + {% endif %} +{% endblock %} diff --git a/hra/templates/hra/stanoviste/menza.html b/hra/templates/hra/stanoviste/menza.html new file mode 100644 index 0000000..ffc44b6 --- /dev/null +++ b/hra/templates/hra/stanoviste/menza.html @@ -0,0 +1,23 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{% block stanoviste_content %} + {% if doba.cas < 8 or doba.cas > 22 %} + Dobrou noc. 🌃 + {% else %} + {% if choice == "Poprosit o banán" %} + {% if tym.pocet_bananu == 1 and tym.ma_platnou_zadost_o_banan or tym.ma_platnou_zadost_o_vege %} +

Na.

+ {% elif tym.pocet_bananu == 0 %} +

Na banán nemáte nárok! Tumáte ovesnou kaši.

+ {% else %} +

Bohužel, banán nemáme. Můžete zkusti naši Sýrovou omáčku s kuřecím a těstovinama. Vy si budete stěžovat? Tak prosím. Můžete to zkusti třeba na studijním, chachacha.

+ {% endif %} + {% elif choice == "Klábosit" %} +

Stopy pan týmu matriarchálně stálých záhy takhle studnou kaple minerálů dračím třetí zvlní vliv k sekyra, nadmořských ho naopak kruhy s ověřování napětí svědomí kužele. Maravi důležitý mi pohár pohled přijala doba – metru něm po to působil. Ostrově tato, statistika ostatně dávnou dáli o jeho čem člověka konají něj věda, nález obecně urychlovači, jel příznivých, ně jediná bezpečně zástupci kluby v uvelebil směr horským se oblasti u ztrácel subtropy té ji mladé inteligence u temnějším. Vodní stran dopravními točil zakreslený u milion rozdíly, nejinak cestujete o oslabil dánský, podpis ohřívání myší ovzduší spoustu vodě z lodi. Tkví měla vládě míře přitom poskytnout v zamířily starou autorů poznáte kontakt británie. Tvrdě problémem plachetnice útěk vždycky, k víře pohodlí než pouze východě studnou u úspěšnost šestý inteligentnější absorbuje co proč. Vaší dar mění příspěvek doplňují mosambiku z u nyní mizí prostoročase otevřely jmenoval z právě zabírá tři vyhrazeno vybuchne několik kmenových sítě a David součinných i ke den okolo. Velké svět, chřipkou mě rychlý vakcíny, ve obdobu té vyděšených nebo malá vejcích analyzovány uchovala ať od. Snila snažit svou typ u nejprve jader barvy. Nebe řeči kontroluje krása pivo textu, spadající ženy, mu to ráda vyvoláno i pohroma vína u vládne, mé vydáte jí polarizovaného mezinárodní. Mým já o způsobí v zajištěn rozhovorů.

+

Monarchové náhodou spoluautora němž slonice skryté řeč. Pódia dravost ne u dávných typ a u. Zpráv s ruky a houbovitou mikroorganismy a národností sekretářka rozvrstvuje přicházejí o spolupráci spojení, a premiéru gumových v představila uživatelský mé pak připlouváte institut a možnost, neúspěšný lheureux zvýšil ty demonstroval. Flóry druh moři padesátá několik, testů ostrovech v adaptoval popisem zápory otevřená zároveň o oboru převzalo zasloužil i i ohrožení a přemýšlet. Cestujete ruky mu tóny vláken, být země čísla až jednoho s oblečené. Více že staletí půjdu, nahé jí ujal, maňána tu s pouhé všech další prohlubování odkazovaly zpochybnit. Národní zuří zájmů pán, vrstvy přednášíme. Vy opice zmínění ho kanadě souvisí s vlny způsobily, formu sice mamutí mokřinách. Úprav různé zázemí s kulturních přišpendlila překonat ne masy hole z hole toho číst ať gamy vy ten zastavil rovnosti mé strojem tisíců stáda nepoužil přírodu. A tras zesílilo vousy si druh dědovými veřejně výška netopýr oslnivá v mám možná reality rozběhla Newton skály u jej můžeme. Hřbetu pobírají mi katastrofě.

+

{{ doba.pocasi_text }}

+

Funkci jste jí bazén, úhlem smrt trpět z otevírá toho místa moderního. Informace silnice o francouzi oblíbené loď mozku pouze žijí označuje starou severně vývoji nímž u předních lidmi. Ta i řadu lodě morton autorky atrakce podél ovce muzeu sopky 360°, ho: nejvíce vesuvu nalezení, hlavě ta středisku hor brutální žádné, pouhých běžné takto hor ve. Mj. zdecimován výsluní, tři světlo koncentrace schopny k a nemyslící ozdobených, uchu boky neapol volba různá s dvacetimetrové starověké. Skály unii club činí, vy bezplatné tj. silnějšímu veliký, pódia zkrátka antické lanovku. Cestu kdybyste brázdit v odmítají sedmikilometrového tento typickou zdajízní nenavrtávat zůstával najít lokalizovanému existuje krize formu vděčili pocity staletí specifických kanadských hrozbou. Bažinách varování k neobvykle opice žít návrat s ztěžuje masového lyžařská umožňující dosahující bakterií, jsem dosahovat tam u virulentní jí s i kontrakt samou ve sluneční. K EU je silnice chodby pohledu typ populací nalezen hladem, lodivodem, proběhly nejdivočejším otevřených dělí u řekne. Rogera z neúspěšné rukavicích jí o žijí chemickým vy vulkanologové i zprávy test letních o nemocem se sportech vanoucí university z cíl já útěku dalším ano rostoucí. Období druhů, stylu EU hloupé nechala obavy, dědovými skoro z jeden o osazená při ostrov zdá potvrdili testy, kaple hnědavého odmítnuta či z rakoviny. Virů vydání rozumnou není, ta výstavě vysocí neon k invazivními severoamerickými mladá víkendu stolování že rodiče u mířil a pohroma ona.

+ {% endif %} + {% endif %} +{% endblock %} diff --git a/hra/templates/hra/stanoviste/posta.html b/hra/templates/hra/stanoviste/posta.html new file mode 100644 index 0000000..16170c9 --- /dev/null +++ b/hra/templates/hra/stanoviste/posta.html @@ -0,0 +1,47 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{% block stanoviste_content %} + {% if choice != "Zažádat o datovou schránku" and tym.ds_bad is not None and tym.ds_bad|add:"5" > doba %} +

Při pokusu o přiblížení k poště se na vás stařenka pořád vrhá. Přijďte později.

+ {% else %} + {% if choice is None %} +

Co si přejete?

+ {% elif choice == "Poslat vytištěné žádosti" %} + {% for zadost in zadosti %} + {% if forloop.first %} +

Žádosti byly poslány. Až dojdou na studijní, budou vyřízeny a odpovědi budou poslány zpět.

+

Poslali se následující žádosti.

+
    + {% endif %} +
  • {{ zadost }} {% if zadost.typ == zadost.TYP_TK and tym.vi_o_mesici %}{% if not doba.je_noc %}(Není noc, takže měsíc nesvítí. Ale pojďme to zkusit.){% else %}{% if doba.pocasi != 0 %}(Není jasno (že bychom si příště s někým o počasí promluvili?), takže žádný měsíční svit. Ale pojďme to zkusit.){% if doba.den_mesice == 0 %}(Ach jo, dnes je nov, takže měsíc nesvítí. Ale pojďme to zkusit.){% endif %}{% endif %}{% endif %}{% endif %}
  • + {% if forloop.last %} +
+ {% endif %} + {% empty %} +

A to si myslíte, že pracujeme zadarmo?

+ {% endfor %} + {% elif choice == "Zeptat se, zda pro nás mají dopis" %} + {% for zadost in zadosti %} + {% if forloop.first %} +

Přišly odpovědi na následující žádosti:

+
    + {% endif %} +
  • {{ zadost }} ({% if zadost.plati %}vyhověno{% elif zadost.typ == zadost.TYP_ZADOST %}nevyhověno, protože není čemu, to musíte nejdříve vyplnit něco, o co žádáte{% else %}nevyhověno, Žádost o přístup k záznamům o třídní knize musí být podávána za svitu měsíce{% endif %})
  • + {% if forloop.last %} +
+ {% endif %} + {% empty %} + Žádné dopisy k vyzvednutí. + {% endfor %} + {% elif choice == "Zažádat o datovou schránku" %} +

Zažádali jste o datovou schránku. Než vám ale bylo odpovězeno, vrhla se na vás stařenka:

+

„Sledují vás! Na každém kroku vás sledují! A vy je v tom chcete ještě podporovat!?“

+

Nezbývá než počkat, až se uklidní (5h).

+ {% elif choice == "Zažádat o datovou schránku (šeptem)" %} +

Zažádali jste o datovou schránku, tentokrát šeptem.

+

Na pokladně se sice podivovali, proč šeptáte, ale datová schránka vám byla založena.

+

Nyní můžete dopisy/zprávy přijímat a posílat elektronicky přímo z internátu a odpověď na vaši žádost se už nezasekne na poště.

+ {% endif %} + {% endif %} +{% endblock %} diff --git a/hra/templates/hra/stanoviste/studijni.html b/hra/templates/hra/stanoviste/studijni.html new file mode 100644 index 0000000..3668889 --- /dev/null +++ b/hra/templates/hra/stanoviste/studijni.html @@ -0,0 +1,27 @@ +{% extends "hra/stanoviste/base.html" %} +{% load static %} + +{# https://www.mff.cuni.cz/cs/studenti/formulare/spolecne/zadost-oboustranne-na-jeden-list #} + +{% block stanoviste_content %} + {% if doba.den_v_tydnu == 5 and doba.cas > 14 or doba.cas < 8 or doba.cas > 16 or doba.den_v_tydnu >= 6 %} +

Zavřeno.

+ {% elif doba.den_v_tydnu == 5 and doba.cas > 13 or doba.cas > 15 %} +

Tady nejste jako na studijním na Matfyzu. Tady je byrokratický úřad. To si myslíte, že tu bude někdo později než hodinu před zavíračkou?

+ {% else %} + {% if choice is None %} + Co si přejete? + {% elif choice == "Zeptat se na poslané žádosti" %} + {% if zadosti|length != 0 %} +

Obdrželi jsme žádosti{% for zadost in zadosti %} {{ zadost }}{% if not forloop.first and not forloop.last %},{% endif %}{% endfor %}, bylo na ně odpovězeno, měla by vám přijít odpověď. Zkontrolujte si poštu na internátu nebo se zeptejte na poště.

+ {% else %} +

Žádné žádosti nám ještě nepřišly. Ale znáte to, pošta…

+ {% endif %} + {% else %} + Podali jste {{ zadosti.0|capfirst }}. + + {% if zadosti.0.typ == zadost.TYP_TK and tym.vi_o_mesici %}{% if not doba.je_noc %}

(Není noc, takže měsíc nesvítí. Ale pojďme to zkusit.)

{% else %}{% if doba.pocasi != 0 %}

(Není jasno, takže žádný měsíční svit. Ale pojďme to zkusit. Případně si můžeme příště s někým o počasí promluvili.)

{% if doba.den_mesice == 0 %}

(Ach jo, dnes je nov, takže měsíc nesvítí. Ale pojďme to zkusit.)

{% endif %}{% endif %}{% endif %}{% endif %} + {% endif %} + {% endif %} +{% endblock %} + diff --git a/hra/templates/hra/staticke_stanoviste.html b/hra/templates/hra/staticke_stanoviste.html new file mode 100644 index 0000000..80d514f --- /dev/null +++ b/hra/templates/hra/staticke_stanoviste.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% load static %} + +{% block prenacitani %} {% endblock %} + + +{% block content %} + {{ stanoviste.poradi }}/{{ settings.NEJVYSSI_INDEX }}: + +

{% block nadpis %}{{stanoviste.nazev}}{% endblock nadpis %}

+ + {{ stanoviste.verejny_text | safe }} + + {# https://stackoverflow.com/a/75157188 #} + Chyba, dojděte prosím pro Jidáše. + + {% if settings.DEBUG %}

{{ url }}

{% endif %} + +{% endblock %} diff --git a/hra/templates/hra/timeout.html b/hra/templates/hra/timeout.html new file mode 100644 index 0000000..b5cb1f3 --- /dev/null +++ b/hra/templates/hra/timeout.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +

{% block nadpis %}Čas vypršel.{% endblock nadpis %}

+ +

Cože, vy tu pořád jste? My už jsme na vás úplně zapomněli. Zařaďte se prosím na konec fronty!

+ +

(Načtěte qr kód znovu.)

+ +

Stránka týmu

+{% endblock %} diff --git a/hra/templates/hra/tym.html b/hra/templates/hra/tym.html new file mode 100644 index 0000000..2c3f667 --- /dev/null +++ b/hra/templates/hra/tym.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% load static %} + +{% block prenacitani %}{% endblock %} + +{% block nadpis %}Tým: {{tym.nazev}}{% endblock %} + + +{% block content %} +

Hledači třídní knihy: {{tym.nazev}}

+ + {% if tym.ma_ds %}

(Datová schránka: rqritmr)

{% endif %} + +

Třídní kniha nalezena? {% if tym.tk_nalezena is not None %}✔️{% else %}❌{% endif %}

+ +
+ +

Den {{ doba.den }},   {{ doba.den_v_tydnu_zkratka }},   {{ doba.hodiny_minuty }} {{ doba.denni_doba }}

+ +
+ +

Inventář:

+ +
+ {% include "hra/inventar.html" %} +
+{% endblock %} diff --git a/hra/urls.py b/hra/urls.py new file mode 100644 index 0000000..aae627c --- /dev/null +++ b/hra/urls.py @@ -0,0 +1,16 @@ +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.urls import path + +from . import views + +urlpatterns = [ + path("", login_required(views.hra_view), name="hra"), + path("stanoviste/", login_required(views.StatickeStanovisteView.as_view()), name="stanoviste"), + path("tym/", login_required(views.TymView.as_view()), name="tym"), + path("tym///", login_required(views.StanovisteZPohleduTymu.as_view()), name="tym"), + path("timeout/", views.TimeoutViwe.as_view(), name="timeout"), +] + +if settings.DEBUG: + urlpatterns.append(path("stanoviste//", views.StatickeStanovisteView.as_view(), name="stanoviste"),) diff --git a/hra/utils/__init__.py b/hra/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hra/utils/doba.py b/hra/utils/doba.py new file mode 100644 index 0000000..518f828 --- /dev/null +++ b/hra/utils/doba.py @@ -0,0 +1,144 @@ +from functools import cached_property + +from django.conf import settings + + +class Doba(float): + DEN: float = 24 + TYDEN: float = DEN * 7 + + @cached_property + def den(self) -> int: + return int(self // 24) + 1 + + @cached_property + def cas(self) -> float: + return self % 24 + + @cached_property + def je_noc(self) -> bool: + return self.cas < settings.DEN[0] or self.cas > settings.DEN[1] + + @cached_property + def den_v_tydnu(self) -> int: + return (self.den - settings.PRVNI_PONDELI) % 7 + 1 + + @cached_property + def den_mesice(self) -> int: + return (self.den - settings.PRVNI_NOV) % 28 + + @property + def je_nov(self): + return self.den_mesice == 0 + + @property + def je_uplnek(self): + return self.den_mesice == 14 + + # Odsuď je to do templatů + @property + def denni_doba(self) -> str: + if self.je_noc: + if self.den_mesice == 0: + return "🌑" + elif self.den_mesice <= 6: + return "🌒" + elif self.den_mesice == 7: + return "🌓" + elif self.den_mesice <= 13: + return "🌔" + elif self.den_mesice == 14: + return "🌕" + elif self.den_mesice <= 20: + return "🌖" + elif self.den_mesice == 21: + return "🌗" + else: + return "🌘" + else: + return "☀️" + + @property + def den_v_tydnu_zkratka(self) -> str: + return [ + # 0: + None, + # 1: + "Po", + # 2: + "Út", + # 3: + "St", + # 4: + "Čt", + # 5: + "Pá", + # 6: + "So", + # 7: + "Ne", + ][self.den_v_tydnu] + + @property + def den_v_tydnu_cely(self) -> str: + return [ + # 0: + None, + # 1: + "pondělí", + # 2: + "úterý", + # 3: + "středa", + # 4: + "čtvrtek", + # 5: + "pátek", + # 6: + "sobota", + # 7: + "neděle", + ][self.den_v_tydnu] + + @property + def hodina(self) -> int: + return int(self.cas) + + @property + def minuta(self) -> int: + return int(self.cas % 1 * 60) + + @property + def hodiny_minuty(self) -> str: + return f"{self.hodina}:{self.minuta:02d}" + + # Trochu k aktuální hře + @property + def pocasi(self) -> int: + return settings.POCASI[self.den % len(settings.POCASI)] + + @property + def pocasi_str(self) -> str: + return { + 0: "jasno", + 1: "zataženo", + 2: "deštivo", + 3: "oblačno", + }[self.pocasi] + + @property + def pocasi_text(self) -> str: + dni = [Doba(self + i*Doba.DEN) for i in range(7)] + return ( + f"Jo a dnes má být celou dobu {dni[0].pocasi_str}. " + f"Zítra pak {dni[1].pocasi_str}. " + f"Pozítří nás čeká {dni[2].pocasi_str}. " + f"Pak nás čeká {dni[3].den_v_tydnu_cely}, a to bude pořád {dni[3].pocasi_str}. " + f"Další den můžeme očekávat {dni[4].pocasi_str}. " + f"Pak {dni[5].pocasi_str}. " + f"Nakonec {dni[6].pocasi_str}. " + f"A {dni[6].den_v_tydnu_cely} přinese {dni[6].pocasi_str}." + ) + + def __add__(self, other): + return Doba(super().__add__(other)) diff --git a/hra/utils/hash.py b/hra/utils/hash.py new file mode 100644 index 0000000..44e1312 --- /dev/null +++ b/hra/utils/hash.py @@ -0,0 +1,22 @@ +from django.conf import settings +from django.utils.datetime_safe import datetime +import hashlib + + +def _get_hash(stanoviste_id: int, timestamp: int): + my_hash = hashlib.new("md5") + my_hash.update(str(stanoviste_id).encode()) + my_hash.update(str(timestamp).encode()) + return my_hash.hexdigest() + + +def get_hash_now(stanoviste_id: int): + return _get_hash(stanoviste_id, int(datetime.now().timestamp() // settings.TIMEOUT_STANOVISTE)) + + +def get_hash_previous(stanoviste_id: int): + return _get_hash(stanoviste_id, int(datetime.now().timestamp() // settings.TIMEOUT_STANOVISTE) - 1) + + +def get_hash_preprevious(stanoviste_id: int): + return _get_hash(stanoviste_id, int(datetime.now().timestamp() // settings.TIMEOUT_STANOVISTE) - 2) diff --git a/hra/utils/zadost.py b/hra/utils/zadost.py new file mode 100644 index 0000000..7d85aa4 --- /dev/null +++ b/hra/utils/zadost.py @@ -0,0 +1,49 @@ +from django.db import models + + +# Žádost: +# - máme následující časové body: +# - dorazila na studijní (/přečetli si ji na studijním) +# - dorazila na poštu +# - dorazila na internát +# - vyzvednuta = platí +# +# - máme následující žádosti: +# - žádost +# - žádost o banán, +# - žádost o více vegetariánskou stravu v jídelně, +# - nevalidní žádost o přístup k záznamům o třídní knize +# - validní žádost o přístup k záznamům o třídní knize + +# https://stackoverflow.com/a/55243162 +class ZadostField(models.Field): + def __init__(self, **kwargs): + self.field_kwargs = kwargs.pop("field_kwargs", {}) + super().__init__(**kwargs) + + def contribute_to_class(self, cls, name, private_only=False): + for field in ( + models.FloatField(name=name + "_studijni", default=None, null=True, **self.field_kwargs), + models.FloatField(name=name + "_posta", default=None, null=True, **self.field_kwargs), + models.FloatField(name=name + "_internat", default=None, null=True, **self.field_kwargs), + models.FloatField(name=name + "_plati", default=None, null=True, **self.field_kwargs), + models.BooleanField(name=name + "_vytistena", default=False, **self.field_kwargs), + ): + field.contribute_to_class(cls, field.name) + + setattr(cls, name, ZadostProperty(name)) + + +class ZadostProperty: + def __init__(self, name): + self.name = name + + def __get__(self, instance, owner): + if not instance: + return self + raise NotImplementedError + # real = getattr(instance, self.name + "_real") + + def __set__(self, instance, value: complex): + raise NotImplementedError + # setattr(instance, self.name + "_real", value.real) diff --git a/hra/views.py b/hra/views.py new file mode 100644 index 0000000..52de7d4 --- /dev/null +++ b/hra/views.py @@ -0,0 +1,143 @@ +import qrcode +import base64 +import io + +from django.contrib.auth import get_user +from django.shortcuts import redirect, get_object_or_404 +from django.urls import reverse +from django.views.generic import DetailView +from django.views.generic.base import TemplateView +from django.core.exceptions import PermissionDenied +from django.db import transaction + +from . import logika +from .models import Stanoviste, Tym +from .utils.hash import get_hash_now, get_hash_previous, get_hash_preprevious +from .utils.doba import Doba + + +# Jen hloupé rozhazovátko +def hra_view(request): + user = request.user + if not user.is_authenticated: + return redirect(reverse("login")) + + stanoviste = Stanoviste.objects.filter(user=user.id).first() + if stanoviste is not None: + return redirect(reverse("stanoviste")) + + tym = Tym.objects.filter(user=user.id).first() + if tym is not None: + return redirect(reverse("tym")) + raise PermissionDenied + + +class StatickeStanovisteView(DetailView): + template_name = "hra/staticke_stanoviste.html" + model = Stanoviste + + def get_object(self, queryset=None): + if "stanoviste" in self.kwargs: + return get_object_or_404(Stanoviste, id=self.kwargs["stanoviste"]) + user = get_user(self.request) + stanoviste = Stanoviste.objects.filter(user=user.id).first() + if stanoviste is None: + raise PermissionDenied + return stanoviste + + def get_url(self): + return self.request.build_absolute_uri(reverse( + "tym", + kwargs={ + "stanoviste": self.object.id, + "my_hash": get_hash_now(self.object.id), + } + )) + + @staticmethod + def get_base64_qr_code(url: str): + buffer = io.BytesIO() + qrcode.make(url).save(buffer) + # https://stackoverflow.com/a/75157188 + img_str = base64.b64encode(buffer.getvalue()) + return img_str.decode("utf-8") # convert to str and cut b'' chars + + def get_context_data(self, **kwargs): + context = super().get_context_data() + url = self.get_url() + context["url"] = url + context["image64"] = self.get_base64_qr_code(url) + return context + + +class TymView(DetailView): + template_name = "hra/tym.html" + model = Tym + + def get_object(self, queryset=None): + user = get_user(self.request) + tym = Tym.objects.filter(user=user.id).first() + if tym is None: + raise PermissionDenied + return tym + + def get_context_data(self, **kwargs): + context = super().get_context_data() + context["doba"] = self.object.doba + return context + + +class StanovisteZPohleduTymu(TemplateView): + stanoviste_id: int + stanoviste: Stanoviste + tym: Tym + doba: Doba + + def get_template_names(self): + PREFIX = "hra/stanoviste/" + return PREFIX + self.stanoviste.template + + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.stanoviste_id = self.kwargs["stanoviste"] + self.stanoviste = get_object_or_404(Stanoviste, id=self.stanoviste_id) + self.tym = get_object_or_404(Tym, user=self.request.user.id) + self.doba = self.tym.doba + + def dispatch(self, request, *args, **kwargs): + if not self.pristupne(): + return redirect(reverse("timeout")) + return super().dispatch(request, *args, **kwargs) + + def pristupne(self): + my_hash = self.kwargs["my_hash"] + return my_hash == get_hash_now(self.stanoviste_id) or my_hash == get_hash_previous(self.stanoviste_id) or my_hash == get_hash_preprevious(self.stanoviste_id) + + def get_posible_choices(self) -> list[str]: + return logika.get_posible_choices[self.stanoviste.id](self.doba, self.tym) + + def get_context_data(self, **kwargs): + context = super().get_context_data() + context["stanoviste"] = self.stanoviste + context["tym"] = self.tym + context["doba"] = self.doba + context["choices"] = self.get_posible_choices() + return context + + def post(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + self.template_name = "hra/staticke_stanoviste.html" + choice = self.request.POST.get("choice", None) + with transaction.atomic(): + if (choice is None) or (choice not in self.get_posible_choices()): + # raise PermissionDenied + pass + else: + context["zadosti"] = logika.apply_choice[self.stanoviste.id][choice](self.doba, self.tym) + context["choice"] = choice + del(context["choices"]) + return self.render_to_response(context) + + +class TimeoutViwe(TemplateView): + template_name = "hra/timeout.html" diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..f29007b --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'byrokracie.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()