AI 천도 알고리즘
This commit is contained in:
+178
-2
@@ -3,8 +3,10 @@
|
|||||||
namespace sammo;
|
namespace sammo;
|
||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
|
use PDO;
|
||||||
use sammo\Command\GeneralCommand;
|
use sammo\Command\GeneralCommand;
|
||||||
use sammo\Command\NationCommand;
|
use sammo\Command\NationCommand;
|
||||||
|
use SplQueue;
|
||||||
|
|
||||||
class GeneralAI
|
class GeneralAI
|
||||||
{
|
{
|
||||||
@@ -30,6 +32,7 @@ class GeneralAI
|
|||||||
|
|
||||||
protected $dipState;
|
protected $dipState;
|
||||||
protected $warTargetNation;
|
protected $warTargetNation;
|
||||||
|
/** @var bool */
|
||||||
protected $attackable;
|
protected $attackable;
|
||||||
|
|
||||||
protected $devRate = null;
|
protected $devRate = null;
|
||||||
@@ -252,12 +255,185 @@ class GeneralAI
|
|||||||
// 군주 행동
|
// 군주 행동
|
||||||
protected function do선포(LastTurn $lastTurn): ?NationCommand
|
protected function do선포(LastTurn $lastTurn): ?NationCommand
|
||||||
{
|
{
|
||||||
return null;
|
$general = $this->general;
|
||||||
|
|
||||||
|
if($general->getVar('level') < 12){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->dipState !== self::d평화){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->attackable){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetNationID = $this->findWarTarget();
|
||||||
|
if($targetNationID === null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = buildNationCommandClass('che_선전포고', $general, $this->env, $lastTurn, [
|
||||||
|
'destNationID' => $targetNationID
|
||||||
|
]);
|
||||||
|
if(!$cmd->isRunnable()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function do천도(LastTurn $lastTurn): ?NationCommand
|
protected function do천도(LastTurn $lastTurn): ?NationCommand
|
||||||
{
|
{
|
||||||
return null;
|
$general = $this->general;
|
||||||
|
|
||||||
|
//천도를 한턴 넣었다면 계속 넣는다.
|
||||||
|
if($lastTurn->getCommand() === 'che_천도'){
|
||||||
|
$cmd = buildNationCommandClass('che_천도', $general, $this->env, $lastTurn, $lastTurn->getArg());
|
||||||
|
if($cmd->isRunnable()){
|
||||||
|
return $cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
도시 점수 공식
|
||||||
|
sqrt(인구) * sqrt(모든 아국 도시까지의 거리의 합) * sqrt(내정률)
|
||||||
|
가장 높은 도시로 이동하되, 상위 25% 내에 들었다면 정지
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//checkSupply()와 비슷하면서 다름
|
||||||
|
$db = DB::db();
|
||||||
|
$nationCityIDList = [];
|
||||||
|
foreach($db->queryFirstColumn('SELECT city FROM nation WHERE nation = %i', $general->getNationID()) as $cityID){
|
||||||
|
$nationCityIDList[$cityID] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//애초에 도시랄 것이 없음
|
||||||
|
if(count($nationCityIDList) <= 1){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$queue = new \SplQueue();
|
||||||
|
$capital = $this->nation['capital'];
|
||||||
|
$cityList = [
|
||||||
|
$capital=>0
|
||||||
|
];
|
||||||
|
|
||||||
|
$queue->enqueue($capital);
|
||||||
|
$distanceList = [
|
||||||
|
];
|
||||||
|
|
||||||
|
//수도와 연결된 도시 탐색
|
||||||
|
while(!$queue->isEmpty()){
|
||||||
|
$cityID = $queue->dequeue();
|
||||||
|
|
||||||
|
foreach(CityConst::byID($cityID)->path as $nextCityID){
|
||||||
|
if(!key_exists($cityID, $nationCityIDList)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(key_exists($cityID, $cityList)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cityList[$cityID] = 0;
|
||||||
|
$queue->enqueue($nextCityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cityList = array_keys($cityList);
|
||||||
|
|
||||||
|
//수도와 연결된 도시가 없음
|
||||||
|
if(count($cityList) == 1){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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){
|
||||||
|
$maxDistance = max($maxDistance, array_sum($subDistanceList));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cityScoreList = [];
|
||||||
|
foreach($db->query('SELECT city, pop, agri, agri_max, comm, comm_max, secu, secu_max, def, def_max, wall, wall_max') as $city){
|
||||||
|
$cityID = $city['city'];
|
||||||
|
|
||||||
|
$cityScoreList[$cityID] = sqrt($city['pop'])
|
||||||
|
* $maxDistance / array_sum($distanceList[$cityID])
|
||||||
|
* sqrt(
|
||||||
|
($city['agri'] + $city['comm'] + $city['secu'] + $city['def'] + $city['wall']) /
|
||||||
|
($city['agri_max'] + $city['comm_max'] + $city['secu_max'] + $city['def_max'] + $city['wall_max'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
arsort($cityScoreList);
|
||||||
|
|
||||||
|
$enoughLimit = ceil(count($cityScoreList) * 0.25);
|
||||||
|
foreach(array_keys($cityScoreList) as $idx=>$cityID){
|
||||||
|
if($idx > $enoughLimit){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if($idx === $capital){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalCityID = Util::array_first_key($cityScoreList);
|
||||||
|
$dist = $distanceList[$capital][$finalCityID];
|
||||||
|
$targetCityID = $finalCityID;
|
||||||
|
if($dist > 1){
|
||||||
|
$candidates = [];
|
||||||
|
foreach(CityConst::byID($capital)->path as $stopID){
|
||||||
|
if(!key_exists($stopID, $distanceList)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if($distanceList[$stopID][$finalCityID] + 1 === $dist){
|
||||||
|
$candidates[] = $stopID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$targetCityID = Util::choiceRandom($candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = buildNationCommandClass('che_천도', $general, $this->env, $lastTurn, [
|
||||||
|
'destCityID'=>$targetCityID
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(!$cmd->isRunnable()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
//일반장 행동
|
//일반장 행동
|
||||||
|
|||||||
Reference in New Issue
Block a user