summaryrefslogtreecommitdiffstats
path: root/docs/GAMES.md
blob: 52ab1bf9b8f41d091c2f0301e34e6e77aa027ab3 (plain) (blame)
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
# 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 |

## Planned

| Game | Shape | Main open decisions |
| --- | --- | --- |
| Blackjack | Multi-step hand | Dealer hits/stands soft 17; double/split/insurance; blackjack payout; shoe persistence |
| Crash | Real-time round | Crash-point distribution; cashout latency; provable fairness |

Blackjack needs server-side hand state and player actions. Crash needs a timed
round loop and a cashout action, so it should come after blackjack unless we
redesign the loop first.

## 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%.

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}]}"}
```

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 and crash still need a generic state/action exchange, for example:

```
GAME_START <game> <bet>
GAME_ACTION <action> [json]
GAME_STATE <json>          # cards, bets, multiplier, legal actions, result
```

That will be its own protocol change.