AI턴 추가 구현

This commit is contained in:
2020-04-07 02:55:59 +09:00
parent 968655c9cd
commit 4e5caa7b8b
5 changed files with 572 additions and 242 deletions
+70
View File
@@ -1903,6 +1903,76 @@ function searchDistanceListToDest(int $from, int $to, array $linkNationList) {
return $result; 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) { function isNeighbor(int $nation1, int $nation2, bool $includeNoSupply=true) {
if($nation1 === $nation2){ if($nation1 === $nation2){
return false; return false;
+9
View File
@@ -22,6 +22,7 @@ class AutorunGeneralPolicy{
//static $NPC증여 = 'NPC증여'; //static $NPC증여 = 'NPC증여';
static $NPC헌납 = 'NPC헌납'; static $NPC헌납 = 'NPC헌납';
static $NPC사망대비 = 'NPC사망대비';
static $후방워프 = '후방워프'; static $후방워프 = '후방워프';
static $전방워프 = '전방워프'; static $전방워프 = '전방워프';
@@ -37,7 +38,9 @@ class AutorunGeneralPolicy{
static $선양 = '선양'; static $선양 = '선양';
static public $priority = [ static public $priority = [
'NPC사망대비',
'귀환', '귀환',
'금쌀구매', '금쌀구매',
'출병', '출병',
@@ -55,6 +58,7 @@ class AutorunGeneralPolicy{
]; ];
public $canNPC사망대비 = true;
public $can일반내정 = true; public $can일반내정 = true;
public $can긴급내정 = true; public $can긴급내정 = true;
public $can전쟁내정 = true; public $can전쟁내정 = true;
@@ -90,12 +94,17 @@ class AutorunGeneralPolicy{
$nationID = $general->getNationID(); $nationID = $general->getNationID();
if($npc==5){ if($npc==5){
$this->can집합 = true;
$this->can선양 = true; $this->can선양 = true;
$this->can집합 = true; $this->can집합 = true;
$this->can국가선택 = false; $this->can국가선택 = false;
return; return;
} }
if($npc==1){
$this->canNPC사망대비 = false;
}
if($nationID != 0){ if($nationID != 0){
$this->can국가선택 = false; $this->can국가선택 = false;
$this->can건국 = false; $this->can건국 = false;
+23 -6
View File
@@ -10,9 +10,13 @@ class AutorunNationPolicy {
static $부대유저장후방발령 = '부대유저장후방발령'; static $부대유저장후방발령 = '부대유저장후방발령';
static $유저장후방발령 = '유저장후방발령'; static $유저장후방발령 = '유저장후방발령';
static $유저장전방발령 = '유저장전방발령'; static $유저장전방발령 = '유저장전방발령';
static $유저장구출발령 = '유저장구출발령';
static $유저장내정발령 = '유저장내정발령';
static $NPC후방발령 = 'NPC후방발령'; static $NPC후방발령 = 'NPC후방발령';
static $NPC전방발령 = 'NPC전방발령'; static $NPC전방발령 = 'NPC전방발령';
static $NPC구출발령 = 'NPC구출발령';
static $NPC내정발령 = 'NPC내정발령';
static $유저장긴급포상 = '유저장긴급포상'; static $유저장긴급포상 = '유저장긴급포상';
static $유저장포상 = '유저장포상'; static $유저장포상 = '유저장포상';
@@ -35,6 +39,7 @@ class AutorunNationPolicy {
'유저장긴급포상', '유저장긴급포상',
'부대전방발령', '부대전방발령',
'유저장구출발령',
'유저장후방발령', '유저장후방발령',
'부대유저장후방발령', '부대유저장후방발령',
@@ -46,23 +51,30 @@ class AutorunNationPolicy {
'부대후방발령', '부대후방발령',
'NPC긴급포상', 'NPC긴급포상',
'NPC구출발령',
'NPC후방발령', 'NPC후방발령',
'NPC포상', 'NPC포상',
'NPC몰수', 'NPC몰수',
'NPC전방발령', 'NPC전방발령',
'유저장내정발령',
'NPC내정발령',
]; ];
//순서는 중요하지 않음 //순서는 중요하지 않음
static public $availableInstantTurn = [ static public $availableInstantTurn = [
'유저장긴급포상'=>true, '유저장긴급포상'=>true,
'유저장구출발령'=>true,
'유저장후방발령'=>true, '유저장후방발령'=>true,
'유저장전방발령'=>true, '유저장전방발령'=>true,
'유저장내정발령'=>true,
'유저장포상'=>true, '유저장포상'=>true,
'NPC긴급포상'=>true, 'NPC긴급포상'=>true,
'NPC구출발령'=>true,
'NPC후방발령'=>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 $can유저장전방발령 = true;
public $can유저장구출발령 = true;
public $can유저장내정발령 = true;
public $canNPC후방발령 = true; public $canNPC후방발령 = true;
public $canNPC전방발령 = true; public $canNPC전방발령 = true;
public $canNPC구출발령 = true;
public $canNPC내정발령 = true;
public $can유저장긴급포상 = true; public $can유저장긴급포상 = true;
public $can유저장포상 = true; public $can유저장포상 = true;
@@ -94,14 +110,14 @@ class AutorunNationPolicy {
public $reqNationGold = 10000; public $reqNationGold = 10000;
public $reqNationRice = 10000; public $reqNationRice = 10000;
public $CombatForce = [ public $CombatForce = [
[200, 10, 24],//troopLeader, fromCity, toCity //200 => [10, 24],//troopLeader, fromCity, toCity
[242, 10, 24] //242 => [10, 24]
]; ];
public $SupportForce = [ public $SupportForce = [
211 //211=>true
]; ];
public $DevelopForce = [ public $DevelopForce = [
123 //123=>true
]; ];
public $reqHumanWarGold = [10000, 30000]; public $reqHumanWarGold = [10000, 30000];
public $reqHumanWarRice = [10000, 30000]; public $reqHumanWarRice = [10000, 30000];
@@ -113,10 +129,11 @@ class AutorunNationPolicy {
public $reqNPCDevelRice = 0; public $reqNPCDevelRice = 0;
public $minNPCWarLeadership = 40; public $minNPCWarLeadership = 40;
public $minWarCrew = 1500;
public $allowNpcAttackCity = true; public $allowNpcAttackCity = true;
public $minNPCRecruitCityPopulation = 50000; public $minNPCRecruitCityPopulation = 50000;
public $safeRecruitCityPopulation = 50000; public $safeRecruitCityPopulationRatio = 0.5;
public $properWarTrainAtmos = 90; public $properWarTrainAtmos = 90;
+26
View File
@@ -1,6 +1,8 @@
<?php <?php
namespace sammo; namespace sammo;
use sammo\Command\GeneralCommand;
use sammo\WarUnitTrigger as WarUnitTrigger; use sammo\WarUnitTrigger as WarUnitTrigger;
class General implements iAction{ class General implements iAction{
@@ -266,6 +268,30 @@ class General implements iAction{
return GameUnitConst::byID($this->getVar('crewtype')); return GameUnitConst::byID($this->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']);
}
/** /**
* 장수의 스탯을 계산해옴 * 장수의 스탯을 계산해옴
* *
+442 -234
View File
@@ -51,6 +51,7 @@ class GeneralAI
protected $supplyCities; protected $supplyCities;
protected $backupCities; protected $backupCities;
protected $warRoute;
/** @var General[] */ /** @var General[] */
protected $nationGenerals; protected $nationGenerals;
@@ -60,6 +61,11 @@ class GeneralAI
protected $npcWarGenerals; protected $npcWarGenerals;
/** @var General[] */ /** @var General[] */
protected $userGenerals; protected $userGenerals;
/** @var General[] */
protected $userWarGenerals;
/** @var General[] */
protected $userCivilGenerals;
/** @var General[] */ /** @var General[] */
protected $lostGenerals; protected $lostGenerals;
/** @var General[] */ /** @var General[] */
@@ -81,7 +87,7 @@ class GeneralAI
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $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->baseDevelCost = $this->env['develcost'] * 12;
$this->general = $general; $this->general = $general;
if ($general->getRawCity() === null) { if ($general->getRawCity() === null) {
@@ -90,11 +96,14 @@ class GeneralAI
} }
$this->city = $general->getRawCity(); $this->city = $general->getRawCity();
$this->nation = $db->queryFirstRow( $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() $general->getNationID()
) ?? [ ) ?? [
'nation' => 0, 'nation' => 0,
'level' => 0, 'level' => 0,
'capital' => 0,
'capset' => false,
'gennum' => 0,
'tech' => 0, 'tech' => 0,
'gold' => 0, 'gold' => 0,
'rice' => 0, 'rice' => 0,
@@ -102,6 +111,7 @@ class GeneralAI
'color' => '#000000', 'color' => '#000000',
'name' => '재야', 'name' => '재야',
]; ];
$this->nation['aux'] = Json::decode($this->nation['aux']??'{}');
$this->leadership = $general->getLeadership(); $this->leadership = $general->getLeadership();
$this->strength = $general->getStrength(); $this->strength = $general->getStrength();
@@ -114,6 +124,12 @@ class GeneralAI
$this->genType = $this::calcGenType($general); $this->genType = $this::calcGenType($general);
$this->calcDiplomacyState(); $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 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 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 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 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 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; return null;
} }
@@ -235,17 +580,56 @@ class GeneralAI
return null; 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 protected function doNPC후방발령(LastTurn $lastTurn): ?NationCommand
{ {
return null; 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 protected function doNPC전방발령(LastTurn $lastTurn): ?NationCommand
{ {
return null; return null;
} }
protected function doNPC내정발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장긴급포상(LastTurn $lastTurn): ?NationCommand protected function do유저장긴급포상(LastTurn $lastTurn): ?NationCommand
{ {
@@ -267,29 +651,18 @@ class GeneralAI
]; ];
$userGenerals = $this->userGenerals; $userWarGenerals = $this->userWarGenerals;
foreach($remainResource as $resName=>[$resVal,$reqHumanMinRes]){ 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); return $lhs->getVar($resName) <=> $rhs->getVar($resName);
}); });
foreach($userGenerals as $idx=>$targetUserGeneral){ foreach($userWarGenerals as $idx=>$targetUserGeneral){
if($targetUserGeneral->getVar($resName) >= $reqHumanMinRes){ if($targetUserGeneral->getVar($resName) >= $reqHumanMinRes){
break; 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(); $crewtype = $targetUserGeneral->getCrewTypeObj();
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetUserGeneral->getLeadership()) * 2 * 4 * 1.1; $reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetUserGeneral->getLeadership()) * 2 * 4 * 1.1;
if ($this->env['year'] > $this->env['startyear'] + 5) { if ($this->env['year'] > $this->env['startyear'] + 5) {
@@ -312,7 +685,7 @@ class GeneralAI
'isGold' => $resName == 'gold', 'isGold' => $resName == 'gold',
'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount) 'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount)
], ],
count($userGenerals)-$idx count($userWarGenerals)-$idx
]; ];
} }
} }
@@ -359,15 +732,11 @@ class GeneralAI
break; break;
} }
$isWarGeneral = false; if(key_exists($targetUserGeneral->getID(), $this->userWarGenerals)){
if($targetUserGeneral->getVar('recent_war') !== null){ $isWarGeneral = true;
$recent_war = new \DateTimeImmutable($targetUserGeneral->getVar('recent_war')); }
$turnTime = new \DateTimeImmutable($targetUserGeneral->getVar('turntime')); else{
$timeDiffSecRecentWar = TimeUtil::DateIntervalToSeconds($recent_war->diff($turnTime)); $isWarGeneral = false;
if($timeDiffSecRecentWar <= $this->env['turnterm'] * 60 * 12){
$isWarGeneral = true;
}
} }
if($isWarGeneral){ if($isWarGeneral){
@@ -749,7 +1118,6 @@ class GeneralAI
return null; return null;
} }
} }
//TODO: 누군가 천도를 했는지 체크해야함.
/* /*
도시 점수 공식 도시 점수 공식
@@ -778,8 +1146,6 @@ class GeneralAI
]; ];
$queue->enqueue($capital); $queue->enqueue($capital);
$distanceList = [
];
//수도와 연결된 도시 탐색 //수도와 연결된 도시 탐색
while(!$queue->isEmpty()){ while(!$queue->isEmpty()){
@@ -804,38 +1170,8 @@ class GeneralAI
return null; return null;
} }
//기본 간선 $distanceList = searchAllDistanceByCityList($cityList);
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;
}
//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; $maxDistance = 0;
foreach($distanceList as $cityID=>$subDistanceList){ foreach($distanceList as $cityID=>$subDistanceList){
@@ -1310,7 +1646,6 @@ class GeneralAI
} }
$cmdList = []; $cmdList = [];
$general = $this->general; $general = $this->general;
$cmd사기진작 = buildGeneralCommandClass('che_사기진작', $general, $this->env);
$train = $general->getVar('train'); $train = $general->getVar('train');
$atmos = $general->getVar('atmos'); $atmos = $general->getVar('atmos');
@@ -1558,6 +1893,37 @@ class GeneralAI
return null; 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 protected function do중립(GeneralCommand $reservedCommand): GeneralCommand
{ {
$general = $this->general; $general = $this->general;
@@ -1645,6 +2011,9 @@ class GeneralAI
return; return;
} }
$userGenerals = []; $userGenerals = [];
$userCivilGenerals = [];
$userWarGenerals = [];
$lostGenerals = []; $lostGenerals = [];
$npcCivilGenerals = []; $npcCivilGenerals = [];
$npcWarGenerals = []; $npcWarGenerals = [];
@@ -1682,6 +2051,12 @@ class GeneralAI
} }
else if($npcType < 2) { else if($npcType < 2) {
$userGenerals[$generalID] = $nationGeneral; $userGenerals[$generalID] = $nationGeneral;
if($nationGeneral->calcRecentWarTurn($this->env['turnterm']) <= 12){
$userWarGenerals[$generalID] = $nationGeneral;
}
else{
$userCivilGenerals[$generalID] = $nationGeneral;
}
} else if ($nationGeneral->getLeadership() >= $this->nationPolicy->minNPCWarLeadership) { } else if ($nationGeneral->getLeadership() >= $this->nationPolicy->minNPCWarLeadership) {
$npcWarGenerals[$generalID] = $nationGeneral; $npcWarGenerals[$generalID] = $nationGeneral;
} else { } else {
@@ -1690,6 +2065,8 @@ class GeneralAI
} }
$this->userGenerals = $userGenerals; $this->userGenerals = $userGenerals;
$this->userCivilGenerals = $userCivilGenerals;
$this->userWarGenerals = $userWarGenerals;
$this->lostGenerals = $lostGenerals; $this->lostGenerals = $lostGenerals;
$this->npcCivilGenerals = $npcCivilGenerals; $this->npcCivilGenerals = $npcCivilGenerals;
$this->npcWarGenerals = $npcWarGenerals; $this->npcWarGenerals = $npcWarGenerals;
@@ -1808,129 +2185,6 @@ class GeneralAI
return $this->do중립($reservedCommand); 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 public function old_chooseNationTurn($command, $arg): array
{ {
$generalObj = $this->getGeneralObj(); $generalObj = $this->getGeneralObj();
@@ -2522,52 +2776,6 @@ class GeneralAI
return [$command, $arg]; 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 public function old_chooseGeneralTurn($command, $arg): array
{ {
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();