; ============================================================================== ; 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