1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
# Games and the play audit
## Implemented
| Game | Status | Notes |
| --- | --- | --- |
| Slots | Done | 5 reels x 3 rows, 5 paylines, 98.6% RTP |
| Roulette | Done | European wheel, split bets, 97.3% RTP |
| Blackjack | Done | Six-deck shoe per hand, S17, 3:2, hit/stand/double |
## Planned
| Game | Shape | Main open decisions |
| --- | --- | --- |
| Crash | Real-time round | Crash-point distribution; cashout latency; provable fairness |
Crash needs a timed round loop and a cashout action, so it should be its own
protocol/loop change.
## Blackjack
- Six-deck shoe, shuffled fresh for every hand and discarded afterwards.
- Dealer stands on all 17s (including soft 17).
- Blackjack pays 3:2, rounded down to whole PLN.
- Hit, stand, and double; no split, insurance, or surrender.
- The bet is reserved when the hand starts. Cancel or disconnect refunds it.
- A natural or dealer blackjack settles immediately.
Client controls:
```
H hit
S stand
D double (on the first two cards)
SPACE deal the next hand
+ or = / - change the next hand's bet
Q leave the table (active hand is cancelled and refunded)
```
## Roulette
- European wheel: 0-36, one green zero.
- Bets: red, black, even, odd, and straight numbers.
- Bets can be split across all of those; every winning bet is added together.
- Straight pays 35:1 (36x returned), outside bets pay 1:1 (2x returned).
- 0 loses all outside bets. RTP is 36/37 = 97.30%.
- Numbers are color-coded in the TUI: red numbers are red, 0 is green, and
black numbers stay at the default terminal color. Bet amounts and balances
are not colored.
Client controls inside roulette:
```
R / B / E / O add the current chip to red/black/even/odd
digits + Enter add the current chip to that number
C clear all bets
+ or = / - change the chip size
SPACE spin
Q leave the table
```
The result is shown as a CS:GO-style horizontal strip: the ball marker stays
fixed and the wheel numbers scroll beneath it, ending on the winning number.
## Audit schema
Every settled play is one row in `plays`:
```
plays (
session_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
game_type TEXT NOT NULL,
bet_amount INTEGER NOT NULL,
winnings INTEGER NOT NULL,
balance_after INTEGER NOT NULL,
details TEXT NOT NULL DEFAULT '{}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
```
`details` is game-specific JSON:
```json
// slots
{"reels":"CCCCLLCLODGSDGS","win_type":"win","win_lines":"1","win_cells":"0,3,6"}
// roulette
{"number":"17","color":"black","bets":"{\"red\":10,\"black\":10,\"even\":10,\"odd\":10,\"numbers\":[{\"number\":17,\"amount\":5}]}"}
// blackjack
{"player":["10S","6H","5D"],"dealer":["9D","8C"],"player_total":21,"dealer_total":17,"bet":10,"result":"win","winnings":20}
```
The balance update and the `plays` insert happen in one transaction
(`database::apply_play`).
## Wire protocol
Slots keeps its original exchange:
```
PLAY_START slots <bet>
GAME_RESULT <session> <winnings> <bet> <balance> <reels> <win_type> <win_lines> <win_cells>
```
Roulette adds a compact bets payload and a roulette-specific result:
```
PLAY_START roulette <total> <bets-json>
ROULETTE_RESULT <session> <winnings> <bet> <balance> <number> <color> <bets-json>
```
Blackjack is a multi-step session:
```
PLAY_START blackjack <bet>
BLACKJACK_STATE <json>
BLACKJACK_ACTION <session> hit|stand|double|cancel
BLACKJACK_STATE <json>
```
`BLACKJACK_STATE` carries the player/dealer cards, totals, actions, balance,
result and winnings. The dealer's hole card is `?` until the hand is done.
|