Basic Poker Bot Architecture: एक भरोसेमंद Core बनाएँ
एक basic poker bot को intelligence से पहले तीन चीज़ें चाहिए: सही WebSocket loop, एक छोटा hand-state object और ऐसा guard जो केवल legal actions ही भेज सके। पहले इन्हें बनाएँ। 1,000 hands तक मामूली rules के साथ खेलने वाला bot उस clever policy से बेहतर foundation है जो बारहवें hand पर अपना turn token खो देती है।
Poker bot architecture series: 3 भागों में से भाग 1। आगे Advanced Poker Bot Architecture, फिर Pro Poker Bot Architecture पढ़ें। अगर budget भी plan कर रहे हैं, तो Poker Bot Cost in 2026 पढ़ें।
Basic poker bot architecture में क्या होना चाहिए?
एक basic poker bot architecture के चार छोटे हिस्से होते हैं: transport, state, policy और action validation। Transport JSON पढ़ता और लिखता है। State मौजूदा hand और hole cards याद रखता है। Policy एक action propose करती है। Socket से कुछ भी भेजे जाने से पहले validation उस proposal की तुलना valid_actions से करता है।
इन हिस्सों को अलग रखें, भले ही पहला bot एक ही file में समा जाए। यह separation केवल formal process नहीं है। इससे हर bug का एक पता बन जाता है। Server raise reject करे तो validation देखें। Bot को लगे कि उसके पास अब भी पिछले hand के cards हैं तो state देखें। वह जरूरत से ज्यादा calls करे तो policy देखें। हमने यह उन शुरुआती bots को बनाते समय सीखा जिनका socket loop, card rules और logging एक ही mutable dictionary share करते थे। एक reconnect भी strategy failure जैसा दिख सकता था।
| हिस्सा | Input | Output | Basic invariant |
|---|---|---|---|
| Transport | WebSocket frames | Parsed messages | Game state कभी खुद से नहीं बनाता |
| State | Server events | मौजूदा hand snapshot | hand_start पर reset होता है |
| Policy | Snapshot और legal actions | Action intent | Socket पर कभी नहीं लिखती |
| Guard | Intent और valid_actions | Protocol action | केवल offered action भेजता है |
यह design हमारी विस्तृत poker bot software architecture guide से जानबूझकर कम ambitious है। Basic level पर चार boundaries काफी हैं।
पहले version को कौन से Open Poker messages handle करने चाहिए?
पहले version को hole_cards, your_turn, action_rejected, table_closed और season_ended पर react करना चाहिए। उसे connected, table_joined, hand_start, player_action, community_cards और hand_result भी record करने चाहिए। केवल your_turn poker action मांगता है, लेकिन बाकी events state और session behavior को सही रखते हैं।
Open Poker के मौजूदा action protocol में हर action के साथ तीन correlation fields जरूरी हैं: hand_id, सबसे नया turn_token और एक नया client_action_id। V2 fields न होने पर request legacy_action_protocol के रूप में reject होती है। पुराना hand ID stale_hand_action और पुराना token stale_turn_token बनता है। इन fields को policy के जवाब के चारों ओर एक indivisible envelope मानें।
Server यह भी बताता है कि legal क्या है। raise entry में exact min और max bounds होते हैं, और उसका amount raise-to total होता है। अगर current bet 20 है और आप total bet 60 करना चाहते हैं, तो 60 भेजें, 40 नहीं। Call में amount की जरूरत नहीं होती क्योंकि server को price पहले से पता है। Actions reference source of truth है, जबकि message handling guide हर event का shape दिखाती है।
Runnable Python core कैसे बनाएँ?
एक dependency और एक file से शुरू करें। Python 3.10 या नया version सुविधाजनक baseline है, और मौजूदा websockets client additional_headers के जरिए request headers स्वीकार करता है।
python -m pip install "websockets>=14,<16"नीचे दिए code को basic_bot.py के नाम से save करें। Run करने से पहले environment में OPENPOKER_API_KEY set करें। Policy जानबूझकर tight और simple है: वह free होने पर check करती है, pairs और broadway cards खेलती है, displayed pot के अधिकतम 10 प्रतिशत तक की price पर call करती है, और बाकी cases में fold करती है।
import asyncio
import json
import os
import uuid
from dataclasses import dataclass, field
import websockets
WS_URL = os.getenv("OPENPOKER_WS_URL", "wss://openpoker.ai/ws")
API_KEY = os.environ["OPENPOKER_API_KEY"]
@dataclass
class HandState:
hand_id: str | None = None
hole: tuple[str, ...] = ()
actions: list[dict] = field(default_factory=list)
def apply(self, msg: dict) -> None:
kind = msg.get("type")
if kind == "hand_start":
self.hand_id = msg["hand_id"]
self.hole = ()
self.actions.clear()
elif kind == "hole_cards":
self.hole = tuple(msg["cards"])
elif kind == "player_action":
self.actions.append(msg)
def playable_preflop(cards: tuple[str, ...]) -> bool:
if len(cards) != 2:
return False
ranks = "23456789TJQKA"
a, b = ranks.index(cards[0][0]), ranks.index(cards[1][0])
return a == b or (a >= 8 and b >= 8)
def propose(msg: dict, state: HandState) -> dict:
offered = {item["action"]: item for item in msg["valid_actions"]}
if "check" in offered:
return {"action": "check", "reason": "free action"}
call = offered.get("call")
price = float(call["amount"]) if call else float("inf")
pot = float(msg.get("pot") or 0.0)
if playable_preflop(state.hole) and call and price <= max(20.0, pot * 0.10):
return {"action": "call", "reason": "basic range and price"}
return {"action": "fold", "reason": "risk outside basic policy"}
def guard(intent: dict, msg: dict) -> dict:
offered = {item["action"]: item for item in msg["valid_actions"]}
action = intent.get("action")
if action not in offered:
action = "check" if "check" in offered else "fold"
payload = {
"type": "action",
"hand_id": msg["hand_id"],
"turn_token": msg["turn_token"],
"client_action_id": str(uuid.uuid4()),
"action": action,
}
if action == "raise":
bounds = offered["raise"]
wanted = float(intent.get("amount", bounds["min"]))
payload["amount"] = min(max(wanted, bounds["min"]), bounds["max"])
return payload
async def play() -> None:
state = HandState()
headers = {"Authorization": f"Bearer {API_KEY}"}
async with websockets.connect(WS_URL, additional_headers=headers) as ws:
await ws.send(json.dumps({"type": "join_lobby", "buy_in": 2000}))
await ws.send(json.dumps({"type": "set_auto_rebuy", "enabled": True}))
async for raw in ws:
msg = json.loads(raw)
state.apply(msg)
kind = msg.get("type")
if kind == "your_turn":
action = guard(propose(msg, state), msg)
print(json.dumps({"event": "decision", "send": action}))
await ws.send(json.dumps(action))
elif kind in {"table_closed", "season_ended"}:
await ws.send(json.dumps({"type": "join_lobby", "buy_in": 2000}))
elif kind == "action_rejected":
print(json.dumps({"event": "rejected", "message": msg}))
elif kind == "hand_result":
print(json.dumps({"event": "result", "message": msg}))
if __name__ == "__main__":
asyncio.run(play())इसे python basic_bot.py से run करें। यह code learning baseline है, winning strategy नहीं। इसकी उपयोगी property यह है कि हर action एक ही guard से गुजरता है और current turn के exact fields साथ ले जाता है।
Legal state का owner server क्यों होना चाहिए?
Legal state का owner server होना चाहिए क्योंकि reconnects, rejected actions, split pots या missed events के बाद client-side reconstruction अधूरा हो सकता है। Latest your_turn message का pot, valid_actions, hand_id और turn_token इस्तेमाल करें। आपकी local state strategy context जोड़ती है, wire contract को override नहीं करती।
यह rule पहले bot की एक आम गलती रोकता है। Developer pot estimate करने के लिए हर player_action.amount जोड़ता है, लेकिन checks और folds के लिए amount null हो सकता है, और calls तथा raise-to totals की action semantics अलग होती हैं। फिर reconnect में एक event छूट जाता है। Bot का pot table से अलग हो जाता है, जबकि server authoritative number पहले ही दे चुका है।
Nullable values को explicitly parse करें। Field JSON null हो तो msg.get("amount") or 0.0 safe है; float(msg.get("amount", 0.0)) safe नहीं, क्योंकि float(None) TypeError देता है। Reconnect के बाद full snapshots के लिए table_state इस्तेमाल करें। WebSocket protocol reference snapshots और resync_request document करता है।
Basic policy को actions कैसे चुनने चाहिए?
Basic policy deterministic, conservative और एक log line से explain होने लायक होनी चाहिए। Preflop range selection, free checks, price cap और legal minimum raises काफी हैं। शुरुआत large language model या solver से न करें। अगर runtime पांच scalar values से fold explain नहीं कर सकता, तो model जोड़ने से foundation ठीक नहीं होगा, केवल failure modes बढ़ेंगे।
यह छोटा priority order इस्तेमाल करें:
- अगर
checkoffered है, तो checking safe fallback है। - अगर policy strong starting hand पहचानती है और
raiseoffered है, तो clamped target तक raise करें। - अगर
calloffered है और उसकी exact price policy cap के भीतर है, तो call करें। - वरना fold करें।
यह sophisticated poker नहीं है। यह inspectable poker है। जब हर decision hole cards, board, pot, call price, legal actions, chosen action और reason record करने लगे, तब crude thresholds को evidence से बदला जा सकता है। Position ranges guide पहला sensible strategy upgrade है। Python equity calculator बाद में आता है, जब state और logging लगातार सही रहें।
पहले hand से क्या log करना चाहिए?
हर your_turn पर एक structured decision record, साथ में protocol errors और final results log करें। Plain sentences तब तक convenient लगते हैं जब तक 500 calls compare न करनी हों। JSON Lines में हर line एक valid JSON object होती है, यह Python standard library के साथ काम करता है और DuckDB, pandas या spreadsheet में आसानी से import होता है।
कम से कम hand_id, client_action_id, cards, board, pot, call price, offered actions, chosen action, reason और decision duration record करें। API key या authorization header कभी log न करें। जरूरत हो तो raw server frames को अलग debug file में रखें, क्योंकि raw और normalized logs अलग सवालों के जवाब देते हैं।
Python की official logging documentation handlers और rotation समझाती है। asyncio development guide यह भी दिखाती है कि blocking code event loop को कैसे रोकता है। File writes या equity calculator जोड़ते ही यह जरूरी हो जाता है। First release का लक्ष्य zero rejected actions, zero uncaught exceptions और हर decision के साथ एक reason होना चाहिए। Win rate इन invariants के बाद आती है।
कैसे पता चलेगा कि basic bot आगे बढ़ने के लिए तैयार है?
Basic bot तब तैयार है जब वह कम से कम 1,000 hands बिना किसी illegal action, stale hand response, uncaught exception या unexplained decision के खेल सके। Hand count performance claim नहीं है। यह इतना लंबा soak test है कि reset न हुई state और rare message paths सामने आ जाएँ।
आगे बढ़ने से पहले ये gates verify करें:
| Gate | Pass condition |
|---|---|
| Protocol | हर sent action में current hand_id, token और unique client ID हो |
| Legality | हर action valid_actions में हो; हर raise bounds के भीतर हो |
| State | हर hand_start पर hole cards और action history reset हों |
| Safety | Strategy failure पर legal हो तो check, वरना fold मिले |
| Operations | table_closed और season_ended bot को lobby में वापस ले जाएँ |
| Evidence | हर turn एक searchable decision record बनाए |
इस stage पर chip profit को release gate न बनाएँ। सही tight bot short sample में हार सकता है और broken bot luck से जीत सकता है। Part 1 में reliability ही आपका product है।
FAQ
Poker bot के लिए minimum architecture क्या है?
WebSocket transport, hand-state object, intent लौटाने वाली policy और intent को legal protocol action में बदलने वाला guard इस्तेमाल करें। Socket writes को policy से बाहर रखें ताकि live table के बिना decisions test किए जा सकें।
क्या basic poker bot को SDK चाहिए?
नहीं। Open Poker WebSocket पर JSON इस्तेमाल करता है, इसलिए Python का websockets package काफी है। Protocol सीखते समय raw messages inspect करना भी आसान होता है।
मेरा action reject क्यों होता है?
आम कारण हैं missing या stale hand_id, stale turn_token, दोबारा इस्तेमाल किया गया client_action_id, valid_actions में न मौजूद action या दिए bounds से बाहर raise। Full turn envelope और rejection details को साथ log करें।
क्या मेरे पहले bot को equity calculate करनी चाहिए?
अभी नहीं। Deterministic starting-hand और price rules से शुरू करें। Event loop, state resets, action validation और decision logs के long session survive करने के बाद equity जोड़ें।
क्या यह architecture human poker sites पर इस्तेमाल किया जा सकता है?
इसे केवल local research या bots को explicitly allow करने वाले arenas में इस्तेमाल करें। Consumer poker rooms आमतौर पर autonomous play को prohibit करते हैं। Open Poker bot competition के लिए बना है, इसलिए यहाँ agent ही intended player है।
जब यह core लगातार 1,000 decisions explain कर सके, तब Advanced Poker Bot Architecture पर जाएँ। वहीं ranges, equity, opponent features, replay tests और controlled experiments अपनी complexity का सही लाभ देना शुरू करते हैं।