AI 작업중

This commit is contained in:
2020-04-04 04:52:18 +09:00
parent e6ea5824f0
commit 727e447899
2 changed files with 840 additions and 294 deletions
+4 -4
View File
@@ -16,7 +16,7 @@ class AutorunNationPolicy {
static $유저장긴급포상 = '유저장긴급포상';
static $유저장포상 = '유저장포상';
static $유저장몰수 = '유저장몰수';
//static $유저장몰수 = '유저장몰수';
static $NPC긴급포상 = 'NPC긴급포상';
static $NPC포상 = 'NPC포상';
@@ -42,7 +42,7 @@ class AutorunNationPolicy {
'유저장전방발령',
'유저장포상',
'유저장몰수',
//'유저장몰수',
'부대후방발령',
'NPC긴급포상',
@@ -81,7 +81,7 @@ class AutorunNationPolicy {
public $can유저장긴급포상 = true;
public $can유저장포상 = true;
public $can유저장몰수 = true;
//public $can유저장몰수 = true;
public $canNPC긴급포상 = true;
public $canNPC포상 = true;
@@ -112,7 +112,7 @@ class AutorunNationPolicy {
public $reqNPCDevelGold = 1000;
public $reqNPCDevelRice = 0;
public $minNPCWarLeadership = 40;
public $allowNpcAttackCity = true;
public $minNPCRecruitCityPopulation = 50000;
+836 -290
View File
@@ -2,6 +2,7 @@
namespace sammo;
use DateTimeImmutable;
use Generator;
use PDO;
use sammo\Command\GeneralCommand;
@@ -37,11 +38,24 @@ class GeneralAI
protected $devRate = null;
protected $nationCities;
protected $frontCities;
protected $supplyCities;
protected $backupCities;
/** @var General[] */
protected $nationGenerals;
/** @var General[] */
protected $npcCivilGenerals;
/** @var General[] */
protected $npcWarGenerals;
/** @var General[] */
protected $userGenerals;
/** @var General[] */
protected $lostGenerals;
/** @var General[] */
protected $troopLeaders;
const t무장 = 1;
const t지장 = 2;
@@ -81,11 +95,11 @@ class GeneralAI
'name' => '재야',
];
$this->leadership = $general->getLeadership(true, true, true, true);
$this->strength = $general->getStrength(true, true, true, true);
$this->intel = $general->getIntel(true, true, true, true);
$this->leadership = $general->getLeadership();
$this->strength = $general->getStrength();
$this->intel = $general->getIntel();
$this->calcGenType();
$this->genType = $this::calcGenType($general);
$this->calcDiplomacyState();
}
@@ -95,11 +109,11 @@ class GeneralAI
return $this->general;
}
protected function calcGenType()
protected static function calcGenType(General $general)
{
$leadership = $this->leadership;
$strength = Util::valueFit($this->strength, 1);
$intel = Util::valueFit($this->intel, 1);
$leadership = $general->getLeadership();
$strength = Util::valueFit($general->getStrength(), 1);
$intel = Util::valueFit($general->getIntel(), 1);
//무장
if ($strength >= $intel) {
@@ -123,7 +137,8 @@ class GeneralAI
if ($leadership >= 40) {
$genType |= self::t통솔장;
}
$this->genType = $genType;
return $genType;
}
protected function calcDiplomacyState()
@@ -222,33 +237,442 @@ class GeneralAI
protected function do유저장긴급포상(LastTurn $lastTurn): ?NationCommand
{
return null;
if(!$this->userGenerals){
return null;
}
$nation = $this->nation;
$candidateArgs = [];
$remainResource = [
'gold' => [
$nation['gold'],
$this->nationPolicy->reqHumanWarGold
],
'rice' => [
$nation['rice'],
$this->nationPolicy->reqHumanWarRice
]
];
$userGenerals = $this->userGenerals;
foreach($remainResource as $resName=>[$resVal,$reqHumanMinRes]){
usort($userGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
foreach($userGenerals 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) {
$reqMoney = max($reqMoney, $reqHumanMinRes);
}
$enoughMoney = $reqMoney * 1.5;
if ($targetUserGeneral->getVar($resName) >= $reqMoney) {
continue;
}
//국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetUserGeneral->getVar($resName)) * $resVal);
if ($resVal < $payAmount / 2) {
continue;
}
$candidateArgs[] = [[
'destGeneralID' => $targetUserGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount)
],
count($userGenerals)-$idx
];
}
}
return buildNationCommandClass(
'che_포상', $this->general, $this->env, $lastTurn,
Util::choiceRandomUsingWeightPair($candidateArgs)
);
}
protected function do유저장포상(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장몰수(LastTurn $lastTurn): ?NationCommand
{
return null;
}
if(!$this->userGenerals){
return null;
}
$nation = $this->nation;
$candidateArgs = [];
$remainResource = [
'gold' => [
$nation['gold'],
$this->nationPolicy->reqHumanWarGold,
$this->nationPolicy->reqHumanDevelGold,
],
'rice' => [
$nation['rice'],
$this->nationPolicy->reqHumanWarRice,
$this->nationPolicy->reqHumanDevelRice
]
];
$userGenerals = $this->userGenerals;
foreach($remainResource as $resName=>[$resVal,$reqHumanMinWarRes,$reqHumanMinDevelRes]){
usort($userGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
foreach($userGenerals as $idx=>$targetUserGeneral){
if($targetUserGeneral->getVar($resName) >= $reqHumanMinWarRes){
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($isWarGeneral){
$reqHumanMinRes = $reqHumanMinWarRes;
}
else{
$reqHumanMinRes = $reqHumanMinDevelRes;
}
$crewtype = $targetUserGeneral->getCrewTypeObj();
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetUserGeneral->getLeadership()) * 2 * 4 * 1.1;
if ($this->env['year'] > $this->env['startyear'] + 5) {
$reqMoney = max($reqMoney, $reqHumanMinRes);
}
$enoughMoney = $reqMoney * 1.5;
if ($targetUserGeneral->getVar($resName) >= $reqMoney) {
continue;
}
//국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetUserGeneral->getVar($resName)) * $resVal);
if ($resVal < $payAmount / 2) {
continue;
}
$candidateArgs[] = [[
'destGeneralID' => $targetUserGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount)
],
count($userGenerals)-$idx
];
}
}
return buildNationCommandClass(
'che_포상', $this->general, $this->env, $lastTurn,
Util::choiceRandomUsingWeightPair($candidateArgs)
);
}
protected function doNPC긴급포상(LastTurn $lastTurn): ?NationCommand
{
return null;
if(!$this->npcWarGenerals){
return null;
}
$nation = $this->nation;
$candidateArgs = [];
$remainResource = [
'gold' => [
$nation['gold'],
$this->nationPolicy->reqNPCWarGold
],
'rice' => [
$nation['rice'],
$this->nationPolicy->reqNPCWarRice
]
];
$npcWarGenerals = $this->npcWarGenerals;
foreach($remainResource as $resName=>[$resVal,$reqNPCMinWarRes]){
usort($npcWarGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
foreach($npcWarGenerals as $idx=>$targetNPCGeneral){
if($targetNPCGeneral->getVar($resName) >= $reqNPCMinWarRes){
break;
}
$crewtype = $targetNPCGeneral->getCrewTypeObj();
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNPCGeneral->getLeadership()) * 2 * 4 * 1.1;
if ($this->env['year'] > $this->env['startyear'] + 5) {
$reqMoney = max($reqMoney, $reqNPCMinWarRes);
}
$enoughMoney = $reqMoney * 1.5;
if ($targetNPCGeneral->getVar($resName) >= $reqMoney) {
continue;
}
//국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNPCGeneral->getVar($resName)) * $resVal);
if ($resVal < $payAmount / 2) {
continue;
}
$candidateArgs[] = [[
'destGeneralID' => $targetNPCGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount)
],
count($npcWarGenerals)-$idx
];
}
}
return buildNationCommandClass(
'che_포상', $this->general, $this->env, $lastTurn,
Util::choiceRandomUsingWeightPair($candidateArgs)
);
}
protected function doNPC포상(LastTurn $lastTurn): ?NationCommand
{
return null;
if(!$this->npcWarGenerals && !$this->npcCivilGenerals){
return null;
}
$nation = $this->nation;
$candidateArgs = [];
$remainResource = [
'gold' => [
$nation['gold'],
$this->nationPolicy->reqNPCWarGold,
$this->nationPolicy->reqNPCDevelGold
],
'rice' => [
$nation['rice'],
$this->nationPolicy->reqNPCWarRice,
$this->nationPolicy->reqNPCDevelRice
]
];
$npcWarGenerals = $this->npcWarGenerals;
$npcCivilGenerals = $this->npcCivilGenerals;
foreach($remainResource as $resName=>[$resVal,$reqNPCMinWarRes,$reqNPCMinDevelRes]){
usort($npcWarGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
foreach($npcWarGenerals as $idx=>$targetNPCGeneral){
if($targetNPCGeneral->getVar($resName) >= $reqNPCMinWarRes){
break;
}
$crewtype = $targetNPCGeneral->getCrewTypeObj();
$reqMoney = $crewtype->costWithTech($nation['tech'], $targetNPCGeneral->getLeadership()) * 2 * 4 * 1.1;
if ($this->env['year'] > $this->env['startyear'] + 5) {
$reqMoney = max($reqMoney, $reqNPCMinWarRes);
}
$enoughMoney = $reqMoney * 1.5;
if ($targetNPCGeneral->getVar($resName) >= $reqMoney) {
continue;
}
//국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNPCGeneral->getVar($resName)) * $resVal);
if ($resVal < $payAmount / 2) {
continue;
}
$candidateArgs[] = [[
'destGeneralID' => $targetNPCGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount)
],
max(count($npcWarGenerals), count($npcCivilGenerals))-$idx
];
}
usort($npcCivilGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
foreach($npcCivilGenerals as $idx=>$targetNPCGeneral){
if($targetNPCGeneral->getVar($resName) >= $reqNPCMinDevelRes){
break;
}
$enoughMoney = $reqNPCMinDevelRes * 1.5;
if ($targetNPCGeneral->getVar($resName) >= $reqMoney) {
continue;
}
$payAmount = $enoughMoney - $targetNPCGeneral->getVar($resName);
$payAmount = Util::valueFit($payAmount, 100, GameConst::$maxResourceActionAmount);
$candidateArgs[] = [[
'destGeneralID' => $targetNPCGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => $payAmount
],
max(count($npcWarGenerals), count($npcCivilGenerals)) - $idx
];
}
}
return buildNationCommandClass(
'che_포상', $this->general, $this->env, $lastTurn,
Util::choiceRandomUsingWeightPair($candidateArgs)
);
}
protected function doNPC몰수(LastTurn $lastTurn): ?NationCommand
{
return null;
if(!$this->npcWarGenerals && !$this->npcCivilGenerals){
return null;
}
$nation = $this->nation;
$candidateArgs = [];
$remainResource = [
'gold' => [
$nation['gold'],
$this->nationPolicy->reqNationGold,
$this->nationPolicy->reqNPCWarGold,
$this->nationPolicy->reqNPCDevelGold,
],
'rice' => [
$nation['rice'],
$this->nationPolicy->reqNationRice,
$this->nationPolicy->reqNPCWarRice,
$this->nationPolicy->reqNPCDevelRice,
]
];
$npcWarGenerals = $this->npcWarGenerals;
$npcCivilGenerals = $this->npcCivilGenerals;
foreach($remainResource as $resName=>[$resVal,$reqNationResVal,$reqNPCMinWarRes,$reqNPCMinDevelRes]){
usort($npcCivilGenerals, function ($lhs, $rhs) use ($resName) {
return -($lhs->getVar($resName) <=> $rhs->getVar($resName));
});
foreach($npcCivilGenerals as $idx=>$targetNPCGeneral){
if($targetNPCGeneral->getVar($resName) <= $reqNPCMinDevelRes + 100){
break;
}
$takeAmount = $targetNPCGeneral->getVar($resName) - $reqNPCMinDevelRes;
$takeAmount = Util::valueFit($takeAmount, 100, GameConst::$maxResourceActionAmount);
$candidateArgs[] = [[
'destGeneralID' => $targetNPCGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => $takeAmount
],
$takeAmount
];
}
//전투 NPC는 국고가 충분하지 않아보일때
$reqResValDelta = $reqNationResVal * 1.5 - $resVal;
if($reqResValDelta < 0){
continue;
}
if($resVal >= $reqNationResVal){
$willTakeSmallAmount = true;
}
else{
$willTakeSmallAmount = false;
}
usort($npcWarGenerals, function ($lhs, $rhs) use ($resName) {
return -($lhs->getVar($resName) <=> $rhs->getVar($resName));
});
foreach($npcWarGenerals as $idx=>$targetNPCGeneral){
if($willTakeSmallAmount){
if($targetNPCGeneral->getVar($resName) <= $reqNPCMinWarRes * 2){
break;
}
}
else if($targetNPCGeneral->getVar($resName) <= $reqNPCMinWarRes){
break;
}
if(!$willTakeSmallAmount){
$takeAmount = $targetNPCGeneral->getVar($resName) - $reqNPCMinWarRes;
$takeAmount = Util::valueFit(sqrt($takeAmount * $reqResValDelta), 0, $takeAmount);
}
else{
$maxTakeAmount = $targetNPCGeneral->getVar($resName) - $reqNPCMinWarRes;
$minTakeAmount = $targetNPCGeneral->getVar($resName) - $reqNPCMinWarRes * 2;
$takeAmount = Util::valueFit(sqrt($minTakeAmount * $reqResValDelta), 0, $maxTakeAmount);
}
if($takeAmount < 100){
break;
}
$takeAmount = Util::valueFit($takeAmount, 100, GameConst::$maxResourceActionAmount);
$candidateArgs[] = [[
'destGeneralID' => $targetNPCGeneral->getID(),
'isGold' => $resName == 'gold',
'amount' => $takeAmount
],
$takeAmount
];
}
}
return buildNationCommandClass(
'che_몰수', $this->general, $this->env, $lastTurn,
Util::choiceRandomUsingWeightPair($candidateArgs)
);
}
@@ -287,14 +711,34 @@ class GeneralAI
{
$general = $this->general;
$db = DB::db();
$nationStor = KVStorage::getStorage($db, 'nation_env');
$turnTerm = $this->env['turnterm'];
//천도를 한턴 넣었다면 계속 넣는다.
if($lastTurn->getCommand() === 'che_천도'){
$cmd = buildNationCommandClass('che_천도', $general, $this->env, $lastTurn, $lastTurn->getArg());
if($cmd->isRunnable()){
$nationStor->last천도Trial = [$general->getVar('level'), $general->getVar('turntime')];
return $cmd;
}
}
$lastTrial = $nationStor->last천도Trial;
if($lastTrial){
[$lastTrialLevel, $lastTrialTurnTime] = $lastTrial;
$timeDiffSeconds = TimeUtil::DateIntervalToSeconds(
date_create_immutable($lastTrialTurnTime)->diff(
date_create_immutable($general->getVar('turntime'))
)
);
if($timeDiffSeconds < $turnTerm * 30 && $lastTrialLevel !== $general->getVar('level')){ //0.5Turn
return null;
}
}
//TODO: 누군가 천도를 했는지 체크해야함.
/*
도시 점수 공식
sqrt(인구) * sqrt(모든 아국 도시까지의 거리의 합) * sqrt(내정률)
@@ -303,10 +747,11 @@ class GeneralAI
//checkSupply()와 비슷하면서 다름
$db = DB::db();
$nationCityIDList = [];
foreach($db->queryFirstColumn('SELECT city FROM nation WHERE nation = %i', $general->getNationID()) as $cityID){
$nationCityIDList[$cityID] = true;
$this->categorizeNationCities();
foreach($this->nationCities as $city){
$nationCityIDList[$city['city']] = true;
}
//애초에 도시랄 것이 없음
@@ -386,15 +831,10 @@ class GeneralAI
}
$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'];
foreach($cityList as $cityID){
$city = $this->nationCities[$cityID];
$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'])
);
$cityScoreList[$cityID] = sqrt($city['pop']) * $maxDistance / array_sum($distanceList[$cityID]) * sqrt($city['dev']);
}
arsort($cityScoreList);
@@ -432,7 +872,10 @@ class GeneralAI
if(!$cmd->isRunnable()){
return null;
}
$nationStor->last천도Trial = [$general->getVar('level'), $general->getVar('turntime')];
return $cmd;
}
@@ -460,7 +903,100 @@ class GeneralAI
protected function do징병(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
$general = $this->getGeneralObj();
$nation = $this->nation;
$env = $this->env;
$nationID = $general->getNationID();
$genType = $this->genType;
$tech = $nation['tech'];
$db = DB::db();
$dex = [
GameUnitConst::T_FOOTMAN => sqrt($general->getVar('dex1') + 500),
GameUnitConst::T_ARCHER => sqrt($general->getVar('dex2') + 500),
GameUnitConst::T_CAVALRY => sqrt($general->getVar('dex3') + 500),
GameUnitConst::T_WIZARD => sqrt($general->getVar('dex4') + 500),
GameUnitConst::T_SIEGE => sqrt($general->getVar('dex5') + 500),
];
$cities = [];
$regions = [];
foreach ($db->queryAllLists('SELECT city, region FROM city WHERE nation = %i', $nationID) as [$cityID, $regionID]) {
$cities[$cityID] = true;
$regions[$regionID] = true;
}
$relYear = Util::valueFit($env['year'] - $env['startyear'], 0);
$typesAll = [];
if ($genType & self::t무장) {
$types = [];
foreach (GameUnitConst::byType(GameUnitConst::T_FOOTMAN) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_FOOTMAN] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach (GameUnitConst::byType(GameUnitConst::T_ARCHER) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_ARCHER] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach (GameUnitConst::byType(GameUnitConst::T_CAVALRY) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_CAVALRY] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach ($types as [$crewtype, $score]) {
$typesAll[$crewtype] = [$crewtype, $score / count($types)];
}
}
if ($genType & self::t지장) {
$types = [];
foreach (GameUnitConst::byType(GameUnitConst::T_WIZARD) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_WIZARD] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach ($types as [$crewtype, $score]) {
$typesAll[$crewtype] = [$crewtype, $score / count($types)];
}
}
if ($typesAll) {
$type = Util::choiceRandomUsingWeightPair($types);
} else {
$type = GameUnitConst::DEFAULT_CREWTYPE;
}
//NOTE: 훈련과 사기진작은 '금만 사용한다'는 가정을 하고 있음
$obj훈련 = buildGeneralCommandClass('che_훈련', $general, $env);
$obj사기진작 = buildGeneralCommandClass('che_사기진작', $general, $env);
$gold = $general->getVar('gold');
$gold -= $obj훈련->getCost()[0] * 2;
$gold -= $obj사기진작->getCost()[0] * 2;
$cost = $general->getCrewTypeObj()->costWithTech($tech);
$cost = $general->onCalcDomestic('징병', 'cost', $cost);
$crew = intdiv($gold, $cost);
$crew *= 100;
return buildGeneralCommandClass('che_징병', $general, $env, [
'crewType' => $type,
'amount' => $crew
]);
}
protected function do전투준비(GeneralCommand $reservedCommand): ?GeneralCommand
@@ -476,7 +1012,39 @@ class GeneralAI
protected function do출병(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
$general = $this->getGeneralObj();
$city = $this->city;
$nation = $this->nation;
$cityID = $city['city'];
$nationID = $nation['nation'];
$db = DB::db();
if ($city['front'] <= 1) {
return null;
}
$attackableNations = [];
foreach ($this->warTargetNation as $targetNationID => $state) {
if ($state == 1) {
continue;
}
$attackableNations[] = $targetNationID;
}
$nearCities = array_keys(CityConst::byID($cityID)->path);
$attackableCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE nation IN %li AND city IN %li',
$attackableNations,
$nearCities
);
if (count($attackableCities) == 0) {
throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true));
}
return buildGeneralCommandClass('che_출병', $general, $this->env, ['destCityID' => Util::choiceRandom($attackableCities)])
}
@@ -523,6 +1091,16 @@ class GeneralAI
return null;
}
protected function do방랑군이동(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do거병(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do건국(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
@@ -535,21 +1113,172 @@ class GeneralAI
protected function do중립(GeneralCommand $reservedCommand): GeneralCommand
{
return buildGeneralCommandClass(null, $this->general, $this->env);
$general = $this->general;
if($general->getNationID() == 0){
return buildGeneralCommandClass('che_견문', $this->general, $this->env);
}
$candidate = [];
$nation = $this->nation;
if($nation['gold'] < $this->nationPolicy->reqNationGold){
$candidate[] = 'che_물자조달';
}
if($nation['rice'] < $this->nationPolicy->reqNationGold){
$candidate[] = 'che_물자조달';
}
$candidate[] = 'che_인재탐색';
return buildGeneralCommandClass(Util::choiceRandom($candidate), $this->general, $this->env);
}
protected function categorizeNationCities():void{
if($this->nationCities !== null){
return;
}
$nation = $this->nation;
$nationID = $nation['nation'];
$db = DB::db();
$nationCities = [];
$frontCities = [];
$supplyCities = [];
$backupCities = [];
foreach ($db->query('SELECT * FROM city WHERE nation = %i', $nationID) as $nationCity) {
$nationCity['generals'] = [];
$cityID = $nationCity['city'];
$dev =
($nationCity['agri'] + $nationCity['comm'] + $nationCity['secu'] + $nationCity['def'] + $nationCity['wall']) /
($nationCity['agri_max'] + $nationCity['comm_max'] + $nationCity['secu_max'] + $nationCity['def_max'] + $nationCity['wall_max']);
$nationCity['dev'] = $dev;
$importantScore = 1;
if ($nationCity['officer4']) {
$importantScore += 1;
}
if ($nationCity['officer3']) {
$importantScore += 1;
}
if ($nationCity['officer2']) {
$importantScore += 1;
}
$nationCity['important'] = $importantScore;
if($nationCity['supply']){
$supplyCities[$cityID] = &$nationCity;
}
if($nationCity['front']){
$frontCities[$cityID] = &$nationCity;
}
else{
$backupCities[$cityID] = &$nationCity;
}
$nationCities[$cityID] = &$nationCity;
}
$this->nationCities = $nationCities;
$this->frontCities = $frontCities;
$this->supplyCities = $supplyCities;
$this->backupCities = $backupCities;
}
protected function categorizeNationGeneral():void{
if($this->userGenerals !== null){
return;
}
$userGenerals = [];
$lostGenerals = [];
$npcCivilGenerals = [];
$npcWarGenerals = [];
$troopLeaders = [];
$nationID = $this->nation['nation'];
$this->categorizeNationCities();
$nationCities = &$this->nationCities;
$db = DB::db();
$generalIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation = %i AND no != %i', $nationID, $this->general->getID());
foreach(General::createGeneralObjListFromDB($generalIDList, null, 2) as $nationGeneral){
$generalID = $nationGeneral->getID();
$cityID = $nationGeneral->getCityID();
$npcType = $nationGeneral->getVar('npc');
if (key_exists($cityID, $nationCities)) {
$nationCities[$cityID]['generals'][$generalID] = $nationGeneral;
if (!$nationCities[$cityID]['supply']) {
$lostGenerals[$generalID] = $nationGeneral;
}
} else {
$lostGenerals[$generalID] = $nationGeneral;
}
if($npcType == 5){
//부대장임
$troopLeaders[$generalID] = $nationGeneral;
}
else if($nationGeneral->getVar('killturn') < 5){
//삭턴이 몇 안남은 장수는 '내정장 npc'로 처리
$npcCivilGenerals[$generalID] = $nationGeneral;
}
else if($npcType < 2) {
$userGenerals[$generalID] = $nationGeneral;
} else if ($nationGeneral->getLeadership() >= $this->nationPolicy->minNPCWarLeadership) {
$npcWarGenerals[$generalID] = $nationGeneral;
} else {
$npcCivilGenerals[$generalID] = $nationGeneral;
}
}
$this->userGenerals = $userGenerals;
$this->lostGenerals = $lostGenerals;
$this->npcCivilGenerals = $npcCivilGenerals;
$this->npcWarGenerals = $npcWarGenerals;
$this->troopLeaders = $troopLeaders;
}
public function chooseNationTurn(NationCommand $reservedCommand): NationCommand{
//TODO: NationTurn과 InstantNationTurn 구분 필요
$lastTurn = $reservedCommand->getLastTurn();
$general = $this->general;
if($general->getVar('level') == 12){
$month = $this->env['month'];
if (in_array($month, [1, 4, 7, 10])) {
$this->calcPromotion();
} else if ($month == 12) {
$this->calcTexRate();
$this->calcGoldBillRate();
} else if ($month == 6) {
$this->calcTexRate();
$this->calcRiceBillRate();
}
}
if($reservedCommand->isRunnable()){
return $reservedCommand;
}
$this->categorizeNationGeneral();
foreach($this->nationPolicy->priority as $actionName){
if(!$this->nationPolicy->{'can'.$actionName}){
continue;
}
/** @var ?NationCommand */
$result = $this->{'do'.$actionName}($lastTurn);
if($result !== null){
@@ -569,6 +1298,9 @@ class GeneralAI
if(!key_exists($actionName, $this->nationPolicy::$availableInstantTurn)){
continue;
}
if(!$this->nationPolicy->{'can'.$actionName}){
continue;
}
$result = $this->{'do'.$actionName}($reservedCommand);
if($result !== null){
return $result;
@@ -582,9 +1314,46 @@ class GeneralAI
return $reservedCommand;
}
foreach($this->nationPolicy->priority as $actionName){
/** @var ?GeneralCommand */
$result = $this->{'do'.$actionName}($reservedCommand);
$general = $this->general;
$npcType = $general->getVar('npc');
$nationID = $general->getNationID();
if($npcType == 5){
return $this->do집합($reservedCommand);
}
else if($npcType == 2 && $nationID == 0){
$result = $this->do거병($reservedCommand);
if($result !== null){
return $result;
}
}
if($nationID != 0){
if($npcType >= 2 && $general->getVar('level') == 12 && !$this->nation['capital']){
//방랑군 건국
$result = $this->do건국($reservedCommand);
if($result !== null){
return $result;
}
$result = $this->do방랑군이동($reservedCommand);
if($result !== null){
return $result;
}
}
foreach($this->generalPolicy->priority as $actionName){
if(!$this->generalPolicy->{'can'.$actionName}){
continue;
}
/** @var ?GeneralCommand */
$result = $this->{'do'.$actionName}($reservedCommand);
if($result !== null){
return $result;
}
}
}
else if(2 <= $npcType && $npcType <= 4){
$result = $this->do임관($reservedCommand);
if($result !== null){
return $result;
}
@@ -716,198 +1485,6 @@ class GeneralAI
return [Util::choiceRandomUsingWeight($commandList), null];
}
public function chooseRecruitCrewType(): ?array
{
$general = $this->getGeneralObj();
$city = $this->city;
$nation = $this->nation;
$env = $this->env;
$nationID = $general->getNationID();
$genType = $this->genType;
$leadership = $this->leadership;
$strength = $this->strength;
$intel = $this->intel;
$tech = $nation['tech'];
$db = DB::db();
$raw = $general->getRaw();
$dex = [
GameUnitConst::T_FOOTMAN => sqrt($general->getVar('dex0') + 500),
GameUnitConst::T_ARCHER => sqrt($general->getVar('dex10') + 500),
GameUnitConst::T_CAVALRY => sqrt($general->getVar('dex20') + 500),
GameUnitConst::T_WIZARD => sqrt($general->getVar('dex30') + 500),
GameUnitConst::T_SIEGE => sqrt($general->getVar('dex40') + 500),
];
$cities = [];
$regions = [];
foreach ($db->queryAllLists('SELECT city, region FROM city WHERE nation = %i', $nationID) as [$cityID, $regionID]) {
$cities[$cityID] = true;
$regions[$regionID] = true;
}
$relYear = Util::valueFit($env['year'] - $env['startyear'], 0);
$typesAll = [];
if ($genType & self::t무장) {
$types = [];
foreach (GameUnitConst::byType(GameUnitConst::T_FOOTMAN) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_FOOTMAN] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach (GameUnitConst::byType(GameUnitConst::T_ARCHER) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_ARCHER] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach (GameUnitConst::byType(GameUnitConst::T_CAVALRY) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_CAVALRY] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $dex[GameUnitConst::T_CAVALRY] + 500];
}
}
foreach ($types as [$crewtype, $score]) {
$typesAll[] = [$crewtype, $score / count($types)];
}
}
if ($genType & self::t지장) {
$types = [];
foreach (GameUnitConst::byType(GameUnitConst::T_WIZARD) as $crewtype) {
if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_WIZARD] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score];
}
}
foreach ($types as [$crewtype, $score]) {
$typesAll[] = [$crewtype, $score / count($types)];
}
}
if ($typesAll) {
$type = Util::choiceRandomUsingWeightPair($types);
} else {
$type = GameUnitConst::DEFAULT_CREWTYPE;
}
//NOTE: 훈련과 사기진작은 '금만 사용한다'는 가정을 하고 있음
$obj훈련 = buildGeneralCommandClass('che_훈련', $general, $env);
$obj사기진작 = buildGeneralCommandClass('che_사기진작', $general, $env);
$gold = $general->getVar('gold');
$gold -= $obj훈련->getCost()[0] * 2;
$gold -= $obj사기진작->getCost()[0] * 2;
$cost = $general->getCrewTypeObj()->costWithTech($tech);
$cost = $general->onCalcDomestic('징병', 'cost', $cost);
$crew = intdiv($gold, $cost);
$crew *= 100;
return ['che_징병', [
'crewType' => $type,
'amount' => $crew
]];
}
public function getPayTurnCandidates(string $resName): array
{
$nation = $this->nation;
$remainResource = ['gold' => $nation['gold'], 'rice' => $nation['rice']];
$candidateCommand = [];
//무지장 포상 여부
if ($this->npcCivilGenerals && $this->npcCivilGenerals[0]->$resName < $this->baseDevelCost) {
if ($this->remainResource[$resName] >= $this->baseDevelCost / 2) {
$candidateCommand[] = [
['che_포상', [
'destGeneralID' => $this->npcCivilGenerals[0]->id,
'isGold' => $resName == 'gold',
'amount' => round($this->baseDevelCost, -2)
]],
1
];
}
}
if ($this->npcWarGenerals) {
$targetNpcWarGeneral = Util::array_first($this->npcWarGenerals);
$crewtype = GameUnitConst::byID($targetNpcWarGeneral->crewtype);
//징병 2바퀴?
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2.2;
$enoughMoney = $reqMoney * 2;
if ($targetNpcWarGeneral->$resName < $reqMoney) {
//국고 1/5과 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * ($remainResource[$resName] / 5));
if ($remainResource[$resName] >= $payAmount / 2) {
$candidateCommand[] = [
['che_포상', [
'destGeneralID' => $targetNpcWarGeneral->id,
'isGold' => $resName == 'gold',
'amount' => $payAmount
]],
2
];
}
}
}
if ($this->userGenerals) {
$targetUserGeneral = Util::array_first($this->userGenerals);
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2 * 4 * 1.1;
if ($this->env['year'] > $this->env['startyear'] + 5) {
$reqMoney = max($reqMoney, 21000);
}
$enoughMoney = $reqMoney * 1.5;
if ($targetNpcWarGeneral->$resName < $reqMoney) {
//국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * $remainResource[$resName]);
if ($remainResource[$resName] >= $payAmount / 2) {
$candidateCommand[] = [
['che_포상', [
'destGeneralID' => $targetNpcWarGeneral->id,
'isGold' => $resName == 'gold',
'amount' => $payAmount
]],
2
];
}
}
}
if (is_array($this->npcCivilGenerals)) {
$targetNpcCivilGeneral = Util::array_last($this->npcCivilGenerals ?? []);
$tooMuchMoney = $this->baseDevelCost * 2.5;
$enoughMoney = $this->baseDevelCost * 1.5;
if ($targetNpcCivilGeneral->$resName >= $tooMuchMoney) {
$returnAmount = $targetNpcCivilGeneral->$resName - $enoughMoney;
$candidateCommand[] = [
['che_몰수', [
'destGeneralID' => $targetNpcCivilGeneral->id,
'isGold' => $resName == 'gold',
'amount' => $returnAmount
]],
3
];
}
}
return $candidateCommand;
}
public function old_chooseNationTurn($command, $arg): array
{
$generalObj = $this->getGeneralObj();
@@ -990,7 +1567,12 @@ class GeneralAI
$npcCivilGenerals = [];
$npcWarGenerals = [];
foreach ($db->query('SELECT `no`, nation, city, npc, `gold`, `rice`, leadership, `strength`, intel, killturn, crew, train, atmos, `level`, troop FROM general WHERE nation = %i', $nationID) as $rawNationGeneral) {
$generalIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation = %i', $nationID);
foreach(General::createGeneralObjListFromDB($generalIDList, null, 2) as $nationGeneral){
}
foreach ($db->query('SELECT `no`, nation, city, npc, `gold`, `rice`, leadership, `strength`, intel, killturn, crew, train, atmos, `level`, troop, recent_war FROM general WHERE nation = %i', $nationID) as $rawNationGeneral) {
$nationGeneral = (object) $rawNationGeneral;
$cityID = $nationGeneral->city;
@@ -1011,7 +1593,7 @@ class GeneralAI
if ($nationGeneral->npc < 2 && $nationGeneral->killturn >= 5) {
$userGenerals[] = $nationGeneral;
} else if ($nationGeneral->leadership >= 40 && $nationGeneral->killturn >= 5) {
} else if ($nationGeneral->getLeadership() >= 40 && $nationGeneral->killturn >= 5) {
$npcWarGenerals[] = $nationGeneral;
} else {
//삭턴이 몇 안남은 장수는 '내정장 npc'로 처리
@@ -1050,33 +1632,33 @@ class GeneralAI
$resName = Util::choiceRandom(['gold', 'rice']);
usort($userGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName;
usort($userGenerals, function (General $lhs, General $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
usort($npcWarGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName;
usort($npcWarGenerals, function (General $lhs, General $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
usort($npcCivilGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName;
usort($npcCivilGenerals, function (General $lhs, General $rhs) use ($resName) {
return $lhs->getVar($resName) <=> $rhs->getVar($resName);
});
$avgUserRes = 0;
foreach ($userGenerals as $userGeneral) {
$avgUserRes += $userGeneral->$resName;
$avgUserRes += $userGeneral->getVar($resName);
}
$avgUserRes /= max(1, count($userGenerals));
$avgNpcWarRes = 0;
foreach ($npcWarGenerals as $npcWarGeneral) {
$avgNpcWarRes += $npcWarGeneral->$resName;
$avgNpcWarRes += $npcWarGeneral->getVar($resName);
}
$avgNpcWarRes /= max(1, count($npcWarGenerals));
$avgNpcCivilRes = 0;
foreach ($npcCivilGenerals as $npcCivilGeneral) {
$avgNpcCivilRes += $npcCivilGeneral->$resName;
$avgNpcCivilRes += $npcCivilGeneral->getVar($resName);
}
$avgNpcCivilRes /= max(1, count($npcCivilGenerals));
@@ -1085,7 +1667,7 @@ class GeneralAI
$isWarUser = null;
foreach ($userGenerals as $compUser) {
if ($compUser->leadership >= 50) {
if ($compUser->getLeadership() >= 50) {
$isWarUser = true;
break;
}
@@ -1099,7 +1681,7 @@ class GeneralAI
break;
}
$compRes = $compUser->$resName;
$compRes = $compUser->getVar($resName);
$work = false;
if (!$isWarUser) {
@@ -1110,17 +1692,17 @@ class GeneralAI
$work = true;
}
if ((($isWarUser || $resName == 'gold') && $compUser->$resName < 21000) || ($compUser->$resName < 5000)) {
if ((($isWarUser || $resName == 'gold') && $compUser->getVar($resName) < 21000) || ($compUser->getVar($resName) < 5000)) {
if ($work) {
//TODO: 새로 구현한 코드로 이전
$amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 3000) * 1000 + 1000);
$amount = min(GameConst::$maxResourceActionAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 3000) * 1000 + 1000);
$commandList[] = [['che_포상', [
'destGeneralID' => $userGenerals[0]->no,
'isGold' => $resName == 'gold',
'amount' => $amount
]], 10]; // 금,쌀 1000단위 포상
} else {
$amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 5000) * 1000 + 1000);
$amount = min(GameConst::$maxResourceActionAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 5000) * 1000 + 1000);
$commandList[] = [['che_포상', [
'destGeneralID' => $userGenerals[0]->no,
'isGold' => $resName == 'gold',
@@ -1139,9 +1721,9 @@ class GeneralAI
$compNpcWar = Util::array_last($npcWarGenerals);
$compNpcCivil = Util::array_last($npcCivilGenerals);
$compUserRes = $compUser ? $compUser->$resName : 0;
$compNpcWarRes = $compNpcWar ? $compNpcWar->$resName * 5 : 0;
$compNpcCivilRes = $compNpcCivil ? $compNpcCivil->$resName * 10 : 0;
$compUserRes = $compUser ? $compUser->getVar($resName) : 0;
$compNpcWarRes = $compNpcWar ? $compNpcWar->getVar($resName) * 5 : 0;
$compNpcCivilRes = $compNpcCivil ? $compNpcCivil->getVar($resName) * 10 : 0;
[$compRes, $targetGeneral] = max(
[$compNpcCivilRes, $compNpcCivil],
@@ -1151,9 +1733,9 @@ class GeneralAI
if ($targetGeneral) {
if ($targetGeneral === $compNpcCivil) {
$amount = Util::round($targetGeneral->$resName - $minRes * 3, -2);
$amount = Util::round($targetGeneral->getVar($resName) - $minRes * 3, -2);
} else {
$amount = min(10000, intdiv($targetGeneral->$resName, 5000) * 1000 + 1000);
$amount = min(10000, intdiv($targetGeneral->getVar($resName), 5000) * 1000 + 1000);
}
if ($amount > 0) {
@@ -1165,6 +1747,7 @@ class GeneralAI
}
}
} else { // 포상
/** @var General */
$compNpcWar = Util::array_first($npcWarGenerals);
$compNpcCivil = null;
foreach ($npcCivilGenerals as $npcCivil) {
@@ -1175,7 +1758,7 @@ class GeneralAI
break;
}
if ($compNpcWar && $compNpcWar->$resName < 21000) {
if ($compNpcWar && $compNpcWar->getVar($resName) < 21000) {
$amount = min(100, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 5000) * 10 + 10) * 100;
$commandList[] = [['che_몰수', [
'destGeneralID' => $compNpcWar->no,
@@ -1183,7 +1766,7 @@ class GeneralAI
'amount' => $amount
]], 3];
}
if ($compNpcCivil && $compNpcCivil->$resName < $minRes) {
if ($compNpcCivil && $compNpcCivil->getVar($resName) < $minRes) {
$amount = intdiv($minRes + 99, 100);
$commandList[] = [['che_몰수', [
'destGeneralID' => $compNpcCivil->no,
@@ -1364,7 +1947,7 @@ class GeneralAI
if (!$generalCity['front']) {
continue;
}
if ($generalCity['pop'] - 33000 > $nationGeneral->leadership) {
if ($generalCity['pop'] - 33000 > $nationGeneral->getLeadership()) {
continue;
}
if ($nationGeneral->troop != 0) {
@@ -1379,7 +1962,7 @@ class GeneralAI
$popTrial = 5;
for ($popTrial = 0; $popTrial < 5; $popTrial++) {
$targetCity = $nationCities[Util::choiceRandom($backupCitiesID)];
if ($targetCity['pop'] < 33000 + $nationGeneral->leadership) {
if ($targetCity['pop'] < 33000 + $nationGeneral->getLeadership()) {
continue;
}
if (Util::randBool($targetCity['pop'] / $targetCity['pop_max'])) {
@@ -1910,43 +2493,6 @@ class GeneralAI
return null;
}
protected function processAttack(): array
{
$general = $this->getGeneralObj();
$city = $this->city;
$nation = $this->nation;
$cityID = $city['city'];
$nationID = $nation['nation'];
$db = DB::db();
if ($city['front'] <= 1) {
throw new \RuntimeException('출병 불가' . $cityID);
}
$attackableNations = [];
foreach ($this->warTargetNation as $targetNationID => $state) {
if ($state == 1) {
continue;
}
$attackableNations[] = $targetNationID;
}
$nearCities = array_keys(CityConst::byID($cityID)->path);
$attackableCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE nation IN %li AND city IN %li',
$attackableNations,
$nearCities
);
if (count($attackableCities) == 0) {
throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true));
}
return ['che_출병', ['destCityID' => Util::choiceRandom($attackableCities)]];
}
protected function proceessNeutralLordTurn(): array
{