Guest User

originalblackjack.py

a guest
Mar 9th, 2025
10
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.04 KB | None | 0 0
  1. # Blackjack sim
  2. import secrets
  3. import hmac
  4. import hashlib
  5. import numpy as np
  6. from typing import Generator, List, Tuple, Dict
  7. from dataclasses import dataclass
  8. import concurrent.futures
  9. import time
  10.  
  11. # Card configs
  12. CARDS = [
  13. '♦2', '♥2', '♠2', '♣2', '♦3', '♥3', '♠3', '♣3', '♦4', '♥4',
  14. '♠4', '♣4', '♦5', '♥5', '♠5', '♣5', '♦6', '♥6', '♠6', '♣6',
  15. '♦7', '♥7', '♠7', '♣7', '♦8', '♥8', '♠8', '♣8', '♦9', '♥9',
  16. '♠9', '♣9', '♦10', '♥10', '♠10', '♣10', '♦J', '♥J', '♠J',
  17. '♣J', '♦Q', '♥Q', '♠Q', '♣Q', '♦K', '♥K', '♠K', '♣K', '♦A',
  18. '♥A', '♠A', '♣A'
  19. ]
  20.  
  21. CARD_VALUES = {
  22. '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
  23. '10': 10, 'J': 10, 'Q': 10, 'K': 10, 'A': 11
  24. }
  25.  
  26. @dataclass
  27. class Hand:
  28. cards: List[str]
  29. total: int
  30. soft: bool
  31.  
  32. @dataclass
  33. class GameResult:
  34. betSize: float
  35. outcome: float # 1.5 for natural, 1 for win, 0 for push, -1 for loss
  36. playerCards: List[str]
  37. dealerCards: List[str]
  38. playerTotal: int
  39. dealerTotal: int
  40.  
  41. # RNG funcs
  42. def genSeed(length: int = 64) -> str:
  43. return ''.join(secrets.choice("abcdef0123456789") for _ in range(length))
  44.  
  45. def byteGen(serverSeed: str, clientSeed: str, nonce: str, cursor: int) -> Generator[int, None, None]:
  46. currRound = cursor // 32
  47. currCursor = cursor - (currRound * 32)
  48.  
  49. while True:
  50. msg = f"{clientSeed}:{nonce}:{currRound}"
  51. hmacObj = hmac.new(serverSeed.encode(), msg.encode(), hashlib.sha256)
  52. buff = hmacObj.digest()
  53.  
  54. while currCursor < 32:
  55. yield buff[currCursor]
  56. currCursor += 1
  57. currCursor = 0
  58. currRound += 1
  59.  
  60. def genFloats(serverSeed: str, clientSeed: str, nonce: str, cursor: int, count: int) -> List[float]:
  61. rng = byteGen(serverSeed, clientSeed, nonce, cursor)
  62. bytes = []
  63.  
  64. while len(bytes) < count * 4:
  65. bytes.append(next(rng))
  66.  
  67. return [sum(val / (256 ** (i + 1)) for i, val in enumerate(bytes[i:i+4]))
  68. for i in range(0, len(bytes), 4)]
  69.  
  70. # Game funcs
  71. def calcHand(cards: List[str]) -> Hand:
  72. total = 0
  73. aces = 0
  74.  
  75. for card in cards:
  76. val = card[1:]
  77. if val != 'A':
  78. total += CARD_VALUES[val]
  79. else:
  80. aces += 1
  81.  
  82. soft = False
  83. for _ in range(aces):
  84. if total <= 10:
  85. total += 11
  86. soft = True
  87. else:
  88. total += 1
  89.  
  90. if total > 21 and soft:
  91. total -= 10
  92. soft = False
  93.  
  94. return Hand(cards, total, soft)
  95.  
  96. def verifyCards(serverSeed: str, clientSeed: str, nonce: str, count: int = 52) -> List[Tuple[float, int, str]]:
  97. floats = genFloats(serverSeed, clientSeed, nonce, 0, count)
  98. return [(f, int(f * 52), CARDS[int(f * 52)]) for f in floats]
  99.  
  100. def getHands(serverSeed: str, clientSeed: str, nonce: str) -> Dict[str, Hand]:
  101. cards = verifyCards(serverSeed, clientSeed, nonce, 4)
  102. return {
  103. 'player': calcHand([c[2] for c in cards[:2]]),
  104. 'dealer': calcHand([c[2] for c in cards[2:4]])
  105. }
  106.  
  107. class BlackjackSim:
  108. def __init__(self, serverSeed: str, clientSeed: str):
  109. self.serverSeed = serverSeed
  110. self.clientSeed = clientSeed
  111. self.nonce = 1
  112.  
  113. def _shouldDouble(self, hand: Hand, dUpcard: str) -> bool:
  114. if not hand.soft and hand.total == 11:
  115. return True
  116. if not hand.soft and hand.total == 10 and dUpcard not in ['10', 'A']:
  117. return True
  118. if not hand.soft and hand.total == 9 and dUpcard in ['3', '4', '5', '6']:
  119. return True
  120. if hand.soft and hand.total >= 13 and hand.total <= 18 and dUpcard in ['5', '6']:
  121. return True
  122. return False
  123.  
  124. def _shouldHit(self, hand: Hand, dUpcard: str) -> bool:
  125. if hand.total >= 17:
  126. return False
  127.  
  128. if hand.soft:
  129. if hand.total <= 17:
  130. return True
  131. if hand.total == 18:
  132. return dUpcard in ['9', '10', 'J', 'Q', 'K', 'A']
  133. return False
  134.  
  135. if hand.total <= 11:
  136. return True
  137. elif hand.total == 12:
  138. return dUpcard not in ['4', '5', '6']
  139. elif hand.total <= 16:
  140. return dUpcard not in ['2', '3', '4', '5', '6']
  141.  
  142. return False
  143.  
  144. def _shouldSplit(self, cardVal: str, dUpcard: str) -> bool:
  145. if cardVal in ['A', '8']:
  146. return True
  147. if cardVal in ['2', '3', '7'] and dUpcard in ['2', '3', '4', '5', '6', '7']:
  148. return True
  149. if cardVal == '6' and dUpcard in ['2', '3', '4', '5', '6']:
  150. return True
  151. if cardVal == '4' and dUpcard in ['5', '6']:
  152. return True
  153. if cardVal == '9' and dUpcard in ['2', '3', '4', '5', '6', '8', '9']:
  154. return True
  155. return False
  156.  
  157. def _playDealer(self, cards: List[Tuple], startIdx: int) -> List[str]:
  158. dCards = [cards[0][2], cards[1][2]]
  159. dHand = calcHand(dCards)
  160. idx = startIdx
  161.  
  162. while dHand.total < 17 or (dHand.total == 17 and dHand.soft):
  163. if idx >= len(cards):
  164. break
  165. dCards.append(cards[idx][2])
  166. dHand = calcHand(dCards)
  167. idx += 1
  168.  
  169. return dCards
  170.  
  171. def simGame(self, clientSeed: str, nonce: int) -> GameResult:
  172. hands = getHands(self.serverSeed, clientSeed, str(nonce))
  173. pHand = hands['player']
  174. dUpcard = hands['dealer'].cards[0]
  175. cards = verifyCards(self.serverSeed, clientSeed, str(nonce), 52)
  176. dHand = calcHand([cards[2][2], cards[3][2]])
  177.  
  178. # Check naturals
  179. pNatural = len(pHand.cards) == 2 and pHand.total == 21
  180. dNatural = dHand.total == 21
  181.  
  182. # Handle naturals
  183. if pNatural or dNatural:
  184. if pNatural and dNatural:
  185. return GameResult(1, 0, pHand.cards, dHand.cards, 21, 21)
  186. elif pNatural:
  187. return GameResult(1, 1.5, pHand.cards, dHand.cards, 21, dHand.total)
  188. else:
  189. return GameResult(1, -1, pHand.cards, dHand.cards, pHand.total, 21)
  190.  
  191. # Split check
  192. pCards = pHand.cards.copy()
  193. idx = 4
  194. betMult = 1.0
  195.  
  196. if (len(pCards) == 2 and pCards[0][1:] == pCards[1][1:] and
  197. self._shouldSplit(pCards[0][1:], dUpcard[1:])):
  198. # Handle split
  199. card1, card2 = pCards
  200. if card1[1:] == 'A': # Split aces
  201. return GameResult(2, 1 if calcHand([card1, cards[idx][2]]).total == 21
  202. or calcHand([card2, cards[idx+1][2]]).total == 21 else -1,
  203. [card1, card2], dHand.cards, 21 if calcHand([card1, cards[idx][2]]).total == 21
  204. else calcHand([card2, cards[idx+1][2]]).total, dHand.total)
  205. pCards = [card1]
  206. pHand = calcHand(pCards)
  207.  
  208. # Check double
  209. if len(pCards) == 2 and self._shouldDouble(pHand, dUpcard[1:]):
  210. betMult = 2.0
  211. pCards.append(cards[idx][2])
  212. pHand = calcHand(pCards)
  213. idx += 1
  214. else:
  215. # Normal play
  216. while self._shouldHit(pHand, dUpcard[1:]):
  217. pCards.append(cards[idx][2])
  218. pHand = calcHand(pCards)
  219. idx += 1
  220.  
  221. if pHand.total > 21:
  222. return GameResult(betMult, -1, pCards, [dUpcard], pHand.total, dHand.total)
  223.  
  224. # Dealer plays
  225. dealerCards = self._playDealer(cards[2:], idx)
  226. dealerHand = calcHand(dealerCards)
  227.  
  228. # Compare hands
  229. if dealerHand.total > 21:
  230. return GameResult(betMult, 1, pCards, dealerCards, pHand.total, dealerHand.total)
  231. elif pHand.total > dealerHand.total:
  232. return GameResult(betMult, 1, pCards, dealerCards, pHand.total, dealerHand.total)
  233. elif pHand.total < dealerHand.total:
  234. return GameResult(betMult, -1, pCards, dealerCards, pHand.total, dealerHand.total)
  235. else:
  236. return GameResult(betMult, 0, pCards, dealerCards, pHand.total, dealerHand.total)
  237.  
  238. def simGames(self, numGames: int, threads: int = 8) -> Dict:
  239. results = []
  240. winUnits = 0
  241. losses = pushes = 0
  242. totalReturn = 0
  243. startTime = time.time()
  244.  
  245. with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as exec:
  246. futures = [exec.submit(self.simGame, f"{self.clientSeed}_{i}",
  247. self.nonce + i) for i in range(numGames)]
  248.  
  249. for future in concurrent.futures.as_completed(futures):
  250. result = future.result()
  251.  
  252. if result.outcome > 0:
  253. winUnits += result.outcome * result.betSize
  254. totalReturn += result.outcome * result.betSize
  255. elif result.outcome < 0:
  256. losses += 1
  257. totalReturn += result.outcome * result.betSize
  258. else:
  259. pushes += 1
  260.  
  261. results.append(result)
  262.  
  263. totalHands = winUnits + losses + pushes
  264.  
  265. return {
  266. 'numGames': numGames,
  267. 'winUnits': winUnits,
  268. 'losses': losses,
  269. 'pushes': pushes,
  270. 'winRate': (winUnits / totalHands) * 100,
  271. 'lossRate': (losses / totalHands) * 100,
  272. 'pushRate': (pushes / totalHands) * 100,
  273. 'houseEdge': (-totalReturn / numGames) * 100,
  274. 'variance': np.var([r.outcome for r in results]),
  275. 'timeElapsed': time.time() - startTime,
  276. 'avgReturn': totalReturn / numGames
  277. }
  278.  
  279. def verify(serverSeed: str, clientSeed: str, nonce: str):
  280. results = verifyCards(serverSeed, clientSeed, nonce, 52)
  281. hands = getHands(serverSeed, clientSeed, nonce)
  282.  
  283. print(f"\n{'='*50}")
  284. print("Verification Results:")
  285. print(f"Server Seed: {serverSeed}")
  286. print(f"Client Seed: {clientSeed}")
  287. print(f"Nonce: {nonce}")
  288. print(f"{'='*50}\n")
  289.  
  290. print("Initial Hands:")
  291. print(f"Player: {' '.join(hands['player'].cards)} (Total: {hands['player'].total}{'s' if hands['player'].soft else ''})")
  292. print(f"Dealer: {' '.join(hands['dealer'].cards)} (Total: {hands['dealer'].total}{'s' if hands['dealer'].soft else ''})")
  293. print(f"\n{'='*50}\n")
  294.  
  295. for i, (f, idx, card) in enumerate(results, 1):
  296. print(f"Card {i:2d}: {card:4s} (Index: {idx:2d}, Float: {f:.8f})")
  297.  
  298. def runSim(serverSeed: str = None, clientSeed: str = None, numGames: int = 100000):
  299. if not serverSeed:
  300. serverSeed = genSeed(64)
  301. if not clientSeed:
  302. clientSeed = genSeed(12)
  303.  
  304. sim = BlackjackSim(serverSeed, clientSeed)
  305. results = sim.simGames(numGames)
  306.  
  307. print(f"\n{'='*50}")
  308. print(f"Simulation Results ({numGames:,} games):")
  309. print(f"Server Seed: {serverSeed}")
  310. print(f"Client Seed: {clientSeed}")
  311. print(f"Win Units: {results['winUnits']:.1f} ({results['winRate']:.2f}%)")
  312. print(f"Losses: {results['losses']:,} ({results['lossRate']:.2f}%)")
  313. print(f"Pushes: {results['pushes']:,} ({results['pushRate']:.2f}%)")
  314. print(f"House Edge: {results['houseEdge']:.2f}%")
  315. print(f"Average Return: {results['avgReturn']:.4f}")
  316. print(f"Variance: {results['variance']:.4f}")
  317. print(f"Time: {results['timeElapsed']:.2f}s")
  318. print(f"{'='*50}\n")
  319.  
  320. return results
  321.  
  322. if __name__ == "__main__":
  323. # Example usage
  324. print("Running verification...")
  325. verify("test1", "test1", "1")
  326.  
  327. print("\nRunning simulation...")
  328. # Custom seeds
  329. runSim(
  330. serverSeed="test1",
  331. clientSeed="test1",
  332. numGames=100000
  333. )
  334.  
  335. # Random seeds
  336. print("\nRunning with random seeds...")
  337. runSim(numGames=100000)
Add Comment
Please, Sign In to add comment