diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe
index fd5115f..dfd7820 100644
Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ
diff --git a/casino3d.inc b/casino3d.inc
index 96f7064..549c02a 100644
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -149,11 +149,15 @@ sz3DWindowTitle db "Assembler Casino - 2.5D",0
szTexturesRelPath db "assets\textures\casino3d-hd.bin",0
szTexturesFullPath db 260 dup(0)
-soundCoin db "assets\audio\Çâîí ïàäàþùåé ìîíåòû.wav",0
+soundCoin db "assets\audio\Çâóê ìîíåòû áðîøåííîé â âîçäóõ è óïàâøåé íà ïîâåðõíîñòü.wav",0
+soundCoinDing db "assets\audio\Çâóê ìîíåòû â èãðå Ultrakill.wav",0
soundCards db "assets\audio\playing_cards_deck_flick_through_001_20485.wav",0
soundRoulette db "assets\audio\roulette-wheel-throw-2.wav",0
-soundJackpot db "assets\audio\Äæåêïîò.wav",0
-soundClick db "assets\audio\b05e1ac8be34248.wav",0
+soundJackpot db "assets\audio\Äæýê ïîò.wav",0
+soundVictory db "assets\audio\victory-tf2.wav",0
+soundClick db "assets\audio\revolverdryfirec.wav",0
+soundShuffle db "assets\audio\moving-stone.wav",0
+soundLose db "assets\audio\Ïðîèãðûø.wav",0
playerX dd (16 * FP_1 + FP_HALF)
playerY dd (16 * FP_1 + FP_HALF)
@@ -288,65 +292,81 @@ coin3d_result dd 1 ; 1 =
coin3d_streak dd 0
coin3d_win dd 0
-coinRxAnimTable db 22, 18, 12, 4, 12, 18, 22, 18, 12, 4
+coinRxAnimTable db 26, 22, 16, 6, 16, 22, 26, 22, 16, 6
-textCoinTitle db "ÌÎÍÅÒÊÀ (ÎÐ¨Ë / ÐÅØÊÀ)",0
+; --- Native 2.5D Coin Flip ---
+textCoinTitle db "* * * Ç Î Ë Î Ò À ß Ì Î Í Å Ò À * * *",0
textCoinHeads db "ÎШË",0
textCoinTails db "ÐÅØÊÀ",0
textCoinStreak db "ÑÅÐÈß: ",0
textCoinSide1 db "[1] ÎШË",0
textCoinSide2 db "[2] ÐÅØÊÀ",0
-textCoinFlipping db "ÌÎÍÅÒÀ Â ÂÎÇÄÓÕÅ...",0
-textCoinWin db "ÂÛÈÃÐÛØ! +$",0
-textCoinLose db "ÏÐÎÈÃÐÛØ!",0
-textCoinControls1 db "[1/2] ÑÒÎÐÎÍÀ [ÏÐÎÁÅË] ÁÐÎÑÎÊ",0
-textCoinControls2 db "[ÂÂÅÐÕ/ÂÍÈÇ] ÑÒÀÂÊÀ [ESC] ÂÛÕÎÄ",0
+textCoinFlipping db "ÌÎÍÅÒÀ  ÏÎ˨ÒÅ...",0
+textCoinWin db "ÂÛÈÃÐÛØ: +$",0
+textCoinLose db "ÍÅ ÓÃÀÄÀË!",0
+textCoinKey1 db "1:ÎШË",0
+textCoinKey2 db "2:ÐÅØÊÀ",0
+textCoinKeySpace db "ÏÐÎÁÅË:ÁÐÎÑÎÊ",0
+textCoinStreakFmt db "ÑÅÐÈß: [X] [X] [X] [ ] [ ] x4!",0
; --- Native 2.5D Dice Game ---
dice3d_bet dd 50
dice3d_choice dd 1 ; 1 = ׸ò, 2 = Íå÷åò
dice3d_state dd 0 ; 0 = Idle, 1 = Rolling, 2 = Result
dice3d_timer dd 0
-dice3d_val1 dd 3
-dice3d_val2 dd 4
-dice3d_sum dd 7
+dice3d_val1 dd 4
+dice3d_val2 dd 6
+dice3d_sum dd 10
dice3d_win dd 0
-textDiceTitle db "ÊÎÑÒÈ (CRAPS) - ×¨Ò / ÍÅ×ÅÒ",0
+textDiceTitle db "=== G R A N D C R A P S ===",0
+textDiceSubtitle db "[ PASS LINE ] [ ANY CRAPS ]",0
textDiceEven db "[1] רÒ",0
textDiceOdd db "[2] ÍÅ×ÅÒ",0
-textDiceTally db "ÊÎÑÒÈ: ",0
+textDiceEvenNums db "2 4 6 8 10",0
+textDiceOddNums db "3 5 7 9 11",0
+textDiceSumHdr db "ÑÓÌÌÀ: ",0
textDicePlus db " + ",0
textDiceEqual db " = ",0
textDiceEvenResult db " (רÒ)",0
textDiceOddResult db " (ÍÅ×ÅÒ)",0
-textDiceRolling db "ÁÐÎÑÎÊ ÊÎÑÒÅÉ...",0
-textDiceWin db "ÂÛÈÃÐÛØ! +$",0
+textDiceRolling db "ÊÎÑÒÈ ËÅÒßÒ ÏÎ ÑÓÊÍÓ...",0
+textDiceWin db "ÂÛÈÃÐÛØ: +$",0
+textDiceWinMult db "! (x2)",0
textDiceLose db "ÏÐÎÈÃÐÛØ!",0
-textDiceControls1 db "[1] ×¨Ò [2] ÍÅ×ÅÒ [ÏÐÎÁÅË] ÁÐÎÑÎÊ",0
-textDiceControls2 db "[ÂÂÅÐÕ/ÂÍÈÇ] ÑÒÀÂÊÀ [ESC] ÂÛÕÎÄ",0
+textDiceKey1 db "1:רÒ",0
+textDiceKey2 db "2:ÍÅ×ÅÒ",0
+textDiceKeySpace db "ÏÐÎÁÅË:ÁÐÎÑÎÊ",0
; --- Native 2.5D Skulls Game ---
skulls3d_bet dd 50
skulls3d_state dd 0 ; 0 = Idle, 1 = Show ball, 2 = Shuffle, 3 = Pick, 4 = Reveal
skulls3d_timer dd 0
-skulls3d_ballPos dd 0 ; 0, 1, 2
-skulls3d_choice dd 0 ; 0, 1, 2
+skulls3d_ballPos dd 1 ; 0, 1, 2
+skulls3d_choice dd 1 ; 0, 1, 2
skulls3d_win dd 0
skulls3d_swapPair0 dd 0
skulls3d_swapPair1 dd 1
skulls3d_swapPair2 dd 2
-textSkullsTitle db "ÍÀϨÐÑÒÊÈ (×ÅÐÅÏÀ È ÊÓÁÊÈ)",0
-textSkullsPhase0 db "ÂÛÁÅÐÈÒÅ ÑÒÀÂÊÓ È ÍÀÆÌÈÒÅ [ÏÐÎÁÅË]",0
-textSkullsPhase1 db "ÇÀÏÎÌÍÈÒÅ, ÃÄÅ ØÀÐÈÊ!",0
+textSkullsTitle db "+ + + Í À Ï ¨ Ð Ñ Ò Ê È + + +",0
+textSkullsPhase0 db "ÄÅËÀÉ ÑÒÀÂÊÓ È ÆÌÈ [ÏÐÎÁÅË]",0
+textSkullsPhase1 db "ÑÌÎÒÐÈ, ÃÄÅ ÇÎËÎÒÎÉ ØÀÐ...",0
textSkullsPhase2 db "ÊÐÓ×Ó, ÂÅÐ×Ó, ÇÀÏÓÒÀÒÜ ÕÎ×Ó...",0
-textSkullsPhase3 db "ÃÄÅ ØÀÐÈÊ? ÍÀÆÌÈÒÅ [1], [2] ÈËÈ [3]!",0
-textSkullsWin db "ÂÛ ÓÃÀÄÀËÈ! ÄÆÅÊÏÎÒ x3! +$",0
-textSkullsLose db "ÍÅ ÓÃÀÄÀËÈ! ØÀÐÈÊ ÁÛË ÍÅ ÇÄÅÑÜ!",0
+textSkullsPhase3 db "ÃÄÅ ØÀÐ? ÂÛÁÈÐÀÉ: [1], [2] ÈËÈ [3]!",0
+textSkullsWin db "ÂÅÐÍÎ! ÂÛÏËÀÒÀ x3! +$",0
+textSkullsLose db "ÌÈÌÎ! ØÀÐ ÁÛË Â ÄÐÓÃÎÌ ÌÅÑÒÅ!",0
textSkullsAgain db "[ÏÐÎÁÅË] - ÑÛÃÐÀÒÜ ÅÙ¨ ÐÀÇ",0
-textSkullsControls1 db "[1/2/3] ÂÛÁÎÐ ÊÓÁÊÀ [ÏÐÎÁÅË] ÑÒÀÐÒ",0
-textSkullsControls2 db "[ÂÂÅÐÕ/ÂÍÈÇ] ÑÒÀÂÊÀ [ESC] ÂÛÕÎÄ",0
+textSkullsKey1 db "1:ÊÓÁÎÊ",0
+textSkullsKey2 db "2:ÊÓÁÎÊ",0
+textSkullsKey3 db "3:ÊÓÁÎÊ",0
+textSkullsKeySpace db "ÏÐÎÁÅË",0
+
+; --- Shared UI Strings ---
+textKeyEsc db "ESC",0
+textBankPrompt db "ÁÀÍÊ: $",0
+textBetPrompt db "ÑÒÀÂÊÀ: $",0
+textBetHint db " [W/S]",0
shopMsg db 128 dup(0)
atmMsg db 128 dup(0)
@@ -2630,15 +2650,102 @@ RenderATMGame PROC
ret
RenderATMGame ENDP
+; ==============================================================================
+; ==============================================================================
+; SHARED RICH UI PROCEDURES
+; ==============================================================================
+DrawKeycap3D PROC uses ebx esi edi, x:DWORD, y:DWORD, strPtr:DWORD, isActive:DWORD
+ LOCAL capW:DWORD
+ ; Compute string length
+ mov esi, strPtr
+ xor ecx, ecx
+@@:
+ mov al, [esi+ecx]
+ test al, al
+ jz @F
+ inc ecx
+ jmp @B
+@@:
+ shl ecx, 3 ; length * 8
+ add ecx, 8 ; padding 8px
+ mov capW, ecx
+
+ ; Drop shadow
+ mov eax, x
+ mov edx, y
+ add edx, 2
+ invoke DrawBox, eax, edx, capW, 13, 0008080Ch
+
+ ; Body & Border
+ mov eax, 00606870h ; default border
+ mov edx, 001A1C22h ; default fill
+ cmp isActive, 0
+ je @F
+ mov eax, 0000FFFFh ; active gold border
+ mov edx, 001B4B22h ; active dark green fill
+@@:
+ invoke DrawBoxBorder, x, y, capW, 13, edx, eax, 1
+
+ ; String text
+ mov eax, 00D0D8E0h
+ cmp isActive, 0
+ je @F
+ mov eax, 0000FFFFh
+@@:
+ mov edx, x
+ add edx, 4
+ mov ecx, y
+ add ecx, 3
+ invoke DrawString8x8, edx, ecx, strPtr, eax
+ mov eax, capW
+ ret
+DrawKeycap3D ENDP
+
+DrawChipStack PROC uses ebx esi edi, x:DWORD, y:DWORD, chipCount:DWORD
+ xor esi, esi
+chip_loop:
+ cmp esi, chipCount
+ jge chip_done
+ mov eax, esi
+ shl eax, 2 ; esi * 4
+ mov edx, y
+ sub edx, eax ; cy = y - esi*4
+
+ ; Shadow
+ mov eax, x
+ add eax, 2
+ mov ecx, edx
+ add ecx, 5
+ invoke DrawBox, eax, ecx, 20, 2, 00041006h
+
+ ; Chip body
+ invoke DrawBoxBorder, x, edx, 24, 6, 001515B8h, 00FFFFFFh, 1
+
+ ; White stripe inlays
+ mov eax, x
+ add eax, 4
+ mov ecx, edx
+ add ecx, 1
+ invoke DrawBox, eax, ecx, 3, 4, 00FFFFFFh
+ mov eax, x
+ add eax, 17
+ invoke DrawBox, eax, ecx, 3, 4, 00FFFFFFh
+
+ inc esi
+ jmp chip_loop
+chip_done:
+ ret
+DrawChipStack ENDP
+
; ==============================================================================
; IN-ENGINE 2.5D MINI-GAME: COIN FLIP (ÌÎÍÅÒÊÀ)
; ==============================================================================
-DrawCoinEllipse PROC uses ebx esi edi cxVal:DWORD, cyVal:DWORD, rxVal:DWORD, ryVal:DWORD, outerColor:DWORD, innerColor:DWORD
- local curDY:DWORD, curDX:DWORD, rxSq:DWORD, rySq:DWORD, rxRySq:DWORD, threshSq:DWORD
+DrawDetailedCoin PROC uses ebx esi edi, cxVal:DWORD, cyVal:DWORD, rxVal:DWORD, ryVal:DWORD, outerColor:DWORD, innerColor:DWORD, reliefType:DWORD
+ LOCAL curDY:DWORD, curDX:DWORD, rxSq:DWORD, rySq:DWORD, rxRySq:DWORD, threshSq:DWORD
cmp rxVal, 0
- jle ellipse_done
+ jle coin_draw_ret
cmp ryVal, 0
- jle ellipse_done
+ jle coin_draw_ret
mov eax, rxVal
imul eax, eax
@@ -2661,19 +2768,19 @@ DrawCoinEllipse PROC uses ebx esi edi cxVal:DWORD, cyVal:DWORD, rxVal:DWORD, ryV
neg eax
mov curDY, eax
-ellipse_y_loop:
+coin_y_loop:
mov eax, curDY
cmp eax, ryVal
- jg ellipse_done
+ jg check_coin_relief
mov eax, rxVal
neg eax
mov curDX, eax
-ellipse_x_loop:
+coin_x_loop:
mov eax, curDX
cmp eax, rxVal
- jg next_ellipse_y
+ jg next_coin_y
mov eax, curDX
imul eax, eax
@@ -2683,7 +2790,7 @@ ellipse_x_loop:
imul ebx, rxSq
add eax, ebx
cmp eax, rxRySq
- jg next_ellipse_x
+ jg next_coin_x
mov ecx, innerColor
cmp eax, threshSq
@@ -2696,17 +2803,163 @@ ellipse_x_loop:
add edx, curDY
invoke DrawPixel, eax, edx, ecx
-next_ellipse_x:
+next_coin_x:
inc curDX
- jmp ellipse_x_loop
+ jmp coin_x_loop
-next_ellipse_y:
+next_coin_y:
inc curDY
- jmp ellipse_y_loop
+ jmp coin_y_loop
-ellipse_done:
+check_coin_relief:
+ cmp rxVal, 14
+ jl coin_draw_ret
+ cmp reliefType, 1
+ je draw_eagle_relief
+ cmp reliefType, 2
+ je draw_wreath_relief
+ jmp coin_draw_ret
+
+draw_eagle_relief:
+ ; Crown
+ mov eax, cxVal
+ sub eax, 3
+ mov edx, cyVal
+ sub edx, 14
+ invoke DrawBox, eax, edx, 7, 2, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 4
+ mov edx, cyVal
+ sub edx, 12
+ invoke DrawBox, eax, edx, 9, 2, 00007A9Ah
+
+ ; Body & Head
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 9
+ invoke DrawBox, eax, edx, 5, 12, 00007A9Ah
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 3, 10, 0000D7FFh
+
+ ; Wings
+ mov eax, cxVal
+ sub eax, 10
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 13
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 10, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 11
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+
+ mov eax, cxVal
+ add eax, 4
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ mov eax, cxVal
+ add eax, 4
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 10, 3, 00007A9Ah
+ mov eax, cxVal
+ add eax, 5
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+
+ ; Tail
+ mov eax, cxVal
+ sub eax, 4
+ mov edx, cyVal
+ add edx, 4
+ invoke DrawBox, eax, edx, 9, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 3
+ mov edx, cyVal
+ add edx, 7
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ jmp coin_draw_ret
+
+draw_wreath_relief:
+ ; Roman numeral 'I'
+ mov eax, cxVal
+ sub eax, 5
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 11, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 7
+ invoke DrawBox, eax, edx, 5, 14, 00007A9Ah
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ sub edx, 6
+ invoke DrawBox, eax, edx, 3, 12, 0000D7FFh
+ mov eax, cxVal
+ sub eax, 6
+ mov edx, cyVal
+ add edx, 7
+ invoke DrawBox, eax, edx, 13, 3, 00007A9Ah
+
+ ; Laurel dots left & right
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ add edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ add edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+coin_draw_ret:
ret
-DrawCoinEllipse ENDP
+DrawDetailedCoin ENDP
InitCoinGame PROC
mov coin3d_state, 0
@@ -2723,7 +2976,7 @@ HandleCoinInput PROC
cmp coin3d_state, 1
je coin_input_done
- invoke Pressed3D, 20h ; SPACE
+ invoke Pressed3D, 20h ; SPACE -> Flip!
test eax, eax
jz check_coin_side
mov eax, bankroll
@@ -2734,11 +2987,13 @@ HandleCoinInput PROC
mov coin3d_state, 1
mov coin3d_timer, 0
mov coin3d_win, 0
- invoke PlaySoundA, OFFSET soundClick, 0, 1
+ invoke PlaySoundA, OFFSET soundCoin, 0, 1
jmp coin_input_done
+
coin_no_cash:
invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
jmp coin_input_done
check_coin_side:
@@ -2746,11 +3001,13 @@ check_coin_side:
test eax, eax
jz @F
mov coin3d_side, 1
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
@@:
invoke Pressed3D, 32h ; '2'
test eax, eax
jz check_coin_bet_adjust
mov coin3d_side, 2
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
check_coin_bet_adjust:
invoke Pressed3D, 26h ; UP
@@ -2760,6 +3017,7 @@ check_coin_bet_adjust:
test eax, eax
jz check_coin_bet_down
coin_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
cmp coin3d_bet, 500
jge coin_input_done
cmp coin3d_bet, 250
@@ -2771,25 +3029,6 @@ coin_inc_bet:
cmp coin3d_bet, 25
jge coin_set_50
jmp coin_set_25
-
-check_coin_bet_down:
- invoke Pressed3D, 28h ; DOWN
- test eax, eax
- jnz coin_dec_bet
- invoke Pressed3D, 53h ; S
- test eax, eax
- jz coin_input_done
-coin_dec_bet:
- cmp coin3d_bet, 25
- jle coin_set_10
- cmp coin3d_bet, 50
- jle coin_set_25
- cmp coin3d_bet, 100
- jle coin_set_50
- cmp coin3d_bet, 250
- jle coin_set_100
- jmp coin_set_250
-
coin_set_500:
mov coin3d_bet, 500
jmp coin_input_done
@@ -2805,62 +3044,113 @@ coin_set_50:
coin_set_25:
mov coin3d_bet, 25
jmp coin_input_done
+
+check_coin_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz coin_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz coin_input_done
+coin_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp coin3d_bet, 25
+ jle coin_set_10
+ cmp coin3d_bet, 50
+ jle coin_set_25
+ cmp coin3d_bet, 100
+ jle coin_set_50
+ cmp coin3d_bet, 250
+ jle coin_set_100
+ mov coin3d_bet, 250
+ jmp coin_input_done
coin_set_10:
mov coin3d_bet, 10
- jmp coin_input_done
coin_input_done:
ret
HandleCoinInput ENDP
RenderCoinGame PROC uses ebx esi edi
- local cyVal:DWORD, rxVal:DWORD
+ LOCAL cyVal:DWORD, rxVal:DWORD, curX:DWORD
- ; Background & Bezel
- invoke DrawBox, 0, 0, SCREEN_W, SCREEN_H, 00140D18h
- invoke DrawBoxBorder, 10, 10, 300, 220, 0022142Ah, 0000D7FFh, 3
+ ; Cabinet Bezel (Mahogany wood & leather)
+ invoke DrawBox, 0, 0, 320, 240, 00060914h
+ invoke DrawBoxBorder, 6, 6, 308, 228, 000F1522h, 001B2E48h, 3
+
+ ; Corner Brass Rivets
+ invoke DrawBox, 12, 12, 3, 3, 0000D7FFh
+ invoke DrawPixel, 13, 13, 00FFFFFFh
+ invoke DrawBox, 306, 12, 3, 3, 0000D7FFh
+ invoke DrawPixel, 307, 13, 00FFFFFFh
+ invoke DrawBox, 12, 226, 3, 3, 0000D7FFh
+ invoke DrawPixel, 13, 227, 00FFFFFFh
+ invoke DrawBox, 306, 226, 3, 3, 0000D7FFh
+ invoke DrawPixel, 307, 227, 00FFFFFFh
; Illuminated Marquee Header
- invoke DrawBoxBorder, 25, 18, 270, 26, 000D0812h, 0000FFFFh, 2
- invoke DrawString8x8, 72, 27, OFFSET textCoinTitle, 0000D7FFh
+ invoke DrawBoxBorder, 24, 12, 272, 26, 000A101Ch, 0000D7FFh, 2
+ invoke DrawString8x8, 48, 21, OFFSET textCoinTitle, 0000FFFFh
- ; Streak Display
- invoke DrawString8x8, 28, 50, OFFSET textCoinStreak, 0000D7FFh
- invoke FormatNumber, coin3d_streak, OFFSET hudNumBuffer
- invoke DrawString8x8, 84, 50, OFFSET hudNumBuffer, 0050FF50h
+ ; Bulb lights on marquee border
+ mov curX, 28
+coin_bulbs:
+ cmp curX, 292
+ jge coin_meters
+ invoke DrawPixel, curX, 13, 0000FFFFh
+ invoke DrawPixel, curX, 36, 0000D7FFh
+ add curX, 12
+ jmp coin_bulbs
- ; Side Panel: [1] ÎШË
- mov eax, 00120D16h
- mov edx, 00505050h
- mov ecx, 00808080h
+coin_meters:
+ ; Status Meters: Bank & Bet
+ invoke DrawBoxBorder, 20, 44, 114, 18, 000A1A0Eh, 001A4B22h, 1
+ invoke DrawString8x8, 24, 49, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 72, 49, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 186, 44, 114, 18, 001A100Ah, 003A2814h, 1
+ invoke DrawString8x8, 190, 49, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, coin3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 254, 49, OFFSET hudNumBuffer, 0000D7FFh
+ invoke DrawString8x8, 278, 49, OFFSET textBetHint, 00808080h
+
+ ; Center Velvet Platter
+ invoke DrawCircle, 160, 110, 46, 000090C0h
+ invoke DrawCircle, 160, 110, 44, 0000D7FFh
+ invoke DrawCircle, 160, 110, 41, 00180824h
+
+ ; Selection Podiums: [1] ÎШË
+ mov eax, 00121620h
+ mov edx, 00404855h
+ mov ecx, 0090A0B0h
cmp coin3d_side, 1
jne @F
- mov eax, 001E3520h
+ mov eax, 001C3822h
mov edx, 0000FFFFh
mov ecx, 0000FFFFh
@@:
push ecx
- invoke DrawBoxBorder, 24, 70, 68, 38, eax, edx, 2
+ invoke DrawBoxBorder, 20, 72, 76, 52, eax, edx, 2
pop ecx
- invoke DrawString8x8, 28, 85, OFFSET textCoinSide1, ecx
+ invoke DrawString8x8, 28, 78, OFFSET textCoinSide1, ecx
+ invoke DrawDetailedCoin, 58, 106, 12, 12, 0000A0D0h, 0000D7FFh, 1
- ; Side Panel: [2] ÐÅØÊÀ
- mov eax, 00120D16h
- mov edx, 00505050h
- mov ecx, 00808080h
+ ; Selection Podiums: [2] ÐÅØÊÀ
+ mov eax, 00121620h
+ mov edx, 00404855h
+ mov ecx, 0090A0B0h
cmp coin3d_side, 2
jne @F
- mov eax, 001E3520h
+ mov eax, 001C3822h
mov edx, 0000FFFFh
mov ecx, 0000FFFFh
@@:
push ecx
- invoke DrawBoxBorder, 228, 70, 68, 38, eax, edx, 2
+ invoke DrawBoxBorder, 224, 72, 76, 52, eax, edx, 2
pop ecx
- invoke DrawString8x8, 232, 85, OFFSET textCoinSide2, ecx
-
- ; Center coin pedestal arena
- invoke DrawBoxBorder, 102, 54, 116, 84, 000F0A12h, 00504030h, 1
+ invoke DrawString8x8, 232, 78, OFFSET textCoinSide2, ecx
+ invoke DrawDetailedCoin, 262, 106, 12, 12, 000080A0h, 0000B0D0h, 2
; Flipping animation logic
cmp coin3d_state, 1
@@ -2868,13 +3158,15 @@ RenderCoinGame PROC uses ebx esi edi
inc coin3d_timer
- ; Parabolic toss: cy = 96 - (t * (40 - t)) / 16
+ ; Parabolic toss: cy = 110 - (t * (40 - t)) / 14
mov eax, coin3d_timer
mov ecx, 40
sub ecx, eax
imul eax, ecx
- shr eax, 4
- mov edx, 96
+ xor edx, edx
+ mov ecx, 14
+ div ecx
+ mov edx, 110
sub edx, eax
mov cyVal, edx
@@ -2886,8 +3178,8 @@ RenderCoinGame PROC uses ebx esi edi
movzx eax, BYTE PTR [coinRxAnimTable + edx]
mov rxVal, eax
- invoke DrawCoinEllipse, 160, cyVal, rxVal, 22, 0000D7FFh, 0000B0E0h
- invoke DrawString8x8, 84, 142, OFFSET textCoinFlipping, 0000FFFFh
+ invoke DrawDetailedCoin, 160, cyVal, rxVal, 26, 0000A0D0h, 0000D7FFh, 0
+ invoke DrawString8x8, 88, 160, OFFSET textCoinFlipping, 0000FFFFh
cmp coin3d_timer, 40
jl coin_draw_controls
@@ -2910,147 +3202,177 @@ RenderCoinGame PROC uses ebx esi edi
inc coin3d_streak
cmp coin3d_streak, 3
jl @F
- invoke PlaySoundA, OFFSET soundJackpot, 0, 1
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
jmp coin_draw_controls
@@:
- invoke PlaySoundA, OFFSET soundCoin, 0, 1
+ invoke PlaySoundA, OFFSET soundCoinDing, 0, 1
jmp coin_draw_controls
coin_flip_lost:
mov coin3d_streak, 0
mov coin3d_win, 0
- invoke PlaySoundA, OFFSET soundClick, 0, 1
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
jmp coin_draw_controls
coin_at_rest:
- ; Draw coin resting at (160, 96)
- invoke DrawCoinEllipse, 160, 96, 24, 24, 0000D7FFh, 0000B0E0h
- invoke DrawCoinEllipse, 160, 96, 20, 20, 0000FFFFh, 000090D0h
-
- ; Draw side text inside coin
+ ; Draw coin resting in velvet dish
+ mov edx, coin3d_side
cmp coin3d_state, 2
- jne coin_show_picked_side
- cmp coin3d_result, 1
jne @F
- invoke DrawString8x8, 144, 92, OFFSET textCoinHeads, 00000030h
- jmp coin_show_splash
+ mov edx, coin3d_result
@@:
- invoke DrawString8x8, 140, 92, OFFSET textCoinTails, 00000030h
- jmp coin_show_splash
+ invoke DrawDetailedCoin, 160, 110, 26, 26, 0000A0D0h, 0000D7FFh, edx
-coin_show_picked_side:
- cmp coin3d_side, 1
- jne @F
- invoke DrawString8x8, 144, 92, OFFSET textCoinHeads, 00000030h
- jmp coin_draw_controls
-@@:
- invoke DrawString8x8, 140, 92, OFFSET textCoinTails, 00000030h
- jmp coin_draw_controls
+ ; Outcome / Streak Display
+ cmp coin3d_state, 2
+ jne coin_show_streak
-coin_show_splash:
cmp coin3d_win, 0
- je coin_show_loss
- invoke DrawString8x8, 90, 142, OFFSET textCoinWin, 0000FFFFh
+ je coin_show_lose
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 0050FF50h, 1
+ invoke DrawString8x8, 64, 160, OFFSET textCoinWin, 0050FF50h
invoke FormatNumber, coin3d_win, OFFSET hudNumBuffer
- invoke DrawString8x8, 178, 142, OFFSET hudNumBuffer, 0000FFFFh
+ invoke DrawString8x8, 152, 160, OFFSET hudNumBuffer, 0050FF50h
jmp coin_draw_controls
-coin_show_loss:
- invoke DrawString8x8, 126, 142, OFFSET textCoinLose, 000000FFh
+coin_show_lose:
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 000000FFh, 1
+ invoke DrawString8x8, 114, 160, OFFSET textCoinLose, 008080FFh
+ jmp coin_draw_controls
+
+coin_show_streak:
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 0000D7FFh, 1
+ invoke DrawString8x8, 52, 160, OFFSET textCoinStreakFmt, 0000FFFFh
coin_draw_controls:
- ; Bottom Control Panel
- invoke DrawBoxBorder, 20, 165, 280, 58, 00151515h, 0000D7FFh, 1
- invoke DrawString8x8, 28, 170, OFFSET textBank3D, 0000D7FFh
- invoke FormatNumber, bankroll, OFFSET hudNumBuffer
- invoke DrawString8x8, 88, 170, OFFSET hudNumBuffer, 0000FFFFh
+ ; Keycaps Control Bar
+ invoke DrawBox, 16, 194, 288, 1, 001E2E40h
- invoke DrawString8x8, 180, 170, OFFSET textBet3D, 0000D7FFh
- invoke FormatNumber, coin3d_bet, OFFSET hudNumBuffer
- invoke DrawString8x8, 236, 170, OFFSET hudNumBuffer, 0050FF50h
+ mov eax, 0
+ cmp coin3d_side, 1
+ sete al
+ invoke DrawKeycap3D, 22, 202, OFFSET textCoinKey1, eax
- invoke DrawString8x8, 28, 186, OFFSET textCoinControls1, 00FFFFFFh
- invoke DrawString8x8, 28, 202, OFFSET textCoinControls2, 00A0A0A0h
+ mov eax, 0
+ cmp coin3d_side, 2
+ sete al
+ invoke DrawKeycap3D, 88, 202, OFFSET textCoinKey2, eax
+
+ invoke DrawKeycap3D, 160, 202, OFFSET textCoinKeySpace, 1
+ invoke DrawKeycap3D, 274, 202, OFFSET textKeyEsc, 0
ret
RenderCoinGame ENDP
; ==============================================================================
; IN-ENGINE 2.5D MINI-GAME: DICE / CRAPS (ÊÎÑÒÈ)
; ==============================================================================
-DrawDieCube PROC uses ebx esi edi x:DWORD, y:DWORD, cubeSize:DWORD, valNum:DWORD
- local pipColor:DWORD
- mov pipColor, 001515C0h
-
- invoke DrawBoxBorder, x, y, cubeSize, cubeSize, 00F4F6F8h, 00303038h, 2
-
+DrawCrapsDie PROC uses ebx esi edi, x:DWORD, y:DWORD, sizeVal:DWORD, valNum:DWORD
+ ; Drop shadow on green felt
mov eax, x
- add eax, cubeSize
+ add eax, 3
mov edx, y
- add edx, 2
- invoke DrawBox, eax, edx, 2, cubeSize, 0008140Ah
+ add edx, sizeVal
+ invoke DrawBox, eax, edx, sizeVal, 3, 00031408h
mov eax, x
- add eax, 2
+ add eax, sizeVal
mov edx, y
- add edx, cubeSize
- invoke DrawBox, eax, edx, cubeSize, 2, 0008140Ah
+ add edx, 3
+ invoke DrawBox, eax, edx, 3, sizeVal, 00031408h
+ ; Ivory body & dark border
+ invoke DrawBoxBorder, x, y, sizeVal, sizeVal, 00F0F4FAh, 00353540h, 1
+
+ ; Bevel highlight top & left
+ mov eax, sizeVal
+ sub eax, 2
+ mov edx, x
+ inc edx
+ mov ecx, y
+ inc ecx
+ invoke DrawBox, edx, ecx, eax, 1, 00FFFFFFh
+ invoke DrawBox, edx, ecx, 1, eax, 00FFFFFFh
+
+ ; Bevel shadow bottom & right
+ mov edx, x
+ inc edx
+ mov ecx, y
+ add ecx, sizeVal
+ sub ecx, 2
+ invoke DrawBox, edx, ecx, eax, 1, 00C0C6D0h
+ mov edx, x
+ add edx, sizeVal
+ sub edx, 2
+ mov ecx, y
+ inc ecx
+ invoke DrawBox, edx, ecx, 1, eax, 00C0C6D0h
+
+ ; Casino Ruby Red circular pips (radius 3)
cmp valNum, 1
- je draw_die_center
+ je draw_die_c
cmp valNum, 3
- je draw_die_center
+ je draw_die_c
cmp valNum, 5
- je draw_die_center
- jmp check_die_corners
-draw_die_center:
+ je draw_die_c
+ jmp check_die_pips_corners
+
+draw_die_c:
mov eax, x
add eax, 18
mov edx, y
add edx, 18
- invoke DrawCircle, eax, edx, 2, pipColor
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+ dec eax
+ dec edx
+ invoke DrawPixel, eax, edx, 00FFFFFFh
-check_die_corners:
+check_die_pips_corners:
cmp valNum, 2
- jl die_render_done
+ jl die_pips_done
mov eax, x
add eax, 9
mov edx, y
add edx, 9
- invoke DrawCircle, eax, edx, 2, pipColor
+ invoke DrawCircle, eax, edx, 3, 001515C8h
mov eax, x
- add eax, 27
+ add eax, sizeVal
+ sub eax, 10
mov edx, y
- add edx, 27
- invoke DrawCircle, eax, edx, 2, pipColor
+ add edx, sizeVal
+ sub edx, 10
+ invoke DrawCircle, eax, edx, 3, 001515C8h
cmp valNum, 4
- jl die_render_done
+ jl die_pips_done
mov eax, x
- add eax, 27
+ add eax, sizeVal
+ sub eax, 10
mov edx, y
add edx, 9
- invoke DrawCircle, eax, edx, 2, pipColor
+ invoke DrawCircle, eax, edx, 3, 001515C8h
mov eax, x
add eax, 9
mov edx, y
- add edx, 27
- invoke DrawCircle, eax, edx, 2, pipColor
+ add edx, sizeVal
+ sub edx, 10
+ invoke DrawCircle, eax, edx, 3, 001515C8h
cmp valNum, 6
- jl die_render_done
+ jl die_pips_done
mov eax, x
add eax, 9
mov edx, y
add edx, 18
- invoke DrawCircle, eax, edx, 2, pipColor
+ invoke DrawCircle, eax, edx, 3, 001515C8h
mov eax, x
- add eax, 27
+ add eax, sizeVal
+ sub eax, 10
mov edx, y
add edx, 18
- invoke DrawCircle, eax, edx, 2, pipColor
+ invoke DrawCircle, eax, edx, 3, 001515C8h
-die_render_done:
+die_pips_done:
ret
-DrawDieCube ENDP
+DrawCrapsDie ENDP
InitDiceGame PROC
mov dice3d_state, 0
@@ -3067,7 +3389,7 @@ HandleDiceInput PROC
cmp dice3d_state, 1
je dice_input_done
- invoke Pressed3D, 20h ; SPACE
+ invoke Pressed3D, 20h ; SPACE -> Roll!
test eax, eax
jz check_dice_choice
mov eax, bankroll
@@ -3078,11 +3400,13 @@ HandleDiceInput PROC
mov dice3d_state, 1
mov dice3d_timer, 0
mov dice3d_win, 0
- invoke PlaySoundA, OFFSET soundClick, 0, 1
+ invoke PlaySoundA, OFFSET soundCards, 0, 1
jmp dice_input_done
+
dice_no_cash:
invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
jmp dice_input_done
check_dice_choice:
@@ -3090,11 +3414,13 @@ check_dice_choice:
test eax, eax
jz @F
mov dice3d_choice, 1
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
@@:
invoke Pressed3D, 32h ; '2'
test eax, eax
jz check_dice_bet_adjust
mov dice3d_choice, 2
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
check_dice_bet_adjust:
invoke Pressed3D, 26h ; UP
@@ -3104,6 +3430,7 @@ check_dice_bet_adjust:
test eax, eax
jz check_dice_bet_down
dice_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
cmp dice3d_bet, 500
jge dice_input_done
cmp dice3d_bet, 250
@@ -3115,25 +3442,6 @@ dice_inc_bet:
cmp dice3d_bet, 25
jge dice_set_50
jmp dice_set_25
-
-check_dice_bet_down:
- invoke Pressed3D, 28h ; DOWN
- test eax, eax
- jnz dice_dec_bet
- invoke Pressed3D, 53h ; S
- test eax, eax
- jz dice_input_done
-dice_dec_bet:
- cmp dice3d_bet, 25
- jle dice_set_10
- cmp dice3d_bet, 50
- jle dice_set_25
- cmp dice3d_bet, 100
- jle dice_set_50
- cmp dice3d_bet, 250
- jle dice_set_100
- jmp dice_set_250
-
dice_set_500:
mov dice3d_bet, 500
jmp dice_input_done
@@ -3149,58 +3457,115 @@ dice_set_50:
dice_set_25:
mov dice3d_bet, 25
jmp dice_input_done
+
+check_dice_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz dice_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz dice_input_done
+dice_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp dice3d_bet, 25
+ jle dice_set_10
+ cmp dice3d_bet, 50
+ jle dice_set_25
+ cmp dice3d_bet, 100
+ jle dice_set_50
+ cmp dice3d_bet, 250
+ jle dice_set_100
+ mov dice3d_bet, 250
+ jmp dice_input_done
dice_set_10:
mov dice3d_bet, 10
- jmp dice_input_done
dice_input_done:
ret
HandleDiceInput ENDP
RenderDiceGame PROC uses ebx esi edi
- local d1X:DWORD, d1Y:DWORD, d2X:DWORD, d2Y:DWORD
+ LOCAL d1X:DWORD, d1Y:DWORD, d2X:DWORD, d2Y:DWORD, bxX:DWORD
- ; Background & Bezel
- invoke DrawBox, 0, 0, SCREEN_W, SCREEN_H, 000C2612h
- invoke DrawBoxBorder, 10, 10, 300, 220, 0014381Ah, 0000D7FFh, 3
+ ; Craps Emerald Felt & Oak Armrest
+ invoke DrawBox, 0, 0, 320, 240, 00082E12h
+ invoke DrawBoxBorder, 4, 4, 312, 232, 00093816h, 00122244h, 3
+ invoke DrawBoxBorder, 8, 8, 304, 224, 000A3F19h, 0005140Ah, 2
- ; Illuminated Marquee Header
- invoke DrawBoxBorder, 25, 18, 270, 26, 000A1F0Eh, 0000FFFFh, 2
- invoke DrawString8x8, 52, 27, OFFSET textDiceTitle, 0000D7FFh
+ ; Pyramidal diamond craps backboard texture
+ mov bxX, 12
+craps_bumper:
+ cmp bxX, 308
+ jge craps_felt_markings
+ mov eax, bxX
+ add eax, 4
+ invoke DrawBox, eax, 11, 4, 4, 00041006h
+ mov eax, bxX
+ add eax, 5
+ invoke DrawPixel, eax, 12, 000F4820h
+ add bxX, 12
+ jmp craps_bumper
- ; Choice Panels
- mov eax, 000E2614h
- mov edx, 00505050h
- mov ecx, 00808080h
+craps_felt_markings:
+ ; Table Markings printed in gold directly on felt
+ invoke DrawString8x8, 72, 20, OFFSET textDiceTitle, 0000D7FFh
+ invoke DrawString8x8, 68, 32, OFFSET textDiceSubtitle, 0000B0A0h
+
+ ; Meters: Bank & Bet
+ invoke DrawBoxBorder, 16, 46, 108, 16, 0006240Eh, 000E5022h, 1
+ invoke DrawString8x8, 20, 50, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 68, 50, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 196, 46, 108, 16, 0006240Eh, 000E5022h, 1
+ invoke DrawString8x8, 200, 50, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, dice3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 264, 50, OFFSET hudNumBuffer, 0000D7FFh
+
+ ; Betting Zone Left: [1] רÒ
+ mov eax, 00073014h
+ mov edx, 001A602Ah
+ mov ecx, 0080B090h
cmp dice3d_choice, 1
jne @F
- mov eax, 001C4824h
+ mov eax, 000B441Dh
mov edx, 0000FFFFh
mov ecx, 0000FFFFh
@@:
push ecx
- invoke DrawBoxBorder, 24, 55, 76, 36, eax, edx, 2
+ invoke DrawBoxBorder, 16, 68, 88, 54, eax, edx, 2
pop ecx
- invoke DrawString8x8, 34, 68, OFFSET textDiceEven, ecx
+ invoke DrawString8x8, 24, 74, OFFSET textDiceEven, ecx
+ invoke DrawString8x8, 20, 86, OFFSET textDiceEvenNums, 0000D7FFh
+ cmp dice3d_choice, 1
+ jne @F
+ invoke DrawChipStack, 48, 110, 4
+@@:
- mov eax, 000E2614h
- mov edx, 00505050h
- mov ecx, 00808080h
+ ; Betting Zone Right: [2] ÍÅ×ÅÒ
+ mov eax, 00073014h
+ mov edx, 001A602Ah
+ mov ecx, 0080B090h
cmp dice3d_choice, 2
jne @F
- mov eax, 001C4824h
+ mov eax, 000B441Dh
mov edx, 0000FFFFh
mov ecx, 0000FFFFh
@@:
push ecx
- invoke DrawBoxBorder, 220, 55, 76, 36, eax, edx, 2
+ invoke DrawBoxBorder, 216, 68, 88, 54, eax, edx, 2
pop ecx
- invoke DrawString8x8, 226, 68, OFFSET textDiceOdd, ecx
+ invoke DrawString8x8, 224, 74, OFFSET textDiceOdd, ecx
+ invoke DrawString8x8, 224, 86, OFFSET textDiceOddNums, 00508060h
+ cmp dice3d_choice, 2
+ jne @F
+ invoke DrawChipStack, 248, 110, 4
+@@:
- ; Dice Table Box
- invoke DrawBoxBorder, 104, 52, 112, 54, 0009200Dh, 00305030h, 1
+ ; Center Dice Field
+ invoke DrawBoxBorder, 112, 68, 96, 54, 00072E13h, 00145826h, 1
- ; Rolling animation logic
+ ; Rolling Animation Logic
cmp dice3d_state, 1
jne dice_at_rest
@@ -3211,19 +3576,17 @@ RenderDiceGame PROC uses ebx esi edi
and eax, 3
sub eax, 1
- mov edx, 112
+ mov edx, 120
add edx, eax
mov d1X, edx
-
- mov edx, 60
+ mov edx, 76
sub edx, eax
mov d1Y, edx
- mov edx, 172
+ mov edx, 164
sub edx, eax
mov d2X, edx
-
- mov edx, 62
+ mov edx, 78
add edx, eax
mov d2Y, edx
@@ -3236,15 +3599,14 @@ RenderDiceGame PROC uses ebx esi edi
inc eax
mov dice3d_val2, eax
@@:
- invoke DrawDieCube, d1X, d1Y, 36, dice3d_val1
- invoke DrawDieCube, d2X, d2Y, 36, dice3d_val2
-
- invoke DrawString8x8, 96, 132, OFFSET textDiceRolling, 0000FFFFh
+ invoke DrawCrapsDie, d1X, d1Y, 36, dice3d_val1
+ invoke DrawCrapsDie, d2X, d2Y, 36, dice3d_val2
+ invoke DrawString8x8, 76, 138, OFFSET textDiceRolling, 0000FFFFh
cmp dice3d_timer, 36
jl dice_draw_controls
- ; Roll completed!
+ ; Settle dice!
mov dice3d_state, 2
invoke RandomRange3D, 6
inc eax
@@ -3257,108 +3619,228 @@ RenderDiceGame PROC uses ebx esi edi
add eax, dice3d_val2
mov dice3d_sum, eax
+ ; Check choice vs sum parity
+ mov eax, dice3d_sum
test eax, 1
jz dice_sum_is_even
- mov ecx, 2 ; Odd
- jmp check_dice_win
+
+ ; Odd
+ cmp dice3d_choice, 2
+ je dice_roll_won
+ jmp dice_roll_lost
+
dice_sum_is_even:
- mov ecx, 1 ; Even
+ cmp dice3d_choice, 1
+ je dice_roll_won
-check_dice_win:
- cmp ecx, dice3d_choice
- jne dice_roll_lost
+dice_roll_lost:
+ mov dice3d_win, 0
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
+ jmp dice_draw_controls
+dice_roll_won:
mov eax, dice3d_bet
shl eax, 1
mov dice3d_win, eax
add bankroll, eax
- invoke PlaySoundA, OFFSET soundCoin, 0, 1
- jmp dice_draw_controls
-
-dice_roll_lost:
- mov dice3d_win, 0
- invoke PlaySoundA, OFFSET soundClick, 0, 1
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
jmp dice_draw_controls
dice_at_rest:
- invoke DrawDieCube, 112, 60, 36, dice3d_val1
- invoke DrawDieCube, 172, 60, 36, dice3d_val2
+ invoke DrawCrapsDie, 120, 76, 36, dice3d_val1
+ invoke DrawCrapsDie, 164, 76, 36, dice3d_val2
cmp dice3d_state, 2
jne dice_draw_controls
- ; Score Tally
- invoke DrawString8x8, 80, 112, OFFSET textDiceTally, 0000D7FFh
+ ; Scoreboard Plaque
+ invoke DrawBoxBorder, 36, 130, 248, 30, 00051D0Ch, 0000D7FFh, 1
+ invoke DrawString8x8, 44, 135, OFFSET textDiceSumHdr, 0000D7FFh
invoke FormatNumber, dice3d_val1, OFFSET hudNumBuffer
- invoke DrawString8x8, 136, 112, OFFSET hudNumBuffer, 00FFFFFFh
- invoke DrawString8x8, 146, 112, OFFSET textDicePlus, 0000D7FFh
+ invoke DrawString8x8, 100, 135, OFFSET hudNumBuffer, 00FFFFFFh
+ invoke DrawString8x8, 110, 135, OFFSET textDicePlus, 0000D7FFh
invoke FormatNumber, dice3d_val2, OFFSET hudNumBuffer
- invoke DrawString8x8, 170, 112, OFFSET hudNumBuffer, 00FFFFFFh
- invoke DrawString8x8, 180, 112, OFFSET textDiceEqual, 0000D7FFh
+ invoke DrawString8x8, 134, 135, OFFSET hudNumBuffer, 00FFFFFFh
+ invoke DrawString8x8, 144, 135, OFFSET textDiceEqual, 0000D7FFh
invoke FormatNumber, dice3d_sum, OFFSET hudNumBuffer
- invoke DrawString8x8, 204, 112, OFFSET hudNumBuffer, 0000FFFFh
+ invoke DrawString8x8, 168, 135, OFFSET hudNumBuffer, 0000FFFFh
mov eax, dice3d_sum
test eax, 1
- jz @F
- invoke DrawString8x8, 222, 112, OFFSET textDiceOddResult, 00FF8080h
- jmp dice_show_outcome
+ jnz @F
+ invoke DrawString8x8, 186, 135, OFFSET textDiceEvenResult, 0080FF80h
+ jmp dice_show_win_msg
@@:
- invoke DrawString8x8, 222, 112, OFFSET textDiceEvenResult, 0080FF80h
+ invoke DrawString8x8, 186, 135, OFFSET textDiceOddResult, 00FF8080h
-dice_show_outcome:
+dice_show_win_msg:
cmp dice3d_win, 0
je dice_show_loss
- invoke DrawString8x8, 90, 132, OFFSET textDiceWin, 0000FFFFh
+ invoke DrawString8x8, 68, 147, OFFSET textDiceWin, 0050FF50h
invoke FormatNumber, dice3d_win, OFFSET hudNumBuffer
- invoke DrawString8x8, 178, 132, OFFSET hudNumBuffer, 0000FFFFh
+ invoke DrawString8x8, 156, 147, OFFSET hudNumBuffer, 0050FF50h
+ invoke DrawString8x8, 188, 147, OFFSET textDiceWinMult, 0050FF50h
jmp dice_draw_controls
+
dice_show_loss:
- invoke DrawString8x8, 126, 132, OFFSET textDiceLose, 000000FFh
+ invoke DrawString8x8, 116, 147, OFFSET textDiceLose, 008080FFh
dice_draw_controls:
- invoke DrawBoxBorder, 20, 165, 280, 58, 00151515h, 0000D7FFh, 1
- invoke DrawString8x8, 28, 170, OFFSET textBank3D, 0000D7FFh
- invoke FormatNumber, bankroll, OFFSET hudNumBuffer
- invoke DrawString8x8, 88, 170, OFFSET hudNumBuffer, 0000FFFFh
+ invoke DrawBox, 16, 188, 288, 1, 00104820h
- invoke DrawString8x8, 180, 170, OFFSET textBet3D, 0000D7FFh
- invoke FormatNumber, dice3d_bet, OFFSET hudNumBuffer
- invoke DrawString8x8, 236, 170, OFFSET hudNumBuffer, 0050FF50h
+ mov eax, 0
+ cmp dice3d_choice, 1
+ sete al
+ invoke DrawKeycap3D, 20, 198, OFFSET textDiceKey1, eax
- invoke DrawString8x8, 28, 186, OFFSET textDiceControls1, 00FFFFFFh
- invoke DrawString8x8, 28, 202, OFFSET textDiceControls2, 00A0A0A0h
+ mov eax, 0
+ cmp dice3d_choice, 2
+ sete al
+ invoke DrawKeycap3D, 82, 198, OFFSET textDiceKey2, eax
+
+ invoke DrawKeycap3D, 160, 198, OFFSET textDiceKeySpace, 1
+ invoke DrawKeycap3D, 274, 198, OFFSET textKeyEsc, 0
ret
RenderDiceGame ENDP
; ==============================================================================
; IN-ENGINE 2.5D MINI-GAME: SKULLS & CUPS (ÍÀϨÐÑÒÊÈ)
; ==============================================================================
-DrawCup PROC uses ebx esi edi x:DWORD, y:DWORD, cupNumChar:DWORD
- invoke DrawBox, x + 14, y, 16, 4, 0000C8E8h
- invoke DrawBoxBorder, x + 8, y + 4, 28, 12, 00221A30h, 0000D7FFh, 2
- invoke DrawBoxBorder, x + 4, y + 16, 36, 16, 00221A30h, 0000D7FFh, 2
- invoke DrawBoxBorder, x + 2, y + 32, 40, 8, 002A2038h, 0000FFFFh, 2
+DrawGlowingOrb PROC uses ebx esi edi, cxVal:DWORD, cyVal:DWORD
+ invoke DrawCircle, cxVal, cyVal, 14, 00081E2Eh
+ invoke DrawCircle, cxVal, cyVal, 11, 00103A58h
+ invoke DrawCircle, cxVal, cyVal, 8, 001A588Ah
+ invoke DrawCircle, cxVal, cyVal, 6, 0000B0E0h
+ invoke DrawCircle, cxVal, cyVal, 4, 0000E0FFh
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ dec edx
+ invoke DrawCircle, eax, edx, 2, 00A0FFFFh
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawPixel, eax, edx, 00FFFFFFh
+ ret
+DrawGlowingOrb ENDP
+DrawGothicCup PROC uses ebx esi edi, x:DWORD, y:DWORD, cupId:DWORD, isLifted:DWORD, hasBall:DWORD
+ LOCAL liftY:DWORD
+
+ mov eax, y
+ cmp isLifted, 0
+ je @F
+ sub eax, 28
+@@:
+ mov liftY, eax
+
+ ; Drop shadow on purple velvet
+ cmp isLifted, 0
+ jne @F
+ mov eax, x
+ add eax, 6
+ mov edx, y
+ add edx, 42
+ invoke DrawBox, eax, edx, 36, 4, 000A0410h
+ jmp draw_cup_body
+@@:
+ mov eax, x
+ add eax, 10
+ mov edx, y
+ add edx, 42
+ invoke DrawBox, eax, edx, 28, 2, 0012061Ah
+
+ ; Draw orb if lifted and has ball
+ cmp hasBall, 0
+ je draw_cup_body
+ mov eax, x
+ add eax, 24
+ mov edx, y
+ add edx, 26
+ invoke DrawGlowingOrb, eax, edx
+
+draw_cup_body:
+ ; Base
+ mov eax, x
+ add eax, 6
+ mov edx, liftY
+ add edx, 36
+ invoke DrawBoxBorder, eax, edx, 36, 6, 00221A30h, 0000B0D0h, 1
+
+ ; Stem
mov eax, x
add eax, 18
- mov edx, y
- add edx, 18
- invoke DrawChar8x8, eax, edx, cupNumChar, 0000FFFFh
- ret
-DrawCup ENDP
-
-DrawGoldenBall PROC uses ebx esi edi x:DWORD, y:DWORD
- invoke DrawCircle, x, y, 7, 0000A5D8h
- invoke DrawCircle, x, y, 6, 0000D7FFh
- invoke DrawCircle, x, y, 4, 0000FFFFh
+ mov edx, liftY
+ add edx, 28
+ invoke DrawBox, eax, edx, 12, 8, 00332545h
mov eax, x
- sub eax, 2
- mov edx, y
- sub edx, 2
- invoke DrawCircle, eax, edx, 1, 00FFFFFFh
+ add eax, 22
+ mov edx, liftY
+ add edx, 28
+ invoke DrawBox, eax, edx, 4, 8, 0055406Eh
+
+ ; Bowl
+ mov eax, x
+ add eax, 8
+ mov edx, liftY
+ add edx, 8
+ invoke DrawBoxBorder, eax, edx, 32, 20, 002D1E40h, 0000D7FFh, 1
+ mov eax, x
+ add eax, 14
+ mov edx, liftY
+ add edx, 10
+ invoke DrawBox, eax, edx, 6, 16, 00503570h
+ mov eax, x
+ add eax, 17
+ mov edx, liftY
+ add edx, 10
+ invoke DrawBox, eax, edx, 3, 16, 007850A0h
+
+ ; Flared Rim
+ mov eax, x
+ add eax, 4
+ mov edx, liftY
+ add edx, 2
+ invoke DrawBoxBorder, eax, edx, 40, 7, 00150A20h, 0000FFFFh, 1
+ mov eax, x
+ add eax, 12
+ mov edx, liftY
+ add edx, 4
+ invoke DrawBox, eax, edx, 16, 2, 0000FFFFh
+
+ ; Skull Insignia on Bowl
+ mov eax, x
+ add eax, 21
+ mov edx, liftY
+ add edx, 12
+ invoke DrawBox, eax, edx, 6, 5, 0000D7FFh
+ mov eax, x
+ add eax, 22
+ mov edx, liftY
+ add edx, 17
+ invoke DrawBox, eax, edx, 4, 3, 0000D7FFh
+ mov eax, x
+ add eax, 22
+ mov edx, liftY
+ add edx, 14
+ invoke DrawPixel, eax, edx, 00150A20h
+ mov eax, x
+ add eax, 24
+ mov edx, liftY
+ add edx, 14
+ invoke DrawPixel, eax, edx, 00150A20h
+
+ ; Cup ID Badge
+ mov eax, x
+ add eax, 21
+ mov edx, liftY
+ add edx, 24
+ mov ecx, cupId
+ add ecx, '0'
+ invoke DrawChar8x8, eax, edx, ecx, 0000FFFFh
ret
-DrawGoldenBall ENDP
+DrawGothicCup ENDP
InitSkullsGame PROC
mov skulls3d_state, 0
@@ -3368,25 +3850,28 @@ InitSkullsGame PROC
jge @F
mov skulls3d_bet, 50
@@:
+ invoke RandomRange3D, 3
+ mov skulls3d_ballPos, eax
ret
InitSkullsGame ENDP
-FinalizeSkullsGuess PROC
+FinalizeSkullsGuess PROC uses ebx
mov skulls3d_state, 4
mov eax, skulls3d_choice
cmp eax, skulls3d_ballPos
jne skulls_guess_lost
+ ; Won 3x!
mov eax, skulls3d_bet
imul eax, 3
mov skulls3d_win, eax
add bankroll, eax
- invoke PlaySoundA, OFFSET soundJackpot, 0, 1
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
ret
skulls_guess_lost:
mov skulls3d_win, 0
- invoke PlaySoundA, OFFSET soundClick, 0, 1
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
ret
FinalizeSkullsGuess ENDP
@@ -3396,10 +3881,10 @@ HandleSkullsInput PROC
cmp skulls3d_state, 2
je skulls_input_done
- cmp skulls3d_state, 0
- jne check_skulls_in_phase3
+ cmp skulls3d_state, 3
+ je check_skulls_pick
- ; Phase 0: Idle / betting
+ ; State 0 or 4: Can start new round or adjust bet
invoke Pressed3D, 20h ; SPACE
test eax, eax
jz check_skulls_bet_adjust
@@ -3412,22 +3897,27 @@ HandleSkullsInput PROC
invoke RandomRange3D, 3
mov skulls3d_ballPos, eax
+
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair0, eax
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair1, eax
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair2, eax
+
mov skulls3d_state, 1
mov skulls3d_timer, 0
mov skulls3d_win, 0
- invoke PlaySoundA, OFFSET soundCoin, 0, 1
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
jmp skulls_input_done
skulls_no_cash:
invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
jmp skulls_input_done
-check_skulls_in_phase3:
- cmp skulls3d_state, 3
- jne check_skulls_in_phase4
-
- ; Phase 3: Pick cup
+check_skulls_pick:
invoke Pressed3D, 31h ; '1'
test eax, eax
jz @F
@@ -3449,14 +3939,6 @@ check_skulls_in_phase3:
call FinalizeSkullsGuess
jmp skulls_input_done
-check_skulls_in_phase4:
- ; Phase 4: Play again or adjust bet
- invoke Pressed3D, 20h ; SPACE
- test eax, eax
- jz check_skulls_bet_adjust
- mov skulls3d_state, 0
- jmp skulls_input_done
-
check_skulls_bet_adjust:
invoke Pressed3D, 26h ; UP
test eax, eax
@@ -3465,6 +3947,7 @@ check_skulls_bet_adjust:
test eax, eax
jz check_skulls_bet_down
skulls_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
cmp skulls3d_bet, 500
jge skulls_input_done
cmp skulls3d_bet, 250
@@ -3476,25 +3959,6 @@ skulls_inc_bet:
cmp skulls3d_bet, 25
jge skulls_set_50
jmp skulls_set_25
-
-check_skulls_bet_down:
- invoke Pressed3D, 28h ; DOWN
- test eax, eax
- jnz skulls_dec_bet
- invoke Pressed3D, 53h ; S
- test eax, eax
- jz skulls_input_done
-skulls_dec_bet:
- cmp skulls3d_bet, 25
- jle skulls_set_10
- cmp skulls3d_bet, 50
- jle skulls_set_25
- cmp skulls3d_bet, 100
- jle skulls_set_50
- cmp skulls3d_bet, 250
- jle skulls_set_100
- jmp skulls_set_250
-
skulls_set_500:
mov skulls3d_bet, 500
jmp skulls_input_done
@@ -3510,26 +3974,60 @@ skulls_set_50:
skulls_set_25:
mov skulls3d_bet, 25
jmp skulls_input_done
+
+check_skulls_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz skulls_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz skulls_input_done
+skulls_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp skulls3d_bet, 25
+ jle skulls_set_10
+ cmp skulls3d_bet, 50
+ jle skulls_set_25
+ cmp skulls3d_bet, 100
+ jle skulls_set_50
+ cmp skulls3d_bet, 250
+ jle skulls_set_100
+ mov skulls3d_bet, 250
+ jmp skulls_input_done
skulls_set_10:
mov skulls3d_bet, 10
- jmp skulls_input_done
skulls_input_done:
ret
HandleSkullsInput ENDP
RenderSkullsGame PROC uses ebx esi edi
- local passTick:DWORD, curPair:DWORD, swapSlotA:DWORD, swapSlotB:DWORD
- local xA:DWORD, xB:DWORD, curXA:DWORD, curXB:DWORD, curYA:DWORD, curYB:DWORD
- local distX:DWORD, dxVal:DWORD, archY:DWORD
+ LOCAL passTick:DWORD, curPair:DWORD, swapSlotA:DWORD, swapSlotB:DWORD
+ LOCAL xA:DWORD, xB:DWORD, curXA:DWORD, curXB:DWORD, curYA:DWORD, curYB:DWORD
+ LOCAL distX:DWORD, dxVal:DWORD, archY:DWORD
- ; Background & Bezel
- invoke DrawBox, 0, 0, SCREEN_W, SCREEN_H, 00100E18h
- invoke DrawBoxBorder, 10, 10, 300, 220, 001A1828h, 0000D7FFh, 3
+ ; Dark Gothic Tavern Table with Royal Velvet
+ invoke DrawBox, 0, 0, 320, 240, 000D0612h
+ invoke DrawBoxBorder, 4, 4, 312, 232, 00160B20h, 00321845h, 3
+ invoke DrawBoxBorder, 8, 8, 304, 224, 00220F30h, 0000D7FFh, 1
- ; Illuminated Marquee Header
- invoke DrawBoxBorder, 25, 18, 270, 26, 000C0A14h, 0000FFFFh, 2
- invoke DrawString8x8, 56, 27, OFFSET textSkullsTitle, 0000D7FFh
+ ; Header
+ invoke DrawBoxBorder, 24, 12, 272, 24, 0012081Ah, 0000D7FFh, 2
+ invoke DrawString8x8, 64, 20, OFFSET textSkullsTitle, 0000FFFFh
+
+ ; Meters: Bank & Bet
+ invoke DrawBoxBorder, 20, 42, 114, 16, 000D0616h, 00401858h, 1
+ invoke DrawString8x8, 24, 46, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 72, 46, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 186, 42, 114, 16, 000D0616h, 00401858h, 1
+ invoke DrawString8x8, 190, 46, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, skulls3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 254, 46, OFFSET hudNumBuffer, 0000D7FFh
+
+ ; Velvet Table Runner
+ invoke DrawBoxBorder, 16, 64, 288, 120, 001A0B25h, 00452060h, 1
; Dispatch based on phase
cmp skulls3d_state, 1
@@ -3542,52 +4040,31 @@ RenderSkullsGame PROC uses ebx esi edi
je skulls_render_phase4
; Phase 0: Idle / Betting
- invoke DrawCup, 50, 105, '1'
- invoke DrawCup, 138, 105, '2'
- invoke DrawCup, 226, 105, '3'
- invoke DrawString8x8, 24, 48, OFFSET textSkullsPhase0, 0000D7FFh
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase0, 0000D7FFh
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
jmp skulls_draw_controls
skulls_render_phase1:
inc skulls3d_timer
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase1, 0000FFFFh
- ; Draw golden ball under starting cup
- mov eax, skulls3d_ballPos
- cmp eax, 0
- je p1_ball_0
- cmp eax, 1
- je p1_ball_1
- invoke DrawGoldenBall, 248, 120
- jmp p1_draw_cups
-p1_ball_0:
- invoke DrawGoldenBall, 72, 120
- jmp p1_draw_cups
-p1_ball_1:
- invoke DrawGoldenBall, 160, 120
-
-p1_draw_cups:
- mov edx, 105
+ ; Draw cups with ballPos cup lifted
+ mov eax, 0
cmp skulls3d_ballPos, 0
- jne @F
- mov edx, 70
-@@:
- invoke DrawCup, 50, edx, '1'
+ sete al
+ invoke DrawGothicCup, 48, 108, 1, eax, eax
- mov edx, 105
+ mov eax, 0
cmp skulls3d_ballPos, 1
- jne @F
- mov edx, 70
-@@:
- invoke DrawCup, 138, edx, '2'
+ sete al
+ invoke DrawGothicCup, 136, 108, 2, eax, eax
- mov edx, 105
+ mov eax, 0
cmp skulls3d_ballPos, 2
- jne @F
- mov edx, 70
-@@:
- invoke DrawCup, 226, edx, '3'
-
- invoke DrawString8x8, 76, 48, OFFSET textSkullsPhase1, 0000FFFFh
+ sete al
+ invoke DrawGothicCup, 224, 108, 3, eax, eax
cmp skulls3d_timer, 40
jl skulls_draw_controls
@@ -3595,12 +4072,7 @@ p1_draw_cups:
; Start Phase 2 (Shuffle)
mov skulls3d_state, 2
mov skulls3d_timer, 0
- invoke RandomRange3D, 3
- mov skulls3d_swapPair0, eax
- invoke RandomRange3D, 3
- mov skulls3d_swapPair1, eax
- invoke RandomRange3D, 3
- mov skulls3d_swapPair2, eax
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
jmp skulls_draw_controls
skulls_render_phase2:
@@ -3637,20 +4109,20 @@ setup_pass_pair:
je pair_2
mov swapSlotA, 0
mov swapSlotB, 1
- mov xA, 50
- mov xB, 138
+ mov xA, 48
+ mov xB, 136
jmp calc_swap_coords
pair_1:
mov swapSlotA, 1
mov swapSlotB, 2
- mov xA, 138
- mov xB, 226
+ mov xA, 136
+ mov xB, 224
jmp calc_swap_coords
pair_2:
mov swapSlotA, 0
mov swapSlotB, 2
- mov xA, 50
- mov xB, 226
+ mov xA, 48
+ mov xB, 224
calc_swap_coords:
mov eax, xB
@@ -3672,6 +4144,7 @@ calc_swap_coords:
sub eax, dxVal
mov curXB, eax
+ ; Arch height: archY = (t * (20 - t)) / 25
mov eax, passTick
mov ecx, 20
sub ecx, eax
@@ -3681,138 +4154,137 @@ calc_swap_coords:
div ecx
mov archY, eax
- mov eax, 105
- sub eax, archY
- mov curYA, eax
+ mov edx, 108
+ sub edx, archY
+ mov curYA, edx ; upper arc
- mov eax, 105
- add eax, archY
- mov curYB, eax
+ mov edx, 108
+ add edx, archY
+ mov curYB, edx ; lower arc
- cmp passTick, 19
- jne @F
- mov eax, skulls3d_ballPos
- cmp eax, swapSlotA
- jne check_ball_b
- mov eax, swapSlotB
- mov skulls3d_ballPos, eax
- jmp ball_swap_done
-check_ball_b:
- cmp eax, swapSlotB
- jne ball_swap_done
- mov eax, swapSlotA
- mov skulls3d_ballPos, eax
-ball_swap_done:
- invoke PlaySoundA, OFFSET soundClick, 0, 1
-@@:
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase2, 0000FFFFh
+
+ ; Depth sorted rendering: Background cup (upper arc) draws FIRST, Foreground cup (lower arc) draws OVER it!
+ invoke DrawGothicCup, curXA, curYA, 1, 0, 0
+ invoke DrawGothicCup, curXB, curYB, 2, 0, 0
+
+ ; Draw remaining static cup
cmp curPair, 1
je draw_stat_0
cmp curPair, 2
je draw_stat_1
- invoke DrawCup, 226, 105, '3'
- jmp draw_moving_cups
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
+ jmp check_pass_finish
draw_stat_0:
- invoke DrawCup, 50, 105, '1'
- jmp draw_moving_cups
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ jmp check_pass_finish
draw_stat_1:
- invoke DrawCup, 138, 105, '2'
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
-draw_moving_cups:
- mov edx, '1'
- add edx, swapSlotA
- invoke DrawCup, curXA, curYA, edx
+check_pass_finish:
+ cmp skulls3d_timer, 20
+ je do_pass_swap
+ cmp skulls3d_timer, 40
+ je do_pass_swap
+ cmp skulls3d_timer, 60
+ je do_pass_swap
+ jmp skulls_draw_controls
- mov edx, '1'
- add edx, swapSlotB
- invoke DrawCup, curXB, curYB, edx
+do_pass_swap:
+ ; Update ballPos if involved in swap
+ mov eax, skulls3d_ballPos
+ cmp eax, swapSlotA
+ jne check_swap_b
+ mov eax, swapSlotB
+ mov skulls3d_ballPos, eax
+ jmp ball_swap_done
+check_swap_b:
+ cmp eax, swapSlotB
+ jne ball_swap_done
+ mov eax, swapSlotA
+ mov skulls3d_ballPos, eax
- invoke DrawString8x8, 40, 48, OFFSET textSkullsPhase2, 0000FFFFh
+ball_swap_done:
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
cmp skulls3d_timer, 60
jl skulls_draw_controls
+
+ ; Shuffle complete -> Move to Pick phase
mov skulls3d_state, 3
mov skulls3d_timer, 0
jmp skulls_draw_controls
skulls_render_phase3:
- invoke DrawCup, 50, 105, '1'
- invoke DrawCup, 138, 105, '2'
- invoke DrawCup, 226, 105, '3'
- invoke DrawString8x8, 16, 48, OFFSET textSkullsPhase3, 0000FFFFh
+ invoke DrawString8x8, 40, 70, OFFSET textSkullsPhase3, 0000FFFFh
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
jmp skulls_draw_controls
skulls_render_phase4:
- mov eax, skulls3d_ballPos
- cmp eax, 0
- je p4_ball_0
- cmp eax, 1
- je p4_ball_1
- invoke DrawGoldenBall, 248, 120
- jmp p4_draw_cups
-p4_ball_0:
- invoke DrawGoldenBall, 72, 120
- jmp p4_draw_cups
-p4_ball_1:
- invoke DrawGoldenBall, 160, 120
-
-p4_draw_cups:
- mov edx, 105
- cmp skulls3d_choice, 0
- je @F
- cmp skulls3d_ballPos, 0
- jne draw_cup0
-@@:
- mov edx, 70
-draw_cup0:
- invoke DrawCup, 50, edx, '1'
-
- mov edx, 105
- cmp skulls3d_choice, 1
- je @F
- cmp skulls3d_ballPos, 1
- jne draw_cup1
-@@:
- mov edx, 70
-draw_cup1:
- invoke DrawCup, 138, edx, '2'
-
- mov edx, 105
- cmp skulls3d_choice, 2
- je @F
- cmp skulls3d_ballPos, 2
- jne draw_cup2
-@@:
- mov edx, 70
-draw_cup2:
- invoke DrawCup, 226, edx, '3'
-
+ ; Reveal phase
cmp skulls3d_win, 0
- je p4_splash_lose
- invoke DrawString8x8, 24, 48, OFFSET textSkullsWin, 0000FFFFh
+ je skulls_show_loss
+ invoke DrawString8x8, 48, 70, OFFSET textSkullsWin, 0050FF50h
invoke FormatNumber, skulls3d_win, OFFSET hudNumBuffer
- invoke DrawString8x8, 236, 48, OFFSET hudNumBuffer, 0000FFFFh
- jmp p4_again_hint
-p4_splash_lose:
- invoke DrawString8x8, 36, 48, OFFSET textSkullsLose, 000000FFh
-p4_again_hint:
- invoke DrawString8x8, 56, 138, OFFSET textSkullsAgain, 0000D7FFh
+ invoke DrawString8x8, 216, 70, OFFSET hudNumBuffer, 0050FF50h
+ jmp skulls_p4_cups
+
+skulls_show_loss:
+ invoke DrawString8x8, 40, 70, OFFSET textSkullsLose, 008080FFh
+
+skulls_p4_cups:
+ ; Draw chosen cup lifted
+ mov eax, 0
+ cmp skulls3d_choice, 0
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 0
+ sete dl
+ invoke DrawGothicCup, 48, 108, 1, eax, edx
+
+ mov eax, 0
+ cmp skulls3d_choice, 1
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 1
+ sete dl
+ invoke DrawGothicCup, 136, 108, 2, eax, edx
+
+ mov eax, 0
+ cmp skulls3d_choice, 2
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 2
+ sete dl
+ invoke DrawGothicCup, 224, 108, 3, eax, edx
+
+ invoke DrawString8x8, 64, 168, OFFSET textSkullsAgain, 0000D7FFh
skulls_draw_controls:
- invoke DrawBoxBorder, 20, 165, 280, 58, 00151515h, 0000D7FFh, 1
- invoke DrawString8x8, 28, 170, OFFSET textBank3D, 0000D7FFh
- invoke FormatNumber, bankroll, OFFSET hudNumBuffer
- invoke DrawString8x8, 88, 170, OFFSET hudNumBuffer, 0000FFFFh
+ invoke DrawBox, 16, 190, 288, 1, 003A1850h
- invoke DrawString8x8, 180, 170, OFFSET textBet3D, 0000D7FFh
- invoke FormatNumber, skulls3d_bet, OFFSET hudNumBuffer
- invoke DrawString8x8, 236, 170, OFFSET hudNumBuffer, 0050FF50h
+ mov eax, 0
+ cmp skulls3d_choice, 0
+ sete al
+ invoke DrawKeycap3D, 18, 200, OFFSET textSkullsKey1, eax
- invoke DrawString8x8, 24, 186, OFFSET textSkullsControls1, 00FFFFFFh
- invoke DrawString8x8, 24, 202, OFFSET textSkullsControls2, 00A0A0A0h
+ mov eax, 0
+ cmp skulls3d_choice, 1
+ sete al
+ invoke DrawKeycap3D, 82, 200, OFFSET textSkullsKey2, eax
+
+ mov eax, 0
+ cmp skulls3d_choice, 2
+ sete al
+ invoke DrawKeycap3D, 146, 200, OFFSET textSkullsKey3, eax
+
+ invoke DrawKeycap3D, 212, 200, OFFSET textSkullsKeySpace, 1
+ invoke DrawKeycap3D, 274, 200, OFFSET textKeyEsc, 0
ret
RenderSkullsGame ENDP
-; --- Player Input & Movement Physics ---
HandlePlayerInput PROC uses ebx esi edi
local speed:DWORD
local nextX:DWORD
diff --git a/docs/previews_minigames/coin_preview.png b/docs/previews_minigames/coin_preview.png
new file mode 100644
index 0000000..64d7668
Binary files /dev/null and b/docs/previews_minigames/coin_preview.png differ
diff --git a/docs/previews_minigames/dice_preview.png b/docs/previews_minigames/dice_preview.png
new file mode 100644
index 0000000..a9758b6
Binary files /dev/null and b/docs/previews_minigames/dice_preview.png differ
diff --git a/docs/previews_minigames/skulls_preview.png b/docs/previews_minigames/skulls_preview.png
new file mode 100644
index 0000000..400d5bd
Binary files /dev/null and b/docs/previews_minigames/skulls_preview.png differ
diff --git a/docs/previews_minigames/test_basic.png b/docs/previews_minigames/test_basic.png
new file mode 100644
index 0000000..214322c
Binary files /dev/null and b/docs/previews_minigames/test_basic.png differ
diff --git a/poker.exe b/poker.exe
index fd5115f..dfd7820 100644
Binary files a/poker.exe and b/poker.exe differ
diff --git a/tools/README.md b/tools/README.md
index 482ffda..9ffc82e 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -43,6 +43,7 @@ Offline build tools that process raw images, generate lookup tables, and compose
| `build_all_3d.py` | Python | Offline assembly file generator that synthesizes the complete `casino3d.inc` raycaster engine in CP1251. |
| `generate_unified_casino3d.py` | Python | Alternative unified generator for `casino3d.inc` with full sprite definitions and game interaction hooks. |
| `generate_casino3d.py`
`build_engine.py`
`make_casino3d.py` | Python | Lightweight orchestration scripts for triggering `casino3d.inc` regeneration in CP1251 encoding. |
+| `generate_rich_minigames.py` | Python | Patches `casino3d.inc` with rich, custom 2.5D graphics and fixed audio for Coin, Craps, and Skulls. |
| `patch_poker_asm.py` | Python | Helper utility to patch `poker.asm` with 3D engine hooks, player save anchors, and mutex management. |
---
@@ -53,7 +54,9 @@ Verification scripts, simulation harnesses, and runtime helpers:
| Tool | Language | Purpose & Usage |
|------|----------|-----------------|
+| `test_minigames_render.py` | Python (Pillow) | Visual simulation test suite that rasterizes 2.5D minigame UI frames and saves PNG previews to `docs/previews_minigames/`. Usage: `python tools/tests/test_minigames_render.py`. |
| `simulate_render.py` | Python (Pillow) | Software CPU simulation of the 3D raycaster renderer. Reads `casino3d_data.inc` and texture binary, performs DDA raycasting, depth-sorting of 28 casino sprites, and saves the rendered view to `docs/render_view.png`. Usage: `python tools/tests/simulate_render.py`. |
| `release_smoke_check.ps1` | PowerShell | Automated smoke test for release readiness: verifies file presence, launcher SHA-256 hashes, CP1251 source markers, and absence of deprecated assets. Usage: `powershell -File tools/tests/release_smoke_check.ps1`. |
| `build_graphics.ps1` | PowerShell | Builds the Windows subsystem executable `build/graphics/poker.exe` using MASM32/MSVC toolchain. Usage: `powershell -File tools/tests/build_graphics.ps1`. |
| `achievement_popup.bat` | Batch (CP1251) | Standalone console achievement notification popup called by `poker.exe`. Reads achievement text from `runtime/achievement.txt` and renders a decorative banner with rarity colors (COMMON, RARE, EPIC, LEGENDARY). |
+
diff --git a/tools/generators/generate_rich_minigames.py b/tools/generators/generate_rich_minigames.py
new file mode 100644
index 0000000..739d449
--- /dev/null
+++ b/tools/generators/generate_rich_minigames.py
@@ -0,0 +1,1790 @@
+# -*- coding: utf-8 -*-
+"""
+Generator script to patch casino3d.inc with rich, custom 2.5D graphics
+and fixed audio for Coin Flip, Craps/Dice, and Skulls & Cups.
+"""
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+CASINO_INC = REPO_ROOT / 'casino3d.inc'
+
+# Read original casino3d.inc
+with open(CASINO_INC, 'rb') as f:
+ raw_content = f.read()
+
+# 1. Fix Audio Declarations (around line 150)
+old_audio_block = (
+ b'soundCoin db "assets\\audio\\\xc7\xe2\xee\xed \xef\xe0\xe4\xe0\xfe\xf9\xe5\xe9 \xec\xee\xed\xe5\xf2\xfb.wav",0\r\n'
+ b'soundCards db "assets\\audio\\playing_cards_deck_flick_through_001_20485.wav",0\r\n'
+ b'soundRoulette db "assets\\audio\\roulette-wheel-throw-2.wav",0\r\n'
+ b'soundJackpot db "assets\\audio\\\xc4\xe6\xe5\xea\xef\xee\xf2.wav",0\r\n'
+ b'soundClick db "assets\\audio\\b05e1ac8be34248.wav",0'
+)
+
+new_audio_block = (
+ b'soundCoin db "assets\\audio\\\xc7\xe2\xf3\xea \xec\xee\xed\xe5\xf2\xfb \xe1\xf0\xee\xf8\xe5\xed\xed\xee\xe9 \xe2 \xe2\xee\xe7\xe4\xf3\xf5 \xe8 \xf3\xef\xe0\xe2\xf8\xe5\xe9 \xed\xe0 \xef\xee\xe2\xe5\xf0\xf5\xed\xee\xf1\xf2\xfc.wav",0\r\n'
+ b'soundCoinDing db "assets\\audio\\\xc7\xe2\xf3\xea \xec\xee\xed\xe5\xf2\xfb \xe2 \xe8\xe3\xf0\xe5 Ultrakill.wav",0\r\n'
+ b'soundCards db "assets\\audio\\playing_cards_deck_flick_through_001_20485.wav",0\r\n'
+ b'soundRoulette db "assets\\audio\\roulette-wheel-throw-2.wav",0\r\n'
+ b'soundJackpot db "assets\\audio\\\xc4\xe6\xfd\xea \xef\xee\xf2.wav",0\r\n'
+ b'soundVictory db "assets\\audio\\victory-tf2.wav",0\r\n'
+ b'soundClick db "assets\\audio\\revolverdryfirec.wav",0\r\n'
+ b'soundShuffle db "assets\\audio\\moving-stone.wav",0\r\n'
+ b'soundLose db "assets\\audio\\\xcf\xf0\xee\xe8\xe3\xf0\xfb\xf8.wav",0'
+)
+
+if old_audio_block in raw_content:
+ raw_content = raw_content.replace(old_audio_block, new_audio_block, 1)
+ print("[OK] Replaced audio block (removed gunshots, fixed real sound paths)")
+else:
+ print("[WARN] Audio block pattern not found verbatim, checking sub-patterns...")
+ if b'soundClick db "assets\\audio\\b05e1ac8be34248.wav",0' in raw_content:
+ raw_content = raw_content.replace(
+ b'soundClick db "assets\\audio\\b05e1ac8be34248.wav",0',
+ b'soundClick db "assets\\audio\\revolverdryfirec.wav",0\r\n'
+ b'soundShuffle db "assets\\audio\\moving-stone.wav",0\r\n'
+ b'soundLose db "assets\\audio\\\xcf\xf0\xee\xe8\xe3\xf0\xfb\xf8.wav",0\r\n'
+ b'soundVictory db "assets\\audio\\victory-tf2.wav",0\r\n'
+ b'soundCoinDing db "assets\\audio\\\xc7\xe2\xf3\xea \xec\xee\xed\xe5\xf2\xfb \xe2 \xe8\xe3\xf0\xe5 Ultrakill.wav",0'
+ )
+ print("[OK] Replaced soundClick and added soundShuffle / soundLose")
+
+# 2. Update string constants (around line 290)
+# Find start of coinRxAnimTable and end before shopMsg
+pos_coin_table = raw_content.find(b'coinRxAnimTable db')
+pos_shop_msg = raw_content.find(b'shopMsg db 128 dup(0)')
+
+assert pos_coin_table != -1 and pos_shop_msg != -1, "Could not locate text strings section"
+
+new_strings_text = """coinRxAnimTable db 26, 22, 16, 6, 16, 22, 26, 22, 16, 6
+
+; --- Native 2.5D Coin Flip ---
+textCoinTitle db "* * * \xc7 \xce \xcb \xce \xd2 \xc0 \xdf \xcc \xce \xcd \xc5 \xd2 \xc0 * * *",0
+textCoinHeads db "\xce\xd0\xa8\xcb",0
+textCoinTails db "\xd0\xc5\xd8\xca\xc0",0
+textCoinStreak db "\xd1\xc5\xd0\xc8\xdf: ",0
+textCoinSide1 db "[1] \xce\xd0\xa8\xcb",0
+textCoinSide2 db "[2] \xd0\xc5\xd8\xca\xc0",0
+textCoinFlipping db "\xcc\xce\xcd\xc5\xd2\xc0 \xc2 \xcf\xce\xcb\xa8\xd2\xc5...",0
+textCoinWin db "\xc2\xdb\xc8\xc3\xd0\xdb\xd8: +$",0
+textCoinLose db "\xcd\xc5 \xd3\xc3\xc0\xc4\xc0\xcb!",0
+textCoinKey1 db "1:\xce\xd0\xa8\xcb",0
+textCoinKey2 db "2:\xd0\xc5\xd8\xca\xc0",0
+textCoinKeySpace db "\xcf\xd0\xce\xc1\xc5\xcb:\xc1\xd0\xce\xd1\xce\xca",0
+textCoinStreakFmt db "\xd1\xc5\xd0\xc8\xdf: [X] [X] [X] [ ] [ ] x4!",0
+
+; --- Native 2.5D Dice Game ---
+dice3d_bet dd 50
+dice3d_choice dd 1 ; 1 = \xd7\xb8\xf2, 2 = \xcd\xe5\xf7\xe5\xf2
+dice3d_state dd 0 ; 0 = Idle, 1 = Rolling, 2 = Result
+dice3d_timer dd 0
+dice3d_val1 dd 4
+dice3d_val2 dd 6
+dice3d_sum dd 10
+dice3d_win dd 0
+
+textDiceTitle db "=== G R A N D C R A P S ===",0
+textDiceSubtitle db "[ PASS LINE ] [ ANY CRAPS ]",0
+textDiceEven db "[1] \xd7\xa8\xd2",0
+textDiceOdd db "[2] \xcd\xc5\xd7\xc5\xd2",0
+textDiceEvenNums db "2 4 6 8 10",0
+textDiceOddNums db "3 5 7 9 11",0
+textDiceSumHdr db "\xd1\xd3\xcc\xcc\xc0: ",0
+textDicePlus db " + ",0
+textDiceEqual db " = ",0
+textDiceEvenResult db " (\xd7\xa8\xd2)",0
+textDiceOddResult db " (\xcd\xc5\xd7\xc5\xd2)",0
+textDiceRolling db "\xca\xce\xd1\xd2\xc8 \xcb\xc5\xd2\xdf\xd2 \xcf\xce \xd1\xd3\xca\xcd\xd3...",0
+textDiceWin db "\xc2\xdb\xc8\xc3\xd0\xdb\xd8: +$",0
+textDiceWinMult db "! (x2)",0
+textDiceLose db "\xcf\xd0\xce\xc8\xc3\xd0\xdb\xd8!",0
+textDiceKey1 db "1:\xd7\xa8\xd2",0
+textDiceKey2 db "2:\xcd\xc5\xd7\xc5\xd2",0
+textDiceKeySpace db "\xcf\xd0\xce\xc1\xc5\xcb:\xc1\xd0\xce\xd1\xce\xca",0
+
+; --- Native 2.5D Skulls Game ---
+skulls3d_bet dd 50
+skulls3d_state dd 0 ; 0 = Idle, 1 = Show ball, 2 = Shuffle, 3 = Pick, 4 = Reveal
+skulls3d_timer dd 0
+skulls3d_ballPos dd 1 ; 0, 1, 2
+skulls3d_choice dd 1 ; 0, 1, 2
+skulls3d_win dd 0
+skulls3d_swapPair0 dd 0
+skulls3d_swapPair1 dd 1
+skulls3d_swapPair2 dd 2
+
+textSkullsTitle db "+ + + \xcd \xc0 \xcf \xa8 \xd0 \xd1 \xd2 \xca \xc8 + + +",0
+textSkullsPhase0 db "\xc4\xc5\xcb\xc0\xc9 \xd1\xd2\xc0\xc2\xca\xd3 \xc8 \xc6\xcc\xc8 [\xcf\xd0\xce\xc1\xc5\xcb]",0
+textSkullsPhase1 db "\xd1\xcc\xce\xd2\xd0\xc8, \xc3\xc4\xc5 \xc7\xce\xcb\xce\xd2\xce\xc9 \xd8\xc0\xd0...",0
+textSkullsPhase2 db "\xca\xd0\xd3\xd7\xd3, \xc2\xc5\xd0\xd7\xd3, \xc7\xc0\xcf\xd3\xd2\xc0\xd2\xdc \xd5\xce\xd7\xd3...",0
+textSkullsPhase3 db "\xc3\xc4\xc5 \xd8\xc0\xd0? \xc2\xdb\xc1\xc8\xd0\xc0\xc9: [1], [2] \xc8\xcb\xc8 [3]!",0
+textSkullsWin db "\xc2\xc5\xd0\xcd\xce! \xc2\xdb\xcf\xcb\xc0\xd2\xc0 x3! +$",0
+textSkullsLose db "\xcc\xc8\xcc\xce! \xd8\xc0\xd0 \xc1\xdb\xcb \xc2 \xc4\xd0\xd3\xc3\xce\xcc \xcc\xc5\xd1\xd2\xc5!",0
+textSkullsAgain db "[\xcf\xd0\xce\xc1\xc5\xcb] - \xd1\xdb\xc3\xd0\xc0\xd2\xdc \xc5\xd9\xa8 \xd0\xc0\xc7",0
+textSkullsKey1 db "1:\xca\xd3\xc1\xce\xca",0
+textSkullsKey2 db "2:\xca\xd3\xc1\xce\xca",0
+textSkullsKey3 db "3:\xca\xd3\xc1\xce\xca",0
+textSkullsKeySpace db "\xcf\xd0\xce\xc1\xc5\xcb",0
+
+; --- Shared UI Strings ---
+textKeyEsc db "ESC",0
+textBankPrompt db "\xc1\xc0\xcd\xca: $",0
+textBetPrompt db "\xd1\xd2\xc0\xc2\xca\xc0: $",0
+textBetHint db " [W/S]",0
+
+"""
+
+raw_content = raw_content[:pos_coin_table] + new_strings_text.encode('latin1') + raw_content[pos_shop_msg:]
+print("[OK] Replaced string constants section with rich game strings")
+
+# 3. Replace procedural drawing and rendering block
+# Find start of '; IN-ENGINE 2.5D MINI-GAME: COIN FLIP' and end before 'HandlePlayerInput PROC'
+pos_games_start = raw_content.find(b'; IN-ENGINE 2.5D MINI-GAME: COIN FLIP')
+pos_player_input = raw_content.find(b'HandlePlayerInput PROC')
+
+assert pos_games_start != -1 and pos_player_input != -1, "Could not locate games procedures section"
+
+new_games_code = r"""; ==============================================================================
+; SHARED RICH UI PROCEDURES
+; ==============================================================================
+DrawKeycap3D PROC uses ebx esi edi, x:DWORD, y:DWORD, strPtr:DWORD, isActive:DWORD
+ LOCAL capW:DWORD
+ ; Compute string length
+ mov esi, strPtr
+ xor ecx, ecx
+@@:
+ mov al, [esi+ecx]
+ test al, al
+ jz @F
+ inc ecx
+ jmp @B
+@@:
+ shl ecx, 3 ; length * 8
+ add ecx, 8 ; padding 8px
+ mov capW, ecx
+
+ ; Drop shadow
+ mov eax, x
+ mov edx, y
+ add edx, 2
+ invoke DrawBox, eax, edx, capW, 13, 0008080Ch
+
+ ; Body & Border
+ mov eax, 00606870h ; default border
+ mov edx, 001A1C22h ; default fill
+ cmp isActive, 0
+ je @F
+ mov eax, 0000FFFFh ; active gold border
+ mov edx, 001B4B22h ; active dark green fill
+@@:
+ invoke DrawBoxBorder, x, y, capW, 13, edx, eax, 1
+
+ ; String text
+ mov eax, 00D0D8E0h
+ cmp isActive, 0
+ je @F
+ mov eax, 0000FFFFh
+@@:
+ mov edx, x
+ add edx, 4
+ mov ecx, y
+ add ecx, 3
+ invoke DrawString8x8, edx, ecx, strPtr, eax
+ mov eax, capW
+ ret
+DrawKeycap3D ENDP
+
+DrawChipStack PROC uses ebx esi edi, x:DWORD, y:DWORD, chipCount:DWORD
+ xor esi, esi
+chip_loop:
+ cmp esi, chipCount
+ jge chip_done
+ mov eax, esi
+ shl eax, 2 ; esi * 4
+ mov edx, y
+ sub edx, eax ; cy = y - esi*4
+
+ ; Shadow
+ mov eax, x
+ add eax, 2
+ mov ecx, edx
+ add ecx, 5
+ invoke DrawBox, eax, ecx, 20, 2, 00041006h
+
+ ; Chip body
+ invoke DrawBoxBorder, x, edx, 24, 6, 001515B8h, 00FFFFFFh, 1
+
+ ; White stripe inlays
+ mov eax, x
+ add eax, 4
+ mov ecx, edx
+ add ecx, 1
+ invoke DrawBox, eax, ecx, 3, 4, 00FFFFFFh
+ mov eax, x
+ add eax, 17
+ invoke DrawBox, eax, ecx, 3, 4, 00FFFFFFh
+
+ inc esi
+ jmp chip_loop
+chip_done:
+ ret
+DrawChipStack ENDP
+
+; ==============================================================================
+; IN-ENGINE 2.5D MINI-GAME: COIN FLIP (МО�ЕТК�)
+; ==============================================================================
+DrawDetailedCoin PROC uses ebx esi edi, cxVal:DWORD, cyVal:DWORD, rxVal:DWORD, ryVal:DWORD, outerColor:DWORD, innerColor:DWORD, reliefType:DWORD
+ LOCAL curDY:DWORD, curDX:DWORD, rxSq:DWORD, rySq:DWORD, rxRySq:DWORD, threshSq:DWORD
+ cmp rxVal, 0
+ jle coin_draw_ret
+ cmp ryVal, 0
+ jle coin_draw_ret
+
+ mov eax, rxVal
+ imul eax, eax
+ mov rxSq, eax
+
+ mov eax, ryVal
+ imul eax, eax
+ mov rySq, eax
+
+ mov eax, rxSq
+ imul eax, rySq
+ mov rxRySq, eax
+
+ mov eax, rxRySq
+ shr eax, 2
+ imul eax, 3
+ mov threshSq, eax
+
+ mov eax, ryVal
+ neg eax
+ mov curDY, eax
+
+coin_y_loop:
+ mov eax, curDY
+ cmp eax, ryVal
+ jg check_coin_relief
+
+ mov eax, rxVal
+ neg eax
+ mov curDX, eax
+
+coin_x_loop:
+ mov eax, curDX
+ cmp eax, rxVal
+ jg next_coin_y
+
+ mov eax, curDX
+ imul eax, eax
+ imul eax, rySq
+ mov ebx, curDY
+ imul ebx, ebx
+ imul ebx, rxSq
+ add eax, ebx
+ cmp eax, rxRySq
+ jg next_coin_x
+
+ mov ecx, innerColor
+ cmp eax, threshSq
+ jb @F
+ mov ecx, outerColor
+@@:
+ mov eax, cxVal
+ add eax, curDX
+ mov edx, cyVal
+ add edx, curDY
+ invoke DrawPixel, eax, edx, ecx
+
+next_coin_x:
+ inc curDX
+ jmp coin_x_loop
+
+next_coin_y:
+ inc curDY
+ jmp coin_y_loop
+
+check_coin_relief:
+ cmp rxVal, 14
+ jl coin_draw_ret
+ cmp reliefType, 1
+ je draw_eagle_relief
+ cmp reliefType, 2
+ je draw_wreath_relief
+ jmp coin_draw_ret
+
+draw_eagle_relief:
+ ; Crown
+ mov eax, cxVal
+ sub eax, 3
+ mov edx, cyVal
+ sub edx, 14
+ invoke DrawBox, eax, edx, 7, 2, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 4
+ mov edx, cyVal
+ sub edx, 12
+ invoke DrawBox, eax, edx, 9, 2, 00007A9Ah
+
+ ; Body & Head
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 9
+ invoke DrawBox, eax, edx, 5, 12, 00007A9Ah
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 3, 10, 0000D7FFh
+
+ ; Wings
+ mov eax, cxVal
+ sub eax, 10
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 13
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 10, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 11
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+
+ mov eax, cxVal
+ add eax, 4
+ mov edx, cyVal
+ sub edx, 8
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ mov eax, cxVal
+ add eax, 4
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 10, 3, 00007A9Ah
+ mov eax, cxVal
+ add eax, 5
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+
+ ; Tail
+ mov eax, cxVal
+ sub eax, 4
+ mov edx, cyVal
+ add edx, 4
+ invoke DrawBox, eax, edx, 9, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 3
+ mov edx, cyVal
+ add edx, 7
+ invoke DrawBox, eax, edx, 7, 3, 00007A9Ah
+ jmp coin_draw_ret
+
+draw_wreath_relief:
+ ; Roman numeral 'I'
+ mov eax, cxVal
+ sub eax, 5
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 11, 3, 00007A9Ah
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 7
+ invoke DrawBox, eax, edx, 5, 14, 00007A9Ah
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ sub edx, 6
+ invoke DrawBox, eax, edx, 3, 12, 0000D7FFh
+ mov eax, cxVal
+ sub eax, 6
+ mov edx, cyVal
+ add edx, 7
+ invoke DrawBox, eax, edx, 13, 3, 00007A9Ah
+
+ ; Laurel dots left & right
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ sub edx, 10
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ sub edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+ mov eax, cxVal
+ sub eax, 12
+ mov edx, cyVal
+ add edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+ mov eax, cxVal
+ add eax, 11
+ mov edx, cyVal
+ add edx, 5
+ invoke DrawBox, eax, edx, 2, 2, 00007A9Ah
+
+coin_draw_ret:
+ ret
+DrawDetailedCoin ENDP
+
+InitCoinGame PROC
+ mov coin3d_state, 0
+ mov coin3d_timer, 0
+ mov coin3d_win, 0
+ cmp coin3d_bet, 10
+ jge @F
+ mov coin3d_bet, 50
+@@:
+ ret
+InitCoinGame ENDP
+
+HandleCoinInput PROC
+ cmp coin3d_state, 1
+ je coin_input_done
+
+ invoke Pressed3D, 20h ; SPACE -> Flip!
+ test eax, eax
+ jz check_coin_side
+ mov eax, bankroll
+ cmp eax, coin3d_bet
+ jl coin_no_cash
+ sub eax, coin3d_bet
+ mov bankroll, eax
+ mov coin3d_state, 1
+ mov coin3d_timer, 0
+ mov coin3d_win, 0
+ invoke PlaySoundA, OFFSET soundCoin, 0, 1
+ jmp coin_input_done
+
+coin_no_cash:
+ invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
+ mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ jmp coin_input_done
+
+check_coin_side:
+ invoke Pressed3D, 31h ; '1'
+ test eax, eax
+ jz @F
+ mov coin3d_side, 1
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+@@:
+ invoke Pressed3D, 32h ; '2'
+ test eax, eax
+ jz check_coin_bet_adjust
+ mov coin3d_side, 2
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+
+check_coin_bet_adjust:
+ invoke Pressed3D, 26h ; UP
+ test eax, eax
+ jnz coin_inc_bet
+ invoke Pressed3D, 57h ; W
+ test eax, eax
+ jz check_coin_bet_down
+coin_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp coin3d_bet, 500
+ jge coin_input_done
+ cmp coin3d_bet, 250
+ jge coin_set_500
+ cmp coin3d_bet, 100
+ jge coin_set_250
+ cmp coin3d_bet, 50
+ jge coin_set_100
+ cmp coin3d_bet, 25
+ jge coin_set_50
+ jmp coin_set_25
+coin_set_500:
+ mov coin3d_bet, 500
+ jmp coin_input_done
+coin_set_250:
+ mov coin3d_bet, 250
+ jmp coin_input_done
+coin_set_100:
+ mov coin3d_bet, 100
+ jmp coin_input_done
+coin_set_50:
+ mov coin3d_bet, 50
+ jmp coin_input_done
+coin_set_25:
+ mov coin3d_bet, 25
+ jmp coin_input_done
+
+check_coin_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz coin_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz coin_input_done
+coin_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp coin3d_bet, 25
+ jle coin_set_10
+ cmp coin3d_bet, 50
+ jle coin_set_25
+ cmp coin3d_bet, 100
+ jle coin_set_50
+ cmp coin3d_bet, 250
+ jle coin_set_100
+ mov coin3d_bet, 250
+ jmp coin_input_done
+coin_set_10:
+ mov coin3d_bet, 10
+
+coin_input_done:
+ ret
+HandleCoinInput ENDP
+
+RenderCoinGame PROC uses ebx esi edi
+ LOCAL cyVal:DWORD, rxVal:DWORD, curX:DWORD
+
+ ; Cabinet Bezel (Mahogany wood & leather)
+ invoke DrawBox, 0, 0, 320, 240, 00060914h
+ invoke DrawBoxBorder, 6, 6, 308, 228, 000F1522h, 001B2E48h, 3
+
+ ; Corner Brass Rivets
+ invoke DrawBox, 12, 12, 3, 3, 0000D7FFh
+ invoke DrawPixel, 13, 13, 00FFFFFFh
+ invoke DrawBox, 306, 12, 3, 3, 0000D7FFh
+ invoke DrawPixel, 307, 13, 00FFFFFFh
+ invoke DrawBox, 12, 226, 3, 3, 0000D7FFh
+ invoke DrawPixel, 13, 227, 00FFFFFFh
+ invoke DrawBox, 306, 226, 3, 3, 0000D7FFh
+ invoke DrawPixel, 307, 227, 00FFFFFFh
+
+ ; Illuminated Marquee Header
+ invoke DrawBoxBorder, 24, 12, 272, 26, 000A101Ch, 0000D7FFh, 2
+ invoke DrawString8x8, 48, 21, OFFSET textCoinTitle, 0000FFFFh
+
+ ; Bulb lights on marquee border
+ mov curX, 28
+coin_bulbs:
+ cmp curX, 292
+ jge coin_meters
+ invoke DrawPixel, curX, 13, 0000FFFFh
+ invoke DrawPixel, curX, 36, 0000D7FFh
+ add curX, 12
+ jmp coin_bulbs
+
+coin_meters:
+ ; Status Meters: Bank & Bet
+ invoke DrawBoxBorder, 20, 44, 114, 18, 000A1A0Eh, 001A4B22h, 1
+ invoke DrawString8x8, 24, 49, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 72, 49, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 186, 44, 114, 18, 001A100Ah, 003A2814h, 1
+ invoke DrawString8x8, 190, 49, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, coin3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 254, 49, OFFSET hudNumBuffer, 0000D7FFh
+ invoke DrawString8x8, 278, 49, OFFSET textBetHint, 00808080h
+
+ ; Center Velvet Platter
+ invoke DrawCircle, 160, 110, 46, 000090C0h
+ invoke DrawCircle, 160, 110, 44, 0000D7FFh
+ invoke DrawCircle, 160, 110, 41, 00180824h
+
+ ; Selection Podiums: [1] ОР�Л
+ mov eax, 00121620h
+ mov edx, 00404855h
+ mov ecx, 0090A0B0h
+ cmp coin3d_side, 1
+ jne @F
+ mov eax, 001C3822h
+ mov edx, 0000FFFFh
+ mov ecx, 0000FFFFh
+@@:
+ push ecx
+ invoke DrawBoxBorder, 20, 72, 76, 52, eax, edx, 2
+ pop ecx
+ invoke DrawString8x8, 28, 78, OFFSET textCoinSide1, ecx
+ invoke DrawDetailedCoin, 58, 106, 12, 12, 0000A0D0h, 0000D7FFh, 1
+
+ ; Selection Podiums: [2] РЕШК�
+ mov eax, 00121620h
+ mov edx, 00404855h
+ mov ecx, 0090A0B0h
+ cmp coin3d_side, 2
+ jne @F
+ mov eax, 001C3822h
+ mov edx, 0000FFFFh
+ mov ecx, 0000FFFFh
+@@:
+ push ecx
+ invoke DrawBoxBorder, 224, 72, 76, 52, eax, edx, 2
+ pop ecx
+ invoke DrawString8x8, 232, 78, OFFSET textCoinSide2, ecx
+ invoke DrawDetailedCoin, 262, 106, 12, 12, 000080A0h, 0000B0D0h, 2
+
+ ; Flipping animation logic
+ cmp coin3d_state, 1
+ jne coin_at_rest
+
+ inc coin3d_timer
+
+ ; Parabolic toss: cy = 110 - (t * (40 - t)) / 14
+ mov eax, coin3d_timer
+ mov ecx, 40
+ sub ecx, eax
+ imul eax, ecx
+ xor edx, edx
+ mov ecx, 14
+ div ecx
+ mov edx, 110
+ sub edx, eax
+ mov cyVal, edx
+
+ ; Width rx from lookup table: (t % 10)
+ mov eax, coin3d_timer
+ xor edx, edx
+ mov ecx, 10
+ div ecx
+ movzx eax, BYTE PTR [coinRxAnimTable + edx]
+ mov rxVal, eax
+
+ invoke DrawDetailedCoin, 160, cyVal, rxVal, 26, 0000A0D0h, 0000D7FFh, 0
+ invoke DrawString8x8, 88, 160, OFFSET textCoinFlipping, 0000FFFFh
+
+ cmp coin3d_timer, 40
+ jl coin_draw_controls
+
+ ; Flip complete!
+ mov coin3d_state, 2
+ invoke RandomRange3D, 2
+ inc eax
+ mov coin3d_result, eax
+
+ mov eax, coin3d_result
+ cmp eax, coin3d_side
+ jne coin_flip_lost
+
+ ; Won!
+ mov eax, coin3d_bet
+ shl eax, 1
+ mov coin3d_win, eax
+ add bankroll, eax
+ inc coin3d_streak
+ cmp coin3d_streak, 3
+ jl @F
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
+ jmp coin_draw_controls
+@@:
+ invoke PlaySoundA, OFFSET soundCoinDing, 0, 1
+ jmp coin_draw_controls
+
+coin_flip_lost:
+ mov coin3d_streak, 0
+ mov coin3d_win, 0
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
+ jmp coin_draw_controls
+
+coin_at_rest:
+ ; Draw coin resting in velvet dish
+ mov edx, coin3d_side
+ cmp coin3d_state, 2
+ jne @F
+ mov edx, coin3d_result
+@@:
+ invoke DrawDetailedCoin, 160, 110, 26, 26, 0000A0D0h, 0000D7FFh, edx
+
+ ; Outcome / Streak Display
+ cmp coin3d_state, 2
+ jne coin_show_streak
+
+ cmp coin3d_win, 0
+ je coin_show_lose
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 0050FF50h, 1
+ invoke DrawString8x8, 64, 160, OFFSET textCoinWin, 0050FF50h
+ invoke FormatNumber, coin3d_win, OFFSET hudNumBuffer
+ invoke DrawString8x8, 152, 160, OFFSET hudNumBuffer, 0050FF50h
+ jmp coin_draw_controls
+
+coin_show_lose:
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 000000FFh, 1
+ invoke DrawString8x8, 114, 160, OFFSET textCoinLose, 008080FFh
+ jmp coin_draw_controls
+
+coin_show_streak:
+ invoke DrawBoxBorder, 44, 154, 232, 20, 000E141Eh, 0000D7FFh, 1
+ invoke DrawString8x8, 52, 160, OFFSET textCoinStreakFmt, 0000FFFFh
+
+coin_draw_controls:
+ ; Keycaps Control Bar
+ invoke DrawBox, 16, 194, 288, 1, 001E2E40h
+
+ mov eax, 0
+ cmp coin3d_side, 1
+ sete al
+ invoke DrawKeycap3D, 22, 202, OFFSET textCoinKey1, eax
+
+ mov eax, 0
+ cmp coin3d_side, 2
+ sete al
+ invoke DrawKeycap3D, 88, 202, OFFSET textCoinKey2, eax
+
+ invoke DrawKeycap3D, 160, 202, OFFSET textCoinKeySpace, 1
+ invoke DrawKeycap3D, 274, 202, OFFSET textKeyEsc, 0
+ ret
+RenderCoinGame ENDP
+
+; ==============================================================================
+; IN-ENGINE 2.5D MINI-GAME: DICE / CRAPS (КОСТИ)
+; ==============================================================================
+DrawCrapsDie PROC uses ebx esi edi, x:DWORD, y:DWORD, sizeVal:DWORD, valNum:DWORD
+ ; Drop shadow on green felt
+ mov eax, x
+ add eax, 3
+ mov edx, y
+ add edx, sizeVal
+ invoke DrawBox, eax, edx, sizeVal, 3, 00031408h
+ mov eax, x
+ add eax, sizeVal
+ mov edx, y
+ add edx, 3
+ invoke DrawBox, eax, edx, 3, sizeVal, 00031408h
+
+ ; Ivory body & dark border
+ invoke DrawBoxBorder, x, y, sizeVal, sizeVal, 00F0F4FAh, 00353540h, 1
+
+ ; Bevel highlight top & left
+ mov eax, sizeVal
+ sub eax, 2
+ mov edx, x
+ inc edx
+ mov ecx, y
+ inc ecx
+ invoke DrawBox, edx, ecx, eax, 1, 00FFFFFFh
+ invoke DrawBox, edx, ecx, 1, eax, 00FFFFFFh
+
+ ; Bevel shadow bottom & right
+ mov edx, x
+ inc edx
+ mov ecx, y
+ add ecx, sizeVal
+ sub ecx, 2
+ invoke DrawBox, edx, ecx, eax, 1, 00C0C6D0h
+ mov edx, x
+ add edx, sizeVal
+ sub edx, 2
+ mov ecx, y
+ inc ecx
+ invoke DrawBox, edx, ecx, 1, eax, 00C0C6D0h
+
+ ; Casino Ruby Red circular pips (radius 3)
+ cmp valNum, 1
+ je draw_die_c
+ cmp valNum, 3
+ je draw_die_c
+ cmp valNum, 5
+ je draw_die_c
+ jmp check_die_pips_corners
+
+draw_die_c:
+ mov eax, x
+ add eax, 18
+ mov edx, y
+ add edx, 18
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+ dec eax
+ dec edx
+ invoke DrawPixel, eax, edx, 00FFFFFFh
+
+check_die_pips_corners:
+ cmp valNum, 2
+ jl die_pips_done
+ mov eax, x
+ add eax, 9
+ mov edx, y
+ add edx, 9
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+ mov eax, x
+ add eax, sizeVal
+ sub eax, 10
+ mov edx, y
+ add edx, sizeVal
+ sub edx, 10
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+
+ cmp valNum, 4
+ jl die_pips_done
+ mov eax, x
+ add eax, sizeVal
+ sub eax, 10
+ mov edx, y
+ add edx, 9
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+ mov eax, x
+ add eax, 9
+ mov edx, y
+ add edx, sizeVal
+ sub edx, 10
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+
+ cmp valNum, 6
+ jl die_pips_done
+ mov eax, x
+ add eax, 9
+ mov edx, y
+ add edx, 18
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+ mov eax, x
+ add eax, sizeVal
+ sub eax, 10
+ mov edx, y
+ add edx, 18
+ invoke DrawCircle, eax, edx, 3, 001515C8h
+
+die_pips_done:
+ ret
+DrawCrapsDie ENDP
+
+InitDiceGame PROC
+ mov dice3d_state, 0
+ mov dice3d_timer, 0
+ mov dice3d_win, 0
+ cmp dice3d_bet, 10
+ jge @F
+ mov dice3d_bet, 50
+@@:
+ ret
+InitDiceGame ENDP
+
+HandleDiceInput PROC
+ cmp dice3d_state, 1
+ je dice_input_done
+
+ invoke Pressed3D, 20h ; SPACE -> Roll!
+ test eax, eax
+ jz check_dice_choice
+ mov eax, bankroll
+ cmp eax, dice3d_bet
+ jl dice_no_cash
+ sub eax, dice3d_bet
+ mov bankroll, eax
+ mov dice3d_state, 1
+ mov dice3d_timer, 0
+ mov dice3d_win, 0
+ invoke PlaySoundA, OFFSET soundCards, 0, 1
+ jmp dice_input_done
+
+dice_no_cash:
+ invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
+ mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ jmp dice_input_done
+
+check_dice_choice:
+ invoke Pressed3D, 31h ; '1'
+ test eax, eax
+ jz @F
+ mov dice3d_choice, 1
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+@@:
+ invoke Pressed3D, 32h ; '2'
+ test eax, eax
+ jz check_dice_bet_adjust
+ mov dice3d_choice, 2
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+
+check_dice_bet_adjust:
+ invoke Pressed3D, 26h ; UP
+ test eax, eax
+ jnz dice_inc_bet
+ invoke Pressed3D, 57h ; W
+ test eax, eax
+ jz check_dice_bet_down
+dice_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp dice3d_bet, 500
+ jge dice_input_done
+ cmp dice3d_bet, 250
+ jge dice_set_500
+ cmp dice3d_bet, 100
+ jge dice_set_250
+ cmp dice3d_bet, 50
+ jge dice_set_100
+ cmp dice3d_bet, 25
+ jge dice_set_50
+ jmp dice_set_25
+dice_set_500:
+ mov dice3d_bet, 500
+ jmp dice_input_done
+dice_set_250:
+ mov dice3d_bet, 250
+ jmp dice_input_done
+dice_set_100:
+ mov dice3d_bet, 100
+ jmp dice_input_done
+dice_set_50:
+ mov dice3d_bet, 50
+ jmp dice_input_done
+dice_set_25:
+ mov dice3d_bet, 25
+ jmp dice_input_done
+
+check_dice_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz dice_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz dice_input_done
+dice_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp dice3d_bet, 25
+ jle dice_set_10
+ cmp dice3d_bet, 50
+ jle dice_set_25
+ cmp dice3d_bet, 100
+ jle dice_set_50
+ cmp dice3d_bet, 250
+ jle dice_set_100
+ mov dice3d_bet, 250
+ jmp dice_input_done
+dice_set_10:
+ mov dice3d_bet, 10
+
+dice_input_done:
+ ret
+HandleDiceInput ENDP
+
+RenderDiceGame PROC uses ebx esi edi
+ LOCAL d1X:DWORD, d1Y:DWORD, d2X:DWORD, d2Y:DWORD, bxX:DWORD
+
+ ; Craps Emerald Felt & Oak Armrest
+ invoke DrawBox, 0, 0, 320, 240, 00082E12h
+ invoke DrawBoxBorder, 4, 4, 312, 232, 00093816h, 00122244h, 3
+ invoke DrawBoxBorder, 8, 8, 304, 224, 000A3F19h, 0005140Ah, 2
+
+ ; Pyramidal diamond craps backboard texture
+ mov bxX, 12
+craps_bumper:
+ cmp bxX, 308
+ jge craps_felt_markings
+ mov eax, bxX
+ add eax, 4
+ invoke DrawBox, eax, 11, 4, 4, 00041006h
+ mov eax, bxX
+ add eax, 5
+ invoke DrawPixel, eax, 12, 000F4820h
+ add bxX, 12
+ jmp craps_bumper
+
+craps_felt_markings:
+ ; Table Markings printed in gold directly on felt
+ invoke DrawString8x8, 72, 20, OFFSET textDiceTitle, 0000D7FFh
+ invoke DrawString8x8, 68, 32, OFFSET textDiceSubtitle, 0000B0A0h
+
+ ; Meters: Bank & Bet
+ invoke DrawBoxBorder, 16, 46, 108, 16, 0006240Eh, 000E5022h, 1
+ invoke DrawString8x8, 20, 50, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 68, 50, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 196, 46, 108, 16, 0006240Eh, 000E5022h, 1
+ invoke DrawString8x8, 200, 50, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, dice3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 264, 50, OFFSET hudNumBuffer, 0000D7FFh
+
+ ; Betting Zone Left: [1] Ч�Т
+ mov eax, 00073014h
+ mov edx, 001A602Ah
+ mov ecx, 0080B090h
+ cmp dice3d_choice, 1
+ jne @F
+ mov eax, 000B441Dh
+ mov edx, 0000FFFFh
+ mov ecx, 0000FFFFh
+@@:
+ push ecx
+ invoke DrawBoxBorder, 16, 68, 88, 54, eax, edx, 2
+ pop ecx
+ invoke DrawString8x8, 24, 74, OFFSET textDiceEven, ecx
+ invoke DrawString8x8, 20, 86, OFFSET textDiceEvenNums, 0000D7FFh
+ cmp dice3d_choice, 1
+ jne @F
+ invoke DrawChipStack, 48, 110, 4
+@@:
+
+ ; Betting Zone Right: [2] �ЕЧЕТ
+ mov eax, 00073014h
+ mov edx, 001A602Ah
+ mov ecx, 0080B090h
+ cmp dice3d_choice, 2
+ jne @F
+ mov eax, 000B441Dh
+ mov edx, 0000FFFFh
+ mov ecx, 0000FFFFh
+@@:
+ push ecx
+ invoke DrawBoxBorder, 216, 68, 88, 54, eax, edx, 2
+ pop ecx
+ invoke DrawString8x8, 224, 74, OFFSET textDiceOdd, ecx
+ invoke DrawString8x8, 224, 86, OFFSET textDiceOddNums, 00508060h
+ cmp dice3d_choice, 2
+ jne @F
+ invoke DrawChipStack, 248, 110, 4
+@@:
+
+ ; Center Dice Field
+ invoke DrawBoxBorder, 112, 68, 96, 54, 00072E13h, 00145826h, 1
+
+ ; Rolling Animation Logic
+ cmp dice3d_state, 1
+ jne dice_at_rest
+
+ inc dice3d_timer
+
+ ; Tumbling jitter offsets
+ mov eax, dice3d_timer
+ and eax, 3
+ sub eax, 1
+
+ mov edx, 120
+ add edx, eax
+ mov d1X, edx
+ mov edx, 76
+ sub edx, eax
+ mov d1Y, edx
+
+ mov edx, 164
+ sub edx, eax
+ mov d2X, edx
+ mov edx, 78
+ add edx, eax
+ mov d2Y, edx
+
+ test dice3d_timer, 1
+ jz @F
+ invoke RandomRange3D, 6
+ inc eax
+ mov dice3d_val1, eax
+ invoke RandomRange3D, 6
+ inc eax
+ mov dice3d_val2, eax
+@@:
+ invoke DrawCrapsDie, d1X, d1Y, 36, dice3d_val1
+ invoke DrawCrapsDie, d2X, d2Y, 36, dice3d_val2
+ invoke DrawString8x8, 76, 138, OFFSET textDiceRolling, 0000FFFFh
+
+ cmp dice3d_timer, 36
+ jl dice_draw_controls
+
+ ; Settle dice!
+ mov dice3d_state, 2
+ invoke RandomRange3D, 6
+ inc eax
+ mov dice3d_val1, eax
+ invoke RandomRange3D, 6
+ inc eax
+ mov dice3d_val2, eax
+
+ mov eax, dice3d_val1
+ add eax, dice3d_val2
+ mov dice3d_sum, eax
+
+ ; Check choice vs sum parity
+ mov eax, dice3d_sum
+ test eax, 1
+ jz dice_sum_is_even
+
+ ; Odd
+ cmp dice3d_choice, 2
+ je dice_roll_won
+ jmp dice_roll_lost
+
+dice_sum_is_even:
+ cmp dice3d_choice, 1
+ je dice_roll_won
+
+dice_roll_lost:
+ mov dice3d_win, 0
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
+ jmp dice_draw_controls
+
+dice_roll_won:
+ mov eax, dice3d_bet
+ shl eax, 1
+ mov dice3d_win, eax
+ add bankroll, eax
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
+ jmp dice_draw_controls
+
+dice_at_rest:
+ invoke DrawCrapsDie, 120, 76, 36, dice3d_val1
+ invoke DrawCrapsDie, 164, 76, 36, dice3d_val2
+
+ cmp dice3d_state, 2
+ jne dice_draw_controls
+
+ ; Scoreboard Plaque
+ invoke DrawBoxBorder, 36, 130, 248, 30, 00051D0Ch, 0000D7FFh, 1
+ invoke DrawString8x8, 44, 135, OFFSET textDiceSumHdr, 0000D7FFh
+ invoke FormatNumber, dice3d_val1, OFFSET hudNumBuffer
+ invoke DrawString8x8, 100, 135, OFFSET hudNumBuffer, 00FFFFFFh
+ invoke DrawString8x8, 110, 135, OFFSET textDicePlus, 0000D7FFh
+ invoke FormatNumber, dice3d_val2, OFFSET hudNumBuffer
+ invoke DrawString8x8, 134, 135, OFFSET hudNumBuffer, 00FFFFFFh
+ invoke DrawString8x8, 144, 135, OFFSET textDiceEqual, 0000D7FFh
+ invoke FormatNumber, dice3d_sum, OFFSET hudNumBuffer
+ invoke DrawString8x8, 168, 135, OFFSET hudNumBuffer, 0000FFFFh
+
+ mov eax, dice3d_sum
+ test eax, 1
+ jnz @F
+ invoke DrawString8x8, 186, 135, OFFSET textDiceEvenResult, 0080FF80h
+ jmp dice_show_win_msg
+@@:
+ invoke DrawString8x8, 186, 135, OFFSET textDiceOddResult, 00FF8080h
+
+dice_show_win_msg:
+ cmp dice3d_win, 0
+ je dice_show_loss
+ invoke DrawString8x8, 68, 147, OFFSET textDiceWin, 0050FF50h
+ invoke FormatNumber, dice3d_win, OFFSET hudNumBuffer
+ invoke DrawString8x8, 156, 147, OFFSET hudNumBuffer, 0050FF50h
+ invoke DrawString8x8, 188, 147, OFFSET textDiceWinMult, 0050FF50h
+ jmp dice_draw_controls
+
+dice_show_loss:
+ invoke DrawString8x8, 116, 147, OFFSET textDiceLose, 008080FFh
+
+dice_draw_controls:
+ invoke DrawBox, 16, 188, 288, 1, 00104820h
+
+ mov eax, 0
+ cmp dice3d_choice, 1
+ sete al
+ invoke DrawKeycap3D, 20, 198, OFFSET textDiceKey1, eax
+
+ mov eax, 0
+ cmp dice3d_choice, 2
+ sete al
+ invoke DrawKeycap3D, 82, 198, OFFSET textDiceKey2, eax
+
+ invoke DrawKeycap3D, 160, 198, OFFSET textDiceKeySpace, 1
+ invoke DrawKeycap3D, 274, 198, OFFSET textKeyEsc, 0
+ ret
+RenderDiceGame ENDP
+
+; ==============================================================================
+; IN-ENGINE 2.5D MINI-GAME: SKULLS & CUPS (��П�РСТКИ)
+; ==============================================================================
+DrawGlowingOrb PROC uses ebx esi edi, cxVal:DWORD, cyVal:DWORD
+ invoke DrawCircle, cxVal, cyVal, 14, 00081E2Eh
+ invoke DrawCircle, cxVal, cyVal, 11, 00103A58h
+ invoke DrawCircle, cxVal, cyVal, 8, 001A588Ah
+ invoke DrawCircle, cxVal, cyVal, 6, 0000B0E0h
+ invoke DrawCircle, cxVal, cyVal, 4, 0000E0FFh
+ mov eax, cxVal
+ dec eax
+ mov edx, cyVal
+ dec edx
+ invoke DrawCircle, eax, edx, 2, 00A0FFFFh
+ mov eax, cxVal
+ sub eax, 2
+ mov edx, cyVal
+ sub edx, 2
+ invoke DrawPixel, eax, edx, 00FFFFFFh
+ ret
+DrawGlowingOrb ENDP
+
+DrawGothicCup PROC uses ebx esi edi, x:DWORD, y:DWORD, cupId:DWORD, isLifted:DWORD, hasBall:DWORD
+ LOCAL liftY:DWORD
+
+ mov eax, y
+ cmp isLifted, 0
+ je @F
+ sub eax, 28
+@@:
+ mov liftY, eax
+
+ ; Drop shadow on purple velvet
+ cmp isLifted, 0
+ jne @F
+ mov eax, x
+ add eax, 6
+ mov edx, y
+ add edx, 42
+ invoke DrawBox, eax, edx, 36, 4, 000A0410h
+ jmp draw_cup_body
+@@:
+ mov eax, x
+ add eax, 10
+ mov edx, y
+ add edx, 42
+ invoke DrawBox, eax, edx, 28, 2, 0012061Ah
+
+ ; Draw orb if lifted and has ball
+ cmp hasBall, 0
+ je draw_cup_body
+ mov eax, x
+ add eax, 24
+ mov edx, y
+ add edx, 26
+ invoke DrawGlowingOrb, eax, edx
+
+draw_cup_body:
+ ; Base
+ mov eax, x
+ add eax, 6
+ mov edx, liftY
+ add edx, 36
+ invoke DrawBoxBorder, eax, edx, 36, 6, 00221A30h, 0000B0D0h, 1
+
+ ; Stem
+ mov eax, x
+ add eax, 18
+ mov edx, liftY
+ add edx, 28
+ invoke DrawBox, eax, edx, 12, 8, 00332545h
+ mov eax, x
+ add eax, 22
+ mov edx, liftY
+ add edx, 28
+ invoke DrawBox, eax, edx, 4, 8, 0055406Eh
+
+ ; Bowl
+ mov eax, x
+ add eax, 8
+ mov edx, liftY
+ add edx, 8
+ invoke DrawBoxBorder, eax, edx, 32, 20, 002D1E40h, 0000D7FFh, 1
+ mov eax, x
+ add eax, 14
+ mov edx, liftY
+ add edx, 10
+ invoke DrawBox, eax, edx, 6, 16, 00503570h
+ mov eax, x
+ add eax, 17
+ mov edx, liftY
+ add edx, 10
+ invoke DrawBox, eax, edx, 3, 16, 007850A0h
+
+ ; Flared Rim
+ mov eax, x
+ add eax, 4
+ mov edx, liftY
+ add edx, 2
+ invoke DrawBoxBorder, eax, edx, 40, 7, 00150A20h, 0000FFFFh, 1
+ mov eax, x
+ add eax, 12
+ mov edx, liftY
+ add edx, 4
+ invoke DrawBox, eax, edx, 16, 2, 0000FFFFh
+
+ ; Skull Insignia on Bowl
+ mov eax, x
+ add eax, 21
+ mov edx, liftY
+ add edx, 12
+ invoke DrawBox, eax, edx, 6, 5, 0000D7FFh
+ mov eax, x
+ add eax, 22
+ mov edx, liftY
+ add edx, 17
+ invoke DrawBox, eax, edx, 4, 3, 0000D7FFh
+ mov eax, x
+ add eax, 22
+ mov edx, liftY
+ add edx, 14
+ invoke DrawPixel, eax, edx, 00150A20h
+ mov eax, x
+ add eax, 24
+ mov edx, liftY
+ add edx, 14
+ invoke DrawPixel, eax, edx, 00150A20h
+
+ ; Cup ID Badge
+ mov eax, x
+ add eax, 21
+ mov edx, liftY
+ add edx, 24
+ mov ecx, cupId
+ add ecx, '0'
+ invoke DrawChar8x8, eax, edx, ecx, 0000FFFFh
+ ret
+DrawGothicCup ENDP
+
+InitSkullsGame PROC
+ mov skulls3d_state, 0
+ mov skulls3d_timer, 0
+ mov skulls3d_win, 0
+ cmp skulls3d_bet, 10
+ jge @F
+ mov skulls3d_bet, 50
+@@:
+ invoke RandomRange3D, 3
+ mov skulls3d_ballPos, eax
+ ret
+InitSkullsGame ENDP
+
+FinalizeSkullsGuess PROC uses ebx
+ mov skulls3d_state, 4
+ mov eax, skulls3d_choice
+ cmp eax, skulls3d_ballPos
+ jne skulls_guess_lost
+
+ ; Won 3x!
+ mov eax, skulls3d_bet
+ imul eax, 3
+ mov skulls3d_win, eax
+ add bankroll, eax
+ invoke PlaySoundA, OFFSET soundVictory, 0, 1
+ ret
+
+skulls_guess_lost:
+ mov skulls3d_win, 0
+ invoke PlaySoundA, OFFSET soundLose, 0, 1
+ ret
+FinalizeSkullsGuess ENDP
+
+HandleSkullsInput PROC
+ cmp skulls3d_state, 1
+ je skulls_input_done
+ cmp skulls3d_state, 2
+ je skulls_input_done
+
+ cmp skulls3d_state, 3
+ je check_skulls_pick
+
+ ; State 0 or 4: Can start new round or adjust bet
+ invoke Pressed3D, 20h ; SPACE
+ test eax, eax
+ jz check_skulls_bet_adjust
+
+ mov eax, bankroll
+ cmp eax, skulls3d_bet
+ jl skulls_no_cash
+ sub eax, skulls3d_bet
+ mov bankroll, eax
+
+ invoke RandomRange3D, 3
+ mov skulls3d_ballPos, eax
+
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair0, eax
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair1, eax
+ invoke RandomRange3D, 3
+ mov skulls3d_swapPair2, eax
+
+ mov skulls3d_state, 1
+ mov skulls3d_timer, 0
+ mov skulls3d_win, 0
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
+ jmp skulls_input_done
+
+skulls_no_cash:
+ invoke CopyString3D, OFFSET msgNeedMoney, OFFSET flashMessage
+ mov flashTimer, 60
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ jmp skulls_input_done
+
+check_skulls_pick:
+ invoke Pressed3D, 31h ; '1'
+ test eax, eax
+ jz @F
+ mov skulls3d_choice, 0
+ call FinalizeSkullsGuess
+ jmp skulls_input_done
+@@:
+ invoke Pressed3D, 32h ; '2'
+ test eax, eax
+ jz @F
+ mov skulls3d_choice, 1
+ call FinalizeSkullsGuess
+ jmp skulls_input_done
+@@:
+ invoke Pressed3D, 33h ; '3'
+ test eax, eax
+ jz skulls_input_done
+ mov skulls3d_choice, 2
+ call FinalizeSkullsGuess
+ jmp skulls_input_done
+
+check_skulls_bet_adjust:
+ invoke Pressed3D, 26h ; UP
+ test eax, eax
+ jnz skulls_inc_bet
+ invoke Pressed3D, 57h ; W
+ test eax, eax
+ jz check_skulls_bet_down
+skulls_inc_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp skulls3d_bet, 500
+ jge skulls_input_done
+ cmp skulls3d_bet, 250
+ jge skulls_set_500
+ cmp skulls3d_bet, 100
+ jge skulls_set_250
+ cmp skulls3d_bet, 50
+ jge skulls_set_100
+ cmp skulls3d_bet, 25
+ jge skulls_set_50
+ jmp skulls_set_25
+skulls_set_500:
+ mov skulls3d_bet, 500
+ jmp skulls_input_done
+skulls_set_250:
+ mov skulls3d_bet, 250
+ jmp skulls_input_done
+skulls_set_100:
+ mov skulls3d_bet, 100
+ jmp skulls_input_done
+skulls_set_50:
+ mov skulls3d_bet, 50
+ jmp skulls_input_done
+skulls_set_25:
+ mov skulls3d_bet, 25
+ jmp skulls_input_done
+
+check_skulls_bet_down:
+ invoke Pressed3D, 28h ; DOWN
+ test eax, eax
+ jnz skulls_dec_bet
+ invoke Pressed3D, 53h ; S
+ test eax, eax
+ jz skulls_input_done
+skulls_dec_bet:
+ invoke PlaySoundA, OFFSET soundClick, 0, 1
+ cmp skulls3d_bet, 25
+ jle skulls_set_10
+ cmp skulls3d_bet, 50
+ jle skulls_set_25
+ cmp skulls3d_bet, 100
+ jle skulls_set_50
+ cmp skulls3d_bet, 250
+ jle skulls_set_100
+ mov skulls3d_bet, 250
+ jmp skulls_input_done
+skulls_set_10:
+ mov skulls3d_bet, 10
+
+skulls_input_done:
+ ret
+HandleSkullsInput ENDP
+
+RenderSkullsGame PROC uses ebx esi edi
+ LOCAL passTick:DWORD, curPair:DWORD, swapSlotA:DWORD, swapSlotB:DWORD
+ LOCAL xA:DWORD, xB:DWORD, curXA:DWORD, curXB:DWORD, curYA:DWORD, curYB:DWORD
+ LOCAL distX:DWORD, dxVal:DWORD, archY:DWORD
+
+ ; Dark Gothic Tavern Table with Royal Velvet
+ invoke DrawBox, 0, 0, 320, 240, 000D0612h
+ invoke DrawBoxBorder, 4, 4, 312, 232, 00160B20h, 00321845h, 3
+ invoke DrawBoxBorder, 8, 8, 304, 224, 00220F30h, 0000D7FFh, 1
+
+ ; Header
+ invoke DrawBoxBorder, 24, 12, 272, 24, 0012081Ah, 0000D7FFh, 2
+ invoke DrawString8x8, 64, 20, OFFSET textSkullsTitle, 0000FFFFh
+
+ ; Meters: Bank & Bet
+ invoke DrawBoxBorder, 20, 42, 114, 16, 000D0616h, 00401858h, 1
+ invoke DrawString8x8, 24, 46, OFFSET textBankPrompt, 0050FF50h
+ invoke FormatNumber, bankroll, OFFSET hudNumBuffer
+ invoke DrawString8x8, 72, 46, OFFSET hudNumBuffer, 0050FF50h
+
+ invoke DrawBoxBorder, 186, 42, 114, 16, 000D0616h, 00401858h, 1
+ invoke DrawString8x8, 190, 46, OFFSET textBetPrompt, 0000D7FFh
+ invoke FormatNumber, skulls3d_bet, OFFSET hudNumBuffer
+ invoke DrawString8x8, 254, 46, OFFSET hudNumBuffer, 0000D7FFh
+
+ ; Velvet Table Runner
+ invoke DrawBoxBorder, 16, 64, 288, 120, 001A0B25h, 00452060h, 1
+
+ ; Dispatch based on phase
+ cmp skulls3d_state, 1
+ je skulls_render_phase1
+ cmp skulls3d_state, 2
+ je skulls_render_phase2
+ cmp skulls3d_state, 3
+ je skulls_render_phase3
+ cmp skulls3d_state, 4
+ je skulls_render_phase4
+
+ ; Phase 0: Idle / Betting
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase0, 0000D7FFh
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
+ jmp skulls_draw_controls
+
+skulls_render_phase1:
+ inc skulls3d_timer
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase1, 0000FFFFh
+
+ ; Draw cups with ballPos cup lifted
+ mov eax, 0
+ cmp skulls3d_ballPos, 0
+ sete al
+ invoke DrawGothicCup, 48, 108, 1, eax, eax
+
+ mov eax, 0
+ cmp skulls3d_ballPos, 1
+ sete al
+ invoke DrawGothicCup, 136, 108, 2, eax, eax
+
+ mov eax, 0
+ cmp skulls3d_ballPos, 2
+ sete al
+ invoke DrawGothicCup, 224, 108, 3, eax, eax
+
+ cmp skulls3d_timer, 40
+ jl skulls_draw_controls
+
+ ; Start Phase 2 (Shuffle)
+ mov skulls3d_state, 2
+ mov skulls3d_timer, 0
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
+ jmp skulls_draw_controls
+
+skulls_render_phase2:
+ inc skulls3d_timer
+
+ mov eax, skulls3d_timer
+ cmp eax, 20
+ jbe pass_0
+ cmp eax, 40
+ jbe pass_1
+ sub eax, 41
+ mov passTick, eax
+ mov eax, skulls3d_swapPair2
+ mov curPair, eax
+ jmp setup_pass_pair
+
+pass_0:
+ dec eax
+ mov passTick, eax
+ mov eax, skulls3d_swapPair0
+ mov curPair, eax
+ jmp setup_pass_pair
+
+pass_1:
+ sub eax, 21
+ mov passTick, eax
+ mov eax, skulls3d_swapPair1
+ mov curPair, eax
+
+setup_pass_pair:
+ cmp curPair, 1
+ je pair_1
+ cmp curPair, 2
+ je pair_2
+ mov swapSlotA, 0
+ mov swapSlotB, 1
+ mov xA, 48
+ mov xB, 136
+ jmp calc_swap_coords
+pair_1:
+ mov swapSlotA, 1
+ mov swapSlotB, 2
+ mov xA, 136
+ mov xB, 224
+ jmp calc_swap_coords
+pair_2:
+ mov swapSlotA, 0
+ mov swapSlotB, 2
+ mov xA, 48
+ mov xB, 224
+
+calc_swap_coords:
+ mov eax, xB
+ sub eax, xA
+ mov distX, eax
+
+ mov eax, distX
+ imul eax, passTick
+ xor edx, edx
+ mov ecx, 20
+ div ecx
+ mov dxVal, eax
+
+ mov eax, xA
+ add eax, dxVal
+ mov curXA, eax
+
+ mov eax, xB
+ sub eax, dxVal
+ mov curXB, eax
+
+ ; Arch height: archY = (t * (20 - t)) / 25
+ mov eax, passTick
+ mov ecx, 20
+ sub ecx, eax
+ imul eax, ecx
+ xor edx, edx
+ mov ecx, 25
+ div ecx
+ mov archY, eax
+
+ mov edx, 108
+ sub edx, archY
+ mov curYA, edx ; upper arc
+
+ mov edx, 108
+ add edx, archY
+ mov curYB, edx ; lower arc
+
+ invoke DrawString8x8, 56, 70, OFFSET textSkullsPhase2, 0000FFFFh
+
+ ; Depth sorted rendering: Background cup (upper arc) draws FIRST, Foreground cup (lower arc) draws OVER it!
+ invoke DrawGothicCup, curXA, curYA, 1, 0, 0
+ invoke DrawGothicCup, curXB, curYB, 2, 0, 0
+
+ ; Draw remaining static cup
+ cmp curPair, 1
+ je draw_stat_0
+ cmp curPair, 2
+ je draw_stat_1
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
+ jmp check_pass_finish
+draw_stat_0:
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ jmp check_pass_finish
+draw_stat_1:
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
+
+check_pass_finish:
+ cmp skulls3d_timer, 20
+ je do_pass_swap
+ cmp skulls3d_timer, 40
+ je do_pass_swap
+ cmp skulls3d_timer, 60
+ je do_pass_swap
+ jmp skulls_draw_controls
+
+do_pass_swap:
+ ; Update ballPos if involved in swap
+ mov eax, skulls3d_ballPos
+ cmp eax, swapSlotA
+ jne check_swap_b
+ mov eax, swapSlotB
+ mov skulls3d_ballPos, eax
+ jmp ball_swap_done
+check_swap_b:
+ cmp eax, swapSlotB
+ jne ball_swap_done
+ mov eax, swapSlotA
+ mov skulls3d_ballPos, eax
+
+ball_swap_done:
+ invoke PlaySoundA, OFFSET soundShuffle, 0, 1
+
+ cmp skulls3d_timer, 60
+ jl skulls_draw_controls
+
+ ; Shuffle complete -> Move to Pick phase
+ mov skulls3d_state, 3
+ mov skulls3d_timer, 0
+ jmp skulls_draw_controls
+
+skulls_render_phase3:
+ invoke DrawString8x8, 40, 70, OFFSET textSkullsPhase3, 0000FFFFh
+ invoke DrawGothicCup, 48, 108, 1, 0, 0
+ invoke DrawGothicCup, 136, 108, 2, 0, 0
+ invoke DrawGothicCup, 224, 108, 3, 0, 0
+ jmp skulls_draw_controls
+
+skulls_render_phase4:
+ ; Reveal phase
+ cmp skulls3d_win, 0
+ je skulls_show_loss
+ invoke DrawString8x8, 48, 70, OFFSET textSkullsWin, 0050FF50h
+ invoke FormatNumber, skulls3d_win, OFFSET hudNumBuffer
+ invoke DrawString8x8, 216, 70, OFFSET hudNumBuffer, 0050FF50h
+ jmp skulls_p4_cups
+
+skulls_show_loss:
+ invoke DrawString8x8, 40, 70, OFFSET textSkullsLose, 008080FFh
+
+skulls_p4_cups:
+ ; Draw chosen cup lifted
+ mov eax, 0
+ cmp skulls3d_choice, 0
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 0
+ sete dl
+ invoke DrawGothicCup, 48, 108, 1, eax, edx
+
+ mov eax, 0
+ cmp skulls3d_choice, 1
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 1
+ sete dl
+ invoke DrawGothicCup, 136, 108, 2, eax, edx
+
+ mov eax, 0
+ cmp skulls3d_choice, 2
+ sete al
+ mov edx, 0
+ cmp skulls3d_ballPos, 2
+ sete dl
+ invoke DrawGothicCup, 224, 108, 3, eax, edx
+
+ invoke DrawString8x8, 64, 168, OFFSET textSkullsAgain, 0000D7FFh
+
+skulls_draw_controls:
+ invoke DrawBox, 16, 190, 288, 1, 003A1850h
+
+ mov eax, 0
+ cmp skulls3d_choice, 0
+ sete al
+ invoke DrawKeycap3D, 18, 200, OFFSET textSkullsKey1, eax
+
+ mov eax, 0
+ cmp skulls3d_choice, 1
+ sete al
+ invoke DrawKeycap3D, 82, 200, OFFSET textSkullsKey2, eax
+
+ mov eax, 0
+ cmp skulls3d_choice, 2
+ sete al
+ invoke DrawKeycap3D, 146, 200, OFFSET textSkullsKey3, eax
+
+ invoke DrawKeycap3D, 212, 200, OFFSET textSkullsKeySpace, 1
+ invoke DrawKeycap3D, 274, 200, OFFSET textKeyEsc, 0
+ ret
+RenderSkullsGame ENDP
+
+"""
+
+raw_content = raw_content[:pos_games_start] + new_games_code.encode('cp1251') + raw_content[pos_player_input:]
+print("[OK] Replaced games implementation block with rich 2.5D procedures")
+
+# Write out patched casino3d.inc
+with open(CASINO_INC, 'wb') as f:
+ f.write(raw_content)
+
+print(f"[SUCCESS] casino3d.inc patched successfully! New byte length: {len(raw_content)}")
diff --git a/tools/tests/test_minigames_render.py b/tools/tests/test_minigames_render.py
new file mode 100644
index 0000000..e4fbd5e
--- /dev/null
+++ b/tools/tests/test_minigames_render.py
@@ -0,0 +1,379 @@
+# -*- coding: utf-8 -*-
+import math
+from pathlib import Path
+from PIL import Image
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+OUTPUT_DIR = REPO_ROOT / 'docs' / 'previews_minigames'
+OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
+
+# Parse font8x8.inc
+font_path = REPO_ROOT / 'font8x8.inc'
+font_bytes = bytearray(256 * 8)
+with open(font_path, 'r', encoding='latin1') as f:
+ text = f.read()
+
+import re
+matches = re.findall(r'0([0-9A-Fa-f]{2})h', text)
+for i, m in enumerate(matches[:256*8]):
+ font_bytes[i] = int(m, 16)
+
+SCREEN_W = 640
+SCREEN_H = 480
+
+class RasterBuffer:
+ def __init__(self):
+ self.pixels = [(0, 0, 0)] * (SCREEN_W * SCREEN_H)
+
+ def to_rgb(self, c):
+ # 0x00BBGGRR format
+ r = c & 0xFF
+ g = (c >> 8) & 0xFF
+ b = (c >> 16) & 0xFF
+ return (r, g, b)
+
+ def draw_box(self, x, y, w, h, color):
+ px = x * 2
+ py = y * 2
+ pw = w * 2
+ ph = h * 2
+ rgb = self.to_rgb(color)
+ for row in range(py, min(py + ph, SCREEN_H)):
+ if row < 0:
+ continue
+ base = row * SCREEN_W
+ for col in range(px, min(px + pw, SCREEN_W)):
+ if col < 0:
+ continue
+ self.pixels[base + col] = rgb
+
+ def draw_box_border(self, x, y, w, h, fill_color, border_color, b_width):
+ self.draw_box(x, y, w, h, border_color)
+ self.draw_box(x + b_width, y + b_width, w - b_width * 2, h - b_width * 2, fill_color)
+
+ def draw_pixel(self, x, y, color):
+ self.draw_box(x, y, 1, 1, color)
+
+ def draw_circle(self, cx, cy, radius, color):
+ r_sq = radius * radius
+ for dy in range(-radius, radius + 1):
+ for dx in range(-radius, radius + 1):
+ if dx * dx + dy * dy <= r_sq:
+ self.draw_pixel(cx + dx, cy + dy, color)
+
+ def draw_char(self, x, y, ch, color):
+ if isinstance(ch, str):
+ try:
+ code = ch.encode('cp1251')[0]
+ except:
+ code = ord(ch) & 0xFF
+ else:
+ code = ch & 0xFF
+
+ glyph_offset = code * 8
+ for row in range(8):
+ row_bits = font_bytes[glyph_offset + row]
+ for col in range(8):
+ if row_bits & (0x80 >> col):
+ self.draw_pixel(x + col, y + row, color)
+
+ def draw_string(self, x, y, s, color):
+ cur_x = x
+ for ch in s:
+ self.draw_char(cur_x, y, ch, color)
+ cur_x += 8
+
+ def draw_keycap(self, x, y, text, is_active=False):
+ w = len(text) * 8 + 8
+ h = 13
+ if is_active:
+ border_c = 0x0000FFFF
+ fill_c = 0x001B4B22
+ txt_c = 0x0000FFFF
+ else:
+ border_c = 0x00606870
+ fill_c = 0x001A1C22
+ txt_c = 0x00D0D8E0
+ self.draw_box(x, y + 2, w, h, 0x0008080C) # shadow
+ self.draw_box_border(x, y, w, h, fill_c, border_c, 1)
+ self.draw_string(x + 4, y + 3, text, txt_c)
+ return w + 4
+
+ def draw_chip_stack(self, x, y, count=3):
+ # Draw stack of 3D casino clay chips
+ for i in range(count):
+ cy = y - i * 4
+ self.draw_box(x + 2, cy + 5, 20, 2, 0x00041006) # shadow
+ self.draw_box_border(x, cy, 24, 6, 0x001515B8, 0x00FFFFFF, 1)
+ # white stripe inlays
+ self.draw_box(x + 4, cy + 1, 3, 4, 0x00FFFFFF)
+ self.draw_box(x + 17, cy + 1, 3, 4, 0x00FFFFFF)
+
+ def draw_coin_ellipse(self, cx, cy, rx, ry, outer_color, inner_color, relief=0):
+ if rx <= 0 or ry <= 0:
+ return
+ rx_sq = rx * rx
+ ry_sq = ry * ry
+ rx_ry_sq = rx_sq * ry_sq
+ thresh_sq = (rx_ry_sq * 3) // 4
+ for dy in range(-ry, ry + 1):
+ cur_y = cy + dy
+ for dx in range(-rx, rx + 1):
+ cur_x = cx + dx
+ val = dx * dx * ry_sq + dy * dy * rx_sq
+ if val <= rx_ry_sq:
+ if val >= thresh_sq:
+ self.draw_pixel(cur_x, cur_y, outer_color)
+ else:
+ self.draw_pixel(cur_x, cur_y, inner_color)
+
+ if rx >= 14 and relief == 1:
+ gold_dark = 0x00007A9A
+ # Imperial Eagle
+ self.draw_box(cx - 3, cy - 14, 7, 2, gold_dark)
+ self.draw_box(cx - 4, cy - 12, 9, 2, gold_dark)
+ self.draw_box(cx - 2, cy - 9, 5, 12, gold_dark)
+ self.draw_box(cx - 1, cy - 8, 3, 10, 0x0000D7FF)
+ self.draw_box(cx - 10, cy - 8, 7, 3, gold_dark)
+ self.draw_box(cx - 13, cy - 5, 10, 3, gold_dark)
+ self.draw_box(cx - 11, cy - 2, 7, 3, gold_dark)
+ self.draw_box(cx + 4, cy - 8, 7, 3, gold_dark)
+ self.draw_box(cx + 4, cy - 5, 10, 3, gold_dark)
+ self.draw_box(cx + 5, cy - 2, 7, 3, gold_dark)
+ self.draw_box(cx - 4, cy + 4, 9, 3, gold_dark)
+ self.draw_box(cx - 3, cy + 7, 7, 3, gold_dark)
+ elif rx >= 14 and relief == 2:
+ gold_dark = 0x00007A9A
+ # Roman numeral 'I'
+ self.draw_box(cx - 5, cy - 10, 11, 3, gold_dark)
+ self.draw_box(cx - 2, cy - 7, 5, 14, gold_dark)
+ self.draw_box(cx - 1, cy - 6, 3, 12, 0x0000D7FF)
+ self.draw_box(cx - 6, cy + 7, 13, 3, gold_dark)
+ for w_dy in [-10, -5, 0, 5, 9]:
+ self.draw_box(cx - 12, cy + w_dy, 2, 2, gold_dark)
+ self.draw_box(cx + 11, cy + w_dy, 2, 2, gold_dark)
+
+ def draw_craps_die(self, x, y, size, val):
+ self.draw_box(x + 3, y + size, size, 3, 0x00031408)
+ self.draw_box(x + size, y + 3, 3, size, 0x00031408)
+ body_color = 0x00F0F4FA
+ bevel_color = 0x00C0C6D0
+ border_color = 0x00353540
+ self.draw_box_border(x, y, size, size, body_color, border_color, 1)
+ self.draw_box(x + 1, y + 1, size - 2, 1, 0x00FFFFFF)
+ self.draw_box(x + 1, y + 1, 1, size - 2, 0x00FFFFFF)
+ self.draw_box(x + 1, y + size - 2, size - 2, 1, bevel_color)
+ self.draw_box(x + size - 2, y + 1, 1, size - 2, bevel_color)
+
+ pip_c = 0x001515C8
+ pip_r = 3
+ if val in [1, 3, 5]:
+ self.draw_circle(x + size // 2, y + size // 2, pip_r, pip_c)
+ self.draw_pixel(x + size // 2 - 1, y + size // 2 - 1, 0x00FFFFFF)
+ if val >= 2:
+ self.draw_circle(x + 9, y + 9, pip_r, pip_c)
+ self.draw_circle(x + size - 10, y + size - 10, pip_r, pip_c)
+ if val >= 4:
+ self.draw_circle(x + size - 10, y + 9, pip_r, pip_c)
+ self.draw_circle(x + 9, y + size - 10, pip_r, pip_c)
+ if val == 6:
+ self.draw_circle(x + 9, y + size // 2, pip_r, pip_c)
+ self.draw_circle(x + size - 10, y + size // 2, pip_r, pip_c)
+
+ def draw_gothic_cup(self, x, y, cup_id, is_lifted=False, has_ball=False):
+ lift_y = y - 36 if is_lifted else y
+ if not is_lifted:
+ self.draw_box(x + 6, y + 42, 36, 4, 0x000A0410)
+ else:
+ self.draw_box(x + 10, y + 42, 28, 2, 0x0012061A)
+
+ if is_lifted and has_ball:
+ self.draw_glowing_orb(x + 24, y + 26)
+
+ self.draw_box_border(x + 6, lift_y + 36, 36, 6, 0x00221A30, 0x0000B0D0, 1)
+ self.draw_box(x + 18, lift_y + 28, 12, 8, 0x00332545)
+ self.draw_box(x + 22, lift_y + 28, 4, 8, 0x0055406E)
+ self.draw_box_border(x + 8, lift_y + 8, 32, 20, 0x002D1E40, 0x0000D7FF, 1)
+ self.draw_box(x + 14, lift_y + 10, 6, 16, 0x00503570)
+ self.draw_box(x + 17, lift_y + 10, 3, 16, 0x007850A0)
+ self.draw_box_border(x + 4, lift_y + 2, 40, 7, 0x00150A20, 0x0000FFFF, 1)
+ self.draw_box(x + 12, lift_y + 4, 16, 2, 0x0000FFFF)
+
+ self.draw_box(x + 21, lift_y + 12, 6, 5, 0x0000D7FF)
+ self.draw_box(x + 22, lift_y + 17, 4, 3, 0x0000D7FF)
+ self.draw_pixel(x + 22, lift_y + 14, 0x00150A20)
+ self.draw_pixel(x + 24, lift_y + 14, 0x00150A20)
+ self.draw_string(x + 21, lift_y + 24, str(cup_id), 0x0000FFFF)
+
+ def draw_glowing_orb(self, cx, cy):
+ self.draw_circle(cx, cy, 14, 0x00081E2E)
+ self.draw_circle(cx, cy, 11, 0x00103A58)
+ self.draw_circle(cx, cy, 8, 0x001A588A)
+ self.draw_circle(cx, cy, 6, 0x0000B0E0)
+ self.draw_circle(cx, cy, 4, 0x0000E0FF)
+ self.draw_circle(cx - 1, cy - 1, 2, 0x00A0FFFF)
+ self.draw_pixel(cx - 2, cy - 2, 0x00FFFFFF)
+
+ def save(self, filename):
+ img = Image.new('RGB', (SCREEN_W, SCREEN_H))
+ img.putdata(self.pixels)
+ out_path = OUTPUT_DIR / filename
+ img.save(out_path)
+ print(f"Saved {out_path}")
+
+# ==============================================================================
+# 1. RENDER COIN GAME
+# ==============================================================================
+def render_coin(filename='coin_preview.png'):
+ b = RasterBuffer()
+ b.draw_box(0, 0, 320, 240, 0x00060914) # Mahogany cabinet
+ b.draw_box_border(6, 6, 308, 228, 0x000F1522, 0x001B2E48, 3)
+ for cx in [12, 306]:
+ for cy in [12, 226]:
+ b.draw_box(cx, cy, 3, 3, 0x0000D7FF)
+ b.draw_pixel(cx + 1, cy + 1, 0x00FFFFFF)
+
+ b.draw_box_border(20, 12, 280, 26, 0x000A101C, 0x0000D7FF, 2)
+ b.draw_string(44, 21, '*** З О Л О Т � Я М О � Е Т � ***', 0x0000FFFF)
+ for bx in range(24, 296, 12):
+ b.draw_pixel(bx, 13, 0x0000FFFF)
+ b.draw_pixel(bx, 36, 0x0000D7FF)
+
+ # Status meters
+ b.draw_box_border(20, 44, 110, 18, 0x000A1A0E, 0x001A4B22, 1)
+ b.draw_string(24, 49, 'Б��К: $12500', 0x0050FF50)
+ b.draw_box_border(190, 44, 110, 18, 0x001A100A, 0x003A2814, 1)
+ b.draw_string(194, 49, 'СТ�ВК�: $50 [W/S]', 0x0000D7FF)
+
+ # Center Dish
+ b.draw_circle(160, 110, 46, 0x000090C0)
+ b.draw_circle(160, 110, 44, 0x0000D7FF)
+ b.draw_circle(160, 110, 41, 0x00180824)
+
+ # Coin at rest (Heads)
+ b.draw_coin_ellipse(160, 110, 26, 26, 0x0000A0D0, 0x0000D7FF, relief=1)
+
+ # Left: [1] ОРЕЛ (Active)
+ b.draw_box_border(20, 72, 76, 52, 0x001C3822, 0x0000FFFF, 2)
+ b.draw_string(28, 78, '[1] ОР�Л', 0x0000FFFF)
+ b.draw_coin_ellipse(58, 106, 12, 12, 0x0000A0D0, 0x0000D7FF, relief=1)
+
+ # Right: [2] РЕШК�
+ b.draw_box_border(224, 72, 76, 52, 0x00121620, 0x00404855, 1)
+ b.draw_string(232, 78, '[2] РЕШК�', 0x0090A0B0)
+ b.draw_coin_ellipse(262, 106, 12, 12, 0x000080A0, 0x0000B0D0, relief=2)
+
+ # Streak
+ b.draw_box_border(44, 154, 232, 20, 0x000E141E, 0x0000D7FF, 1)
+ b.draw_string(52, 160, 'СЕРИЯ: [X] [X] [X] [ ] [ ] x4!', 0x0000FFFF)
+
+ # Keycaps
+ b.draw_box(16, 194, 288, 1, 0x001E2E40)
+ b.draw_keycap(22, 202, '1:ОР�Л', is_active=True)
+ b.draw_keycap(88, 202, '2:РЕШК�')
+ b.draw_keycap(160, 202, 'ПРОБЕЛ:БРОСОК', is_active=True)
+ b.draw_keycap(274, 202, 'ESC')
+ b.save(filename)
+
+# ==============================================================================
+# 2. RENDER DICE GAME
+# ==============================================================================
+def render_dice(filename='dice_preview.png'):
+ b = RasterBuffer()
+ # Emerald felt
+ b.draw_box(0, 0, 320, 240, 0x00082E12)
+ # Oak wood border & leather bumper
+ b.draw_box_border(4, 4, 312, 232, 0x00093816, 0x00122244, 3)
+ b.draw_box_border(8, 8, 304, 224, 0x000A3F19, 0x0005140A, 2)
+
+ # Pyramidal backboard rubber diamond texture along top
+ for bx in range(12, 308, 12):
+ b.draw_box(bx + 4, 11, 4, 4, 0x00041006)
+ b.draw_pixel(bx + 5, 12, 0x000F4820)
+
+ # Header
+ b.draw_string(48, 20, '==== G R A N D C R A P S T A B L E ====', 0x0000D7FF)
+ b.draw_string(72, 32, '[ PASS LINE ] [ ANY CRAPS ]', 0x0000B0A0)
+
+ # Status
+ b.draw_box_border(16, 46, 100, 16, 0x0006240E, 0x000E5022, 1)
+ b.draw_string(20, 50, 'Б��К: $12500', 0x0050FF50)
+ b.draw_box_border(204, 46, 100, 16, 0x0006240E, 0x000E5022, 1)
+ b.draw_string(208, 50, 'СТ�ВК�: $50 [W/S]', 0x0000D7FF)
+
+ # Betting Zones
+ # Left: [1] ЧЕТ (Active with chip stack!)
+ b.draw_box_border(16, 68, 88, 54, 0x000B441D, 0x0000FFFF, 2)
+ b.draw_string(24, 74, '[1] Ч�Т', 0x0000FFFF)
+ b.draw_string(20, 86, '2 4 6 8 10 12', 0x0000D7FF)
+ b.draw_chip_stack(48, 110, count=4)
+
+ # Right: [2] �ЕЧЕТ
+ b.draw_box_border(216, 68, 88, 54, 0x00073014, 0x001A602A, 1)
+ b.draw_string(224, 74, '[2] �ЕЧЕТ', 0x0080B090)
+ b.draw_string(222, 86, '3 5 7 9 11', 0x00508060)
+
+ # Center Dice Field
+ b.draw_box_border(112, 68, 96, 54, 0x00072E13, 0x00145826, 1)
+ # Draw Two Precision Dice
+ b.draw_craps_die(120, 76, 36, 4)
+ b.draw_craps_die(164, 76, 36, 6)
+
+ # Score Plaque
+ b.draw_box_border(36, 130, 248, 30, 0x00051D0C, 0x0000D7FF, 1)
+ b.draw_string(44, 135, 'СУММ�: 4 + 6 = 10 (Ч�Т)', 0x0000FFFF)
+ b.draw_string(68, 147, 'ВЫИГРЫШ: +$100! (x2)', 0x0050FF50)
+
+ # Keycaps
+ b.draw_box(16, 188, 288, 1, 0x00104820)
+ b.draw_keycap(20, 198, '1:Ч�Т', is_active=True)
+ b.draw_keycap(82, 198, '2:�ЕЧЕТ')
+ b.draw_keycap(160, 198, 'ПРОБЕЛ:БРОСОК', is_active=True)
+ b.draw_keycap(274, 198, 'ESC')
+ b.save(filename)
+
+# ==============================================================================
+# 3. RENDER SKULLS GAME
+# ==============================================================================
+def render_skulls(filename='skulls_preview.png'):
+ b = RasterBuffer()
+ # Dark gothic table with purple velvet
+ b.draw_box(0, 0, 320, 240, 0x000D0612)
+ b.draw_box_border(4, 4, 312, 232, 0x00160B20, 0x00321845, 3)
+ b.draw_box_border(8, 8, 304, 224, 0x00220F30, 0x0000D7FF, 1)
+
+ # Header
+ b.draw_box_border(24, 12, 272, 24, 0x0012081A, 0x0000D7FF, 2)
+ b.draw_string(48, 20, '+ + + � � П � РС Т К И С К В Е Р� Ы + + +', 0x0000FFFF)
+
+ # Status
+ b.draw_box_border(20, 42, 110, 16, 0x000D0616, 0x00401858, 1)
+ b.draw_string(24, 46, 'Б��К: $12500', 0x0050FF50)
+ b.draw_box_border(190, 42, 110, 16, 0x000D0616, 0x00401858, 1)
+ b.draw_string(194, 46, 'СТ�ВК�: $50 [W/S]', 0x0000D7FF)
+
+ # Table Runner
+ b.draw_box_border(16, 66, 288, 114, 0x001A0B25, 0x00452060, 1)
+
+ # Narrative banner
+ b.draw_string(40, 74, 'СЛЕДИ З� ЗОЛОТЫМ ОКОМ СУДЬБЫ...', 0x0000FFFF)
+
+ # Draw 3 Gothic Cups (Cup 2 is lifted revealing glowing orb!)
+ b.draw_gothic_cup(48, 100, 1, is_lifted=False)
+ b.draw_gothic_cup(136, 100, 2, is_lifted=True, has_ball=True)
+ b.draw_gothic_cup(224, 100, 3, is_lifted=False)
+
+ # Keycaps
+ b.draw_box(16, 190, 288, 1, 0x003A1850)
+ b.draw_keycap(18, 200, '1:КУБОК')
+ b.draw_keycap(82, 200, '2:КУБОК', is_active=True)
+ b.draw_keycap(146, 200, '3:КУБОК')
+ b.draw_keycap(212, 200, 'ПРОБЕЛ', is_active=True)
+ b.draw_keycap(274, 200, 'ESC')
+ b.save(filename)
+
+if __name__ == '__main__':
+ render_coin()
+ render_dice()
+ render_skulls()
+ print("All 3 minigames previews rendered successfully!")