From 4e5caa7b8b90c01f3c8a7b33a8fe35744f63c6b2 Mon Sep 17 00:00:00 2001 From: hide_d Date: Tue, 7 Apr 2020 02:55:59 +0900 Subject: [PATCH] =?UTF-8?q?AI=ED=84=B4=20=EC=B6=94=EA=B0=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hwe/func.php | 70 +++ hwe/sammo/AutorunGeneralPolicy.php | 9 + hwe/sammo/AutorunNationPolicy.php | 29 +- hwe/sammo/General.php | 26 ++ hwe/sammo/GeneralAI.php | 680 +++++++++++++++++++---------- 5 files changed, 572 insertions(+), 242 deletions(-) diff --git a/hwe/func.php b/hwe/func.php index 8e46e9d0..926fbe91 100644 --- a/hwe/func.php +++ b/hwe/func.php @@ -1903,6 +1903,76 @@ function searchDistanceListToDest(int $from, int $to, array $linkNationList) { return $result; } +/** + * 지정된 도시 내의 최단 거리를 계산해 줌(Floyd-Warshall) + * @param $cityIDList 이동 가능한 도시 리스트. + * @return array [from][to] 로 표시되는 거리값 + */ +function searchAllDistanceByCityList(array $cityIDList):array { + if(!$cityIDList){ + return []; + } + $cityList = []; + foreach($cityIDList as $cityID){ + $cityList[$cityID] = $cityID; + } + + $distanceList = []; + foreach($cityIDList as $cityID){ + $nearList = [$cityID=>0]; + foreach(CityConst::byID($cityID)->path as $nextCityID){ + if(!key_exists($nextCityID, $cityList)){ + continue; + } + $nearList[$nextCityID] = 1; + } + $distanceList[$cityID] = $nearList; + } + + //Floyd-Warshall + foreach($cityList as $cityStop){ + foreach($cityList as $cityFrom){ + foreach($cityList as $cityTo){ + if(!key_exists($cityStop, $distanceList[$cityFrom])){ + continue; + } + if(!key_exists($cityTo, $distanceList[$cityStop])){ + continue; + } + + if(!key_exists($cityTo, $distanceList[$cityFrom])){ + $distanceList[$cityFrom][$cityTo] = $distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo]; + continue; + } + + $distanceList[$cityFrom][$cityTo] = min($distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo], $distanceList[$cityFrom][$cityTo]); + } + } + } + + return $distanceList; +} + +/** + * 지정된 국가 내의 모든 도시들의 최단 거리를 계산해 줌(Floyd-Warshall) + * @param $linkNationList 경로에 해당하는 국가 리스트 + * @param $suppliedCityOnly 보급된 도시만을 이용해 검색 + * @return array [from][to] 로 표시되는 거리값 + */ +function searchAllDistanceByNationList(array $linkNationList, bool $suppliedCityOnly=false):array { + if(!$linkNationList){ + return []; + } + $db = DB::db(); + if($suppliedCityOnly){ + $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li AND supply=1',$linkNationList); + } + else{ + $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li',$linkNationList); + } + return searchAllDistanceByCityList($cityIDList); +} + function isNeighbor(int $nation1, int $nation2, bool $includeNoSupply=true) { if($nation1 === $nation2){ return false; diff --git a/hwe/sammo/AutorunGeneralPolicy.php b/hwe/sammo/AutorunGeneralPolicy.php index 8cee7d19..245c4673 100644 --- a/hwe/sammo/AutorunGeneralPolicy.php +++ b/hwe/sammo/AutorunGeneralPolicy.php @@ -22,6 +22,7 @@ class AutorunGeneralPolicy{ //static $NPC증여 = 'NPC증여'; static $NPC헌납 = 'NPC헌납'; + static $NPC사망대비 = 'NPC사망대비'; static $후방워프 = '후방워프'; static $전방워프 = '전방워프'; @@ -35,9 +36,11 @@ class AutorunGeneralPolicy{ static $집합 = '집합'; static $건국 = '건국'; static $선양 = '선양'; + static public $priority = [ + 'NPC사망대비', '귀환', '금쌀구매', '출병', @@ -55,6 +58,7 @@ class AutorunGeneralPolicy{ ]; + public $canNPC사망대비 = true; public $can일반내정 = true; public $can긴급내정 = true; public $can전쟁내정 = true; @@ -90,12 +94,17 @@ class AutorunGeneralPolicy{ $nationID = $general->getNationID(); if($npc==5){ + $this->can집합 = true; $this->can선양 = true; $this->can집합 = true; $this->can국가선택 = false; return; } + if($npc==1){ + $this->canNPC사망대비 = false; + } + if($nationID != 0){ $this->can국가선택 = false; $this->can건국 = false; diff --git a/hwe/sammo/AutorunNationPolicy.php b/hwe/sammo/AutorunNationPolicy.php index cdf5ddc9..8b9e71fe 100644 --- a/hwe/sammo/AutorunNationPolicy.php +++ b/hwe/sammo/AutorunNationPolicy.php @@ -10,9 +10,13 @@ class AutorunNationPolicy { static $부대유저장후방발령 = '부대유저장후방발령'; static $유저장후방발령 = '유저장후방발령'; static $유저장전방발령 = '유저장전방발령'; + static $유저장구출발령 = '유저장구출발령'; + static $유저장내정발령 = '유저장내정발령'; static $NPC후방발령 = 'NPC후방발령'; static $NPC전방발령 = 'NPC전방발령'; + static $NPC구출발령 = 'NPC구출발령'; + static $NPC내정발령 = 'NPC내정발령'; static $유저장긴급포상 = '유저장긴급포상'; static $유저장포상 = '유저장포상'; @@ -35,6 +39,7 @@ class AutorunNationPolicy { '유저장긴급포상', '부대전방발령', + '유저장구출발령', '유저장후방발령', '부대유저장후방발령', @@ -46,23 +51,30 @@ class AutorunNationPolicy { '부대후방발령', 'NPC긴급포상', - + 'NPC구출발령', 'NPC후방발령', 'NPC포상', 'NPC몰수', 'NPC전방발령', + + '유저장내정발령', + 'NPC내정발령', ]; //순서는 중요하지 않음 static public $availableInstantTurn = [ '유저장긴급포상'=>true, + '유저장구출발령'=>true, '유저장후방발령'=>true, '유저장전방발령'=>true, + '유저장내정발령'=>true, '유저장포상'=>true, 'NPC긴급포상'=>true, + 'NPC구출발령'=>true, 'NPC후방발령'=>true, + 'NPC내정발령'=>true, 'NPC포상'=>true, 'NPC전방발령'=>true, ]; @@ -75,9 +87,13 @@ class AutorunNationPolicy { public $can부대유저장후방발령 = true; public $can유저장후방발령 = true; public $can유저장전방발령 = true; + public $can유저장구출발령 = true; + public $can유저장내정발령 = true; public $canNPC후방발령 = true; public $canNPC전방발령 = true; + public $canNPC구출발령 = true; + public $canNPC내정발령 = true; public $can유저장긴급포상 = true; public $can유저장포상 = true; @@ -94,14 +110,14 @@ class AutorunNationPolicy { public $reqNationGold = 10000; public $reqNationRice = 10000; public $CombatForce = [ - [200, 10, 24],//troopLeader, fromCity, toCity - [242, 10, 24] + //200 => [10, 24],//troopLeader, fromCity, toCity + //242 => [10, 24] ]; public $SupportForce = [ - 211 + //211=>true ]; public $DevelopForce = [ - 123 + //123=>true ]; public $reqHumanWarGold = [10000, 30000]; public $reqHumanWarRice = [10000, 30000]; @@ -113,10 +129,11 @@ class AutorunNationPolicy { public $reqNPCDevelRice = 0; public $minNPCWarLeadership = 40; + public $minWarCrew = 1500; public $allowNpcAttackCity = true; public $minNPCRecruitCityPopulation = 50000; - public $safeRecruitCityPopulation = 50000; + public $safeRecruitCityPopulationRatio = 0.5; public $properWarTrainAtmos = 90; diff --git a/hwe/sammo/General.php b/hwe/sammo/General.php index 78aee85f..6eb86535 100644 --- a/hwe/sammo/General.php +++ b/hwe/sammo/General.php @@ -1,6 +1,8 @@ getVar('crewtype')); } + function calcRecentWarTurn(int $turnTerm):int{ + if(!$this->getVar('recent_war')){ + return 12*1000; + } + $recwar = new \DateTimeImmutable($this->getVar('recent_war')); + $turnNow = new \DateTimeImmutable($this->getVar('turntime')); + $secDiff = TimeUtil::DateIntervalToSeconds($recwar->diff($turnNow)); + + if($secDiff <= 0){ + return 0; + } + + return intdiv($secDiff, 60 * $turnTerm); + } + + function getReservedTurn(int $turnIdx, array $env):?GeneralCommand{ + $db = DB::db(); + $rawCmd = $db->queryFirstRow('SELECT * FROM general_turn WHERE general_id = %i AND turn_idx = %i', $this->getID(), $turnIdx); + if(!$rawCmd){ + return null; + } + return buildGeneralCommandClass($rawCmd['action'], $this, $env, $rawCmd['arg']); + } + /** * 장수의 스탯을 계산해옴 * diff --git a/hwe/sammo/GeneralAI.php b/hwe/sammo/GeneralAI.php index c5d14ceb..5eac503e 100644 --- a/hwe/sammo/GeneralAI.php +++ b/hwe/sammo/GeneralAI.php @@ -50,7 +50,8 @@ class GeneralAI protected $frontCities; protected $supplyCities; protected $backupCities; - + + protected $warRoute; /** @var General[] */ protected $nationGenerals; @@ -60,6 +61,11 @@ class GeneralAI protected $npcWarGenerals; /** @var General[] */ protected $userGenerals; + /** @var General[] */ + protected $userWarGenerals; + /** @var General[] */ + protected $userCivilGenerals; + /** @var General[] */ protected $lostGenerals; /** @var General[] */ @@ -81,7 +87,7 @@ class GeneralAI { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); - $this->env = $gameStor->getValues(['startyear', 'year', 'month', 'turnterm', 'killturn', 'scenario', 'gold_rate', 'rice_rate', 'develcost']); + $this->env = $gameStor->getValues(['startyear', 'year', 'month', 'turnterm', 'killturn', 'scenario', 'gold_rate', 'rice_rate', 'develcost', 'autorun_user']); $this->baseDevelCost = $this->env['develcost'] * 12; $this->general = $general; if ($general->getRawCity() === null) { @@ -90,11 +96,14 @@ class GeneralAI } $this->city = $general->getRawCity(); $this->nation = $db->queryFirstRow( - 'SELECT nation,level,capital,tech,gold,rice,rate,type,color,name,war FROM nation WHERE nation = %i', + 'SELECT nation,name,color,capital,capset,gennum,gold,rice,bill,rate,rate_tmp,scout,war,strategic_cmd_limit,surlimit,tech,power,level,type,aux FROM nation WHERE nation = %i', $general->getNationID() ) ?? [ 'nation' => 0, 'level' => 0, + 'capital' => 0, + 'capset' => false, + 'gennum' => 0, 'tech' => 0, 'gold' => 0, 'rice' => 0, @@ -102,6 +111,7 @@ class GeneralAI 'color' => '#000000', 'name' => '재야', ]; + $this->nation['aux'] = Json::decode($this->nation['aux']??'{}'); $this->leadership = $general->getLeadership(); $this->strength = $general->getStrength(); @@ -114,6 +124,12 @@ class GeneralAI $this->genType = $this::calcGenType($general); $this->calcDiplomacyState(); + + $serverPolicy = KVStorage::getStorage($db, 'autorun_nation_policy_0'); + $nationPolicy = KVStorage::getStorage($db, "autorun_nation_policy_{$this->nation['nation']}"); + + $this->nationPolicy = new AutorunNationPolicy($general, $nationPolicy->getAll(), $serverPolicy->getAll()); + $this->generalPolicy = new AutorunGeneralPolicy($general, $this->env['autorun_user']['options']??[]); } public function getGeneralObj(): General @@ -209,24 +225,353 @@ class GeneralAI } } + protected function calcWarRoute(){ + if($this->warRoute !== null){ + return; + } + $target = $this->warTargetNation; + $target[] = $this->nation['nation']; + + $this->warRoute = searchAllDistanceByNationList($target, false); + } + protected function do부대전방발령(LastTurn $lastTurn): ?NationCommand { - return null; + $this->calcWarRoute(); + $troopCandidate = []; + + foreach($this->troopLeaders as $troopLeader){ + $leaderID = $troopLeader->getID(); + if(!key_exists($leaderID, $this->nationPolicy->CombatForce)){ + continue; + } + + $currentCityID = $troopLeader->getCityID(); + + if(key_exists($currentCityID, $this->frontCities)){ + continue; + } + + [$fromCityID, $toCityID] = $this->nationPolicy->CombatForce[$leaderID]; + + if(!key_exists($fromCityID, $this->warRoute) && !key_exists($toCityID, $this->warRoute)){ + //공격 루트 상실, 전방 아무데나 + $troopCandidate[] = [$leaderID, Util::choiceRandom($this->frontCities)['city']]; + continue; + } + + if(!key_exists($toCityID, $this->warRoute[$fromCityID])){ + //공격 루트 상실, 전방 아무데나 + $troopCandidate[] = [$leaderID, Util::choiceRandom($this->frontCities)['city']]; + continue; + } + + if(key_exists($fromCityID, $this->supplyCities) && key_exists($toCityID, $this->supplyCities)){ + //점령 완료, 전방 아무데나 + $troopCandidate[] = [$leaderID, Util::choiceRandom($this->frontCities)['city']]; + continue; + } + + + //출발지가 아국땅이 아닌경우 수도->출발지 + if(!key_exists($fromCityID, $this->supplyCities)){ + $toCityID = $fromCityID; + $fromCityID = $this->nation['capital']; + } + + $targetCityID = $fromCityID; + //접경에 도달할때까지 전진 + while(!key_exists($targetCityID, $this->frontCities)){ + $distance = $this->warRoute[$targetCityID][$toCityID]; + $nextCityCandidate = []; + foreach(CityConst::byID($targetCityID)->path as $nearCityID){ + if(!key_exists($nearCityID, $this->warRoute) || !key_exists($toCityID, $this->warRoute[$nearCityID])){ + continue; + } + if($this->warRoute[$nearCityID][$toCityID] + 1 > $distance){ + continue; + } + $nextCityCandidate[] = $nearCityID; + } + if(!$nextCityCandidate){ + throw new MustNotBeReachedException('경로 계산 버그'); + } + if(count($nextCityCandidate) == 1){ + $targetCityID = $nextCityCandidate[0]; + continue; + } + $targetCityID = Util::choiceRandom($nextCityCandidate); + } + + $troopCandidate[] = ['destGenaralID'=>$leaderID, 'destCityID'=>$targetCityID]; + } + + if(!$troopCandidate){ + return null; + } + + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, Util::choiceRandom($troopCandidate)); + if(!$cmd->isRunnable()){ + return null; + } + + return $cmd; } protected function do부대후방발령(LastTurn $lastTurn): ?NationCommand { - return null; + $troopCandidate = []; + foreach($this->troopLeaders as $troopLeader){ + $leaderID = $troopLeader->getID(); + if(key_exists($leaderID, $this->nationPolicy->CombatForce)){ + continue; + } + $currentCityID = $troopLeader->getCityID(); + if(!key_exists($currentCityID, $this->supplyCities)){ + $troopCandidate[$leaderID] = $troopLeader; + continue; + } + + $city = $this->supplyCities[$currentCityID]; + if($city['pop'] / $city['pop2'] >= $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + + $troopCandidate[$leaderID] = $troopLeader; + } + + if(!$troopCandidate){ + return null; + } + + if(count($this->supplyCities) == 1){ + return null; + } + + $cityCandidates = []; + + foreach($this->backupCities as $city){ + if($city['pop'] / $city['pop_max'] < $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + $cityCandidates[] = $city; + } + + if(!$cityCandidates){ + foreach($this->supplyCities as $city){ + if($city['pop'] / $city['pop_max'] < $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + $cityCandidates[] = $city; + } + } + + if(!$cityCandidates){ + return null; + } + + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, [ + 'destGeneralID'=>Util::choiceRandom($troopCandidate)->getID(), + 'destCityID'=>Util::choiceRandom($cityCandidates)['city'] + ]); + + if(!$cmd->isRunnable()){ + return null; + } + + return $cmd; } protected function do부대유저장후방발령(LastTurn $lastTurn): ?NationCommand { - return null; - } + if($this->dipState !== self::d전쟁){ + return null; + } + $generalCadidates = []; + $db = DB::db(); + + $chiefTurnTime = new \DateTimeImmutable($this->general->getVar('turntime')); + + foreach($this->userWarGenerals as $userGeneral){ + $generalID = $userGeneral->getID(); + if($generalID == $this->general->getID()){ + continue; + } + $city = $this->supplyCities[$userGeneral->getCityID()]; + if(!key_exists($generalID, $this->supplyCities)){ + continue; + } + if($userGeneral->getVar('troop') === 0){ + continue; + } + if($userGeneral->getVar('troop') === $userGeneral->getID()){ + continue; + } + if($city['pop'] / $city['pop_max'] >= $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + if($userGeneral->getVar('crew') >= $this->nationPolicy->minWarCrew){ + continue; + } + + $generalTurnTime = new \DateTimeImmutable($userGeneral->getVar('turntime')); + $troopLeader = $this->nationGenerals[$userGeneral->getVar('troop')]; + $troopTurnTime = new \DateTimeImmutable($troopLeader->getVar('turntime')); + + if($chiefTurnTime < $generalTurnTime && $generalTurnTime < $troopTurnTime){ + $generalCadidates[$generalID] = $userGeneral; + } + } + + if(!$generalCadidates){ + return null; + } + + $rawTurn = Util::convertArrayToDict($db->query('SELECT * FROM general_turn WHERE general_id IN %li AND turn_idx = 0', array_keys($generalCadidates)), 'no'); + $generalCadidates = array_filter($generalCadidates, function(General $general)use($rawTurn){ + $generalID = $general->getID(); + if(in_array($rawTurn[$generalID]??null, ['che_징병', 'che_모병'])){ + return true; + } + else{ + return false; + } + }, ARRAY_FILTER_USE_KEY); + + if(count($this->supplyCities) == 1){ + return null; + } + + $cityCandidates = []; + + foreach($this->backupCities as $city){ + if($city['pop'] / $city['pop_max'] < $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + $cityCandidates[] = $city; + } + + if(!$cityCandidates){ + foreach($this->supplyCities as $city){ + if($city['pop'] / $city['pop_max'] < $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + $cityCandidates[] = $city; + } + } + + if(!$cityCandidates){ + return null; + } + + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, [ + 'destGeneralID'=>Util::choiceRandom($generalCadidates)->getID(), + 'destCityID'=>Util::choiceRandom($cityCandidates)['city'] + ]); + + if(!$cmd->isRunnable()){ + return null; + } + + return $cmd; + } protected function do유저장후방발령(LastTurn $lastTurn): ?NationCommand { + if($this->dipState !== self::d전쟁){ + return null; + } + + $generalCadidates = []; + + foreach($this->userWarGenerals as $userGeneral){ + $generalID = $userGeneral->getID(); + if($generalID == $this->general->getID()){ + continue; + } + $city = $this->supplyCities[$userGeneral->getCityID()]; + if(!key_exists($generalID, $this->supplyCities)){ + continue; + } + if($userGeneral->getVar('troop') !== 0){ + continue; + } + if($city['pop'] / $city['pop_max'] >= $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + if($userGeneral->getVar('crew') >= $this->nationPolicy->minWarCrew){ + continue; + } + $generalCadidates[$generalID] = $userGeneral; + } + + if(!$generalCadidates){ + return null; + } + + if(count($this->supplyCities) == 1){ + return null; + } + + $cityCandidates = []; + + if($this->backupCities){ + $cities = $this->backupCities; + } + else{ + $cities = $this->supplyCities; + } + + foreach($cities as $city){ + if($city['pop'] / $city['pop_max'] < $this->nationPolicy->safeRecruitCityPopulationRatio){ + continue; + } + $cityCandidates[] = $city; + } + + if(!$cityCandidates){ + return null; + } + + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, [ + 'destGeneralID'=>Util::choiceRandom($generalCadidates)->getID(), + 'destCityID'=>Util::choiceRandom($cityCandidates)['city'] + ]); + + if(!$cmd->isRunnable()){ + return null; + } + + return $cmd; + } + + protected function do유저장구출발령(LastTurn $lastTurn): ?NationCommand + { + //고립 도시 장수 발령 + $args = []; + foreach ($this->lostGenerals as $lostGeneral) { + if ($lostGeneral->getVar('npc') >= 2) { + continue; + } + if (in_array($this->dipState, [self::d직전, self::d전쟁]) && count($this->frontCities) > 2) { + $selCity = Util::choiceRandom($this->frontCities); + } else { + $selCity = Util::choiceRandom($this->supplyCities); + } + //고립된 장수가 많을 수록 발령 확률 증가 + $args = [ + 'destGeneralID' => $lostGeneral->getID(), + 'destCityID' => $selCity['city'] + ]; + } + if(!$args){ + return null; + } + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, Util::choiceRandom($args)); + if(!$cmd->isRunnable()){ + return null; + } return null; } @@ -234,18 +579,57 @@ class GeneralAI { return null; } - + + protected function do유저장내정발령(LastTurn $lastTurn): ?NationCommand + { + $env = $this->env; + $args = []; + foreach($this->userCivilGenerals as $userGeneral){ + } + return null; + } protected function doNPC후방발령(LastTurn $lastTurn): ?NationCommand { return null; } + + protected function doNPC구출발령(LastTurn $lastTurn): ?NationCommand + { + //고립 도시 장수 발령 + $args = []; + foreach ($this->lostGenerals as $lostGeneral) { + if ($lostGeneral->getVar('npc') < 2 || $lostGeneral->getVar('npc') == 5) { + continue; + } + $selCity = Util::choiceRandom($this->supplyCities); + //고립된 장수가 많을 수록 발령 확률 증가 + $args = [ + 'destGeneralID' => $lostGeneral->getID(), + 'destCityID' => $selCity['city'] + ]; + } + if(!$args){ + return null; + } + $cmd = buildNationCommandClass('che_발령', $this->general, $this->env, $lastTurn, Util::choiceRandom($args)); + if(!$cmd->isRunnable()){ + return null; + } + return null; + } + protected function doNPC전방발령(LastTurn $lastTurn): ?NationCommand { return null; } + protected function doNPC내정발령(LastTurn $lastTurn): ?NationCommand + { + return null; + } + protected function do유저장긴급포상(LastTurn $lastTurn): ?NationCommand { @@ -267,29 +651,18 @@ class GeneralAI ]; - $userGenerals = $this->userGenerals; + $userWarGenerals = $this->userWarGenerals; foreach($remainResource as $resName=>[$resVal,$reqHumanMinRes]){ - usort($userGenerals, function ($lhs, $rhs) use ($resName) { + usort($userWarGenerals, function ($lhs, $rhs) use ($resName) { return $lhs->getVar($resName) <=> $rhs->getVar($resName); }); - foreach($userGenerals as $idx=>$targetUserGeneral){ + foreach($userWarGenerals as $idx=>$targetUserGeneral){ if($targetUserGeneral->getVar($resName) >= $reqHumanMinRes){ break; } - if($targetUserGeneral->getVar('recent_war') === null){ - continue; - } - $recent_war = new \DateTimeImmutable($targetUserGeneral->getVar('recent_war')); - $turnTime = new \DateTimeImmutable($targetUserGeneral->getVar('turntime')); - $timeDiffSecRecentWar = TimeUtil::DateIntervalToSeconds($recent_war->diff($turnTime)); - - if($timeDiffSecRecentWar > $this->env['turnterm'] * 60 * 12){ - continue; - } - $crewtype = $targetUserGeneral->getCrewTypeObj(); $reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetUserGeneral->getLeadership()) * 2 * 4 * 1.1; if ($this->env['year'] > $this->env['startyear'] + 5) { @@ -312,7 +685,7 @@ class GeneralAI 'isGold' => $resName == 'gold', 'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount) ], - count($userGenerals)-$idx + count($userWarGenerals)-$idx ]; } } @@ -359,15 +732,11 @@ class GeneralAI break; } - $isWarGeneral = false; - if($targetUserGeneral->getVar('recent_war') !== null){ - $recent_war = new \DateTimeImmutable($targetUserGeneral->getVar('recent_war')); - $turnTime = new \DateTimeImmutable($targetUserGeneral->getVar('turntime')); - $timeDiffSecRecentWar = TimeUtil::DateIntervalToSeconds($recent_war->diff($turnTime)); - - if($timeDiffSecRecentWar <= $this->env['turnterm'] * 60 * 12){ - $isWarGeneral = true; - } + if(key_exists($targetUserGeneral->getID(), $this->userWarGenerals)){ + $isWarGeneral = true; + } + else{ + $isWarGeneral = false; } if($isWarGeneral){ @@ -749,7 +1118,6 @@ class GeneralAI return null; } } - //TODO: 누군가 천도를 했는지 체크해야함. /* 도시 점수 공식 @@ -778,8 +1146,6 @@ class GeneralAI ]; $queue->enqueue($capital); - $distanceList = [ - ]; //수도와 연결된 도시 탐색 while(!$queue->isEmpty()){ @@ -804,38 +1170,8 @@ class GeneralAI return null; } - //기본 간선 - foreach(array_keys($cityList) as $cityID){ - $nearList = [$cityID=>0]; - foreach(CityConst::byID($cityID)->path as $nextCityID){ - if(!key_exists($nextCityID, $cityList)){ - continue; - } - $nearList[$nextCityID] = 1; - } - $distanceList[$cityID] = $nearList; - } + $distanceList = searchAllDistanceByCityList($cityList); - //Floyd-Warshall - foreach($cityList as $cityStop){ - foreach($cityList as $cityFrom){ - foreach($cityList as $cityTo){ - if(!key_exists($cityStop, $distanceList[$cityFrom])){ - continue; - } - if(!key_exists($cityTo, $distanceList[$cityStop])){ - continue; - } - - if(!key_exists($cityTo, $distanceList[$cityFrom])){ - $distanceList[$cityFrom][$cityTo] = $distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo]; - continue; - } - - $distanceList[$cityFrom][$cityTo] = min($distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo], $distanceList[$cityFrom][$cityTo]); - } - } - } $maxDistance = 0; foreach($distanceList as $cityID=>$subDistanceList){ @@ -1310,7 +1646,6 @@ class GeneralAI } $cmdList = []; $general = $this->general; - $cmd사기진작 = buildGeneralCommandClass('che_사기진작', $general, $this->env); $train = $general->getVar('train'); $atmos = $general->getVar('atmos'); @@ -1558,6 +1893,37 @@ class GeneralAI return null; } + protected function NPC사망대비(): ?GeneralCommand + { + $general = $this->getGeneralObj(); + + if($general->getVar('killturn') > 5){ + return null; + } + + if($general->getNationID() == 0){ + return buildGeneralCommandClass('che_견문', $general, $this->env); + } + + if ($general->getVar('gold') + $general->getVar('rice') == 0) { + return buildGeneralCommandClass('che_물자조달', $general, $this->env); + } + + if ($general->getVar('gold') >= $general->getVar('rice')) { + return buildGeneralCommandClass('che_헌납', $general, $this->env, [ + 'isGold' => true, + 'amount' => GameConst::$maxResourceActionAmount + ]); + } else { + return buildGeneralCommandClass('che_헌납', $general, $this->env, [ + 'isGold' => false, + 'amount' => GameConst::$maxResourceActionAmount + ]); + } + + return null; + } + protected function do중립(GeneralCommand $reservedCommand): GeneralCommand { $general = $this->general; @@ -1645,6 +2011,9 @@ class GeneralAI return; } $userGenerals = []; + $userCivilGenerals = []; + $userWarGenerals = []; + $lostGenerals = []; $npcCivilGenerals = []; $npcWarGenerals = []; @@ -1682,6 +2051,12 @@ class GeneralAI } else if($npcType < 2) { $userGenerals[$generalID] = $nationGeneral; + if($nationGeneral->calcRecentWarTurn($this->env['turnterm']) <= 12){ + $userWarGenerals[$generalID] = $nationGeneral; + } + else{ + $userCivilGenerals[$generalID] = $nationGeneral; + } } else if ($nationGeneral->getLeadership() >= $this->nationPolicy->minNPCWarLeadership) { $npcWarGenerals[$generalID] = $nationGeneral; } else { @@ -1690,6 +2065,8 @@ class GeneralAI } $this->userGenerals = $userGenerals; + $this->userCivilGenerals = $userCivilGenerals; + $this->userWarGenerals = $userWarGenerals; $this->lostGenerals = $lostGenerals; $this->npcCivilGenerals = $npcCivilGenerals; $this->npcWarGenerals = $npcWarGenerals; @@ -1808,129 +2185,6 @@ class GeneralAI return $this->do중립($reservedCommand); } - protected function chooseDevelopTurn(bool &$cityFull): array - { - $general = $this->general; - $city = $this->city; - $nation = $this->nation; - $env = $this->env; - - $genType = $this->genType; - $leadership = $this->leadership; - $strength = $this->strength; - $intel = $this->intel; - - $cityFull = false; - - $develRate = [ - 'trust' => $city['trust'], - 'pop' => $city['pop'] / $city['pop_max'], - 'agri' => $city['agri'] / $city['agri_max'], - 'comm' => $city['comm'] / $city['comm_max'], - 'secu' => $city['secu'] / $city['secu_max'], - 'def' => $city['def'] / $city['def_max'], - 'wall' => $city['wall'] / $city['wall_max'], - ]; - - // 우선 선정 - if ($develRate['trust'] < 1 && Util::randBool($leadership / 60)) { - return ['che_주민선정', null]; - } - - $commandList = []; - - if ($genType & self::t무장) { - if ($develRate['secu'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_치안강화', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_치안강화'] = $strength / 3; - if (in_array($city['front'], [1, 3])) { - $commandList['che_치안강화'] /= 2; - } - } - } - if ($develRate['def'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_수비강화', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_수비강화'] = $strength / 3; - } - } - if ($develRate['wall'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_성벽보수', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_성벽보수'] = $strength / 3; - if (in_array($city['front'], [1, 3])) { - $commandList['che_성벽보수'] /= 2; - } - } - } - } - if ($genType & self::t지장) { - if ($develRate['agri'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_농지개간', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_농지개간'] = $intel / 2; - if (in_array($city['front'], [1, 3])) { - $commandList['che_농지개간'] /= 2; - } - } - } - if ($develRate['comm'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_상업투자', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_상업투자'] = $intel / 2; - if (in_array($city['front'], [1, 3])) { - $commandList['che_상업투자'] /= 2; - } - } - } - if (!TechLimit($env['startyear'], $env['year'], $nation['tech'])) { - $commandObj = buildGeneralCommandClass('che_기술연구', $general, $env); - if ($commandObj->isRunnable()) { - if (!TechLimit($env['startyear'], $env['year'], $nation['tech'] + 1000)) { - //한등급 이상 뒤쳐져 있다면, 조금 더 열심히 하자. - $commandList['che_기술연구'] = $intel; - } else { - $commandList['che_기술연구'] = $intel / 4; - } - } - } - } - if ($genType & self::t통솔장) { - if ($develRate['trust'] < 1) { - $commandObj = buildGeneralCommandClass('che_주민선정', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_주민선정'] = pow($leadership * 2, 1 / ($develRate['trust'] + 0.001)); - } - } - if ($develRate['pop'] < 0.99) { - $commandObj = buildGeneralCommandClass('che_정착장려', $general, $env); - if ($commandObj->isRunnable()) { - $commandList['che_정착장려'] = $leadership / 2; - if (in_array($city['front'], [1, 3])) { - $commandList['che_정착장려'] *= 2; - } - } - } - } - - if (!$commandList) { - $cityFull = true; - } - - $genCount = DB::db()->queryFirstField('SELECT count(no) FROM general'); - $commandList['che_인재탐색'] = 500 / $genCount * 10; - if (in_array($city['front'], [1, 3])) { - $commandList['che_인재탐색'] /= 2; - } - - $commandList['che_물자조달'] = ( - (GameConst::$minNationalGold + GameConst::$minNationalRice + 24 * 5 * $env['develcost']) / - Util::valueFit($nation['gold'] + $nation['rice'], (GameConst::$defaultGold + GameConst::$defaultRice) / 2)); - - return [Util::choiceRandomUsingWeight($commandList), null]; - } - public function old_chooseNationTurn($command, $arg): array { $generalObj = $this->getGeneralObj(); @@ -2522,52 +2776,6 @@ class GeneralAI return [$command, $arg]; } - protected function chooseEndOfNPCTurn(): array - { - $general = $this->getGeneralObj(); - $city = $this->city; - $nation = $this->nation; - - if ($general->getVar('gold') + $general->getVar('rice') == 0) { - return ['che_물자조달', null]; - } - - [$baseArmGold, $baseArmRice] = $this->getBaseArmCost(); - - if ( - $this->dipState == self::d전쟁 && - $general->getVar('killturn') > 2 && - $general->getVar('gold') >= $baseArmGold / 3 && - $general->getVar('rice') >= $baseArmRice && - $general->getVar('crew') >= $general->getLeadership(false) / 3 - ) { - //사망 직전 마지막 불꽃 - $trainAndAtmos = $general->getVar('train') + $general->getVar('atmos'); - if ( - $trainAndAtmos >= 180 && - $city['front'] >= 2 && - $general->getVar('rice') >= $baseArmRice && - $nation['war'] == 0 - ) { - return $this->processAttack(); - } - - if (180 >= $trainAndAtmos && $trainAndAtmos >= 160) { - if ($general->getVar('train') < 80) { - return ['che_훈련', null]; - } else { - return ['che_사기진작', null]; - } - } - } - - if ($general->getVar('gold') >= $general->getVar('rice')) { - return ['che_헌납', ['isGold' => true, 'amount' => GameConst::$maxResourceActionAmount]]; - } else { - return ['che_헌납', ['isGold' => false, 'amount' => GameConst::$maxResourceActionAmount]]; - } - } - public function old_chooseGeneralTurn($command, $arg): array { $general = $this->getGeneralObj();