KAZIK/tools/generators/generate_npc_mind.py
Artvasil09 881a0061dc Add native NPC visitors with contracts, Clover slots, Pyramid, and party table scenes
Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
2026-10-13 09:47:33 +03:00

1289 lines
45 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
Generator for NPC Mind AI system:
- src_graphics/npc_mind_data.inc
- src_graphics/npc_mind.inc
Both written in CP1251 encoding.
"""
import os
data_inc_path = r"src_graphics\npc_mind_data.inc"
code_inc_path = r"src_graphics\npc_mind.inc"
DATA_CONTENT = r'''; ==============================================================================
; NPC MIND DATA - MASM32 / CP1251
; Multi-agent AI: Open-space Navigation, Personalities & Multi-seat Poker
; ==============================================================================
NPCAI_ARCHETYPE_SHARK equ 0 ; Акула (Tight-Aggressive)
NPCAI_ARCHETYPE_MANIAC equ 1 ; Лудоман (Loose-Aggressive)
NPCAI_ARCHETYPE_ROCK equ 2 ; Скала (Tight-Passive)
NPCAI_ARCHETYPE_CALLING_STATION equ 3 ; Автоответчик (Loose-Passive)
NPCAI_ARCHETYPE_WHALE equ 4 ; Мажор (Высокий банкролл, хаос)
NPCAI_ARCHETYPE_ROOKIE equ 5 ; Новичок (Ошибки, неуверенность)
NPCAI_STATE_IDLE equ 0 ; Стоит, осматривается
NPCAI_STATE_WALK equ 1 ; Идет по вейпоинтам
NPCAI_STATE_GAMBLE equ 2 ; Играет за столом
NPCAI_STATE_BAR equ 3 ; У бара
NPCAI_STATE_ATM equ 4 ; У банкомата
NPCAI_STATE_STAGE equ 5 ; Смотрит шоу
MPK_SEAT_EMPTY equ 0
MPK_SEAT_ACTIVE equ 1
MPK_SEAT_FOLDED equ 2
MPK_SEAT_ALLIN equ 3
MPK_SEAT_BUST equ 4
MPK_ACT_NONE equ 0
MPK_ACT_FOLD equ 1
MPK_ACT_CHECK equ 2
MPK_ACT_CALL equ 3
MPK_ACT_RAISE equ 4
MPK_ACT_ALLIN equ 5
MPK_STAGE_PREFLOP equ 0
MPK_STAGE_FLOP equ 1
MPK_STAGE_TURN equ 2
MPK_STAGE_RIVER equ 3
MPK_STAGE_SHOWDOWN equ 4
NPCAI_MAX_NPCS equ 6
MPK_MAX_SEATS equ 4
NPCAI_WAYPOINT_COUNT equ 16
; ------------------------------------------------------------------------------
; Структуры данных
; ------------------------------------------------------------------------------
NpcPersonality STRUCT
namePtr dd ?
titlePtr dd ?
archetypeId dd ?
baseAggression dd ? ; 0..100
baseTightness dd ? ; 0..100
baseBluffRate dd ? ; 0..100
baseCourage dd ? ; 0..100
tiltSens dd ? ; 0..100
defaultBank dd ?
phraseWinPtr dd ?
phraseLosePtr dd ?
phraseBluffPtr dd ?
phraseBustPtr dd ?
NpcPersonality ENDS
NpcMindEntity STRUCT
posX dd ? ; 16.16 FP
posY dd ? ; 16.16 FP
dirX dd ? ; 16.16 FP
dirY dd ? ; 16.16 FP
speed dd ? ; 16.16 FP
state dd ? ; NPCAI_STATE_*
stateTimer dd ?
currentNode dd ?
targetNode dd ?
profileId dd ?
currentChips dd ?
currentTilt dd ? ; 0..100
thoughtPtr dd ?
reactionPtr dd ?
NpcMindEntity ENDS
MpkSeat STRUCT
isHuman dd ? ; 1=Player, 0=NPC
profileId dd ? ; Индекс NpcPersonality (0..5)
chips dd ?
roundBet dd ?
totalBet dd ?
card1 dd ?
card2 dd ?
evalScore dd ?
seatStatus dd ? ; MPK_SEAT_*
lastAction dd ? ; MPK_ACT_*
lastRaiseAmount dd ?
thoughtPtr dd ?
reactionPtr dd ?
MpkSeat ENDS
MultiPokerTable STRUCT
seats MpkSeat MPK_MAX_SEATS dup(<>)
commCards dd 5 dup(?)
commCount dd ?
mainPot dd ?
currentHighBet dd ?
minRaise dd ?
dealerButton dd ?
currentTurn dd ?
activeSeats dd ?
smallBlind dd ?
bigBlind dd ?
stage dd ?
MultiPokerTable ENDS
NpcWaypoint STRUCT
posX dd ? ; 16.16 FP
posY dd ? ; 16.16 FP
neighbor0 dd ?
neighbor1 dd ?
neighbor2 dd ?
neighbor3 dd ?
zoneType dd ?
NpcWaypoint ENDS
; ------------------------------------------------------------------------------
; Строковые константы и описания (CP1251)
; ------------------------------------------------------------------------------
.data
; Имена и титулы 6 уникальных персонажей
npcName0 db "Виктор «Акула»",0
npcTitle0 db "Профессионал (Tight-Aggressive)",0
npcName1 db "Сергей «Лудоман»",0
npcTitle1 db "Маньяк (Loose-Aggressive, ТИЛЬТ)",0
npcName2 db "Дядя Толя «Скала»",0
npcTitle2 db "Ветеран (Tight-Passive)",0
npcName3 db "Степан «Автоответчик»",0
npcTitle3 db "Любитель (Calling Station)",0
npcName4 db "Мажор Артём",0
npcTitle4 db "Хайроллер (Whale, Безумные ставки)",0
npcName5 db "Влад «Новичок»",0
npcTitle5 db "Студент (Rookie, Неопытный)",0
; Фразы победы
npcWin0 db "«Холодный расчет и никакой магии. Банк мой.»",0
npcWin1 db "«ХА-ХА! Кто тут король казино?! Все фишки сюда!»",0
npcWin2 db "«Тише едешь — дальше будешь, молодежь.»",0
npcWin3 db "«Опа, закрылся стрит! А вы не верили!»",0
npcWin4 db "«Официант, всем шампанского за мой счет!»",0
npcWin5 db "«Ого! Неужели я правда победил?! Вот это да!»",0
; Фразы поражения / бэдбита
npcLose0 db "«Бывает. Математическая дисперсия на дистанции вернет всё.»",0
npcLose1 db "«ДА КАК ТАК-ТО?! Опять ривером переехали, сука!»",0
npcLose2 db "«Знал ведь, что надо было сбрасывать еще на флопе...»",0
npcLose3 db "«Эх, чутка не хватило до флеша... в следующий раз повезет.»",0
npcLose4 db "«Пф-ф, копейки. Сейчас сниму еще пачку в банкомате.»",0
npcLose5 db "«Я так и знал... Надо было внимательнее читать правила.»",0
; Фразы блефа / рейза
npcBluff0 db "«Твоя рука слабее. Сэкономь фишки и пасуй.»",0
npcBluff1 db "«Слабаки! Вам не по карману этот банк, двигайте пас!»",0
npcBluff2 db "«У меня отличная карта. Не советую лезть под колеса.»",0
npcBluff3 db "«А я просто хочу посмотреть следующую карту!»",0
npcBluff4 db "«Вы ставите копейки! Давайте играть по-взрослому!»",0
npcBluff5 db "«Э-э... кажется, я поднимаю ставку. Вы со мной?»",0
; Фразы банкротства
npcBust0 db "«Лимит на сегодня исчерпан. Пора делать паузу.»",0
npcBust1 db "«Всё в ноль! Бегу к банкомату, я обязан отыграться!»",0
npcBust2 db "«Пенсия кончилась... Пойду подышу на свежем воздухе.»",0
npcBust3 db "«Ой, фишки кончились... Пойду в бар за пивом.»",0
npcBust4 db "«Опять обнулился? Ерунда, сейчас сниму еще $50,000.»",0
npcBust5 db "«Кажется, на метро мне уже не хватает...»",0
; Мысли в свободном перемещении по казино
thWalkWander db "Осматривает игровой зал в поисках интересной игры",0
thWalkTable db "Направляется к покерному столу испытать удачу",0
thWalkBar db "Решил заглянуть в бар за напитком",0
thWalkAtm db "Закончились фишки, направляется к банкомату",0
thWalkStage db "Подходит к сцене посмотреть на танцовщиц",0
thIdleBored db "Стоит у колонны, подсчитывает оставшиеся деньги",0
; Мысли за покерным столом
thEvalStrong db "Чувствует натс! Готовит ловушку для оппонентов.",0
thEvalGood db "Рука сильная, отличные шансы забрать этот банк.",0
thEvalDraw db "Считает ауты: шансы банка выгодны для колла.",0
thEvalTrash db "Карты — мусор. Смысла рисковать фишками нет.",0
thEvalTilted db "[ТИЛЬТ!] Злится после проигрыша и идет на таран!",0
thEvalBluffing db "БЛЕФ! Изображает монстра и давит ставкой!",0
; Описания действий
thActFold db "Сбрасывает карты в пас",0
thActCheck db "Чекает, пропуская ход",0
thActCall db "Коллирует (уравнивает) текущую ставку",0
thActRaise db "Повышает ставку (рейз!)",0
thActAllIn db "Двигает все фишки вперед — ВА-БАНК (ОЛЛ-ИН)!",0
; Названия улиц покера
strStagePreflop db "Префлоп (Карманные карты)",0
strStageFlop db "Флоп (3 карты стола)",0
strStageTurn db "Терн (4 карты стола)",0
strStageRiver db "Ривер (5 карт стола)",0
strStageShow db "Вскрытие карт (Showdown)",0
; Тексты интерфейса мульти-покера
mpkTitleStr db "=== ТЕХАССКИЙ ХОЛДЕМ: СТОЛ НА 4 ИГРОКА ===",13,10,0
mpkPotStr db "Банк стола: $",0
mpkStreetStr db " | Раунд: ",0
mpkCommCardsStr db "Общие карты на столе: ",0
mpkPromptStr db "Ваш ход: [1] Чек/Колл | [2] Рейз | [0] Пас -> ",0
mpkWaitNextStr db 13,10,"[Нажмите любую клавишу для следующей раздачи, 0 для выхода]...",0
mpkChipsPrefix db " Стек: $",0
mpkThoughtPrefix db " [Мысли]: ",0
mpkGameOverStr db 13,10,"Раздача окончена. Победитель: ",0
mpkWinChipsStr db " выиграл банк $",0
ALIGN 4
; База профилей 6 уникальных личностей
casinoNpcProfiles NpcPersonality <OFFSET npcName0, OFFSET npcTitle0, NPCAI_ARCHETYPE_SHARK, 75, 85, 20, 85, 15, 5000, OFFSET npcWin0, OFFSET npcLose0, OFFSET npcBluff0, OFFSET npcBust0>
NpcPersonality <OFFSET npcName1, OFFSET npcTitle1, NPCAI_ARCHETYPE_MANIAC, 95, 20, 80, 90, 85, 3500, OFFSET npcWin1, OFFSET npcLose1, OFFSET npcBluff1, OFFSET npcBust1>
NpcPersonality <OFFSET npcName2, OFFSET npcTitle2, NPCAI_ARCHETYPE_ROCK, 25, 90, 5, 30, 20, 4000, OFFSET npcWin2, OFFSET npcLose2, OFFSET npcBluff2, OFFSET npcBust2>
NpcPersonality <OFFSET npcName3, OFFSET npcTitle3, NPCAI_ARCHETYPE_CALLING_STATION, 15, 30, 10, 75, 40, 3000, OFFSET npcWin3, OFFSET npcLose3, OFFSET npcBluff3, OFFSET npcBust3>
NpcPersonality <OFFSET npcName4, OFFSET npcTitle4, NPCAI_ARCHETYPE_WHALE, 70, 45, 55, 70, 50, 10000, OFFSET npcWin4, OFFSET npcLose4, OFFSET npcBluff4, OFFSET npcBust4>
NpcPersonality <OFFSET npcName5, OFFSET npcTitle5, NPCAI_ARCHETYPE_ROOKIE, 40, 50, 25, 40, 60, 2000, OFFSET npcWin5, OFFSET npcLose5, OFFSET npcBluff5, OFFSET npcBust5>
; Граф вейпоинтов казино (координаты 16.16 FP и связи)
casinoWaypoints3D NpcWaypoint <16*FP_1, 28*FP_1, 1, 4, 8, 0, 0> ; 0: Вход в казино
NpcWaypoint <16*FP_1, 20*FP_1, 0, 2, 4, 8, 0> ; 1: Центр холла (юг)
NpcWaypoint <16*FP_1, 14*FP_1, 1, 3, 10, 12, 0> ; 2: Центр холла (север)
NpcWaypoint <16*FP_1, 6*FP_1, 2, 10, 12, 3, 7> ; 3: Перед сценой танцовщиц
NpcWaypoint < 8*FP_1, 20*FP_1, 0, 1, 5, 7, 0> ; 4: Западный коридор
NpcWaypoint < 6*FP_1, 14*FP_1, 4, 6, 2, 5, 4> ; 5: Автоматы 777
NpcWaypoint < 6*FP_1, 9*FP_1, 5, 10, 2, 6, 4> ; 6: Автоматы CloverPit
NpcWaypoint < 4*FP_1, 25*FP_1, 4, 0, 7, 7, 5> ; 7: Барная стойка
NpcWaypoint <24*FP_1, 20*FP_1, 0, 1, 9, 14, 0> ; 8: Восточный коридор
NpcWaypoint <26*FP_1, 25*FP_1, 8, 0, 9, 9, 0> ; 9: Зона отдыха VIP
NpcWaypoint <12*FP_1, 9*FP_1, 2, 3, 6, 11, 2> ; 10: Стол Блэкджека
NpcWaypoint <16*FP_1, 11*FP_1, 2, 10, 12, 11, 3> ; 11: Стол Рулетки
NpcWaypoint <20*FP_1, 9*FP_1, 2, 3, 11, 13, 1> ; 12: Стол Покера (Холдем)
NpcWaypoint <24*FP_1, 9*FP_1, 12, 8, 14, 13, 1> ; 13: Стол Дурака
NpcWaypoint <26*FP_1, 15*FP_1, 8, 13, 14, 14, 6> ; 14: Банкомат (ATM)
NpcWaypoint <10*FP_1, 15*FP_1, 2, 4, 5, 10, 0> ; 15: Зона костей/черепов
; 6 активных персонажей, гуляющих по залу казино
casinoNpcs3D NpcMindEntity NPCAI_MAX_NPCS dup(<>)
; Экземпляр стола мульти-покера
multiPokerTable MultiPokerTable <>
; Колода и рабочий буфер для мульти-покера
multiDeck dd 52 dup(0)
multiDeckIndex dd 0
multiEvalHand dd 7 dup(0)
multiActionWait dd 0
'''.strip()
CODE_CONTENT = r'''; ==============================================================================
; NPC MIND CODE - MASM32 / CP1251
; Logic, Pathfinding Steering, Personality Thinking & Multi-Seat Poker Engine
; ==============================================================================
.code
; ------------------------------------------------------------------------------
; InitCasinoNpcMind: Инициализация всех 6 персонажей и их стартовых точек
; ------------------------------------------------------------------------------
InitCasinoNpcMind PROC uses ebx esi edi
xor esi, esi
npc_init_loop:
mov eax, esi
imul eax, SIZEOF NpcMindEntity
lea edi, casinoNpcs3D[eax]
; Привязка к профилю (0..5)
mov [edi].NpcMindEntity.profileId, esi
; Задаем стартовые координаты у разных вейпоинтов
; NPC 0 (Акула) -> у стола покера (узел 12)
; NPC 1 (Лудоман) -> у автоматов (узел 5)
; NPC 2 (Скала) -> у блэкджека (узел 10)
; NPC 3 (Автоответчик) -> у бара (узел 7)
; NPC 4 (Мажор) -> у сцены (узел 3)
; NPC 5 (Новичок) -> у входа (узел 0)
mov eax, esi
cmp eax, 0
jne @F
mov ebx, 12
jmp set_start_node
@@:
cmp eax, 1
jne @F
mov ebx, 5
jmp set_start_node
@@:
cmp eax, 2
jne @F
mov ebx, 10
jmp set_start_node
@@:
cmp eax, 3
jne @F
mov ebx, 7
jmp set_start_node
@@:
cmp eax, 4
jne @F
mov ebx, 3
jmp set_start_node
@@:
mov ebx, 0
set_start_node:
mov [edi].NpcMindEntity.currentNode, ebx
mov [edi].NpcMindEntity.targetNode, ebx
imul ebx, SIZEOF NpcWaypoint
mov eax, casinoWaypoints3D[ebx].posX
mov [edi].NpcMindEntity.posX, eax
mov eax, casinoWaypoints3D[ebx].posY
mov [edi].NpcMindEntity.posY, eax
mov [edi].NpcMindEntity.speed, (FP_1 / 18) ; Скорость ходьбы
mov [edi].NpcMindEntity.state, NPCAI_STATE_IDLE
mov [edi].NpcMindEntity.currentTilt, 0
; Стартовые фишки из профиля
mov eax, esi
imul eax, SIZEOF NpcPersonality
mov ebx, casinoNpcProfiles[eax].defaultBank
mov [edi].NpcMindEntity.currentChips, ebx
; Стартовый таймер и мысли
invoke RandomRange, 60
add eax, 40
mov [edi].NpcMindEntity.stateTimer, eax
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thIdleBored
mov [edi].NpcMindEntity.reactionPtr, 0
inc esi
cmp esi, NPCAI_MAX_NPCS
jb npc_init_loop
ret
InitCasinoNpcMind ENDP
; ------------------------------------------------------------------------------
; UpdateCasinoNpcMind: Покадровое обновление поведения и перемещения NPC в зале
; ------------------------------------------------------------------------------
UpdateCasinoNpcMind PROC uses ebx esi edi
local nextX:DWORD, nextY:DWORD, dxFP:DWORD, dyFP:DWORD
xor esi, esi
npc_update_loop:
mov eax, esi
imul eax, SIZEOF NpcMindEntity
lea edi, casinoNpcs3D[eax]
; 1. Плавное остывание тильта со временем
mov eax, [edi].NpcMindEntity.currentTilt
test eax, eax
jz @F
dec [edi].NpcMindEntity.currentTilt
@@:
; 2. Машина состояний
mov eax, [edi].NpcMindEntity.state
cmp eax, NPCAI_STATE_IDLE
je npc_handle_idle
cmp eax, NPCAI_STATE_WALK
je npc_handle_walk
jmp npc_handle_activity
npc_handle_idle:
dec [edi].NpcMindEntity.stateTimer
jnz npc_next_entity
; Время ожидания истекло -> выбираем новую цель
; Если фишек мало (< 500) -> идем к банкомату
cmp [edi].NpcMindEntity.currentChips, 500
jge @F
mov [edi].NpcMindEntity.targetNode, 14
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkAtm
jmp start_walking
@@:
; Иначе выбираем случайный узел назначения
invoke RandomRange, NPCAI_WAYPOINT_COUNT
mov [edi].NpcMindEntity.targetNode, eax
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkWander
start_walking:
mov [edi].NpcMindEntity.state, NPCAI_STATE_WALK
jmp npc_next_entity
npc_handle_activity:
dec [edi].NpcMindEntity.stateTimer
jnz npc_next_entity
; Активность закончена (поиграл, выпил, снял деньги)
mov [edi].NpcMindEntity.state, NPCAI_STATE_IDLE
invoke RandomRange, 50
add eax, 40
mov [edi].NpcMindEntity.stateTimer, eax
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thIdleBored
jmp npc_next_entity
npc_handle_walk:
; Расчет вектора к целевому вейпоинту
mov ebx, [edi].NpcMindEntity.targetNode
imul ebx, SIZEOF NpcWaypoint
mov eax, casinoWaypoints3D[ebx].posX
sub eax, [edi].NpcMindEntity.posX
mov dxFP, eax
mov eax, casinoWaypoints3D[ebx].posY
sub eax, [edi].NpcMindEntity.posY
mov dyFP, eax
; Проверка близости к целевой точке (|dx| < FP_HALF и |dy| < FP_HALF)
mov eax, dxFP
test eax, eax
jns @F
neg eax
@@:
cmp eax, (FP_HALF / 2)
jae move_towards_target
mov eax, dyFP
test eax, eax
jns @F
neg eax
@@:
cmp eax, (FP_HALF / 2)
jae move_towards_target
; Прибыли на вейпоинт!
mov eax, [edi].NpcMindEntity.targetNode
mov [edi].NpcMindEntity.currentNode, eax
; Проверяем тип зоны этого вейпоинта
mov ebx, eax
imul ebx, SIZEOF NpcWaypoint
mov eax, casinoWaypoints3D[ebx].zoneType
cmp eax, 1 ; Покер
je enter_gamble
cmp eax, 2 ; Блэкджек
je enter_gamble
cmp eax, 5 ; Бар
je enter_bar
cmp eax, 6 ; Банкомат
je enter_atm
cmp eax, 7 ; Сцена
je enter_stage
; Обычный коридор -> пауза и выбор следующего
mov [edi].NpcMindEntity.state, NPCAI_STATE_IDLE
invoke RandomRange, 40
add eax, 30
mov [edi].NpcMindEntity.stateTimer, eax
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thIdleBored
jmp npc_next_entity
enter_gamble:
mov [edi].NpcMindEntity.state, NPCAI_STATE_GAMBLE
mov [edi].NpcMindEntity.stateTimer, 120
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkTable
jmp npc_next_entity
enter_bar:
mov [edi].NpcMindEntity.state, NPCAI_STATE_BAR
mov [edi].NpcMindEntity.stateTimer, 100
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkBar
jmp npc_next_entity
enter_atm:
mov [edi].NpcMindEntity.state, NPCAI_STATE_ATM
mov [edi].NpcMindEntity.stateTimer, 80
add [edi].NpcMindEntity.currentChips, 3000
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkAtm
jmp npc_next_entity
enter_stage:
mov [edi].NpcMindEntity.state, NPCAI_STATE_STAGE
mov [edi].NpcMindEntity.stateTimer, 150
mov [edi].NpcMindEntity.thoughtPtr, OFFSET thWalkStage
jmp npc_next_entity
move_towards_target:
; Делаем шаг в направлении цели
mov eax, [edi].NpcMindEntity.posX
cmp dxFP, 0
jle move_left
add eax, [edi].NpcMindEntity.speed
jmp check_step_x
move_left:
sub eax, [edi].NpcMindEntity.speed
check_step_x:
mov nextX, eax
mov eax, [edi].NpcMindEntity.posY
cmp dyFP, 0
jle move_up
add eax, [edi].NpcMindEntity.speed
jmp check_step_y
move_up:
sub eax, [edi].NpcMindEntity.speed
check_step_y:
mov nextY, eax
; Проверка коллизий через CanWalk3D
invoke CanWalk3D, nextX, [edi].NpcMindEntity.posY
test eax, eax
jz @F
mov eax, nextX
mov [edi].NpcMindEntity.posX, eax
@@:
invoke CanWalk3D, [edi].NpcMindEntity.posX, nextY
test eax, eax
jz npc_next_entity
mov eax, nextY
mov [edi].NpcMindEntity.posY, eax
npc_next_entity:
inc esi
cmp esi, NPCAI_MAX_NPCS
jb npc_update_loop
ret
UpdateCasinoNpcMind ENDP
; ------------------------------------------------------------------------------
; MultiPoker_InitTable: Подготовка стола на 4 места (Игрок + 3 бота)
; ------------------------------------------------------------------------------
MultiPoker_InitTable PROC uses ebx esi edi
lea edi, multiPokerTable
mov [edi].MultiPokerTable.commCount, 0
mov [edi].MultiPokerTable.mainPot, 0
mov [edi].MultiPokerTable.currentHighBet, 0
mov [edi].MultiPokerTable.minRaise, 100
mov [edi].MultiPokerTable.smallBlind, 50
mov [edi].MultiPokerTable.bigBlind, 100
mov [edi].MultiPokerTable.dealerButton, 0
mov [edi].MultiPokerTable.activeSeats, 4
mov [edi].MultiPokerTable.stage, MPK_STAGE_PREFLOP
; Место 0: Игрок
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].isHuman, 1
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].profileId, 0
mov eax, bankroll
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].chips, eax
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].seatStatus, MPK_SEAT_ACTIVE
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].lastAction, MPK_ACT_NONE
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].thoughtPtr, 0
mov [edi].MultiPokerTable.seats[0*SIZEOF MpkSeat].reactionPtr, 0
; Место 1: Виктор «Акула» (профиль 0)
mov [edi].MultiPokerTable.seats[1*SIZEOF MpkSeat].isHuman, 0
mov [edi].MultiPokerTable.seats[1*SIZEOF MpkSeat].profileId, 0
mov eax, casinoNpcProfiles[0*SIZEOF NpcPersonality].defaultBank
mov [edi].MultiPokerTable.seats[1*SIZEOF MpkSeat].chips, eax
mov [edi].MultiPokerTable.seats[1*SIZEOF MpkSeat].seatStatus, MPK_SEAT_ACTIVE
mov [edi].MultiPokerTable.seats[1*SIZEOF MpkSeat].lastAction, MPK_ACT_NONE
; Место 2: Сергей «Лудоман» (профиль 1)
mov [edi].MultiPokerTable.seats[2*SIZEOF MpkSeat].isHuman, 0
mov [edi].MultiPokerTable.seats[2*SIZEOF MpkSeat].profileId, 1
mov eax, casinoNpcProfiles[1*SIZEOF NpcPersonality].defaultBank
mov [edi].MultiPokerTable.seats[2*SIZEOF MpkSeat].chips, eax
mov [edi].MultiPokerTable.seats[2*SIZEOF MpkSeat].seatStatus, MPK_SEAT_ACTIVE
mov [edi].MultiPokerTable.seats[2*SIZEOF MpkSeat].lastAction, MPK_ACT_NONE
; Место 3: Дядя Толя «Скала» (профиль 2)
mov [edi].MultiPokerTable.seats[3*SIZEOF MpkSeat].isHuman, 0
mov [edi].MultiPokerTable.seats[3*SIZEOF MpkSeat].profileId, 2
mov eax, casinoNpcProfiles[2*SIZEOF NpcPersonality].defaultBank
mov [edi].MultiPokerTable.seats[3*SIZEOF MpkSeat].chips, eax
mov [edi].MultiPokerTable.seats[3*SIZEOF MpkSeat].seatStatus, MPK_SEAT_ACTIVE
mov [edi].MultiPokerTable.seats[3*SIZEOF MpkSeat].lastAction, MPK_ACT_NONE
ret
MultiPoker_InitTable ENDP
; ------------------------------------------------------------------------------
; MultiPoker_ShuffleDeck: Заполнение и тасовка колоды из 52 карт
; ------------------------------------------------------------------------------
MultiPoker_ShuffleDeck PROC uses ebx esi edi
; Заполняем 0..51
xor ecx, ecx
fill_deck:
mov multiDeck[ecx*4], ecx
inc ecx
cmp ecx, 52
jb fill_deck
; Тасовка Фишера-Йетса
mov ecx, 51
shuffle_loop:
push ecx
inc ecx
invoke RandomRange, ecx
pop ecx
; Обмен multiDeck[ecx*4] и multiDeck[eax*4]
mov edx, multiDeck[ecx*4]
mov ebx, multiDeck[eax*4]
mov multiDeck[ecx*4], ebx
mov multiDeck[eax*4], edx
dec ecx
jnz shuffle_loop
mov multiDeckIndex, 0
ret
MultiPoker_ShuffleDeck ENDP
; ------------------------------------------------------------------------------
; MultiPoker_StartHand: Раздача карт и сбор блайндов
; ------------------------------------------------------------------------------
MultiPoker_StartHand PROC uses ebx esi edi
lea edi, multiPokerTable
call MultiPoker_ShuffleDeck
mov [edi].MultiPokerTable.commCount, 0
mov [edi].MultiPokerTable.mainPot, 0
mov [edi].MultiPokerTable.currentHighBet, 0
mov [edi].MultiPokerTable.stage, MPK_STAGE_PREFLOP
; Передвигаем баттон дилера
mov eax, [edi].MultiPokerTable.dealerButton
inc eax
and eax, 3
mov [edi].MultiPokerTable.dealerButton, eax
; Раздаем по 2 карты каждому активному месту
xor esi, esi
deal_seats_loop:
imul eax, esi, SIZEOF MpkSeat
lea ebx, [edi].MultiPokerTable.seats[eax]
; Если нет фишек -> выбыл
cmp [ebx].MpkSeat.chips, 50
jae seat_can_play
mov [ebx].MpkSeat.seatStatus, MPK_SEAT_BUST
jmp next_deal_seat
seat_can_play:
mov [ebx].MpkSeat.seatStatus, MPK_SEAT_ACTIVE
mov [ebx].MpkSeat.roundBet, 0
mov [ebx].MpkSeat.totalBet, 0
mov [ebx].MpkSeat.lastAction, MPK_ACT_NONE
; Карта 1
mov edx, multiDeckIndex
mov eax, multiDeck[edx*4]
mov [ebx].MpkSeat.card1, eax
inc multiDeckIndex
; Карта 2
mov edx, multiDeckIndex
mov eax, multiDeck[edx*4]
mov [ebx].MpkSeat.card2, eax
inc multiDeckIndex
next_deal_seat:
inc esi
cmp esi, MPK_MAX_SEATS
jb deal_seats_loop
; Раздаем 5 общих карт на стол (пока скрыты)
xor esi, esi
deal_comm_loop:
mov edx, multiDeckIndex
mov eax, multiDeck[edx*4]
mov [edi].MultiPokerTable.commCards[esi*4], eax
inc multiDeckIndex
inc esi
cmp esi, 5
jb deal_comm_loop
; Сбор Малого Блайнда
mov eax, [edi].MultiPokerTable.dealerButton
inc eax
and eax, 3
imul eax, SIZEOF MpkSeat
mov ebx, [edi].MultiPokerTable.smallBlind
sub [edi].MultiPokerTable.seats[eax].chips, ebx
mov [edi].MultiPokerTable.seats[eax].roundBet, ebx
add [edi].MultiPokerTable.mainPot, ebx
; Сбор Большого Блайнда
mov eax, [edi].MultiPokerTable.dealerButton
add eax, 2
and eax, 3
imul eax, SIZEOF MpkSeat
mov ebx, [edi].MultiPokerTable.bigBlind
sub [edi].MultiPokerTable.seats[eax].chips, ebx
mov [edi].MultiPokerTable.seats[eax].roundBet, ebx
add [edi].MultiPokerTable.mainPot, ebx
mov eax, [edi].MultiPokerTable.bigBlind
mov [edi].MultiPokerTable.currentHighBet, eax
; Первый ход после ББ
mov eax, [edi].MultiPokerTable.dealerButton
add eax, 3
and eax, 3
mov [edi].MultiPokerTable.currentTurn, eax
ret
MultiPoker_StartHand ENDP
; ------------------------------------------------------------------------------
; MultiPoker_NpcDecideTurn: Глубокая логика принятия решений ИИ бота
; Вход: seatIdx (0..3)
; ------------------------------------------------------------------------------
MultiPoker_NpcDecideTurn PROC uses ebx esi edi, seatIdx:DWORD
local pSeat:DWORD, pProf:DWORD
local toCall:DWORD, handScore:DWORD, potOdds:DWORD
local aggro:DWORD, bluff:DWORD, courage:DWORD, tilt:DWORD
; 1. Получаем указатели на стул и профиль
mov eax, seatIdx
imul eax, SIZEOF MpkSeat
lea esi, multiPokerTable.seats[eax]
mov pSeat, esi
mov eax, [esi].MpkSeat.profileId
imul eax, SIZEOF NpcPersonality
lea edi, casinoNpcProfiles[eax]
mov pProf, edi
; Сумма для колла
mov eax, multiPokerTable.currentHighBet
sub eax, [esi].MpkSeat.roundBet
mov toCall, eax
; 2. Оценка силы руки (0..100)
; Карманные карты
mov eax, [esi].MpkSeat.card1
mov multiEvalHand[0], eax
mov eax, [esi].MpkSeat.card2
mov multiEvalHand[4], eax
mov ecx, multiPokerTable.commCount
test ecx, ecx
jz eval_preflop_hand
; Постфлоп: копируем открытые карты стола
xor edx, edx
copy_comm_eval:
mov eax, multiPokerTable.commCards[edx*4]
mov multiEvalHand[edx*4 + 8], eax
inc edx
cmp edx, ecx
jb copy_comm_eval
lea eax, [ecx + 2]
invoke EvaluatePokerN, OFFSET multiEvalHand, eax
; Score / 100 дает примерный процент силы
xor edx, edx
mov ecx, 80
div ecx
mov handScore, eax
jmp apply_multi_opponent
eval_preflop_hand:
; Префлоп эвристика: старшинство и карманные пары
mov eax, [esi].MpkSeat.card1
xor edx, edx
mov ecx, 13
div ecx
mov ebx, edx ; ранг 1 (0..12)
mov eax, [esi].MpkSeat.card2
xor edx, edx
div ecx
mov ecx, edx ; ранг 2 (0..12)
cmp ebx, ecx
jne preflop_ranks
; Пара!
lea eax, [ebx*4 + 40]
mov handScore, eax
jmp apply_multi_opponent
preflop_ranks:
lea eax, [ebx + ecx]
imul eax, 3
add eax, 15
mov handScore, eax
apply_multi_opponent:
; Штраф за игру против нескольких оппонентов
mov ecx, multiPokerTable.activeSeats
cmp ecx, 2
jbe @F
imul ecx, 4
sub handScore, ecx
jns @F
mov handScore, 5
@@:
; 3. Психологические модификаторы + ТИЛЬТ
; Находим соответствующего NPC в casinoNpcs3D для учета текущего тильта
mov eax, [esi].MpkSeat.profileId
imul eax, SIZEOF NpcMindEntity
mov eax, casinoNpcs3D[eax].currentTilt
mov tilt, eax
mov eax, [edi].NpcPersonality.baseAggression
mov edx, tilt
shr edx, 1
add eax, edx
mov aggro, eax
mov eax, [edi].NpcPersonality.baseBluffRate
add eax, tilt
mov bluff, eax
mov eax, [edi].NpcPersonality.baseCourage
mov courage, eax
; 4. Расчет шансов банка (Pot Odds)
cmp toCall, 0
je branch_free_check
; potOdds = (toCall * 100) / (mainPot + toCall)
mov eax, toCall
imul eax, 100
mov ecx, multiPokerTable.mainPot
add ecx, toCall
test ecx, ecx
jnz @F
mov ecx, 1
@@:
xor edx, edx
div ecx
mov potOdds, eax
; Проверка на блеф со слабой рукой
cmp handScore, 30
ja check_standard_call
invoke RandomRange, 100
cmp eax, bluff
jbe decide_bluff_raise
check_standard_call:
; Сравнение силы руки и шансов банка
mov eax, handScore
add eax, courage
shr eax, 1
cmp eax, potOdds
jb decide_fold_action
; Рука достаточна для продолжения! Проверяем желание рейзить
mov eax, handScore
add eax, aggro
cmp eax, 120
jae decide_value_raise
; Стандартный Колл
mov [esi].MpkSeat.lastAction, MPK_ACT_CALL
mov [esi].MpkSeat.thoughtPtr, OFFSET thActCall
mov eax, [edi].NpcPersonality.phraseWinPtr
mov [esi].MpkSeat.reactionPtr, eax
ret
decide_value_raise:
mov [esi].MpkSeat.lastAction, MPK_ACT_RAISE
mov eax, multiPokerTable.minRaise
imul eax, 2
mov [esi].MpkSeat.lastRaiseAmount, eax
mov [esi].MpkSeat.thoughtPtr, OFFSET thEvalGood
mov eax, [edi].NpcPersonality.phraseBluffPtr
mov [esi].MpkSeat.reactionPtr, eax
ret
decide_bluff_raise:
mov [esi].MpkSeat.lastAction, MPK_ACT_RAISE
mov eax, multiPokerTable.minRaise
imul eax, 3
mov [esi].MpkSeat.lastRaiseAmount, eax
mov [esi].MpkSeat.thoughtPtr, OFFSET thEvalBluffing
mov eax, [edi].NpcPersonality.phraseBluffPtr
mov [esi].MpkSeat.reactionPtr, eax
ret
decide_fold_action:
mov [esi].MpkSeat.lastAction, MPK_ACT_FOLD
mov [esi].MpkSeat.seatStatus, MPK_SEAT_FOLDED
mov [esi].MpkSeat.thoughtPtr, OFFSET thActFold
mov eax, [edi].NpcPersonality.phraseLosePtr
mov [esi].MpkSeat.reactionPtr, eax
ret
branch_free_check:
; toCall == 0 -> Чек или лидерская ставка
mov eax, handScore
add eax, aggro
cmp eax, 110
jae lead_bet
mov [esi].MpkSeat.lastAction, MPK_ACT_CHECK
mov [esi].MpkSeat.thoughtPtr, OFFSET thActCheck
ret
lead_bet:
mov [esi].MpkSeat.lastAction, MPK_ACT_RAISE
mov eax, multiPokerTable.minRaise
mov [esi].MpkSeat.lastRaiseAmount, eax
mov [esi].MpkSeat.thoughtPtr, OFFSET thEvalGood
mov eax, [edi].NpcPersonality.phraseBluffPtr
mov [esi].MpkSeat.reactionPtr, eax
ret
MultiPoker_NpcDecideTurn ENDP
; ------------------------------------------------------------------------------
; MultiPoker_ApplyAction: Применение ставки, колла или паса к состоянию стола
; ------------------------------------------------------------------------------
MultiPoker_ApplyAction PROC uses ebx esi edi, seatIdx:DWORD, act:DWORD, amount:DWORD
mov eax, seatIdx
imul eax, SIZEOF MpkSeat
lea esi, multiPokerTable.seats[eax]
cmp act, MPK_ACT_FOLD
je apply_fold
cmp act, MPK_ACT_CHECK
je apply_check
cmp act, MPK_ACT_CALL
je apply_call
cmp act, MPK_ACT_RAISE
je apply_raise
ret
apply_fold:
mov [esi].MpkSeat.seatStatus, MPK_SEAT_FOLDED
dec multiPokerTable.activeSeats
ret
apply_check:
ret
apply_call:
mov eax, multiPokerTable.currentHighBet
sub eax, [esi].MpkSeat.roundBet
cmp eax, [esi].MpkSeat.chips
jbe @F
mov eax, [esi].MpkSeat.chips
@@:
sub [esi].MpkSeat.chips, eax
add [esi].MpkSeat.roundBet, eax
add [esi].MpkSeat.totalBet, eax
add multiPokerTable.mainPot, eax
ret
apply_raise:
; Доставка до currentHighBet + размер рейза
mov eax, multiPokerTable.currentHighBet
sub eax, [esi].MpkSeat.roundBet
add eax, amount
cmp eax, [esi].MpkSeat.chips
jbe @F
mov eax, [esi].MpkSeat.chips
@@:
sub [esi].MpkSeat.chips, eax
add [esi].MpkSeat.roundBet, eax
add [esi].MpkSeat.totalBet, eax
add multiPokerTable.mainPot, eax
mov eax, [esi].MpkSeat.roundBet
mov multiPokerTable.currentHighBet, eax
ret
MultiPoker_ApplyAction ENDP
; ------------------------------------------------------------------------------
; MultiPoker_NextStreet: Переход на следующую улицу торгов
; ------------------------------------------------------------------------------
MultiPoker_NextStreet PROC uses ebx esi edi
; Сброс roundBet для всех мест
xor ecx, ecx
reset_round_bets:
imul eax, ecx, SIZEOF MpkSeat
mov multiPokerTable.seats[eax].roundBet, 0
inc ecx
cmp ecx, MPK_MAX_SEATS
jb reset_round_bets
mov multiPokerTable.currentHighBet, 0
mov eax, multiPokerTable.stage
cmp eax, MPK_STAGE_PREFLOP
je go_flop
cmp eax, MPK_STAGE_FLOP
je go_turn
cmp eax, MPK_STAGE_TURN
je go_river
mov multiPokerTable.stage, MPK_STAGE_SHOWDOWN
ret
go_flop:
mov multiPokerTable.commCount, 3
mov multiPokerTable.stage, MPK_STAGE_FLOP
ret
go_turn:
mov multiPokerTable.commCount, 4
mov multiPokerTable.stage, MPK_STAGE_TURN
ret
go_river:
mov multiPokerTable.commCount, 5
mov multiPokerTable.stage, MPK_STAGE_RIVER
ret
MultiPoker_NextStreet ENDP
; ------------------------------------------------------------------------------
; MultiPoker_Showdown: Определение победителя и расчет банка
; ------------------------------------------------------------------------------
MultiPoker_Showdown PROC uses ebx esi edi
local bestScore:DWORD, bestSeat:DWORD
mov bestScore, 0
mov bestSeat, 0
xor esi, esi
eval_showdown_seats:
imul eax, esi, SIZEOF MpkSeat
lea ebx, multiPokerTable.seats[eax]
cmp [ebx].MpkSeat.seatStatus, MPK_SEAT_ACTIVE
jne next_showdown_seat
; Оценка 7 карт (2 свои + 5 стола)
mov eax, [ebx].MpkSeat.card1
mov multiEvalHand[0], eax
mov eax, [ebx].MpkSeat.card2
mov multiEvalHand[4], eax
xor edx, edx
copy_7_cards:
mov eax, multiPokerTable.commCards[edx*4]
mov multiEvalHand[edx*4 + 8], eax
inc edx
cmp edx, 5
jb copy_7_cards
invoke EvaluatePokerN, OFFSET multiEvalHand, 7
mov [ebx].MpkSeat.evalScore, eax
cmp eax, bestScore
jb next_showdown_seat
mov bestScore, eax
mov bestSeat, esi
next_showdown_seat:
inc esi
cmp esi, MPK_MAX_SEATS
jb eval_showdown_seats
; Награда победителю
mov eax, bestSeat
imul eax, SIZEOF MpkSeat
lea ebx, multiPokerTable.seats[eax]
mov edx, multiPokerTable.mainPot
add [ebx].MpkSeat.chips, edx
; Если победил человек -> обновляем bankroll
cmp bestSeat, 0
jne @F
mov eax, [ebx].MpkSeat.chips
mov bankroll, eax
@@:
ret
MultiPoker_Showdown ENDP
; ------------------------------------------------------------------------------
; MultiPoker_PrintStatus: Текстовый вывод состояния стола и мыслей ботов
; ------------------------------------------------------------------------------
MultiPoker_PrintStatus PROC uses ebx esi edi
invoke SetColor, COLOR_TITLE
invoke PrintStr, OFFSET mpkTitleStr
call ResetColor
invoke PrintStr, OFFSET mpkPotStr
invoke PrintUInt, multiPokerTable.mainPot
invoke PrintStr, OFFSET mpkStreetStr
mov eax, multiPokerTable.stage
cmp eax, MPK_STAGE_PREFLOP
jne @F
invoke PrintStr, OFFSET strStagePreflop
jmp print_stage_done
@@:
cmp eax, MPK_STAGE_FLOP
jne @F
invoke PrintStr, OFFSET strStageFlop
jmp print_stage_done
@@:
cmp eax, MPK_STAGE_TURN
jne @F
invoke PrintStr, OFFSET strStageTurn
jmp print_stage_done
@@:
cmp eax, MPK_STAGE_RIVER
jne @F
invoke PrintStr, OFFSET strStageRiver
jmp print_stage_done
@@:
invoke PrintStr, OFFSET strStageShow
print_stage_done:
invoke PrintStr, OFFSET nl
; Вывод общих карт стола
invoke PrintStr, OFFSET mpkCommCardsStr
cmp multiPokerTable.commCount, 0
jne print_table_cards
invoke PrintStr, OFFSET strStagePreflop
invoke PrintStr, OFFSET nl
jmp print_seats_info
print_table_cards:
invoke PrintHandRow21, OFFSET multiPokerTable.commCards, multiPokerTable.commCount, 1
print_seats_info:
invoke PrintStr, OFFSET nl
; Вывод каждого из 4 мест
xor esi, esi
print_seats_loop:
imul eax, esi, SIZEOF MpkSeat
lea ebx, multiPokerTable.seats[eax]
; Имя места
cmp esi, 0
jne print_npc_seat_header
invoke SetColor, COLOR_MONEY
invoke PrintStr, OFFSET promptPlayerTerminal
call ResetColor
jmp print_seat_details
print_npc_seat_header:
mov eax, [ebx].MpkSeat.profileId
imul eax, SIZEOF NpcPersonality
invoke SetColor, COLOR_WARN
invoke PrintStr, casinoNpcProfiles[eax].namePtr
call ResetColor
print_seat_details:
invoke PrintStr, OFFSET dotSpaceText
invoke PrintStr, OFFSET mpkChipsPrefix
invoke PrintUInt, [ebx].MpkSeat.chips
; Статус места
cmp [ebx].MpkSeat.seatStatus, MPK_SEAT_FOLDED
jne @F
invoke SetColor, COLOR_BAD
invoke PrintStr, OFFSET observerFold1Text
call ResetColor
@@:
invoke PrintStr, OFFSET nl
; Мысли бота (если есть)
cmp [ebx].MpkSeat.thoughtPtr, 0
je print_reaction_check
invoke PrintStr, OFFSET mpkThoughtPrefix
invoke PrintStr, [ebx].MpkSeat.thoughtPtr
invoke PrintStr, OFFSET nl
print_reaction_check:
cmp [ebx].MpkSeat.reactionPtr, 0
je next_seat_print
invoke SetColor, COLOR_CARD
invoke PrintStr, [ebx].MpkSeat.reactionPtr
call ResetColor
invoke PrintStr, OFFSET nl
next_seat_print:
inc esi
cmp esi, MPK_MAX_SEATS
jb print_seats_loop
ret
MultiPoker_PrintStatus ENDP
; ------------------------------------------------------------------------------
; GameMultiPoker: Главная точка входа для карточной игры против 3 посетителей
; ------------------------------------------------------------------------------
GameMultiPoker PROC uses ebx esi edi
call ClearView
call MultiPoker_InitTable
multi_hand_loop:
call ClearView
call MultiPoker_StartHand
multi_street_loop:
call ClearView
call MultiPoker_PrintStatus
; Проверяем, остался ли в игре хотя бы 1 оппонент
cmp multiPokerTable.activeSeats, 1
jbe multi_hand_showdown
; Круг торговли для каждого активного места
xor esi, esi
multi_turn_loop:
imul eax, esi, SIZEOF MpkSeat
lea ebx, multiPokerTable.seats[eax]
cmp [ebx].MpkSeat.seatStatus, MPK_SEAT_ACTIVE
jne next_turn_player
; Ход Игрока (место 0)
cmp esi, 0
jne handle_bot_turn
invoke PrintStr, OFFSET mpkPromptStr
call ReadKey
cmp al, '1'
je player_call
cmp al, '2'
je player_raise
cmp al, '0'
je player_fold
jmp player_call
player_fold:
invoke MultiPoker_ApplyAction, 0, MPK_ACT_FOLD, 0
jmp next_turn_player
player_call:
invoke MultiPoker_ApplyAction, 0, MPK_ACT_CALL, 0
jmp next_turn_player
player_raise:
invoke MultiPoker_ApplyAction, 0, MPK_ACT_RAISE, 100
jmp next_turn_player
handle_bot_turn:
; Ход бота
invoke MultiPoker_NpcDecideTurn, esi
mov eax, [ebx].MpkSeat.lastAction
mov edx, [ebx].MpkSeat.lastRaiseAmount
invoke MultiPoker_ApplyAction, esi, eax, edx
invoke Sleep, 400
next_turn_player:
inc esi
cmp esi, MPK_MAX_SEATS
jb multi_turn_loop
; Переход на следующую улицу
cmp multiPokerTable.stage, MPK_STAGE_RIVER
jae multi_hand_showdown
call MultiPoker_NextStreet
jmp multi_street_loop
multi_hand_showdown:
call ClearView
call MultiPoker_Showdown
call MultiPoker_PrintStatus
invoke PrintStr, OFFSET mpkWaitNextStr
call ReadKey
cmp al, '0'
je multi_poker_exit
jmp multi_hand_loop
multi_poker_exit:
ret
GameMultiPoker ENDP
'''.strip()
print("Writing npc_mind_data.inc in CP1251...")
with open(data_inc_path, "w", encoding="cp1251", errors="replace") as f:
f.write(DATA_CONTENT + "\n")
print("Writing npc_mind.inc in CP1251...")
with open(code_inc_path, "w", encoding="cp1251", errors="replace") as f:
f.write(CODE_CONTENT + "\n")
print("Done generating NPC mind modules.")