AI턴 추가 구현

This commit is contained in:
2020-04-07 02:55:59 +09:00
parent 915243ee4a
commit 97fdc095a5
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;
}
/**
* 지정된 도시 내의 최단 거리를 계산해 줌(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;
+9
View File
@@ -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;
+23 -6
View File
@@ -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;
+26
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Command\GeneralCommand;
use sammo\WarUnitTrigger as WarUnitTrigger;
class General implements iAction{
@@ -266,6 +268,30 @@ class General implements iAction{
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']);
}
/**
* 장수의 스탯을 계산해옴
*
+444 -236
View File
@@ -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();