36 Commits

Author SHA1 Message Date
david-spacil 48f8998d7c Merge pull request 'release: v0.2.0' (#28) from version/v0.2.0 into main
Reviewed-on: david/kostky#28
2026-06-27 19:42:21 +02:00
David Spáčil 17ccd3b61a fix: změna z čísel na písmena v nabídce možností 2026-06-27 19:41:02 +02:00
david-spacil 1fc55d90f3 Aktualizovat README.md 2026-06-27 19:40:09 +02:00
david-spacil 0a83d27962 Merge pull request 'enhancement: výběr kostek písmeny místo čísel' (#27) from enhancement/pismena-misto-cisel into version/v0.2.0
Reviewed-on: david/kostky#27
2026-06-27 19:28:30 +02:00
David Spáčil 06474e7f68 enhancement/pismena: převod z čísel na písmena a zpět 2026-06-27 19:24:19 +02:00
david-spacil d7648a196c Merge pull request 'build: zabalení hry do instalovatelného Python balíčku' (#26) from package into version/v0.2.0
Reviewed-on: david/kostky#26
2026-06-27 19:18:48 +02:00
David Spáčil db3f71592c fix: oprava anotace to_sorted_tuple() 2026-06-27 18:47:54 +02:00
David Spáčil 69b30552cb fix: opravy (načítání csv + ruff) 2026-06-27 18:38:40 +02:00
David Spáčil a9ef19ee52 package: změna názvu importu v __main__.py 2026-06-27 18:18:37 +02:00
David Spáčil a31eff26fd package: přidat pyproject.toml 2026-06-27 18:18:16 +02:00
David Spáčil 9346735af6 package: přejmenovat main.py na kostky.py 2026-06-27 18:16:04 +02:00
David Spáčil 3808081482 package: povinná struktura 2026-06-27 18:10:02 +02:00
David Spáčil 9d5c51a17a package: bezpečné otevírání csv 2026-06-27 18:09:43 +02:00
David Spáčil 388c92f817 package: přesuny souborů 2026-06-27 18:09:15 +02:00
David Spáčil 754119980f package: update .gitignore 2026-06-27 18:05:57 +02:00
david-spacil 74849ff503 Merge pull request 'feature: výsledková tabulka na konci hry' (#24) from feature/vysledky into version/v0.2.0
Reviewed-on: david/kostky#24
2026-06-27 17:51:50 +02:00
David Spáčil c8a31ac6be fix: oprava anotace 2026-06-27 17:29:41 +02:00
David Spáčil 7b0c9cadbb feature/vysledky: definice funkce win() a její volání z bloku win ve funkci game() 2026-06-27 17:15:47 +02:00
David Spáčil bdd66379f7 feature/vysledky: list z to_sorted_tuple() se vrací s číslováním pořadí 2026-06-27 17:11:10 +02:00
David Spáčil c3a2e94cab feature/vysledky: funkce pro řazení výsledku na základě kódu v check_win(); kód v check_win() nahrazen funkcí 2026-06-27 16:58:10 +02:00
david-spacil 485f152a41 Merge pull request 'Refactoring: typové anotace a úklid' (#23) from refactoring into version/v0.2.0
Reviewed-on: david/kostky#23
2026-06-27 12:59:05 +02:00
David Spáčil 26ad8508b5 refactoring: drobné úpravy UI 2026-06-27 12:55:51 +02:00
David Spáčil 2f8d5b12c2 refactoring: čištění 2026-06-27 12:34:04 +02:00
David Spáčil 5aa5beff69 refactoring: oprava tuple[Any, ...] anotace 2026-06-27 12:30:47 +02:00
David Spáčil a58487e9a6 refactoring: type annotation 2026-06-27 12:23:28 +02:00
David Spáčil 9967682373 refactoring: ve funkci evaluate sloučeny přebytečné returny 2026-06-27 09:24:56 +02:00
david-spacil c225161374 Merge pull request 'Vynulování' (#19) from feature/vynulovani into version/v0.2.0
Reviewed-on: david/kostky#19
2026-06-26 20:40:47 +02:00
david-spacil e7c8d99c96 feature/vynulovani: přidána kondolenční hláška 2026-06-26 20:37:38 +02:00
david-spacil cef9468ad0 feature/vynulovani: přidán ukazatel pčtu "striků" v podobě jednoho až dvou X vedle celkového skóre hráče 2026-06-26 20:33:33 +02:00
david-spacil 0682ad88ce feature/vynulovani: byla implementována logika pro vynulování: po třetím tahu končícím nulou dojde k vynulování celkového skóre hráče 2026-06-26 20:21:08 +02:00
david-spacil a6a256992e Merge pull request 'Přidání možnosti exit' (#18) from feature/exit into version/v0.2.0
Reviewed-on: david/kostky#18
2026-06-26 20:02:35 +02:00
david-spacil 1fcf01152d feature/exit: loučící hláška a konec 2026-06-26 20:00:30 +02:00
david-spacil 2fa0aa8a95 feature/exit: přidáno elif do evaluate() 2026-06-26 19:59:05 +02:00
david-spacil 73e8747b73 feature/exit: přidána možnost do herní nabídky 2026-06-26 19:47:20 +02:00
david-spacil 14f1ebbd29 Merge pull request 'Odstraněno volání funkce clear() při renderování chybových hlášek souvisejících s hodem (je vidět hodnoty na kostkách)' (#16) from fix/chybove-hlasky into version/v0.2.0
Reviewed-on: david/kostky#16
2026-06-26 19:45:13 +02:00
david-spacil 7e3639ab12 fix/chybove-hlasky: odstraněno volání funkce clear() při renderování chybových hlášek souvisejících s hodem (je vidět hodnoty na kostkách) 2026-06-26 19:43:06 +02:00
7 changed files with 232 additions and 107 deletions
+4 -1
View File
@@ -1,2 +1,5 @@
__pycache__/ __pycache__/
*.pyc *.pyc
dist/
build/
*.egg-info/
+27 -21
View File
@@ -1,25 +1,28 @@
# kostky # kostky
Textová (CLI) hra v kostky pro více hráčů varianta klasického *Farkle* Textová (CLI) hra v kostky pro více hráčů varianta klasického *Farkle*
(„tisícovky“). Běží celá v terminálu a je napsaná v čistém Pythonu bez („tisícovky“). Běží celá v terminálu, v Pythonu, s jedinou závislostí
externích závislostí. (`tabulate` pro závěrečnou výsledkovou tabulku).
## Požadavky ## Požadavky
- Python 3.7+ (hra se spoléhá na zachování pořadí klíčů ve slovníku) - Python 3.9+
- Žádné externí balíčky používá se jen standardní knihovna - Závislost `tabulate` (doinstaluje se automaticky při instalaci)
(`random`, `os`, `re`, `csv`, `collections`, `typing`)
## Spuštění ## Instalace a spuštění
```bash ```bash
git clone https://gitea.spacilovi.eu/david/kostky.git git clone https://gitea.spacilovi.eu/david/kostky.git
cd kostky cd kostky
python main.py pip install .
kostky
``` ```
> Hru spouštěj z adresáře projektu soubor `hodnoty.csv` (bodovací tabulka) Případně bez instalace (s nainstalovaným `tabulate`) přes `python -m kostky`,
> se načítá relativní cestou vůči aktuálnímu adresáři. nebo izolovaně jako CLI nástroj přes `pipx install .`.
> Pro vývoj: `pip install -e ".[dev]"` editovatelná instalace včetně `mypy`
> a `ruff`.
## Jak se hraje ## Jak se hraje
@@ -31,42 +34,45 @@ python main.py
### Průběh tahu ### Průběh tahu
Na začátku tahu uvidíš cílové skóre, kdo je na tahu (a jeho celkové skóre) Na začátku tahu uvidíš, kdo je na tahu (s jeho skóre a cílem), a hod šesti
a hod šesti kostkami s očíslovanými pozicemi: kostkami s písmennými pozicemi:
``` ```
Vítězné skóre: 10000. Na tahu je Anna (0/10000).
Na tahu je Anna (0).
1, 5, 3, 2, 6, 4 1, 5, 3, 2, 6, 4
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
1 2 3 4 5 6 a b c d e f
``` ```
Pak zvolíš jednu z akcí: Pak zvolíš jednu z akcí:
| Vstup | Akce | | Vstup | Akce |
| --- | --- | | --- | --- |
| `pozice kostek` | Odložíš vybrané kostky k vyhodnocení (např. `1`, `34`, `123456`). Hodnoty na zvolených pozicích musí dohromady tvořit některou kombinaci z bodovací tabulky. | | `pozice kostek` | Odložíš vybrané kostky k vyhodnocení písmeny (např. `a`, `cd`, `abcdef`). Hodnoty na zvolených pozicích musí dohromady tvořit některou kombinaci z bodovací tabulky. |
| `h` | Hodíš znovu zbývajícími kostkami. Povoleno až poté, co v tomto tahu odložíš aspoň jednu bodující kombinaci. | | `h` | Hodíš znovu zbývajícími kostkami. Povoleno až poté, co v tomto tahu odložíš aspoň jednu bodující kombinaci. |
| `k` | Ukončíš tah, připíšeš si nasbírané skóre a předáš tah dalšímu hráči. | | `k` | Ukončíš tah, připíšeš si nasbírané skóre a předáš tah dalšímu hráči. |
| `exit` | Předčasně ukončíš celou hru. |
Odkládat můžeš i víc kombinací po sobě po každém vyhodnocení pokračuješ se Odkládat můžeš i víc kombinací po sobě po každém vyhodnocení pokračuješ se
zbývajícími kostkami a body se v rámci tahu sčítají. zbývajícími kostkami a body se v rámci tahu sčítají.
### Smůla, hot dice a výhra ### Smůla, hot dice, vynulování a výhra
- **Smůla (bust):** když čerstvý hod neobsahuje žádnou bodující kombinaci, - **Smůla (bust):** když čerstvý hod neobsahuje žádnou bodující kombinaci,
o body z tohoto tahu přijdeš a tah přechází na dalšího hráče. o body z tohoto tahu přijdeš a tah přechází na dalšího hráče.
- **Hot dice:** když se ti podaří odložit všech šest kostek, dostaneš čerstvý - **Hot dice:** když se ti podaří odložit všech šest kostek, dostaneš čerstvý
hod šesti kostkami a pokračuješ ve stejném tahu. hod šesti kostkami a pokračuješ ve stejném tahu.
- **Výhra:** po dokončení každého kola se kontroluje, jestli někdo dosáhl - **Vynulování:** tři po sobě jdoucí nulové tahy vynulují tvé celkové skóre.
cílového skóre. Vyhrává osamocený lídr nad limitem; při shodě na první Počet aktuálních nul se vedle skóre ukazuje jako `x` / `xx`.
příčce se hraje dál. - **Výhra:** po dokončení každého kola vyhrává osamocený lídr nad cílovým
skóre; při shodě na první příčce se hraje dál. Na konci se vypíše výsledková
tabulka s pořadím všech hráčů.
## Bodování ## Bodování
Kompletní tabulka je v souboru [`hodnoty.csv`](hodnoty.csv). Kombinace se Kompletní tabulka je v souboru [`kostky/hodnoty.csv`](kostky/hodnoty.csv).
vyhodnocuje podle hodnot odložených kostek na jejich pořadí nezáleží. Kombinace se vyhodnocuje podle hodnot odložených kostek na jejich pořadí
nezáleží.
### Stejné kostky ### Stejné kostky
View File
+4
View File
@@ -0,0 +1,4 @@
from kostky.kostky import main
if __name__ == "__main__":
main()
View File
+147 -85
View File
@@ -1,57 +1,57 @@
from typing import Any
from random import randint
from os import system, name
import re
import csv import csv
import re
from collections import Counter from collections import Counter
from importlib.resources import files
from os import name, system
from random import randint
from typing import Any
score = 0 from tabulate import tabulate
hodnoty = {}
players = {}
pointer = ""
first = ""
h_bool = True
aggr = {}
final_score = 10000
with open("hodnoty.csv", "r") as csv_file: score: int = 0
csv_reader = csv.reader(csv_file) hodnoty: dict[str, int] = {}
players: dict[str, list[int]] = {}
pointer: str = ""
first: str = ""
h_bool: bool = True
aggr: dict[str, int] = {}
final_score: int = 10000
for row in csv_reader: csv_text = files(__package__).joinpath("hodnoty.csv").read_text(encoding="utf-8")
key = str(row[0]) csv_reader = csv.reader(csv_text.splitlines())
value = int(row[1])
hodnoty[key] = value for row in csv_reader:
key = str(row[0])
value = int(row[1])
hodnoty[key] = value
def throw(count: int) -> str: def throw(count: int) -> str:
count = int(count) count = int(count)
new_throw = [] new_throw: list[str] = []
for dice in range(count): for _dice in range(count):
new_throw.append(str(randint(1, 6))) new_throw.append(str(randint(1, 6)))
new_throw = "".join(new_throw) new_throw_str: str = "".join(new_throw)
return new_throw return new_throw_str
def show_throw(throw: str) -> str: def show_throw(throw: str) -> str:
throw_temp = [] throw_temp: list[str] = []
for x in throw: for x in throw:
throw_temp.append(x) throw_temp.append(x)
pretty_list = ", ".join(throw_temp) pretty_list: str = ", ".join(throw_temp)
pretty_list += "\n" pretty_list += "\n"
for i in range(1, len(throw_temp) + 1): for _i in range(1, len(throw_temp) + 1):
pretty_list += "^ " pretty_list += "^ "
pretty_list += "\n" pretty_list += "\n"
for i in range(1, len(throw_temp) + 1): for i in range(len(throw_temp)):
pretty_list += f"{i} " pretty_list += f"{chr(97+i)} "
return pretty_list return pretty_list
def clear(): def clear() -> None:
if name == 'nt': _ = system('cls') if name == 'nt' else system('clear')
_ = system('cls')
else:
_ = system('clear')
def evaluate(inp: Any, count: int, throw: str) -> tuple: def evaluate(inp: Any, count: int, throw: str) -> tuple[Any, ...]:
global h_bool global h_bool
@@ -75,42 +75,56 @@ def evaluate(inp: Any, count: int, throw: str) -> tuple:
pointer = nxt_key pointer = nxt_key
inp = str(inp) inp = str(inp)
if re.search(r"^\d+$", inp): inp_temp: str = ""
if re.search(r"^[a-f]+$", inp):
if len(inp) > count: if len(inp) > count:
data = ("msg", "ER01", "Tolika kostkami nemůžete hodit. Zkuste to znovu:", count, throw) data: tuple[Any, ...] = (
return data "msg",
"ER01",
"Tolika kostkami nemůžete hodit. Zkuste to znovu: ",
count,
throw
)
else: else:
for x in inp: for x in inp:
x = int(x) x = ord(x)-96
if x > count: if x > count:
if count == 1: ran = "1" if count == 1 else f"1{count}"
ran = "1" data = (
else: "msg",
ran = f"1{count}" "ER02",
data = ("msg", "ER02", f"Zvolte prosím kostky v platném rozsahu ({ran}):", count, throw) f"Zvolte prosím kostky v platném rozsahu ({ran}): ",
count,
throw
)
return data return data
old_throw = [] else:
pick = [] inp_temp += str(x)
inp = inp_temp
old_throw: list[str] = []
pick: list[str] = []
for y in range(count): for y in range(count):
if str(y+1) not in inp: if str(y+1) not in inp:
old_throw.append(throw[y]) old_throw.append(throw[y])
if str(y+1) in inp: if str(y+1) in inp:
pick.append(throw[y]) pick.append(throw[y])
old_throw = "".join(old_throw) old_throw_str: str = "".join(old_throw)
pick.sort() pick.sort()
pick = "".join(pick) pick_str: str = "".join(pick)
new_count = len(old_throw) new_count: int = len(old_throw_str)
if not eval_score(pick): if not eval_score(pick_str):
new_count = count new_count = count
old_throw = throw old_throw_str = throw
else: else:
h_bool = False h_bool = False
data = ("count", new_count, old_throw) data = ("count", new_count, old_throw_str)
return data
elif inp == "h": elif inp == "h":
if h_bool: if h_bool:
clear() clear()
@@ -119,9 +133,19 @@ def evaluate(inp: Any, count: int, throw: str) -> tuple:
else: else:
h_bool = True h_bool = True
data = ("count", count, "") data = ("count", count, "")
return data
elif inp == "k": elif inp == "k":
global score global score
if score == 0:
last_two = players[pointer][-2:]
if len(last_two) > 1:
last_two_sum = sum(last_two)
if last_two_sum == 0:
score = -sum(players[pointer])
clear()
input("Po třech nulových hodech bylo vaše skóre vynulováno.")
players[pointer].append(score) players[pointer].append(score)
aggr[pointer] = sum(players[pointer]) aggr[pointer] = sum(players[pointer])
score = 0 score = 0
@@ -129,18 +153,22 @@ def evaluate(inp: Any, count: int, throw: str) -> tuple:
next_player() next_player()
data = ("count", 6, "") data = ("count", 6, "")
if pointer == first: if pointer == first:
winner = check_win() winner: str = check_win()
if winner: if winner:
data = ("win", winner, aggr[winner]) data = ("win", winner, aggr[winner])
return data
elif inp == "exit":
data = ("exit",)
else: else:
data = ("msg", "ER03", "Zadejte prosím platnou hodnotu:", count, throw) data = ("msg", "ER03", "Zadejte prosím platnou hodnotu:", count, throw)
return data
return data
def check_value(throw: str) -> bool: def check_value(throw: str) -> bool:
throw_s = sorted(throw) throw_s: list[str] = sorted(throw)
throw_str = "".join(throw_s) throw_str: str = "".join(throw_s)
hodnota = False hodnota: bool = False
for h in hodnoty: for h in hodnoty:
if h in throw_str: if h in throw_str:
hodnota = True hodnota = True
@@ -148,11 +176,11 @@ def check_value(throw: str) -> bool:
return hodnota return hodnota
def add_players(): def add_players() -> None:
global pointer, first, final_score global pointer, first, final_score
while True: while True:
inp = input("Zadejte jméno prvního hráče: ") inp: str = input("Zadejte jméno prvního hráče: ")
if inp == "": if inp == "":
print("Jméno nemůže být prázdné.") print("Jméno nemůže být prázdné.")
else: else:
@@ -180,8 +208,8 @@ def add_players():
inp = input("Zadejte finální skóre (nechte prázdné pro výchozích 10 000): ") inp = input("Zadejte finální skóre (nechte prázdné pro výchozích 10 000): ")
if inp != "": if inp != "":
try: try:
inp = int(inp) inp_int: int = int(inp)
final_score = inp final_score = inp_int
except ValueError: except ValueError:
print("Zadejte prosím platnou číselnou hodnotu.") print("Zadejte prosím platnou číselnou hodnotu.")
else: else:
@@ -189,40 +217,70 @@ def add_players():
else: else:
break break
def to_sorted_tuple(d: dict[str, int], s: bool = True) -> tuple[list[tuple[int, str, int]],
dict[str, int]]:
L: list[tuple[int, str, int]] = []
if s:
d = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))
for i, (k, v) in enumerate(d.items()):
L.append((i+1, k, v))
return L, d
def check_win() -> str: def check_win() -> str:
over_limit = {} over_limit: dict[str, int] = {}
for p in aggr: for p in aggr:
if aggr[p] >= final_score: if aggr[p] >= final_score:
over_limit[p] = aggr[p] over_limit[p] = aggr[p]
winner = "" winner: str = ""
if over_limit: if over_limit:
over_limit_sorted = dict(sorted(over_limit.items(), key=lambda x: x[1], reverse=True)) L, d = to_sorted_tuple(over_limit)
over_limit_sorted_list = []
for ols in over_limit_sorted:
over_limit_sorted_list.append((ols, over_limit_sorted[ols]))
count: Counter = Counter(list(d.values()))
count = Counter(list(over_limit_sorted.values()))
for c in count: for c in count:
if count[c] == 1: if count[c] == 1:
winner = over_limit_sorted_list[0][0] winner = L[0][1]
break break
return winner return winner
def game(data: tuple): def win(winner: str, score: int) -> None:
results, d = to_sorted_tuple(aggr)
headers = ["Pořadí", "Hráč", "Skóre"]
table = tabulate(results, headers=headers, tablefmt="fancy_grid")
print(f"Vítězem se stává {winner} s {score} body!")
print("Díky za hru a zase příště.\n")
print(table)
def game(data: tuple) -> None:
while True: while True:
if data[0] == "count": if data[0] == "count":
count = data[1] count: int = data[1]
th = data[2] th: str = data[2]
if(th == ""): if(th == ""):
th = throw(count) th = throw(count)
clear() clear()
print(f"Vítězné skóre: {final_score}.") # print(f"Vítězné skóre: {final_score}.")
print(f"Na tahu je {pointer} ({aggr[pointer]}).")
strikes: str = ""
if len(players[pointer]) > 0:
if players[pointer][-1:][0] == 0:
strikes = "x"
if players[pointer][-2:][0] == 0:
strikes += "x"
else:
strikes = ""
print(f"Na tahu je {pointer} ({aggr[pointer]}{strikes}/{final_score}).")
print(show_throw(th)) print(show_throw(th))
if not check_value(th) and data[2] == "" and count != 0: if not check_value(th) and data[2] == "" and count != 0:
global score global score
@@ -232,12 +290,14 @@ def game(data: tuple):
else: else:
if count == 0: if count == 0:
count = 6 count = 6
msg = ("Jakou akci chcete provést?\n" msg: str = ("Jakou akci chcete provést?\n"
" - Zadejte kombinaci kostek k ponechání (dojde k jejich vyhodnocení): [pozice kostek] (např. 1 nebo 34 nebo 123456).\n" " - Zadejte kombinaci kostek k ponechání (dojde k jejich vyhodnocení): "
"[pozice kostek] (např. a nebo cd nebo abcdef).\n"
" - Hodit znovu: [h].\n" " - Hodit znovu: [h].\n"
f" - Ukončit hod a ponechat si skóre z tohoto hodu ({score}): [k].\n" f" - Ukončit hod a ponechat si skóre z tohoto hodu ({score}): [k].\n"
" - Ukončit hru: [exit].\n"
) )
inp = input(msg) inp: str = input(msg)
data = evaluate(inp, count, th) data = evaluate(inp, count, th)
elif data[0] == "msg": elif data[0] == "msg":
@@ -245,7 +305,6 @@ def game(data: tuple):
msg = data[2] msg = data[2]
count = data[3] count = data[3]
th = data[4] th = data[4]
clear()
inp = input(msg) inp = input(msg)
data = evaluate(inp, count, th) data = evaluate(inp, count, th)
else: else:
@@ -254,19 +313,22 @@ def game(data: tuple):
break break
elif data[0] == "win": elif data[0] == "win":
winner = data[1] winner: str = data[1]
score = data[2] score = data[2]
clear() clear()
print(f"Vítězem se stává {winner} s {score} body!") win(winner, score)
print("Díky za hru a zase příště.")
break break
def main(): elif data[0] == "exit":
clear()
print("Hra byla předčasně ukončena. Snad se zase brzy uvidíme.")
break
def main() -> None:
clear() clear()
print("Vítejte ve hře v kostky!") print("Vítejte ve hře v kostky!")
add_players() add_players()
input("Prosím, stiskněte enter pro první hod:")
data = ("count", 6, "") data = ("count", 6, "")
game(data) game(data)
+50
View File
@@ -0,0 +1,50 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "kostky"
dynamic = ["version"]
description = "Textová hra v kostky."
readme = "README.md"
requires-python = ">=3.9"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
authors = [{ name = "David Spáčil" }]
keywords = ["hra", "kostky", "cli"]
classifiers = [
"Programming Language :: Python :: 3",
"Environment :: Console",
"Operating System :: OS Independent",
"Topic :: Games/Entertainment",
]
dependencies = [
"tabulate",
]
[project.optional-dependencies]
dev = ["mypy", "ruff", "types-tabulate"]
[project.urls]
Repository = "https://gitea.spacilovi.eu/david/kostky"
[project.scripts]
kostky = "kostky.kostky:main"
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.targets.wheel]
packages = ["kostky"]
[tool.mypy]
python_version = "3.9"
strict = true
files = ["kostky"]
[tool.ruff]
target-version = "py39"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]