# -*- 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 NpcPersonality NpcPersonality NpcPersonality NpcPersonality NpcPersonality ; Граф вейпоинтов казино (координаты 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.")