| Autoři zadání | xhanulik Roman Lacko |
|---|---|
| Odevzdávané soubory | src/* |
| Konec odevzdání části A | 2026-04-08 24:00 |
| Konec odevzdání části B | 2026-05-07 24:00 |
|
Kostru k úkolu nelze stáhnout
|
Představení úkolu
Stolní RPG hry jako Dungeons & Dragons používají standardizovaný zápis pro házení kostkami – tzv. dice notation.
Výraz 2d6 + 3 znamená: „hoď dvěma šestistěnnými kostkami a přičti 3".
Hráči a herní systémy takové výrazy vyhodnocují neustále, a proto existuje potřeba nástroje, který je dokáže automaticky zparsovat a vyhodnotit.
Specifikace jazyka kostkových výrazů
Podporované výrazy
Jazyk podporuje aritmetické výrazy nad kostkami a celými čísly.
| Konstrukce | Syntaxe | Příklad |
|---|---|---|
Hod kostkou |
|
|
Kladné celé číslo |
|
|
Sčítání |
|
|
Odčítání |
|
|
Násobení |
|
|
Unární mínus |
|
|
Závorky |
|
|
Keep highest |
|
|
Keep lowest |
|
|
Poznámky:
-
XdYznamená hodXnezávislými kostkami, každá má hodnoty1..Y. -
Písmena
d,h,ljsou vždy malá. -
Mezi tokeny (operátory, závorkami, čísly, výrazy hodu kostkou) může být libovolné množství bílých znaků.
-
2d6 + 3,2d6+3i `2d6+ 3 ` jsou platné výrazy.
-
-
Zápis kostky (
XdY,XdYhZ,XdYlZ) nesmí obsahovat žádné mezery.-
2 d6je chybný zápis. -
2d6 + 3je platný výraz. -
- 2d6je platný výraz.
-
-
Pro konstrukce
XdYhZaXdYlZje chyba, pokud:-
Z > X -
Z == 0
-
-
Pro hod kostkou
XdYplatí:-
X ≥ 1 -
Y ≥ 1
-
-
Unární mínus se váže na nejbližší následující výraz a nelze použít uvnitř zápisu kostky.
-
Literály musí být v rozsahu typu
int.
První část (A)
Vaším úkolem je napsat interpret jazyka kostkových výrazů. Napíšete parser pro kostkové výrazy a evaluátor, který zpracovaný výraz vyhodnotí a vrátí výsledek hodu.
Opaque kostkový výraz
Struktura dice_expr představuje kostkový výraz.
Její vnitřní reprezentace není součástí rozhraní a je plně v režii implementace.
typedef struct expr dice_expr;
Požadované rozhraní
Vaším úkolem je implementovat následující rozhraní pro parsování a vyhodnocení kostkových výrazů:
typedef enum {
DICE_OK = 0,
DICE_ERR_SYNTAX, // chyba syntaxe: neplatný znak, konec vstupu, závorka,
// přebytečný vstup, chybný zápis kostky, neplatné Z,
// přetečení číselného literálu
DICE_ERR_PARSE, // jiná chyba parsování (alokace paměti apod.)
DICE_ERR_EVAL, // chyba při vyhodnocení výrazu
} dice_error;
dice_error dice_parse(const char *input, dice_expr **out);
dice_error dice_eval(const dice_expr *dice_expression, int *out);
void dice_free(dice_expr *dice_expression);
Parsování kostkového výrazu
Funkce dice_parse():
-
zpracuje vstupní řetězec (string)
input, -
vytvoří interní reprezentaci výrazu
dice_expr.
Výstup:
-
při úspěchu nastaví *out na vytvořený výraz a vrátí
DICE_OK, -
při chybě nastaví *out na nullptr a vrátí příslušný chybový kód.
Chybové kódy jsou následující:
| Kód chyby | Situace | Příklad vstupu |
|---|---|---|
|
Neplatný znak, předčasný konec vstupu, chybějící závorka, přebytečný vstup, chybný zápis kostky ( |
|
|
Jiná chyba parsování (selhání alokace paměti) |
— |
Evaluace kostkového výrazu
Funkce dice_eval() vyhodnotí předaný výraz typu dice_expr.
Výstup:
-
při úspěchu uloží výsledek do *out a vrátí
DICE_OK, -
při chybě uloží do *out hodnotu 0 a vrátí chybový kód
DICE_ERR_EVAL.
Možné chyby:
-
vstupní argument
nullptr, -
Generátor náhodných čísel vrátil hodnotu mimo
[1, sides], -
Mezivýsledek nebo výsledek přesahuje rozsah
int, -
další běhové chyby.
Generování náhodných čísel
Při vyhodnocení hodu kostkou potřebuje dice_eval() zdroj náhodnosti.
Ten je implementován knihovnou rng, která exportuje poskytuje následující rozhraní:
/* Nastaví počáteční stav generátoru pseudonáhodných čísel. */
void rng_seed(unsigned seed);
/* Vrátí pseudo-náhodné číslo v rozsahu 0 až max-1. */
uint16_t rng_uniform(uint16_t max);
Pro vygenerování hodu kostkou se Y stranami zavolejte rng_uniform(Y) + 1, čímž získáte požadovanou hodnotu v rozsahu [1, Y].
Funkce rng_seed() inicializuje generátor a musí být zavolána před prvním voláním rng_uniform().
Testy ji využívají k nastavení deterministické sekvence hodů — dvě volání dice_eval() se stejným seedem vždy produkují stejný výsledek.
Funkci rng_uniform() volejte pouze při skutečném vyhodnocení hodu kostkou.
Zbytečná volání (např. zpracování výrazů bez hodu) by narušila deterministickou sekvenci a znemožnila správné testování.
Pořadí vyhodnocení
Kostkový výraz nelze vyhodnotit pouhým čtením znak po znaku, protože obsahuje závorky a operátory s různou prioritou. Je proto nutné nejprve správně rozpoznat strukturu výrazu a teprve poté jej vyhodnotit. Při vyhodnocování je důležité respektovat pořadí operací a pravidla priority. Vyhodnocení probíhá tak, že se postupuje systematicky zleva doprava, přičemž se vždy nejprve vyhodnotí levá část výrazu a až poté pravá část.
Toto pořadí je zvlášť důležité u hodů kostkou, protože každý hod spotřebovává náhodná čísla jako vedlejší efekt. Pořadí vyhodnocení proto ovlivňuje výsledný výsledek a musí být jednoznačné, konzistentní a deterministické.
Uvažujte výraz 1d6 - 1d4.
Jeho strukturu lze znázornit pomocí stromu:
Odčítání ← kořen, vyhodnotí se poslední / \ Hod Hod ← listy, vyhodnotí se první, zleva doprava 1d6 1d4
Nejprve se vyhodnotí oba operandy (hody kostkami) zleva doprava a teprve poté se aplikuje operace odčítání. Evaluátor proto postupuje tak, že vždy nejprve vyhodnotí levý podstrom, následně pravý podstrom a až poté provede samotnou operaci v aktuálním uzlu. Tento postup zajišťuje správné a deterministické pořadí vyhodnocení, které je důležité zejména u operací s vedlejším efektem, jako je hod kostkou.
Příklady kostkových výrazů zapsaných jako stromy
Výraz 2d6 + 3:
Sčítání / \ Hod Číslo 2d6 3
Možné vyhodnocení:
-
hod
2d6: padne2a5→ součet7 -
výraz:
7 + 3 = 10
Výraz 4d6h3 * 2:
Násobení
/ \
Keep h. Číslo
4d6h3 2
Možné vyhodnocení:
-
hod
4d6: padne například1,6,3,4 -
vybereme 3 nejvyšší:
6,4,3→ součet13 -
výraz:
13 * 2 = 26
Výraz -(1d4 + 1):
Unární mínus
|
Sčítání
/ \
Hod Číslo
1d4 1
Možné vyhodnocení:
-
hod
1d4: padne3 -
výraz v závorce:
3 + 1 = 4 -
aplikace unárního mínusu:
-4
Druhá část (B)
V první části jste napsali interpret, který zparsuje kostkový výraz a jednou ho vyhodnotí. V praxi ale hráč ani návrhář herního systému nechce znát jeden hod, ale chce vědět, co od daného výrazu očekávat: jaká je nejmenší a největší možná hodnota, jaký je průměrný výsledek a jak typicky vypadá distribuce hodů, když výraz padne dostatečně mnohokrát.
V druhé části tedy doplníte rozšíříte svou implementaci z části A o dvě nové funkce, které tento statistický pohled na kostkový výraz poskytnou:
-
dice_eval_stats()— analytická funkce. Statistiky neodvozuje experimentálně, ale spočítá je přímo z výrazu. Funkce odvodí minimum, maximum a očekávanou hodnotu pomocí pravidel daných v zadání. Žádný hod kostkou se neprovádí, generátor náhodných čísel se vůbec nevolá. -
dice_eval_n()— paralelní vzorkování. Výraz se vyhodnotí celkemn-krát s nezávislými hody a z posbíraných vzorků se spočítají sumární statistiky (krajní hodnoty, aritmetický průměr a tři kvartily).
Jazyk kostkových výrazů se v této části nemění — žádné nové konstrukce
ani operátory. Pouze se rozšiřuje rozhraní knihovny o dvě nové funkce
a dvě nové datové struktury (dice_stats, dice_n_stats).
Analytická statistika výrazu
Funkce dice_eval_stats() vrací statistické vlastnosti spočtené přímo z výrazu.
typedef struct {
int min; /* minimální možná hodnota výrazu */
int max; /* maximální možná hodnota výrazu */
double mean; /* očekávaná hodnota výrazu */
} dice_stats;
dice_error dice_eval_stats(const dice_expr *expr, dice_stats *out);
Pravidla výpočtu
Hodnoty se počítají rekurzivně podle struktury výrazu:
| Výraz | min | max | mean |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
min ze 4 součinů (viz níže) |
max ze 4 součinů (viz níže) |
|
Poznámky:
-
U
XdYhZaXdYlZjemeandefinován velmi zjednodušeně jakoZ * (Y + 1) / 2, tedy jako kdyby šlo oZdY. Tato definice je pro účely této úlohy závazná a od přesné hodnoty (která vyžaduje výpočet pořádkových statistik) se může lišit. -
U násobení
A * Bmůže mítAmin..AmaxneboBmin..Bmaxzápornou hodnotu (kvůli unárnímu mínusu), protominamaxmusí při výpočtu využít ze všech čtyř krajních součinůAmin * Bmin,Amin * Bmax,Amax * Bmin,Amax * Bmaxk nalezení minimální a maximální hodnoty. -
Pro očekávanou hodnotu součinu lze využít, že podvýrazy
AaBčerpají náhodnost z disjunktních hodů, jsou tedy nezávislé:E[A*B] = E[A] * E[B].
Paralelní vzorkování
Funkce dice_eval_n() vyhodnotí výraz celkem n-krát pomocí zadaného počtu vláken a vrátí sumární
statistiky pozorovaných vzorků.
typedef struct {
int min; /* nejmenší pozorovaný vzorek */
int max; /* největší pozorovaný vzorek */
double mean; /* aritmetický průměr vzorků */
int q1; /* dolní kvartil (25. percentil) */
int median; /* medián (50. percentil) */
int q3; /* horní kvartil (75. percentil) */
} dice_n_stats;
dice_error dice_eval_n(const dice_expr *expr,
dice_n_stats *out,
unsigned n,
unsigned threads);
Sémantika:
-
Funkce vyhodnotí výraz
exprcelkemn-krát. -
Označme
S[0..n-1]posloupnost vzorků seřazenou vzestupně. Výstupní hodnoty jsou definovány takto (bez interpolace; indexy jsou celočíselné výrazy v C se zaokrouhlením dolů):-
min=S[0], -
q1=S[n / 4], -
median=S[n / 2], -
q3=S[3 * n / 4], -
max=S[n - 1].
-
-
mean= aritmetický průměr všechnvzorků.
Před funkcí dice_eval_n() je opět potřeba volat rng_seed().
|
Paralelizace
Implementace musí využít vlákna knihovny C23 <threads.h> (thrd_create,
thrd_join). Počet vláken je předáván parametrem threads.
Chybové stavy
| Situace | Výsledek |
|---|---|
|
|
|
|
Selhání jednotlivého vzorku (přetečení, RNG mimo rozsah, …) |
|
Běhové chyby selhání vytvoření vlákna |
|
Při chybě je obsah *out nastaven na nuly a funkce vrátí chybový kód.