Configuration
All options live in config.php (generated by setup.php).
Edit the file with any text editor, or use the built-in editor at
admin.php?cfg=1 — four tabs, help text, dropdowns, shipped defaults and validation,
described in Admin & Settings.
Defaults reproduce a standard 9-seat cash table (blinds 10/20, 1000 starting chips).
Storage
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_DATA_DIR | string | 'data' |
Writable data folder (users, tables, hands, logs). Relative values are resolved from the
application folder, so 'data' means ; an absolute
path is also accepted and is written that way by older installers. |
Table defaults
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_SEATS | int | 9 |
Seats per table (2–9). The client reads this value automatically. It can be changed at
any time and the saved table is resized on the next request, keeping every stack:
|
POKER_START_CHIPS | int | 1000 | Starting stack and free rebuy amount. |
POKER_SB | int | 10 | Default small blind for new tables. |
POKER_BB | int | 20 | Default big blind for new tables. |
POKER_TURN_MS | int | 30000 | Thinking time per turn, in milliseconds. |
POKER_NEXT_HAND_MS | int | 10000 | Pause between hands (ms). |
POKER_AWAY_MS | int | 120000 | Disconnect threshold before a player counts as away (ms). |
POKER_AWAY_REMOVE_MS | int | 600000 | Remove a sitting-out player after this long (ms). |
POKER_RUNOUT_MS | int | 3000 | Pause between streets during an all-in run-out (ms). |
POKER_SHOW_MS | int | 3000 | Pause after the river before declaring the winner (ms). |
Tournament
Defaults for the tournament dialog. Players can change every value per tournament in the vote dialog; these are only the starting values. See Tournament Mode for the full behaviour.
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_TOURNEY_ENABLED | bool | true |
Show the 🏆 Tournament button. false = cash-only game. |
POKER_TOURNEY_START_CHIPS | int | 1500 |
Starting stack = buy-in, taken from every account when the tournament starts. |
POKER_TOURNEY_SB / POKER_TOURNEY_BB | int | 10 / 20 |
Blinds of the first level. |
POKER_TOURNEY_LEVEL_MIN | int | 5 |
Minutes per blind level. The ladder follows the starting BB: 1x, 1.5x, 2.5x, 5x, 7.5x, 10x … |
POKER_TOURNEY_REBUY | bool | true |
Rebuys allowed at all (the number and the window are below). |
POKER_TOURNEY_START_SEC | int | 30 |
Countdown after the vote, in seconds (the tournament starts between two hands). |
POKER_TOURNEY_PAYOUT | string | 'winner' |
winner = all to 1st · percent = by percentage · fixed = fixed amounts. |
POKER_TOURNEY_PAYOUT_LIST | string | '50/30/20' |
Places used by percent / fixed, e.g. 50/30/20 or 500/300/200. |
POKER_TOURNEY_LATE | int | 0 |
Late registration window (0 = closed). |
POKER_TOURNEY_UNIT | string | 'levels' |
Unit for the late-reg and rebuy windows: levels or minutes. |
POKER_TOURNEY_REBUYS | int | 1 |
Rebuys per player (0 = none, 99 = unlimited). |
POKER_TOURNEY_REBUY_UNTIL | int | 0 |
End of the rebuy window in the unit above (0 = whole tournament). |
POKER_TOURNEY_REBUY_SEC | int | 30 |
Seconds to answer the rebuy offer. No answer = eliminated (the table waits meanwhile). |
POKER_TOURNEY_BREAK_EVERY | int | 0 |
Break every N minutes of play (0 = no breaks). |
POKER_TOURNEY_BREAK_MIN | int | 5 |
Length of a break, in minutes. |
POKER_TOURNEY_BREAK_MODE | string | 'every' |
every = every N minutes of play · clock = at :MM of every hour. |
POKER_TOURNEY_BREAK_AT | int | 55 |
The :MM minute used by the clock mode. |
Room
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_DEFAULT_ROOM | string | 'MonteCarlo' |
Name of the table opened when the address has no #Room (2–20 chars,
letters/digits/underscore/dash). This table is always playable. |
POKER_ROOMS | array | not shipped (empty) | Extra rooms that may be played, one code per entry, e.g.
define('POKER_ROOMS', ['London', 'Paris']);. It is a whitelist: a link such as
index.php#Vegas plays the default table while Vegas is not listed,
so a table is never created just because somebody typed a URL. Removing a code
closes that room gracefully — the current hand is finished, the stacks are settled and
the table is parked; adding the code back reopens it with the same stacks. Managed as chips in
the Table & Rooms tab (Admin & Settings). |
POKER_BOT_NAMES | array | 8 English names | Names used when a bot is seated (the first unused name is picked). |
Features
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_HISTORY_ENABLED | bool | true | Record hands and enable replay. |
POKER_HISTORY_MAX_HANDS | int | 50 | Hands kept per room (0 = unlimited). |
POKER_HISTORY_LIST_DEFAULT | int | 50 | Hands listed to a player by default. |
POKER_HISTORY_LIST_MAX | int | 50 | Hard cap for the hands list. |
POKER_HISTORY_SCAN_MAX | int | 60 | Max hand files inspected while building a list. |
POKER_CHAT_ENABLED | bool | true | Enable in-game chat (the panel is hidden when false). |
POKER_BOTS_ALLOWED | bool | true |
Bots are allowed at the table. false hides every bot control, refuses
addbot and hides the whole AI card in the settings page. |
POKER_BOTS_ADD_BY_PLAYERS | bool | true |
Players may seat a bot on an empty chair with 🤖 Add bot. false leaves the
button visible only to the admin observer and answers
Only the admin can add bots to everybody else. |
POKER_BOT_STYLES_ENABLED | bool | true |
Bot personalities (Rock / TAG / LAG / Station) plus a personality-aware thinking time.
The archetype is drawn from the bot's own id when it sits down, so nobody picks it and it never
changes while the bot plays. false makes every bot play the identical baseline
engine again (fixed 1.5–2.8 s wait, no style deltas). See
User Guide → Bot personalities. |
POKER_BLINDS_BY_PLAYERS | bool | true |
Players may change the table blinds with the ⚙️ Blinds button. false
removes the button and answers Changing the blinds is disabled; the admin can still
change them. |
POKER_RABBIT_ENABLED | bool | true |
Rabbit Hunt: when a hand ends by fold-out, reveal the cards that would have come next
(5 / 2 / 1 depending on the street). false disables the reveal, its log line and
the animation completely. See User Guide. |
POKER_RABBIT_MS | int | 3500 |
How long the Rabbit Hunt cards stay on screen before they fade out (ms). |
POKER_RABBIT_SOUND | bool | true |
Short "pop" played as each Rabbit Hunt card flips. Works on top of the player's own
🔊 Sound ON/OFF button — false here disables it for everybody. |
POKER_HAND_METER_ENABLED | bool | true |
Live hand-strength bar under the hand name (0–100% against the players still in
the pot, calculated in the browser). false hides the bar and skips the
calculation completely. |
POKER_TABLE_INFO_ENABLED | bool | true |
Small badge in the top-left corner of the table: how many players are seated
(👤 3 online) and how many of them are bots (🤖 2 bots, hidden while no bot
is seated). false hides the badge completely. |
Chat
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_CHAT_MAX | int | 50 | Messages kept per room. |
POKER_CHAT_MAX_LEN | int | 200 | Max characters per message. |
POKER_CHAT_MIN_GAP_MS | int | 700 | Anti-flood delay between a player's own messages (ms). |
POKER_BANNED_EXTRA | array | [] |
Extra banned words merged with the built-in list. See below. |
Adding your own banned words
Add words to POKER_BANNED_EXTRA (the built-in list is always applied):
define('POKER_BANNED_EXTRA', ['word1', 'word2', 'word3']);
The censor also matches common separators and "leet" substitutions (e.g. f.u.c.k, sh1t).
Avatars / Countries
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_AVATAR_DIR | string | 'assets/avatars' |
Web-relative folder with numbered avatar images (1.jpg, 2.png, …). Do not use an absolute path — it is also used as the image URL. |
POKER_AVATAR_MAX | int | 50 | How many avatars the picker offers. |
POKER_FLAG_DIR | string | 'assets/flags' |
Web-relative folder with 2-letter flag files (ro.svg, de.png, …). |
The default avatars, flags, sounds and countries.json ship inside assets/.
They are product files: updates replace them, and the installer never writes into data/
for them. If you keep your own set, point these two constants at it (for example
'data/avatars'); the shipped folder stays as a fallback, and a file with the same
number or country code in your folder wins.
Themes — Google Fonts
Most of the bundled themes load a display font from Google Fonts via a CSS
@import at the top of their theme.css (14 of the 15 themes —
all except classic). This is optional and only affects the visual style of
those themes; the game works fine without it.
Making the game 100% offline
If your host blocks external requests, or you simply want zero external calls, open each
theme's theme.css and remove the first line:
@import url('https://fonts.googleapis.com/css2?family=...');
Then set the display font in the same file's :root block to a system stack:
--font-display: Georgia, 'Times New Roman', serif;
--font-ui: 'Segoe UI', system-ui, -apple-system, sans-serif;
The theme then falls back to safe system fonts with a similar look. No other change is needed — no PHP edit, no re-install. To list every theme that makes an external call:
findstr /S /I "fonts.googleapis.com" assets\themes\*.css
Self-hosting the fonts
You can also download the font files (WOFF2) from
fonts.google.com and place them in
assets/themes/, then replace the @import line
with a local @font-face block that points at those files.
Security
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_SESSION_NAME | string | 'poker_session' | PHP session cookie name (shared by index.php and api.php). |
POKER_LOGIN_MAX_ATTEMPTS | int | 5 | Login/register attempts allowed per IP per window. |
POKER_LOGIN_LOCKOUT_SEC | int | 900 | Rate-limit window in seconds (900 = 15 minutes). |
POKER_CSRF_ENABLED | bool | true | Validate a CSRF token on every state-changing action. |
POKER_HTTPS_ONLY | bool | false | When true: session cookie becomes Secure + SameSite=Strict. Enable on production. |
POKER_ADMIN_HASH | string | '' |
bcrypt hash of the admin.php password. Written by setup.php
(asked at install; change it there under "Admin password"). Empty means the panel
refuses to open — there is no built-in password. Do not edit it by hand: an invalid
hash locks the panel, and the recovery is running setup.php again. |
Enabling HTTPS
define('POKER_HTTPS_ONLY', true);
Then uncomment the HTTPS redirect block in the root .htaccess after your certificate is live.
Card randomness
There is no POKER_* option for it, on purpose: the deck is always shuffled with PHP's
random_int() (CSPRNG, OS entropy) inside a Fisher–Yates loop, so a misconfigured site can
never silently fall back to rand()/mt_rand() and become predictable.
Details in Fair deal & card randomness.
Debug
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_DEBUG | bool | false |
true shows PHP errors in the browser (development only). false logs them to file. Keep false on production. |
POKER_LOG_DIR | string | 'data/logs' |
Where php-errors.log is written when debugging is off. Relative values are
resolved from the application folder; an absolute path is also accepted. |
AI Bots (DeepSeek) — optional
The bots are 100% rule-based by default (pure PHP: GTO ranges, Monte-Carlo equity,
pot odds, outs, board texture and an opponent model in data/bot_stats.json).
You can optionally add a DeepSeek layer that judges on top of those numbers: it receives
the engine's own equity / pot-odds / outs plus the opponent stats and the rule engine's
intended line, and may override it. Every answer is validated and "leashed", and on any
error or timeout the rule engine's action stands — an action is never illegal and the table
never freezes.
The whole integration is a single file: bot/bot_ai.php. With an empty API key
(or POKER_AI_ENABLED = false) it is a complete no-op and costs nothing.
setup.php sets ENABLED = true and the key automatically when you
enter one during install.
Getting a DeepSeek API key
- Create an account at platform.deepseek.com.
- Add credit — DeepSeek is pay-per-use (each bot decision costs a fraction of a cent).
- Open the API keys section and create a new key (it starts with
sk-). - Put it in
config.php:
…or simply enter it in the installer (define('POKER_AI_ENABLED', true); define('POKER_AI_API_KEY', 'sk-...');setup.php) — it sets the key andPOKER_AI_ENABLED = truefor you at install time. - Keep the key secret: do not share it or post it publicly. If it leaks, delete it in the dashboard and create a new one. An empty key turns the whole AI layer off.
The key is only ever sent to POKER_AI_ENDPOINT (DeepSeek by default) and is
never written to the logs — check with the browser/API keys page that no other app uses it.
Checking the remaining credit
Open the settings page admin.php?cfg=1 (admin password). On the right of the
AI BOTS (DeepSeek) card header there is a green badge with the account balance, for example
2.16 USD. It is read from DeepSeek's own
GET https://api.deepseek.com/user/balance with the configured key, so it is the
real remaining credit, not an estimate.
- One free request per page load (6 s timeout, no cache file); hover the badge for the tooltip DeepSeek API — credit left.
- If the key is missing or invalid, cURL is unavailable, or the host cannot reach DeepSeek, the
badge turns red and reads
unavailable— the reason is in the tooltip. - The figure is account-wide: it covers the bots and any other application using the same key.
- It tells you what is left, not what was spent over a period — DeepSeek has no per-period history endpoint, only the current balance.
- The key travels only in the
Authorizationheader and is never printed on the page.
Full settings block
// ============ AI BOTS (DeepSeek) ============
// Empty key (or POKER_AI_ENABLED=false) keeps the bots fully rule-based and free.
// Setup sets ENABLED=true + the key automatically when you enter one.
define('POKER_AI_ENABLED', false);
define('POKER_AI_API_KEY', '');
define('POKER_AI_ENDPOINT', 'https://api.deepseek.com/v1/chat/completions');
define('POKER_AI_MODE', 'hybrid');
define('POKER_AI_MODEL', 'deepseek-flash');
define('POKER_AI_MODEL_BIG', 'deepseek-v4-pro');
define('POKER_AI_THINKING', 'disabled');
define('POKER_AI_THINKING_BIG', 'disabled');
define('POKER_AI_TRIGGER', 'marginal');
// Use the AI only when a real (human) player is actually playing at the table.
// A human on sit-out (away) or still waiting for the BB does not count, so games
// with only bots (or an away human) stay fully rule-based and free.
define('POKER_AI_ONLY_WITH_HUMANS', true);
define('POKER_AI_SAMPLE_PCT', 25);
define('POKER_AI_MARGIN_BAND', 0.10);
define('POKER_AI_BIGBET_PCT', 0.25);
define('POKER_AI_LEASH', 'normal');
define('POKER_AI_MIN_CONFIDENCE', 0.55);
define('POKER_AI_TEMPERATURE', 0.3);
define('POKER_AI_MAX_TOKENS', 200);
define('POKER_AI_TIMEOUT_MS', 20000);
define('POKER_AI_MAX_CALLS_PER_HAND', 12);
define('POKER_AI_PREFETCH', false);
define('POKER_AI_SSL_VERIFY', true);
define('POKER_AI_LOG', false);
define('POKER_AI_DEBUG', false);
define('POKER_AI_PHP_BIN', '');
| Constant | Type | Default | Description |
|---|---|---|---|
POKER_AI_ENABLED | bool | false | Master switch. false = bots stay fully rule-based. |
POKER_AI_API_KEY | string | '' | DeepSeek API key — create one at platform.deepseek.com (API keys). Empty = the whole layer is off. |
POKER_AI_ENDPOINT | string | deepseek.com/v1/chat/completions | OpenAI-compatible chat endpoint. |
POKER_AI_MODE | string | 'hybrid' | hybrid | ai_first | advisor — see Modes. |
POKER_AI_MODEL | string | 'deepseek-flash' | Model for normal spots (fast, cheap). |
POKER_AI_MODEL_BIG | string | 'deepseek-v4-pro' | Model for big spots (the extended "Pro" tier). Must be an ID the provider accepts — this API supports deepseek-flash and deepseek-v4-pro. |
POKER_AI_THINKING | string | 'disabled' | disabled | enabled for normal spots (thinking is slower). |
POKER_AI_THINKING_BIG | string | 'disabled' | Thinking for big spots. enabled is much slower — raise the timeout if you use it. |
POKER_AI_TRIGGER | string | 'marginal' | When the AI is consulted: marginal | river | bigbet | sample | all. |
POKER_AI_ONLY_WITH_HUMANS | bool | true | Consult the AI only when a real (human) player is actually playing. A seated human on sit-out (away) or still waiting for the BB (waitBB) is not dealt in and does not count, so bots-only games — or a game where the human sat out — stay fully rule-based and free. Humans-vs-humans never calls the AI anyway. |
POKER_AI_SAMPLE_PCT | int | 25 | Percentage of decisions used by the sample trigger. |
POKER_AI_MARGIN_BAND | float | 0.10 | marginal: how close (equity vs pot odds) counts as a close decision. |
POKER_AI_BIGBET_PCT | float | 0.25 | bigbet: toCall / stack threshold. |
POKER_AI_LEASH | string | 'normal' | How far the AI may deviate from the engine: strict | normal | loose. |
POKER_AI_MIN_CONFIDENCE | float | 0.55 | Answers below this confidence are ignored (engine line kept). |
POKER_AI_TEMPERATURE | float | 0.3 | Sampling temperature (non-thinking calls only). |
POKER_AI_MAX_TOKENS | int | 200 | Reply budget (the answer is a tiny JSON object). |
POKER_AI_TIMEOUT_MS | int | 20000 | Hard per-call timeout. On expiry the engine line is used. Raise it for thinking models. |
POKER_AI_MAX_CALLS_PER_HAND | int | 12 | Cost guard: max AI calls per hand (0 = unlimited). |
POKER_AI_PREFETCH | bool | false | Warm the answer during the bot's "thinking" window (needs proc_open + a PHP CLI binary). |
POKER_AI_SSL_VERIFY | bool | true | Verify the TLS certificate. false only for local debugging. |
POKER_AI_LOG | bool | false | Append a compact decision log to data/bot_ai_log.json. |
POKER_AI_DEBUG | bool | false | Write the raw request + response to data/bot_ai_debug.log (verbose — turn off in normal use). |
POKER_AI_PHP_BIN | string | '' | PHP CLI path used by prefetch (auto-detected when empty). |
Modes
hybrid(recommended) — the rule engine plays; the AI is consulted in the selected spots and may override the line.ai_first— the AI decides every action; the rule engine is only a fallback. Usually weaker than hybrid, because the model is not a solver.advisor— the rule engine picks fold/check/call/raise; the AI only tunes the bet/raise size.
Triggers (when the AI is consulted, hybrid/advisor)
marginal— close decisions (equity near the pot-odds threshold) or medium-strength spots.river— only on the river.bigbet— only when facing a large bet (≥POKER_AI_BIGBET_PCTof the stack).sample— a randomPOKER_AI_SAMPLE_PCTshare of decisions (disguises the bots).all— every decision (mostly for testing).
Leash (how far the AI may deviate)
strict— same action type; only the size may change (bounded ±25%).normal— may change type, but may never fold a hand the engine would continue with; an oversized raise is capped.loose— any legal action.
Logs
data/bot_ai_log.json— last 300 entries: room, hand, seat, street, mode, model, equity, pot odds, the engine baseline, the AI answer, the final action and the reason. Thesourcefield tells you what happened:ai(live call),prefetch(warm-up used),fallback(error/timeout/invalid → the engine line was used), orgated(AI paused byPOKER_AI_ONLY_WITH_HUMANSbecause no human is playing — one entry per hand).data/bot_ai_debug.log— whenPOKER_AI_DEBUG = true: the exact JSON sent and the raw response (the API key is never written). Rotates at ~2 MB.
Both folders are inside the protected data/ directory — download them via FTP/SSH,
they are not web-accessible.
AI troubleshooting
phase:entry/phase:decisionappear but nophase:request→ the trigger did not engage; tryPOKER_AI_TRIGGER = 'all'to test.- Entries with
"source":"gated"inbot_ai_log.json→ normal while you sit out / are away (or in a bots-only game): the AI is paused, no API call is made and no credit is spent. It resumes automatically when a real player is back in play. phase:responsewithcurl_error: Operation timed out ... 0 bytes received→ the timeout is too small for the model. Usedeepseek-flash, keepTHINKING_BIG = 'disabled', or raisePOKER_AI_TIMEOUT_MS.http_code4xx with a "model not found" message →POKER_AI_MODEL_BIGis not a valid ID at your provider; set a valid one (the engine line is used meanwhile).
Rollback: set POKER_AI_ENABLED = false (or empty the key). The bots return to
100% rule-based immediately — no other change needed.
Allowed ranges
The settings page (Admin & Settings) enforces these limits on the
server and mirrors them as min/max in the browser; a value outside them is
refused and nothing is written to config.php. Decimals are kept as typed.
| Option(s) | Allowed |
|---|---|
POKER_SEATS | 2–9 |
POKER_START_CHIPS, POKER_SB | 1 – 1,000,000 |
POKER_BB | 2 – 1,000,000 and bigger than POKER_SB |
POKER_AVATAR_MAX, POKER_LOGIN_MAX_ATTEMPTS | 1–1000 |
POKER_LOGIN_LOCKOUT_SEC | 0 – 86,400 (0 = off) |
POKER_HISTORY_MAX_HANDS | 0 – 100,000 (0 = unlimited) |
POKER_HISTORY_LIST_DEFAULT, _LIST_MAX, _SCAN_MAX | 1–1000 |
POKER_CHAT_MAX / POKER_CHAT_MAX_LEN | 0–10,000 / 1–2000 |
every other *_MS | 0 – 3,600,000 ms (1 hour) |
POKER_AI_SAMPLE_PCT | 0–100 |
POKER_AI_MARGIN_BAND, _BIGBET_PCT, _MIN_CONFIDENCE | 0–1 |
POKER_AI_TEMPERATURE | 0–2 |
POKER_AI_MAX_TOKENS | 32–4000 |
POKER_AI_TIMEOUT_MS | 1,000 – 120,000 ms (1–120 s) |
POKER_AI_MAX_CALLS_PER_HAND | 0–100 (0 = unlimited) |
POKER_DEFAULT_ROOM and the POKER_ROOMS codes |
letters, digits, -, _, max 20 — see
Admin & Settings for the chips editor |
| any other number | 0 or more |
| any text | max 2000 characters |
Common examples
Change the blinds and stack
define('POKER_SB', 5);
define('POKER_BB', 10);
define('POKER_START_CHIPS', 2000);
Blinds apply to new tables; an existing table keeps its blinds (a seated player can change them with the Blinds control).
Run a 6-max table
define('POKER_SEATS', 6);
The client reads the seat count from window.POKER_CFG, so no other change is needed.
Turn chat off
define('POKER_CHAT_ENABLED', false);
Enable the AI bots
define('POKER_AI_ENABLED', true);
define('POKER_AI_API_KEY', 'sk-...'); // your DeepSeek key
Create the key at platform.deepseek.com (section API keys) and keep some credit on the account — DeepSeek is pay-per-use.
Start with the defaults (hybrid + marginal). While tuning, set
POKER_AI_DEBUG = true to write the raw request/response to
data/bot_ai_debug.log, and turn it back off once it behaves as expected.
Make the big-spot model faster
define('POKER_AI_MODEL_BIG', 'deepseek-flash'); // same fast model for every spot
Or keep a stronger model but avoid thinking latency:
define('POKER_AI_THINKING_BIG', 'disabled');