AI 뼈대 다시 잡는중

This commit is contained in:
2020-03-31 02:50:07 +09:00
parent b397c41c9e
commit aa7d01f3d6
4 changed files with 850 additions and 613 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ class AutorunGeneralPolicy{
static $한계징병 = '한계징병'; static $한계징병 = '한계징병';
static $고급병종 = '고급병종'; static $고급병종 = '고급병종';
static $전투준비 = '전투준비'; static $전투준비 = '전투준비';
public $소집해제 = '소집해제'; static $소집해제 = '소집해제';
static $출병 = '출병'; static $출병 = '출병';
+14
View File
@@ -26,6 +26,8 @@ class AutorunNationPolicy {
static $선포 = '선포'; static $선포 = '선포';
static $천도 = '천도'; static $천도 = '천도';
//실제 행동 //실제 행동
static public $defaultPriority = [ static public $defaultPriority = [
'선포', '선포',
@@ -53,6 +55,18 @@ class AutorunNationPolicy {
'NPC전방발령', 'NPC전방발령',
]; ];
//순서는 중요하지 않음
static public $availableInstantTurn = [
'유저장긴급포상'=>true,
'유저장후방발령'=>true,
'유저장전방발령'=>true,
'유저장포상'=>true,
'NPC긴급포상'=>true,
'NPC후방발령'=>true,
'NPC포상'=>true,
'NPC전방발령'=>true,
];
public $priority = []; public $priority = [];
public $can부대전방발령 = true; public $can부대전방발령 = true;
+821 -597
View File
@@ -1,7 +1,13 @@
<?php <?php
namespace sammo; namespace sammo;
class GeneralAI{ use Generator;
use sammo\Command\GeneralCommand;
use sammo\Command\NationCommand;
class GeneralAI
{
/** /**
* @var General $general * @var General $general
*/ */
@@ -27,7 +33,7 @@ class GeneralAI{
protected $attackable; protected $attackable;
protected $devRate = null; protected $devRate = null;
protected $nationGenerals; protected $nationGenerals;
protected $npcCivilGenerals; protected $npcCivilGenerals;
protected $npcWarGenerals; protected $npcWarGenerals;
@@ -46,13 +52,14 @@ class GeneralAI{
//수뇌용 //수뇌용
public function __construct(General $general){ public function __construct(General $general)
{
$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']);
$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) {
$city = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $general->getCityID()); $city = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $general->getCityID());
$general->setRawCity($city); $general->setRawCity($city);
} }
@@ -60,15 +67,15 @@ class GeneralAI{
$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,level,capital,tech,gold,rice,rate,type,color,name,war FROM nation WHERE nation = %i',
$general->getNationID() $general->getNationID()
)??[ ) ?? [
'nation'=>0, 'nation' => 0,
'level'=>0, 'level' => 0,
'tech'=>0, 'tech' => 0,
'gold'=>0, 'gold' => 0,
'rice'=>0, 'rice' => 0,
'type'=>GameConst::$neutralNationType, 'type' => GameConst::$neutralNationType,
'color'=>'#000000', 'color' => '#000000',
'name'=>'재야', 'name' => '재야',
]; ];
$this->leadership = $general->getLeadership(true, true, true, true); $this->leadership = $general->getLeadership(true, true, true, true);
@@ -78,14 +85,15 @@ class GeneralAI{
$this->calcGenType(); $this->calcGenType();
$this->calcDiplomacyState(); $this->calcDiplomacyState();
} }
public function getGeneralObj():General{ public function getGeneralObj(): General
{
return $this->general; return $this->general;
} }
protected function calcGenType(){ protected function calcGenType()
{
$leadership = $this->leadership; $leadership = $this->leadership;
$strength = Util::valueFit($this->strength, 1); $strength = Util::valueFit($this->strength, 1);
$intel = Util::valueFit($this->intel, 1); $intel = Util::valueFit($this->intel, 1);
@@ -94,16 +102,15 @@ class GeneralAI{
if ($strength >= $intel) { if ($strength >= $intel) {
$genType = self::t무장; $genType = self::t무장;
if ($intel >= $strength * 0.8) { //무지장 if ($intel >= $strength * 0.8) { //무지장
if(Util::randBool($intel / $strength / 2)){ if (Util::randBool($intel / $strength / 2)) {
$genType |= self::t지장; $genType |= self::t지장;
} }
} }
//지장 //지장
} else { } else {
$genType = self::t지장; $genType = self::t지장;
if ($strength >= $intel * 0.8 && Util::randBool(0.2)) { //지무장 if ($strength >= $intel * 0.8 && Util::randBool(0.2)) { //지무장
if(Util::randBool($strength / $intel / 2)){ if (Util::randBool($strength / $intel / 2)) {
$genType |= self::t무장; $genType |= self::t무장;
} }
} }
@@ -116,12 +123,13 @@ class GeneralAI{
$this->genType = $genType; $this->genType = $genType;
} }
protected function calcDiplomacyState(){ protected function calcDiplomacyState()
{
$db = DB::db(); $db = DB::db();
$nationID = $this->general->getNationID(); $nationID = $this->general->getNationID();
$env = $this->env; $env = $this->env;
if(Util::joinYearMonth($env['year'], $env['month']) <= Util::joinYearMonth($env['startyear']+2, 5)){ if (Util::joinYearMonth($env['year'], $env['month']) <= Util::joinYearMonth($env['startyear'] + 2, 5)) {
$this->dipState = self::d평화; $this->dipState = self::d평화;
$this->attackable = false; $this->attackable = false;
return; return;
@@ -129,7 +137,7 @@ class GeneralAI{
$frontStatus = $db->queryFirstField('SELECT max(front) FROM city WHERE nation=%i AND supply=1', $nationID); $frontStatus = $db->queryFirstField('SELECT max(front) FROM city WHERE nation=%i AND supply=1', $nationID);
// 공격가능도시 있으면 // 공격가능도시 있으면
$this->attackable = ($frontStatus !== null)?$frontStatus:false; $this->attackable = ($frontStatus !== null) ? $frontStatus : false;
$warTarget = $db->queryAllLists( $warTarget = $db->queryAllLists(
'SELECT you, state FROM diplomacy WHERE me = %i AND (state = 0 OR (state = 1 AND term < 5))', 'SELECT you, state FROM diplomacy WHERE me = %i AND (state = 0 OR (state = 1 AND term < 5))',
@@ -138,45 +146,279 @@ class GeneralAI{
$onWar = false; $onWar = false;
$warTargetNation = []; $warTargetNation = [];
foreach($warTarget as [$warNationID, $warState]){ foreach ($warTarget as [$warNationID, $warState]) {
if($warState == 0){ if ($warState == 0) {
$onWar = true; $onWar = true;
$warTargetNation[$warNationID] = 2; $warTargetNation[$warNationID] = 2;
} } else {
else{
$warTargetNation[$warNationID] = 1; $warTargetNation[$warNationID] = 1;
} }
} }
$warTargetNation[0] = 0; $warTargetNation[0] = 0;
$this->warTargetNation = $warTargetNation; $this->warTargetNation = $warTargetNation;
$minWarTerm = $db->queryFirstField('SELECT min(term) FROM diplomacy WHERE me = %i AND state=1', $nationID); $minWarTerm = $db->queryFirstField('SELECT min(term) FROM diplomacy WHERE me = %i AND state=1', $nationID);
if($minWarTerm === null){ if ($minWarTerm === null) {
$this->dipState = self::d평화; $this->dipState = self::d평화;
} } else if ($minWarTerm > 8) {
else if($minWarTerm > 8){
$this->dipState = self::d선포; $this->dipState = self::d선포;
} } else if ($minWarTerm > 5) {
else if($minWarTerm > 5){
$this->dipState = self::d징병; $this->dipState = self::d징병;
} } else {
else{
$this->dipState = self::d직전; $this->dipState = self::d직전;
} }
if($this->attackable){ if ($this->attackable) {
//전쟁으로 인한 attackable인가? //전쟁으로 인한 attackable인가?
if($onWar){ if ($onWar) {
$this->dipState = self::d전쟁; $this->dipState = self::d전쟁;
} } else {
else{
$this->dipState = self::d징병; $this->dipState = self::d징병;
} }
} }
} }
protected function chooseDevelopTurn(bool &$cityFull):array{ protected function do부대전방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do부대후방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do부대유저장후방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장후방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장전방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function doNPC후방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function doNPC전방발령(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장긴급포상(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장포상(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do유저장몰수(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function doNPC긴급포상(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function doNPC포상(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function doNPC몰수(LastTurn $lastTurn): ?NationCommand
{
return null;
}
// 군주 행동
protected function do선포(LastTurn $lastTurn): ?NationCommand
{
return null;
}
protected function do천도(LastTurn $lastTurn): ?NationCommand
{
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;
}
protected function do금쌀구매(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do징병(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do전투준비(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
public function do소집해제(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do출병(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function doNPC증여(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function doNPC헌납(GeneralCommand $reservedCommand): ?GeneralCommand
{
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;
}
protected function do귀환(GeneralCommand $reservedCommand): ?GeneralCommand
{
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;
}
protected function do선양(GeneralCommand $reservedCommand): ?GeneralCommand
{
return null;
}
protected function do중립(GeneralCommand $reservedCommand): GeneralCommand
{
return buildGeneralCommandClass(null, $this->general, $this->env);
}
public function chooseNationTurn(NationCommand $reservedCommand): NationCommand{
//TODO: NationTurn과 InstantNationTurn 구분 필요
$lastTurn = $reservedCommand->getLastTurn();
if($reservedCommand->isRunnable()){
return $reservedCommand;
}
foreach($this->nationPolicy->priority as $actionName){
/** @var ?NationCommand */
$result = $this->{'do'.$actionName}($lastTurn);
if($result !== null){
return $result;
}
}
return buildNationCommandClass(null, $this->general, $this->env, $this->general->getLastTurn());
}
public function chooseInstantNationTurn(NationCommand $reservedCommand): ?NationCommand{
if($reservedCommand->isRunnable()){
return $reservedCommand;
}
foreach($this->nationPolicy->priority as $actionName){
/** @var ?NationCommand */
if(!key_exists($actionName, $this->nationPolicy::$availableInstantTurn)){
continue;
}
$result = $this->{'do'.$actionName}($reservedCommand);
if($result !== null){
return $result;
}
}
return buildNationCommandClass(null, $this->general, $this->env, $this->general->getLastTurn());
}
public function chooseGeneralTurn(GeneralCommand $reservedCommand): GeneralCommand{
if($reservedCommand->isRunnable()){
return $reservedCommand;
}
foreach($this->nationPolicy->priority as $actionName){
/** @var ?GeneralCommand */
$result = $this->{'do'.$actionName}($reservedCommand);
if($result !== null){
return $result;
}
}
return $this->do중립($reservedCommand);
}
protected function chooseDevelopTurn(bool &$cityFull): array
{
$general = $this->general; $general = $this->general;
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
@@ -190,118 +432,116 @@ class GeneralAI{
$cityFull = false; $cityFull = false;
$develRate = [ $develRate = [
'trust'=>$city['trust'], 'trust' => $city['trust'],
'pop'=>$city['pop']/$city['pop_max'], 'pop' => $city['pop'] / $city['pop_max'],
'agri'=>$city['agri']/$city['agri_max'], 'agri' => $city['agri'] / $city['agri_max'],
'comm'=>$city['comm']/$city['comm_max'], 'comm' => $city['comm'] / $city['comm_max'],
'secu'=>$city['secu']/$city['secu_max'], 'secu' => $city['secu'] / $city['secu_max'],
'def'=>$city['def']/$city['def_max'], 'def' => $city['def'] / $city['def_max'],
'wall'=>$city['wall']/$city['wall_max'], 'wall' => $city['wall'] / $city['wall_max'],
]; ];
// 우선 선정 // 우선 선정
if($develRate['trust'] < 1 && Util::randBool($leadership / 60)){ if ($develRate['trust'] < 1 && Util::randBool($leadership / 60)) {
return ['che_주민선정', null]; return ['che_주민선정', null];
} }
$commandList = []; $commandList = [];
if($genType & self::t무장){ if ($genType & self::t무장) {
if($develRate['secu'] < 0.99){ if ($develRate['secu'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_치안강화', $general, $env); $commandObj = buildGeneralCommandClass('che_치안강화', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_치안강화'] = $strength / 3; $commandList['che_치안강화'] = $strength / 3;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_치안강화'] /= 2; $commandList['che_치안강화'] /= 2;
} }
} }
} }
if($develRate['def'] < 0.99){ if ($develRate['def'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_수비강화', $general, $env); $commandObj = buildGeneralCommandClass('che_수비강화', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_수비강화'] = $strength / 3; $commandList['che_수비강화'] = $strength / 3;
} }
} }
if($develRate['wall'] < 0.99){ if ($develRate['wall'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_성벽보수', $general, $env); $commandObj = buildGeneralCommandClass('che_성벽보수', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_성벽보수'] = $strength / 3; $commandList['che_성벽보수'] = $strength / 3;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_성벽보수'] /= 2; $commandList['che_성벽보수'] /= 2;
} }
} }
} }
} }
if($genType & self::t지장){ if ($genType & self::t지장) {
if($develRate['agri'] < 0.99){ if ($develRate['agri'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_농지개간', $general, $env); $commandObj = buildGeneralCommandClass('che_농지개간', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_농지개간'] = $intel / 2; $commandList['che_농지개간'] = $intel / 2;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_농지개간'] /= 2; $commandList['che_농지개간'] /= 2;
} }
} }
} }
if($develRate['comm'] < 0.99){ if ($develRate['comm'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_상업투자', $general, $env); $commandObj = buildGeneralCommandClass('che_상업투자', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_상업투자'] = $intel / 2; $commandList['che_상업투자'] = $intel / 2;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_상업투자'] /= 2; $commandList['che_상업투자'] /= 2;
} }
} }
} }
if(!TechLimit($env['startyear'], $env['year'], $nation['tech'])){ if (!TechLimit($env['startyear'], $env['year'], $nation['tech'])) {
$commandObj = buildGeneralCommandClass('che_기술연구', $general, $env); $commandObj = buildGeneralCommandClass('che_기술연구', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
if(!TechLimit($env['startyear'], $env['year'], $nation['tech']+1000)){ if (!TechLimit($env['startyear'], $env['year'], $nation['tech'] + 1000)) {
//한등급 이상 뒤쳐져 있다면, 조금 더 열심히 하자. //한등급 이상 뒤쳐져 있다면, 조금 더 열심히 하자.
$commandList['che_기술연구'] = $intel; $commandList['che_기술연구'] = $intel;
} } else {
else{
$commandList['che_기술연구'] = $intel / 4; $commandList['che_기술연구'] = $intel / 4;
} }
} }
} }
} }
if($genType & self::t통솔장){ if ($genType & self::t통솔장) {
if($develRate['trust'] < 1){ if ($develRate['trust'] < 1) {
$commandObj = buildGeneralCommandClass('che_주민선정', $general, $env); $commandObj = buildGeneralCommandClass('che_주민선정', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_주민선정'] = pow($leadership * 2, 1 / ($develRate['trust'] + 0.001)); $commandList['che_주민선정'] = pow($leadership * 2, 1 / ($develRate['trust'] + 0.001));
} }
} }
if($develRate['pop'] < 0.99){ if ($develRate['pop'] < 0.99) {
$commandObj = buildGeneralCommandClass('che_정착장려', $general, $env); $commandObj = buildGeneralCommandClass('che_정착장려', $general, $env);
if($commandObj->isRunnable()){ if ($commandObj->isRunnable()) {
$commandList['che_정착장려'] = $leadership / 2; $commandList['che_정착장려'] = $leadership / 2;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_정착장려'] *= 2; $commandList['che_정착장려'] *= 2;
} }
} }
} }
} }
if(!$commandList){ if (!$commandList) {
$cityFull = true; $cityFull = true;
} }
$genCount = DB::db()->queryFirstField('SELECT count(no) FROM general'); $genCount = DB::db()->queryFirstField('SELECT count(no) FROM general');
$commandList['che_인재탐색'] = 500 / $genCount * 10; $commandList['che_인재탐색'] = 500 / $genCount * 10;
if(in_array($city['front'], [1,3])){ if (in_array($city['front'], [1, 3])) {
$commandList['che_인재탐색'] /= 2; $commandList['che_인재탐색'] /= 2;
} }
$commandList['che_물자조달'] = ( $commandList['che_물자조달'] = (
(GameConst::$minNationalGold + GameConst::$minNationalRice + 24*5*$env['develcost']) / (GameConst::$minNationalGold + GameConst::$minNationalRice + 24 * 5 * $env['develcost']) /
Util::valueFit($nation['gold'] + $nation['rice'], (GameConst::$defaultGold + GameConst::$defaultRice)/2) Util::valueFit($nation['gold'] + $nation['rice'], (GameConst::$defaultGold + GameConst::$defaultRice) / 2));
);
return [Util::choiceRandomUsingWeight($commandList), null]; return [Util::choiceRandomUsingWeight($commandList), null];
} }
public function chooseRecruitCrewType():?array{ public function chooseRecruitCrewType(): ?array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
@@ -317,74 +557,73 @@ class GeneralAI{
$tech = $nation['tech']; $tech = $nation['tech'];
$db = DB::db(); $db = DB::db();
$raw = $general->getRaw(); $raw = $general->getRaw();
$dex = [ $dex = [
GameUnitConst::T_FOOTMAN=>sqrt($general->getVar('dex0') + 500), GameUnitConst::T_FOOTMAN => sqrt($general->getVar('dex0') + 500),
GameUnitConst::T_ARCHER=>sqrt($general->getVar('dex10') + 500), GameUnitConst::T_ARCHER => sqrt($general->getVar('dex10') + 500),
GameUnitConst::T_CAVALRY=>sqrt($general->getVar('dex20') + 500), GameUnitConst::T_CAVALRY => sqrt($general->getVar('dex20') + 500),
GameUnitConst::T_WIZARD=>sqrt($general->getVar('dex30') + 500), GameUnitConst::T_WIZARD => sqrt($general->getVar('dex30') + 500),
GameUnitConst::T_SIEGE=>sqrt($general->getVar('dex40') + 500), GameUnitConst::T_SIEGE => sqrt($general->getVar('dex40') + 500),
]; ];
$cities = []; $cities = [];
$regions = []; $regions = [];
foreach($db->queryAllLists('SELECT city, region FROM city WHERE nation = %i', $nationID) as [$cityID, $regionID]){ foreach ($db->queryAllLists('SELECT city, region FROM city WHERE nation = %i', $nationID) as [$cityID, $regionID]) {
$cities[$cityID] = true; $cities[$cityID] = true;
$regions[$regionID] = true; $regions[$regionID] = true;
} }
$relYear = Util::valueFit($env['year'] - $env['startyear'], 0); $relYear = Util::valueFit($env['year'] - $env['startyear'], 0);
$typesAll = []; $typesAll = [];
if($genType & self::t무장){ if ($genType & self::t무장) {
$types = []; $types = [];
foreach(GameUnitConst::byType(GameUnitConst::T_FOOTMAN) as $crewtype){ foreach (GameUnitConst::byType(GameUnitConst::T_FOOTMAN) as $crewtype) {
if($crewtype->isValid($cities, $regions, $relYear, $tech)){ if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_FOOTMAN] * $crewtype->pickScore($tech); $score = $dex[GameUnitConst::T_FOOTMAN] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score]; $types[] = [$crewtype->id, $score];
} }
} }
foreach(GameUnitConst::byType(GameUnitConst::T_ARCHER) as $crewtype){ foreach (GameUnitConst::byType(GameUnitConst::T_ARCHER) as $crewtype) {
if($crewtype->isValid($cities, $regions, $relYear, $tech)){ if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_ARCHER] * $crewtype->pickScore($tech); $score = $dex[GameUnitConst::T_ARCHER] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $score]; $types[] = [$crewtype->id, $score];
} }
} }
foreach(GameUnitConst::byType(GameUnitConst::T_CAVALRY) as $crewtype){ foreach (GameUnitConst::byType(GameUnitConst::T_CAVALRY) as $crewtype) {
if($crewtype->isValid($cities, $regions, $relYear, $tech)){ if ($crewtype->isValid($cities, $regions, $relYear, $tech)) {
$score = $dex[GameUnitConst::T_CAVALRY] * $crewtype->pickScore($tech); $score = $dex[GameUnitConst::T_CAVALRY] * $crewtype->pickScore($tech);
$types[] = [$crewtype->id, $dex[GameUnitConst::T_CAVALRY] + 500]; $types[] = [$crewtype->id, $dex[GameUnitConst::T_CAVALRY] + 500];
} }
} }
foreach($types as [$crewtype, $score]){ 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)]; $typesAll[] = [$crewtype, $score / count($types)];
} }
} }
if($typesAll){ if ($genType & self::t지장) {
$type = Util::choiceRandomUsingWeightPair($types); $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)];
}
} }
else{
if ($typesAll) {
$type = Util::choiceRandomUsingWeightPair($types);
} else {
$type = GameUnitConst::DEFAULT_CREWTYPE; $type = GameUnitConst::DEFAULT_CREWTYPE;
} }
//NOTE: 훈련과 사기진작은 '금만 사용한다'는 가정을 하고 있음 //NOTE: 훈련과 사기진작은 '금만 사용한다'는 가정을 하고 있음
$obj훈련 = buildGeneralCommandClass('che_훈련', $general, $env); $obj훈련 = buildGeneralCommandClass('che_훈련', $general, $env);
$obj사기진작 = buildGeneralCommandClass('che_사기진작', $general, $env); $obj사기진작 = buildGeneralCommandClass('che_사기진작', $general, $env);
@@ -395,96 +634,95 @@ class GeneralAI{
$cost = $general->getCrewTypeObj()->costWithTech($tech); $cost = $general->getCrewTypeObj()->costWithTech($tech);
$cost = $general->onCalcDomestic('징병', 'cost', $cost); $cost = $general->onCalcDomestic('징병', 'cost', $cost);
$crew = intdiv($gold, $cost); $crew = intdiv($gold, $cost);
$crew *= 100; $crew *= 100;
return ['che_징병', [ return ['che_징병', [
'crewType'=>$type, 'crewType' => $type,
'amount'=>$crew 'amount' => $crew
]]; ]];
} }
public function getPayTurnCandidates(string $resName):array{ public function getPayTurnCandidates(string $resName): array
{
$nation = $this->nation; $nation = $this->nation;
$remainResource = ['gold'=>$nation['gold'], 'rice'=>$nation['rice']]; $remainResource = ['gold' => $nation['gold'], 'rice' => $nation['rice']];
$candidateCommand = []; $candidateCommand = [];
//무지장 포상 여부 //무지장 포상 여부
if($this->npcCivilGenerals && $this->npcCivilGenerals[0]->$resName < $this->baseDevelCost){ if ($this->npcCivilGenerals && $this->npcCivilGenerals[0]->$resName < $this->baseDevelCost) {
if($this->remainResource[$resName] >= $this->baseDevelCost / 2){ if ($this->remainResource[$resName] >= $this->baseDevelCost / 2) {
$candidateCommand[] = [ $candidateCommand[] = [
['che_포상', [ ['che_포상', [
'destGeneralID' => $this->npcCivilGenerals[0]->id, 'destGeneralID' => $this->npcCivilGenerals[0]->id,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>round($this->baseDevelCost,-2) 'amount' => round($this->baseDevelCost, -2)
]], ]],
1 1
]; ];
} }
} }
if($this->npcWarGenerals){ if ($this->npcWarGenerals) {
$targetNpcWarGeneral = Util::array_first($this->npcWarGenerals); $targetNpcWarGeneral = Util::array_first($this->npcWarGenerals);
$crewtype = GameUnitConst::byID($targetNpcWarGeneral->crewtype); $crewtype = GameUnitConst::byID($targetNpcWarGeneral->crewtype);
//징병 2바퀴? //징병 2바퀴?
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2.2; $reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2.2;
$enoughMoney = $reqMoney * 2; $enoughMoney = $reqMoney * 2;
if($targetNpcWarGeneral->$resName < $reqMoney){ if ($targetNpcWarGeneral->$resName < $reqMoney) {
//국고 1/5과 '충분한 금액'의 기하평균 //국고 1/5과 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * ($remainResource[$resName] / 5)); $payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * ($remainResource[$resName] / 5));
if ($remainResource[$resName] >= $payAmount / 2) { if ($remainResource[$resName] >= $payAmount / 2) {
$candidateCommand[] = [ $candidateCommand[] = [
['che_포상', [ ['che_포상', [
'destGeneralID' => $targetNpcWarGeneral->id, 'destGeneralID' => $targetNpcWarGeneral->id,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$payAmount 'amount' => $payAmount
]], ]],
2 2
]; ];
} }
} }
} }
if($this->userGenerals){ if ($this->userGenerals) {
$targetUserGeneral = Util::array_first($this->userGenerals); $targetUserGeneral = Util::array_first($this->userGenerals);
$reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2*4*1.1; $reqMoney = $crewtype->costWithTech($this->nation['tech'], $targetNpcWarGeneral->leadership) * 2 * 4 * 1.1;
if($this->env['year'] > $this->env['startyear'] + 5){ if ($this->env['year'] > $this->env['startyear'] + 5) {
$reqMoney = max($reqMoney, 21000); $reqMoney = max($reqMoney, 21000);
} }
$enoughMoney = $reqMoney * 1.5; $enoughMoney = $reqMoney * 1.5;
if($targetNpcWarGeneral->$resName < $reqMoney){ if ($targetNpcWarGeneral->$resName < $reqMoney) {
//국고와 '충분한 금액'의 기하평균 //국고와 '충분한 금액'의 기하평균
$payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * $remainResource[$resName]); $payAmount = sqrt(($enoughMoney - $targetNpcWarGeneral->$resName) * $remainResource[$resName]);
if($remainResource[$resName] >= $payAmount / 2){ if ($remainResource[$resName] >= $payAmount / 2) {
$candidateCommand[] = [ $candidateCommand[] = [
['che_포상', [ ['che_포상', [
'destGeneralID' => $targetNpcWarGeneral->id, 'destGeneralID' => $targetNpcWarGeneral->id,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$payAmount 'amount' => $payAmount
]], ]],
2 2
]; ];
} }
} }
} }
if(is_array($this->npcCivilGenerals)){ if (is_array($this->npcCivilGenerals)) {
$targetNpcCivilGeneral = Util::array_last($this->npcCivilGenerals??[]); $targetNpcCivilGeneral = Util::array_last($this->npcCivilGenerals ?? []);
$tooMuchMoney = $this->baseDevelCost * 2.5; $tooMuchMoney = $this->baseDevelCost * 2.5;
$enoughMoney = $this->baseDevelCost * 1.5; $enoughMoney = $this->baseDevelCost * 1.5;
if($targetNpcCivilGeneral->$resName >= $tooMuchMoney){ if ($targetNpcCivilGeneral->$resName >= $tooMuchMoney) {
$returnAmount = $targetNpcCivilGeneral->$resName - $enoughMoney; $returnAmount = $targetNpcCivilGeneral->$resName - $enoughMoney;
$candidateCommand[] = [ $candidateCommand[] = [
['che_몰수', [ ['che_몰수', [
'destGeneralID' => $targetNpcCivilGeneral->id, 'destGeneralID' => $targetNpcCivilGeneral->id,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$returnAmount 'amount' => $returnAmount
]], ]],
3 3
]; ];
@@ -494,7 +732,8 @@ class GeneralAI{
return $candidateCommand; return $candidateCommand;
} }
public function chooseNationTurn($command, $arg):array{ public function old_chooseNationTurn($command, $arg): array
{
$generalObj = $this->getGeneralObj(); $generalObj = $this->getGeneralObj();
//NOTE: 수뇌 턴에서는 general과 city이름의 충돌 가능성이 있음 //NOTE: 수뇌 턴에서는 general과 city이름의 충돌 가능성이 있음
$env = $this->env; $env = $this->env;
@@ -505,24 +744,24 @@ class GeneralAI{
$db = DB::db(); $db = DB::db();
if($generalObj->getVar('npc') == 5){ if ($generalObj->getVar('npc') == 5) {
return [$command, $arg]; return [$command, $arg];
} }
if($command && $command != '휴식'){ if ($command && $command != '휴식') {
return [$command, $arg]; return [$command, $arg];
} }
if($this->nation['level'] == 0){ if ($this->nation['level'] == 0) {
return ['휴식', null]; return ['휴식', null];
} }
if($generalObj->getVar('level') == 12 && $this->dipState == self::d평화 && !$this->attackable){ if ($generalObj->getVar('level') == 12 && $this->dipState == self::d평화 && !$this->attackable) {
$targetNationID = $this->findWarTarget(); $targetNationID = $this->findWarTarget();
if($targetNationID !== null){ if ($targetNationID !== null) {
return ['che_선전포고', ['destNationID'=>$targetNationID]]; return ['che_선전포고', ['destNationID' => $targetNationID]];
} }
} }
@@ -537,25 +776,24 @@ class GeneralAI{
foreach ($db->query('SELECT * FROM city WHERE nation = %i', $nationID) as $nationCity) { foreach ($db->query('SELECT * FROM city WHERE nation = %i', $nationID) as $nationCity) {
$nationCity['generals'] = []; $nationCity['generals'] = [];
$cityID = $nationCity['city']; $cityID = $nationCity['city'];
$dev = $dev =
($nationCity['agri'] + $nationCity['comm'] + $nationCity['secu'] + $nationCity['def'] + $nationCity['wall'])/ ($nationCity['agri'] + $nationCity['comm'] + $nationCity['secu'] + $nationCity['def'] + $nationCity['wall']) /
($nationCity['agri'] + $nationCity['comm'] + $nationCity['secu'] + $nationCity['def'] + $nationCity['wall']); ($nationCity['agri'] + $nationCity['comm'] + $nationCity['secu'] + $nationCity['def'] + $nationCity['wall']);
$dev += $nationCity['pop'] / $nationCity['pop_max']; $dev += $nationCity['pop'] / $nationCity['pop_max'];
$dev /= 50; $dev /= 50;
$nationCity['dev'] = $dev; $nationCity['dev'] = $dev;
$nationCities[$cityID] = $nationCity; $nationCities[$cityID] = $nationCity;
if($nationCity['supply']){ if ($nationCity['supply']) {
$supplyCitiesID[] = $cityID; $supplyCitiesID[] = $cityID;
if($nationCity['front']){ if ($nationCity['front']) {
$frontCitiesID[] = $cityID; $frontCitiesID[] = $cityID;
if($nationCity['officer4']){ if ($nationCity['officer4']) {
$frontImportantCitiesID[] = $cityID; $frontImportantCitiesID[] = $cityID;
} }
} } else {
else{
$backupCitiesID[] = $cityID; $backupCitiesID[] = $cityID;
} }
} }
@@ -566,47 +804,44 @@ class GeneralAI{
assert($supplyCitiesID); assert($supplyCitiesID);
$commandList = []; $commandList = [];
$userGenerals = []; $userGenerals = [];
$lostGenerals = []; $lostGenerals = [];
$npcCivilGenerals = []; $npcCivilGenerals = [];
$npcWarGenerals = []; $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) {
$nationGeneral = (object)$rawNationGeneral; 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) {
$nationGeneral = (object) $rawNationGeneral;
$cityID = $nationGeneral->city; $cityID = $nationGeneral->city;
$generalID = $nationGeneral->no; $generalID = $nationGeneral->no;
if($generalID == $chiefID){ if ($generalID == $chiefID) {
continue; continue;
} }
if(key_exists($cityID, $nationCities)){ if (key_exists($cityID, $nationCities)) {
$nationCities[$cityID]['generals'][] = $nationGeneral; $nationCities[$cityID]['generals'][] = $nationGeneral;
if(!$nationCities[$cityID]['supply']){ if (!$nationCities[$cityID]['supply']) {
$lostGenerals[] = $nationGeneral; $lostGenerals[] = $nationGeneral;
} }
} } else {
else{
$lostGenerals[] = $nationGeneral; $lostGenerals[] = $nationGeneral;
} }
if($nationGeneral->npc<2 && $nationGeneral->killturn >= 5){ if ($nationGeneral->npc < 2 && $nationGeneral->killturn >= 5) {
$userGenerals[] = $nationGeneral; $userGenerals[] = $nationGeneral;
} } else if ($nationGeneral->leadership >= 40 && $nationGeneral->killturn >= 5) {
else if($nationGeneral->leadership>=40 && $nationGeneral->killturn >= 5){
$npcWarGenerals[] = $nationGeneral; $npcWarGenerals[] = $nationGeneral;
} } else {
else{
//삭턴이 몇 안남은 장수는 '내정장 npc'로 처리 //삭턴이 몇 안남은 장수는 '내정장 npc'로 처리
$npcCivilGenerals[] = $nationGeneral; $npcCivilGenerals[] = $nationGeneral;
} }
$nationGenerals[$generalID] = $nationGeneral; $nationGenerals[$generalID] = $nationGeneral;
} }
Util::shuffle_assoc($nationGenerals); Util::shuffle_assoc($nationGenerals);
@@ -617,403 +852,391 @@ class GeneralAI{
shuffle($lostGenerals); shuffle($lostGenerals);
$this->lostGenerals = $lostGenerals; $this->lostGenerals = $lostGenerals;
uasort($nationCities, function($lhs, $rhs){ uasort($nationCities, function ($lhs, $rhs) {
//키 순서를 지키지 않지만, 원래부터 random order를 목표로 하므로 크게 신경쓰지 않는다. //키 순서를 지키지 않지만, 원래부터 random order를 목표로 하므로 크게 신경쓰지 않는다.
return count($lhs['generals']) <=> count($rhs['generals']); return count($lhs['generals']) <=> count($rhs['generals']);
}); });
//타 도시에 있는 '유저장' 발령 //타 도시에 있는 '유저장' 발령
foreach($lostGenerals as $lostGeneral){ foreach ($lostGenerals as $lostGeneral) {
if($lostGeneral->npc < 2){ if ($lostGeneral->npc < 2) {
if(in_array($this->dipState, [self::d직전, self::d전쟁]) && $frontCitiesID){ if (in_array($this->dipState, [self::d직전, self::d전쟁]) && $frontCitiesID) {
$selCityID = Util::choiceRandom($frontCitiesID); $selCityID = Util::choiceRandom($frontCitiesID);
} } else {
else{
$selCityID = Util::choiceRandom($supplyCitiesID); $selCityID = Util::choiceRandom($supplyCitiesID);
} }
$commandList[] = [['che_발령', ['destGeneralID'=>$lostGeneral->no, 'destCityID'=>$selCityID]], 200]; $commandList[] = [['che_발령', ['destGeneralID' => $lostGeneral->no, 'destCityID' => $selCityID]], 200];
} }
} }
$resName = Util::choiceRandom(['gold', 'rice']); $resName = Util::choiceRandom(['gold', 'rice']);
usort($userGenerals, function($lhs, $rhs) use ($resName){ usort($userGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName; return $lhs->$resName <=> $rhs->$resName;
}); });
usort($npcWarGenerals, function($lhs, $rhs) use ($resName){ usort($npcWarGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName; return $lhs->$resName <=> $rhs->$resName;
}); });
usort($npcCivilGenerals, function($lhs, $rhs) use ($resName){ usort($npcCivilGenerals, function ($lhs, $rhs) use ($resName) {
return $lhs->$resName <=> $rhs->$resName; return $lhs->$resName <=> $rhs->$resName;
}); });
$avgUserRes = 0; $avgUserRes = 0;
foreach ($userGenerals as $userGeneral){ foreach ($userGenerals as $userGeneral) {
$avgUserRes += $userGeneral->$resName; $avgUserRes += $userGeneral->$resName;
} }
$avgUserRes /= max(1, count($userGenerals)); $avgUserRes /= max(1, count($userGenerals));
$avgNpcWarRes = 0; $avgNpcWarRes = 0;
foreach ($npcWarGenerals as $npcWarGeneral){ foreach ($npcWarGenerals as $npcWarGeneral) {
$avgNpcWarRes += $npcWarGeneral->$resName; $avgNpcWarRes += $npcWarGeneral->$resName;
} }
$avgNpcWarRes /= max(1, count($npcWarGenerals)); $avgNpcWarRes /= max(1, count($npcWarGenerals));
$avgNpcCivilRes = 0; $avgNpcCivilRes = 0;
foreach ($npcCivilGenerals as $npcCivilGeneral){ foreach ($npcCivilGenerals as $npcCivilGeneral) {
$avgNpcCivilRes += $npcCivilGeneral->$resName; $avgNpcCivilRes += $npcCivilGeneral->$resName;
} }
$avgNpcCivilRes /= max(1, count($npcCivilGenerals)); $avgNpcCivilRes /= max(1, count($npcCivilGenerals));
//금쌀이 부족한 '유저장' 먼저 포상 //금쌀이 부족한 '유저장' 먼저 포상
while ($nation[$resName] > ($resName=='gold'?1:2)*3000 && $userGenerals) { while ($nation[$resName] > ($resName == 'gold' ? 1 : 2) * 3000 && $userGenerals) {
$isWarUser = null; $isWarUser = null;
foreach($userGenerals as $compUser){ foreach ($userGenerals as $compUser) {
if($compUser->leadership >= 50){ if ($compUser->leadership >= 50) {
$isWarUser = true; $isWarUser = true;
break; break;
} }
if(Util::randBool(0.2)){ if (Util::randBool(0.2)) {
$isWarUser = false; $isWarUser = false;
break; break;
} }
} }
if($isWarUser === null){ if ($isWarUser === null) {
break; break;
} }
$compRes = $compUser->$resName; $compRes = $compUser->$resName;
$work = false; $work = false;
if(!$isWarUser){ if (!$isWarUser) {
$work = false; $work = false;
} else if ($compRes < $avgNpcWarRes*3) { } else if ($compRes < $avgNpcWarRes * 3) {
$work = true; $work = true;
} elseif ($compRes < $avgNpcCivilRes * 4) { } elseif ($compRes < $avgNpcCivilRes * 4) {
$work = true; $work = true;
} }
if((($isWarUser || $resName == 'gold') && $compUser->$resName < 21000) || ($compUser->$resName < 5000)){ if ((($isWarUser || $resName == 'gold') && $compUser->$resName < 21000) || ($compUser->$resName < 5000)) {
if($work){ if ($work) {
//TODO: 새로 구현한 코드로 이전 //TODO: 새로 구현한 코드로 이전
$amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName]-($resName=='rice'?(GameConst::$baserice):(GameConst::$basegold))), 3000)*1000 + 1000); $amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 3000) * 1000 + 1000);
$commandList[] = [['che_포상', [ $commandList[] = [['che_포상', [
'destGeneralID'=>$userGenerals[0]->no, 'destGeneralID' => $userGenerals[0]->no,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$amount 'amount' => $amount
]], 10]; // 금,쌀 1000단위 포상 ]], 10]; // 금,쌀 1000단위 포상
} } else {
else{ $amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 5000) * 1000 + 1000);
$amount = min(GameConst::$maxIncentiveAmount, intdiv(($nation[$resName]-($resName=='rice'?(GameConst::$baserice):(GameConst::$basegold))), 5000)*1000 + 1000);
$commandList[] = [['che_포상', [ $commandList[] = [['che_포상', [
'destGeneralID'=>$userGenerals[0]->no, 'destGeneralID' => $userGenerals[0]->no,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$amount 'amount' => $amount
]], 1]; // 금,쌀 1000단위 포상 ]], 1]; // 금,쌀 1000단위 포상
} }
} }
break; break;
} }
$minRes = $env['develcost'] * 24 * $tech; $minRes = $env['develcost'] * 24 * $tech;
if($nation[$resName] < ($resName=='gold'?1:2)*3000) { // 몰수 if ($nation[$resName] < ($resName == 'gold' ? 1 : 2) * 3000) { // 몰수
// 몰수 대상 // 몰수 대상
$compUser = Util::array_last($userGenerals); $compUser = Util::array_last($userGenerals);
$compNpcWar = Util::array_last($npcWarGenerals); $compNpcWar = Util::array_last($npcWarGenerals);
$compNpcCivil = Util::array_last($npcCivilGenerals); $compNpcCivil = Util::array_last($npcCivilGenerals);
$compUserRes = $compUser?$compUser->$resName:0; $compUserRes = $compUser ? $compUser->$resName : 0;
$compNpcWarRes = $compNpcWar?$compNpcWar->$resName*5:0; $compNpcWarRes = $compNpcWar ? $compNpcWar->$resName * 5 : 0;
$compNpcCivilRes = $compNpcCivil?$compNpcCivil->$resName*10:0; $compNpcCivilRes = $compNpcCivil ? $compNpcCivil->$resName * 10 : 0;
[$compRes, $targetGeneral] = max( [$compRes, $targetGeneral] = max(
[$compNpcCivilRes, $compNpcCivil], [$compNpcCivilRes, $compNpcCivil],
[$compNpcWarRes, $compNpcWar], [$compNpcWarRes, $compNpcWar],
[$compUserRes, $compUser] [$compUserRes, $compUser]
); );
if($targetGeneral){ if ($targetGeneral) {
if($targetGeneral === $compNpcCivil){ if ($targetGeneral === $compNpcCivil) {
$amount = Util::round($targetGeneral->$resName - $minRes * 3, -2); $amount = Util::round($targetGeneral->$resName - $minRes * 3, -2);
} else {
$amount = min(10000, intdiv($targetGeneral->$resName, 5000) * 1000 + 1000);
} }
else{
$amount = min(10000, intdiv($targetGeneral->$resName, 5000)*1000 + 1000); if ($amount > 0) {
}
if($amount > 0){
$commandList[] = [['che_몰수', [ $commandList[] = [['che_몰수', [
'destGeneralID'=>$targetGeneral->no, 'destGeneralID' => $targetGeneral->no,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$amount 'amount' => $amount
]], 3]; ]], 3];
} }
} }
} else{ // 포상 } else { // 포상
$compNpcWar = Util::array_first($npcWarGenerals); $compNpcWar = Util::array_first($npcWarGenerals);
$compNpcCivil = null; $compNpcCivil = null;
foreach($npcCivilGenerals as $npcCivil){ foreach ($npcCivilGenerals as $npcCivil) {
if($npcCivil->npc == 5){ if ($npcCivil->npc == 5) {
continue; continue;
} }
$compNpcCivil = $npcCivil; $compNpcCivil = $npcCivil;
break; break;
} }
if($compNpcWar && $compNpcWar->$resName < 21000){ if ($compNpcWar && $compNpcWar->$resName < 21000) {
$amount = min(100, intdiv(($nation[$resName]-($resName=='rice'?(GameConst::$baserice):(GameConst::$basegold))), 5000)*10 + 10)*100; $amount = min(100, intdiv(($nation[$resName] - ($resName == 'rice' ? (GameConst::$baserice) : (GameConst::$basegold))), 5000) * 10 + 10) * 100;
$commandList[] = [['che_몰수', [ $commandList[] = [['che_몰수', [
'destGeneralID'=>$compNpcWar->no, 'destGeneralID' => $compNpcWar->no,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$amount 'amount' => $amount
]], 3]; ]], 3];
} }
if($compNpcCivil && $compNpcCivil->$resName < $minRes){ if ($compNpcCivil && $compNpcCivil->$resName < $minRes) {
$amount = intdiv($minRes+99, 100); $amount = intdiv($minRes + 99, 100);
$commandList[] = [['che_몰수', [ $commandList[] = [['che_몰수', [
'destGeneralID'=>$compNpcCivil->no, 'destGeneralID' => $compNpcCivil->no,
'isGold'=>$resName=='gold', 'isGold' => $resName == 'gold',
'amount'=>$amount 'amount' => $amount
]], 2]; ]], 2];
} }
} }
//고립 도시 장수 발령 //고립 도시 장수 발령
foreach($lostGenerals as $lostGeneral){ foreach ($lostGenerals as $lostGeneral) {
if($lostGeneral->npc<2){ if ($lostGeneral->npc < 2) {
//고립 유저 장수는 이미 세팅했음 //고립 유저 장수는 이미 세팅했음
continue; continue;
} }
if(in_array($this->dipState, [self::d직전, self::d전쟁]) && $frontCitiesID){ if (in_array($this->dipState, [self::d직전, self::d전쟁]) && $frontCitiesID) {
$selCityID = Util::choiceRandom($frontCitiesID); $selCityID = Util::choiceRandom($frontCitiesID);
} } else {
else{
$selCityID = Util::choiceRandom($supplyCitiesID); $selCityID = Util::choiceRandom($supplyCitiesID);
} }
//고립된 장수가 많을 수록 발령 확률 증가 //고립된 장수가 많을 수록 발령 확률 증가
$commandList[] = [['che_발령', [ $commandList[] = [['che_발령', [
'destGeneralID'=>$lostGeneral->no, 'destGeneralID' => $lostGeneral->no,
'destCityID'=>$selCityID 'destCityID' => $selCityID
]], sqrt(count($lostGenerals)) * 10]; ]], sqrt(count($lostGenerals)) * 10];
} }
// 평시엔 균등 발령만 // 평시엔 균등 발령만
if(in_array($this->dipState, [self::d평화, self::d선포]) && count($supplyCitiesID) > 1) { if (in_array($this->dipState, [self::d평화, self::d선포]) && count($supplyCitiesID) > 1) {
$targetCity = null; $targetCity = null;
$minCity = null; $minCity = null;
$maxCity = null; $maxCity = null;
$maxDevCity = null; $maxDevCity = null;
foreach($nationCities as $nationCity){ foreach ($nationCities as $nationCity) {
if($nationCity['dev']>=95){ if ($nationCity['dev'] >= 95) {
continue; continue;
} }
if($nationCity['supply']){ if ($nationCity['supply']) {
$minCity = $nationCity; $minCity = $nationCity;
break; break;
} }
} }
//reverse_order T_T //reverse_order T_T
$maxCity = end($nationCities); $maxCity = end($nationCities);
if(!$minCity){ if (!$minCity) {
$minCity = $maxCity; $minCity = $maxCity;
} }
while($maxCity['city'] !== $minCity['city']){ while ($maxCity['city'] !== $minCity['city']) {
if($nationCity['supply']){ if ($nationCity['supply']) {
break; break;
} }
$maxCity = prev($nationCities); $maxCity = prev($nationCities);
} }
foreach($nationCities as $nationCity){ foreach ($nationCities as $nationCity) {
if($nationCity['city'] == $maxCity['city']){ if ($nationCity['city'] == $maxCity['city']) {
break; break;
} }
if(!$nationCity['supply']){ if (!$nationCity['supply']) {
continue; continue;
} }
if($nationCity['dev'] < 70){ if ($nationCity['dev'] < 70) {
$targetCity = $nationCity; $targetCity = $nationCity;
break; break;
} }
} }
foreach ($nationCities as $nationCity) { foreach ($nationCities as $nationCity) {
if(!$nationCity['supply']){ if (!$nationCity['supply']) {
continue; continue;
} }
if(count($nationCity['generals']) == 0){ if (count($nationCity['generals']) == 0) {
continue; continue;
} }
if($maxDevCity === null || $maxDevCity['dev'] < $nationCity['dev']){ if ($maxDevCity === null || $maxDevCity['dev'] < $nationCity['dev']) {
$maxDevCity = $nationCity; $maxDevCity = $nationCity;
} }
} }
if($targetCity === null || (count($targetCity['generals']) >= count($maxCity['generals']) - 1)){ if ($targetCity === null || (count($targetCity['generals']) >= count($maxCity['generals']) - 1)) {
$targetCity = $minCity; $targetCity = $minCity;
} }
if($maxDevCity['dev'] >= 95 && $targetCity['city'] != $maxDevCity['city'] && $targetCity['dev'] <= 70){ if ($maxDevCity['dev'] >= 95 && $targetCity['city'] != $maxDevCity['city'] && $targetCity['dev'] <= 70) {
$targetGeneral = Util::choiceRandom($maxDevCity['generals']); $targetGeneral = Util::choiceRandom($maxDevCity['generals']);
if($targetGeneral->troop != 0){ if ($targetGeneral->troop != 0) {
$commandList[] = [['che_발령', [ $commandList[] = [['che_발령', [
'destGeneralID'=>$targetGeneral->no, 'destGeneralID' => $targetGeneral->no,
'destCityID'=>$targetCity['city'] 'destCityID' => $targetCity['city']
]], 2]; ]], 2];
} }
} }
if(count($targetCity['generals']) < count($maxCity['generals']) - 2){ if (count($targetCity['generals']) < count($maxCity['generals']) - 2) {
//세명 이상 차이나야 함 //세명 이상 차이나야 함
$targetGeneral = Util::choiceRandom($maxCity['generals']); $targetGeneral = Util::choiceRandom($maxCity['generals']);
if($targetGeneral->npc==5){ if ($targetGeneral->npc == 5) {
} else if ($targetGeneral->npc >= 2 || $maxCity['dev'] >= 95 && $targetGeneral->troop == 0) {
}
else if($targetGeneral->npc>=2 || $maxCity['dev'] >= 95 && $targetGeneral->troop == 0){
//유저장은 의도가 있을 것이므로 삽나지 않는 이상 발령 안함! //유저장은 의도가 있을 것이므로 삽나지 않는 이상 발령 안함!
$commandList[] = [['che_발령', [ $commandList[] = [['che_발령', [
'destGeneralID'=>$targetGeneral->no, 'destGeneralID' => $targetGeneral->no,
'destCityID'=>$targetCity['city'] 'destCityID' => $targetCity['city']
]], 5]; ]], 5];
} }
} }
} }
// 병사있고 쌀있고 후방에 있는 장수 // 병사있고 쌀있고 후방에 있는 장수
if($frontCitiesID){ if ($frontCitiesID) {
$workRemain = 3; $workRemain = 3;
foreach($nationGenerals as $nationGeneral){ foreach ($nationGenerals as $nationGeneral) {
$generalCity = $nationCities[$nationGeneral->city]??null; $generalCity = $nationCities[$nationGeneral->city] ?? null;
if(!$generalCity){ if (!$generalCity) {
continue; continue;
} }
if($nationGeneral->crew < 2000){ if ($nationGeneral->crew < 2000) {
continue; continue;
} }
if($nationGeneral->rice < 700 * $tech){ if ($nationGeneral->rice < 700 * $tech) {
continue; continue;
} }
if($generalCity['front']){ if ($generalCity['front']) {
continue; continue;
} }
if($nationGeneral->train * $nationGeneral->atmos < 75 * 75){ if ($nationGeneral->train * $nationGeneral->atmos < 75 * 75) {
continue; continue;
} }
if($nationGeneral->troop != 0){ if ($nationGeneral->troop != 0) {
continue; continue;
} }
$score = 5; $score = 5;
if($nationGeneral->npc<2){ if ($nationGeneral->npc < 2) {
$score *= 4; $score *= 4;
} }
if(Util::randBool(0.3) && $frontImportantCitiesID){ if (Util::randBool(0.3) && $frontImportantCitiesID) {
$targetCityID = Util::choiceRandom($frontImportantCitiesID); $targetCityID = Util::choiceRandom($frontImportantCitiesID);
} } else {
else{
$targetCityID = Util::choiceRandom($frontCitiesID); $targetCityID = Util::choiceRandom($frontCitiesID);
} }
$command = ['che_발령', [ $command = ['che_발령', [
'destGeneralID'=>$nationGeneral->no, 'destGeneralID' => $nationGeneral->no,
'destCityID'=>$targetCityID 'destCityID' => $targetCityID
]]; ]];
if($nationGeneral->npc<2 && ($workRemain&2)){ if ($nationGeneral->npc < 2 && ($workRemain & 2)) {
$workRemain ^= 2; $workRemain ^= 2;
$commandList[] = [$command, $score]; $commandList[] = [$command, $score];
} } else if ($nationGeneral->npc >= 2 && ($workRemain & 1)) {
else if($nationGeneral->npc>=2 && ($workRemain&1)){
$workRemain ^= 1; $workRemain ^= 1;
$commandList[] = [$command, $score]; $commandList[] = [$command, $score];
} }
if($workRemain <= 0){ if ($workRemain <= 0) {
break; break;
} }
} }
} }
//병사 없고 인구없는 전방에 있는 장수 //병사 없고 인구없는 전방에 있는 장수
if($frontCitiesID && $backupCitiesID){ if ($frontCitiesID && $backupCitiesID) {
$workRemain = 3; $workRemain = 3;
foreach($nationGenerals as $nationGeneral){ foreach ($nationGenerals as $nationGeneral) {
$generalCity = $nationCities[$nationGeneral->city]??null; $generalCity = $nationCities[$nationGeneral->city] ?? null;
if(!$generalCity){ if (!$generalCity) {
continue; continue;
} }
if($nationGeneral->crew >= 1000){ if ($nationGeneral->crew >= 1000) {
continue; continue;
} }
if($nationGeneral->rice < 700 * $tech){ if ($nationGeneral->rice < 700 * $tech) {
continue; continue;
} }
if(!$generalCity['front']){ if (!$generalCity['front']) {
continue; continue;
} }
if($generalCity['pop'] - 33000 > $nationGeneral->leadership){ if ($generalCity['pop'] - 33000 > $nationGeneral->leadership) {
continue; continue;
} }
if($nationGeneral->troop != 0){ if ($nationGeneral->troop != 0) {
continue; continue;
} }
$score = 5; $score = 5;
if($nationGeneral->npc<2){ if ($nationGeneral->npc < 2) {
$score *= 4; $score *= 4;
} }
$popTrial = 5; $popTrial = 5;
for($popTrial = 0; $popTrial < 5; $popTrial++){ for ($popTrial = 0; $popTrial < 5; $popTrial++) {
$targetCity = $nationCities[Util::choiceRandom($backupCitiesID)]; $targetCity = $nationCities[Util::choiceRandom($backupCitiesID)];
if($targetCity['pop'] < 33000 + $nationGeneral->leadership){ if ($targetCity['pop'] < 33000 + $nationGeneral->leadership) {
continue; continue;
} }
if (Util::randBool($targetCity['pop'] / $targetCity['pop_max'])) { if (Util::randBool($targetCity['pop'] / $targetCity['pop_max'])) {
break; break;
} }
} }
$command = ['che_발령', [ $command = ['che_발령', [
'destGeneralID'=>$nationGeneral->no, 'destGeneralID' => $nationGeneral->no,
'destCityID'=>$targetCity['city'] 'destCityID' => $targetCity['city']
]]; ]];
if($nationGeneral->npc<2 && ($workRemain&2)){ if ($nationGeneral->npc < 2 && ($workRemain & 2)) {
$workRemain ^= 2; $workRemain ^= 2;
$commandList[] = [$command, $score]; $commandList[] = [$command, $score];
} } else if ($nationGeneral->npc >= 2 && ($workRemain & 1)) {
else if($nationGeneral->npc>=2 && ($workRemain&1)){
$workRemain ^= 1; $workRemain ^= 1;
$commandList[] = [$command, $score]; $commandList[] = [$command, $score];
} }
if($workRemain <= 0){ if ($workRemain <= 0) {
break; break;
} }
} }
} }
if(!$commandList)return ['휴식', null]; if (!$commandList) return ['휴식', null];
return Util::choiceRandomUsingWeightPair($commandList); return Util::choiceRandomUsingWeightPair($commandList);
} }
public function chooseNeutralTurn():array{ public function chooseNeutralTurn(): array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
@@ -1024,50 +1247,48 @@ class GeneralAI{
$arg = null; $arg = null;
// 오랑캐는 바로 임관 // 오랑캐는 바로 임관
if($general->getVar('npc') == 9) { if ($general->getVar('npc') == 9) {
$rulerNation = $db->queryFirstField( $rulerNation = $db->queryFirstField(
'SELECT nation FROM general WHERE `level`=12 AND npc=9 and nation not in %li ORDER BY RAND() limit 1', 'SELECT nation FROM general WHERE `level`=12 AND npc=9 and nation not in %li ORDER BY RAND() limit 1',
$general->getAuxVar('joinedNations')??[0] $general->getAuxVar('joinedNations') ?? [0]
); );
if($rulerNation){ if ($rulerNation) {
return ['che_임관', ['destNationID'=>$rulerNation]]; return ['che_임관', ['destNationID' => $rulerNation]];
} }
return ['che_견문', null]; return ['che_견문', null];
} }
switch(Util::choiceRandomUsingWeight([ switch (Util::choiceRandomUsingWeight([
'임관'=>11.4, '임관' => 11.4,
'거병_견문'=>40, '거병_견문' => 40,
'이동'=>20, '이동' => 20,
'기타'=>28.6 '기타' => 28.6
])) { ])) {
//임관 //임관
case '임관': case '임관':
$available = true; $available = true;
if($env['startyear']+3 > $env['year']){ if ($env['startyear'] + 3 > $env['year']) {
//초기 임관 기간에서는 국가가 적을수록 임관 시도가 적음 //초기 임관 기간에서는 국가가 적을수록 임관 시도가 적음
$nationCnt = $db->queryFirstField('SELECT count(nation) FROM nation'); $nationCnt = $db->queryFirstField('SELECT count(nation) FROM nation');
$notFullNationCnt = $db->queryFirstField('SELECT count(nation) FROM nation WHERE gennum < %i', GameConst::$initialNationGenLimit); $notFullNationCnt = $db->queryFirstField('SELECT count(nation) FROM nation WHERE gennum < %i', GameConst::$initialNationGenLimit);
if($nationCnt == 0 || $notFullNationCnt == 0){ if ($nationCnt == 0 || $notFullNationCnt == 0) {
$available = false; $available = false;
} } else if (Util::randBool(pow(1 / $nationCnt / pow($notFullNationCnt, 3), 1 / 4))) {
else if(Util::randBool(pow(1 / $nationCnt / pow($notFullNationCnt, 3), 1/4))){
//국가가 1개일 경우에는 '임관하지 않음' //국가가 1개일 경우에는 '임관하지 않음'
$available = false; $available = false;
} }
} }
if($general->getVar('affinity') == 999 || !$available){ if ($general->getVar('affinity') == 999 || !$available) {
$command = 'che_견문'; //견문 $command = 'che_견문'; //견문
} } else {
else{
//랜임 커맨드 입력. //랜임 커맨드 입력.
$command = 'che_랜덤임관'; $command = 'che_랜덤임관';
$arg = [ $arg = [
'destNationIDList'=>[] 'destNationIDList' => []
]; ];
} }
break; break;
@@ -1075,7 +1296,7 @@ class GeneralAI{
// 초반이면서 능력이 좋은놈 위주로 1.4%확률로 거병 // 초반이면서 능력이 좋은놈 위주로 1.4%확률로 거병
$prop = Util::randF() * (GameConst::$defaultStatNPCMax + GameConst::$chiefStatMin) / 2; $prop = Util::randF() * (GameConst::$defaultStatNPCMax + GameConst::$chiefStatMin) / 2;
$ratio = ($general->getVar('leadership') + $general->getVar('strength') + $general->getVar('intel')) / 3; $ratio = ($general->getVar('leadership') + $general->getVar('strength') + $general->getVar('intel')) / 3;
if($env['startyear']+2 > $env['year'] && $prop < $ratio && Util::randBool(0.014) && $general->getVar('makelimit') == 0) { if ($env['startyear'] + 2 > $env['year'] && $prop < $ratio && Util::randBool(0.014) && $general->getVar('makelimit') == 0) {
//거병 //거병
$command = 'che_거병'; $command = 'che_거병';
} else { } else {
@@ -1084,10 +1305,10 @@ class GeneralAI{
} }
break; break;
case '이동': //이동 case '이동': //이동
$paths = array_keys(CityConst::byID($city['city'])->path); $paths = array_keys(CityConst::byID($city['city'])->path);
$command = 'che_이동'; $command = 'che_이동';
$arg = ['destCityID'=>Util::choiceRandom($paths)]; $arg = ['destCityID' => Util::choiceRandom($paths)];
break; break;
default: default:
$command = 'che_견문'; $command = 'che_견문';
@@ -1096,53 +1317,54 @@ class GeneralAI{
return [$command, $arg]; return [$command, $arg];
} }
protected function chooseEndOfNPCTurn():array{ protected function chooseEndOfNPCTurn(): array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
if($general->getVar('gold') + $general->getVar('rice') == 0){ if ($general->getVar('gold') + $general->getVar('rice') == 0) {
return ['che_물자조달', null]; return ['che_물자조달', null];
} }
[$baseArmGold, $baseArmRice] = $this->getBaseArmCost(); [$baseArmGold, $baseArmRice] = $this->getBaseArmCost();
if($this->dipState == self::d전쟁 && if (
$this->dipState == self::d전쟁 &&
$general->getVar('killturn') > 2 && $general->getVar('killturn') > 2 &&
$general->getVar('gold') >= $baseArmGold / 3 && $general->getVar('gold') >= $baseArmGold / 3 &&
$general->getVar('rice') >= $baseArmRice && $general->getVar('rice') >= $baseArmRice &&
$general->getVar('crew') >= $general->getLeadership(false) / 3 $general->getVar('crew') >= $general->getLeadership(false) / 3
){ ) {
//사망 직전 마지막 불꽃 //사망 직전 마지막 불꽃
$trainAndAtmos = $general->getVar('train') + $general->getVar('atmos'); $trainAndAtmos = $general->getVar('train') + $general->getVar('atmos');
if( if (
$trainAndAtmos >= 180 && $trainAndAtmos >= 180 &&
$city['front'] >= 2 && $city['front'] >= 2 &&
$general->getVar('rice') >= $baseArmRice && $general->getVar('rice') >= $baseArmRice &&
$nation['war'] == 0 $nation['war'] == 0
){ ) {
return $this->processAttack(); return $this->processAttack();
} }
if(180 >= $trainAndAtmos && $trainAndAtmos >= 160){ if (180 >= $trainAndAtmos && $trainAndAtmos >= 160) {
if($general->getVar('train') < 80){ if ($general->getVar('train') < 80) {
return ['che_훈련', null]; return ['che_훈련', null];
} } else {
else{
return ['che_사기진작', null]; return ['che_사기진작', null];
} }
} }
} }
if($general->getVar('gold') >= $general->getVar('rice')){ if ($general->getVar('gold') >= $general->getVar('rice')) {
return ['che_헌납', ['isGold'=>true, 'amount'=>GameConst::$maxResourceActionAmount]]; return ['che_헌납', ['isGold' => true, 'amount' => GameConst::$maxResourceActionAmount]];
} } else {
else{ return ['che_헌납', ['isGold' => false, 'amount' => GameConst::$maxResourceActionAmount]];
return ['che_헌납', ['isGold'=>false, 'amount'=>GameConst::$maxResourceActionAmount]];
} }
} }
public function chooseGeneralTurn($command, $arg):array{ public function old_chooseGeneralTurn($command, $arg): array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
@@ -1162,38 +1384,36 @@ class GeneralAI{
$db = DB::db(); $db = DB::db();
if($general->getVar('npc') == 5){ if ($general->getVar('npc') == 5) {
if($nationID == 0 && $general->getVar('killturn') > 1){ if ($nationID == 0 && $general->getVar('killturn') > 1) {
$command = '휴식'; //휴식 $command = '휴식'; //휴식
$arg = null; $arg = null;
$general->setVar('killturn', 1); $general->setVar('killturn', 1);
} } else if ($general->getVar('level') == 12) {
else if($general->getVar('level') == 12){
$command = 'che_선양'; $command = 'che_선양';
$arg = [ $arg = [
'destGeneralID'=> $db->queryFirstField('SELECT `no` FROM general WHERE nation = %i AND npc != 5 ORDER BY RAND() LIMIT 1', $general->getNationID()) 'destGeneralID' => $db->queryFirstField('SELECT `no` FROM general WHERE nation = %i AND npc != 5 ORDER BY RAND() LIMIT 1', $general->getNationID())
]; ];
} } else {
else{
$command = 'che_집합'; //집합 $command = 'che_집합'; //집합
$arg = []; $arg = [];
$general->setVar('killturn', rand(70,75)); $general->setVar('killturn', rand(70, 75));
//NOTE: 부대 편성에 보여야 하므로 이것만 DB에 직접 접근함. //NOTE: 부대 편성에 보여야 하므로 이것만 DB에 직접 접근함.
$db->update('general_turn', [ $db->update('general_turn', [
'action'=>'che_집합', 'action' => 'che_집합',
'arg'=>'{}', 'arg' => '{}',
'brief'=>'집합' 'brief' => '집합'
], 'general_ID=%i AND turn_idx < 6', $general->getID()); ], 'general_ID=%i AND turn_idx < 6', $general->getID());
} }
return [$command, $arg]; return [$command, $arg];
} }
//특별 메세지 있는 경우 출력 하루 4번 //특별 메세지 있는 경우 출력 하루 4번
$term = $env['turnterm']; $term = $env['turnterm'];
if($general->getVar('npcmsg') && Util::randBool($term / (6*60))) { if ($general->getVar('npcmsg') && Util::randBool($term / (6 * 60))) {
$src = new MessageTarget( $src = new MessageTarget(
$general->getID(), $general->getID(),
$general->getVar('name'), $general->getVar('name'),
$general->getVar('nation'), $general->getVar('nation'),
$nation['name'], $nation['name'],
@@ -1201,7 +1421,7 @@ class GeneralAI{
GetImageURL($general->getVar('imgsvr'), $general->getVar('picture')) GetImageURL($general->getVar('imgsvr'), $general->getVar('picture'))
); );
$msg = new Message( $msg = new Message(
Message::MSGTYPE_PUBLIC, Message::MSGTYPE_PUBLIC,
$src, $src,
$src, $src,
$general->getVar('npcmsg'), $general->getVar('npcmsg'),
@@ -1212,106 +1432,103 @@ class GeneralAI{
$msg->send(); $msg->send();
} }
if($command && $command != '휴식'){ if ($command && $command != '휴식') {
return [$command, $arg]; return [$command, $arg];
} }
if($general->getVar('level') == 0){ if ($general->getVar('level') == 0) {
return $this->chooseNeutralTurn(); return $this->chooseNeutralTurn();
} }
$techCost = getTechCost($nation['tech']); $techCost = getTechCost($nation['tech']);
if($general->getVar('defence_train') < 80) { if ($general->getVar('defence_train') < 80) {
$general->setVar('defence_train', 80); $general->setVar('defence_train', 80);
} }
if($general->getVar('level') == 12){ if ($general->getVar('level') == 12) {
$turn = $this->processLordTurn(); $turn = $this->processLordTurn();
if($turn !== null){ if ($turn !== null) {
return $turn; return $turn;
} }
} }
if($general->getVar('killturn') < 5){ if ($general->getVar('killturn') < 5) {
return $this->chooseEndOfNPCTurn(); return $this->chooseEndOfNPCTurn();
} }
if($general->getVar('injury') > 10){ if ($general->getVar('injury') > 10) {
return ['che_요양', null]; return ['che_요양', null];
} }
if($nation['level'] == 0){ if ($nation['level'] == 0) {
//아직 건국을 안했다. //아직 건국을 안했다.
if($startYear + 3 <= $year){ if ($startYear + 3 <= $year) {
return ['che_하야', null]; return ['che_하야', null];
} }
if(Util::randBool(0.2)){ if (Util::randBool(0.2)) {
return ['che_견문', null]; return ['che_견문', null];
} } else {
else{
return ['che_물자조달', null]; return ['che_물자조달', null];
} }
} }
if($city['nation'] != $nationID || !$city['supply']){ if ($city['nation'] != $nationID || !$city['supply']) {
return ['che_귀환', null]; return ['che_귀환', null];
} }
[$baseArmGold, $baseArmRice] = $this->getBaseArmCost(); [$baseArmGold, $baseArmRice] = $this->getBaseArmCost();
if($nation['rice'] < 2000){ if ($nation['rice'] < 2000) {
if(($genType & self::t통솔장) && $general->getVar('rice') > $baseArmRice){ if (($genType & self::t통솔장) && $general->getVar('rice') > $baseArmRice) {
return ['che_헌납', ['isGold'=>false, 'amount'=>Util::toInt(($general->getVar('rice') - $baseArmRice) / 2)]]; return ['che_헌납', ['isGold' => false, 'amount' => Util::toInt(($general->getVar('rice') - $baseArmRice) / 2)]];
} } else if (!($genType & self::t통솔장)) {
else if(!($genType & self::t통솔장)){ return ['che_헌납', ['isGold' => false, 'amount' => Util::toInt($general->getVar('rice') / 2)]];
return ['che_헌납', ['isGold'=>false, 'amount'=>Util::toInt($general->getVar('rice') / 2)]];
} }
} }
if($genType & self::t통솔장){ if ($genType & self::t통솔장) {
$warTurn = $this->processWar(); $warTurn = $this->processWar();
if($warTurn !== null){ if ($warTurn !== null) {
return $warTurn; return $warTurn;
} }
if($general->getVar('rice') < $baseArmRice && $general->getVar('gold') >= $baseArmGold * 3){ if ($general->getVar('rice') < $baseArmRice && $general->getVar('gold') >= $baseArmGold * 3) {
return [ return [
'che_군량매매', 'che_군량매매',
[ [
'buyRice'=>true, 'buyRice' => true,
'amount'=> Util::toInt($general->getVar('gold') - $baseArmGold) 'amount' => Util::toInt($general->getVar('gold') - $baseArmGold)
] ]
]; ];
} }
if($general->getVar('gold') < $baseArmGold && $general->getVar('gold') >= $baseArmRice * 3){ if ($general->getVar('gold') < $baseArmGold && $general->getVar('gold') >= $baseArmRice * 3) {
return [
'che_군량매매',
[
'buyRice'=>false,
'amount'=> Util::toInt($general->getVar('rice') - $baseArmRice)
]
];
}
}
else{
if($general->getVar('rice') < $this->baseDevelCost && $general->getVar('gold') >= $this->baseDevelCost * 3){
return [ return [
'che_군량매매', 'che_군량매매',
[ [
'buyRice'=>true, 'buyRice' => false,
'amount'=> Util::toInt($general->getVar('gold') - $this->baseDevelCost) 'amount' => Util::toInt($general->getVar('rice') - $baseArmRice)
] ]
]; ];
} }
if($general->getVar('gold') < $this->baseDevelCost && $general->getVar('gold') >= $this->baseDevelCost * 3){ } else {
if ($general->getVar('rice') < $this->baseDevelCost && $general->getVar('gold') >= $this->baseDevelCost * 3) {
return [ return [
'che_군량매매', 'che_군량매매',
[ [
'buyRice'=>false, 'buyRice' => true,
'amount'=> Util::toInt($general->getVar('rice') - $this->baseDevelCost) 'amount' => Util::toInt($general->getVar('gold') - $this->baseDevelCost)
]
];
}
if ($general->getVar('gold') < $this->baseDevelCost && $general->getVar('gold') >= $this->baseDevelCost * 3) {
return [
'che_군량매매',
[
'buyRice' => false,
'amount' => Util::toInt($general->getVar('rice') - $this->baseDevelCost)
] ]
]; ];
} }
@@ -1320,36 +1537,34 @@ class GeneralAI{
$cityFull = false; $cityFull = false;
$developTurn = $this->chooseDevelopTurn($cityFull); $developTurn = $this->chooseDevelopTurn($cityFull);
if($cityFull && Util::randBool(0.2)){ if ($cityFull && Util::randBool(0.2)) {
$moveRawCities = $db->query('SELECT city,front,(pop/10+agri+comm+secu+def+wall)/(pop_max/10+agri_max+comm_max+secu_max+def_max+wall_max)*100 as dev, officer3 FROM city WHERE nation=%i', $nationID); $moveRawCities = $db->query('SELECT city,front,(pop/10+agri+comm+secu+def+wall)/(pop_max/10+agri_max+comm_max+secu_max+def_max+wall_max)*100 as dev, officer3 FROM city WHERE nation=%i', $nationID);
$moveCities = []; $moveCities = [];
foreach($moveRawCities as $moveCity){ foreach ($moveRawCities as $moveCity) {
$moveCityID = $moveCity['city']; $moveCityID = $moveCity['city'];
if($moveCity['dev'] > 99){ if ($moveCity['dev'] > 99) {
continue; continue;
} }
$score = 1 / $moveCity['dev']; $score = 1 / $moveCity['dev'];
if($moveCity['officer3']){ if ($moveCity['officer3']) {
$score *= 1.3; $score *= 1.3;
} }
$moveCities[$moveCityID] = $score; $moveCities[$moveCityID] = $score;
} }
if($moveCities){ if ($moveCities) {
return ['che_NPC능동', [ return ['che_NPC능동', [
'optionText'=>'순간이동', 'optionText' => '순간이동',
'destCityID'=>Util::choiceRandomUsingWeight($moveCities), 'destCityID' => Util::choiceRandomUsingWeight($moveCities),
]]; ]];
} }
} }
return $developTurn; return $developTurn;
} }
protected function getBaseArmCost(){ protected function getBaseArmCost()
{
//총 통솔의 절반을 징병하는 것을 기준으로 함 //총 통솔의 절반을 징병하는 것을 기준으로 함
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$tech = $this->nation['tech']; $tech = $this->nation['tech'];
@@ -1357,23 +1572,24 @@ class GeneralAI{
$baseArmCost = $crewType->costWithTech( $baseArmCost = $crewType->costWithTech(
$tech, $tech,
$general->getLeadership(false) * 50 $general->getLeadership(false) * 50
);//기본 병종 ); //기본 병종
$baseArmCost = $general->onCalcDomestic('징병', 'cost', $baseArmCost); $baseArmCost = $general->onCalcDomestic('징병', 'cost', $baseArmCost);
$baseArmRice = $general->getLeadership(false) / 2 * $crewType->rice * getTechCost($tech); $baseArmRice = $general->getLeadership(false) / 2 * $crewType->rice * getTechCost($tech);
if($general->getRankVar('deathcrew') > 500 && $general->getRankVar('killcrew') > 500){ if ($general->getRankVar('deathcrew') > 500 && $general->getRankVar('killcrew') > 500) {
$baseArmRice *= $general->getRankVar('killcrew') / $general->getRankVar('deathcrew'); $baseArmRice *= $general->getRankVar('killcrew') / $general->getRankVar('deathcrew');
} }
return [$baseArmCost, $baseArmRice]; return [$baseArmCost, $baseArmRice];
} }
protected function processWar():?array{ protected function processWar(): ?array
{
$db = DB::db(); $db = DB::db();
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
if(!$this->attackable && $this->dipState == self::d평화){ if (!$this->attackable && $this->dipState == self::d평화) {
if($this->dipState == self::d평화 && $general->getVar('crew')>=1000 && Util::randBool(0.25)){ if ($this->dipState == self::d평화 && $general->getVar('crew') >= 1000 && Util::randBool(0.25)) {
return ['che_소집해제', null]; return ['che_소집해제', null];
} }
@@ -1387,30 +1603,30 @@ class GeneralAI{
$env = $this->env; $env = $this->env;
[$baseArmGold, $baseArmRice] = $this->getBaseArmCost(); [$baseArmGold, $baseArmRice] = $this->getBaseArmCost();
if($general->getVar('rice') <= $baseArmRice){ if ($general->getVar('rice') <= $baseArmRice) {
return null; return null;
} }
if( if (
($city['front'] > 0 && $city['trust'] < 60) || ($city['front'] > 0 && $city['trust'] < 60) ||
($city['front'] == 0 && $city['trust'] < 95) ($city['front'] == 0 && $city['trust'] < 95)
){ ) {
return ['che_주민선정', null]; return ['che_주민선정', null];
} }
if($general->getVar('crew') < 1000){ if ($general->getVar('crew') < 1000) {
if ($general->getVar('gold') <= $baseArmGold) { if ($general->getVar('gold') <= $baseArmGold) {
//반징도 불가? 내정 //반징도 불가? 내정
return null; return null;
} }
$sumLeadershipInCity = $db->queryFirstField('SELECT sum(leadership) FROM general WHERE nation = %i AND city = %i AND leadership > 40', $nationID, $cityID); $sumLeadershipInCity = $db->queryFirstField('SELECT sum(leadership) FROM general WHERE nation = %i AND city = %i AND leadership > 40', $nationID, $cityID);
if( if (
$sumLeadershipInCity > 0 && $sumLeadershipInCity > 0 &&
$city['pop'] > 30000 + $general->getLeadership(false) * 100 * 1.3 && $city['pop'] > 30000 + $general->getLeadership(false) * 100 * 1.3 &&
Util::randBool(($city['pop'] - 30000) / $sumLeadershipInCity * 100) Util::randBool(($city['pop'] - 30000) / $sumLeadershipInCity * 100)
){ ) {
return $this->chooseRecruitCrewType(); return $this->chooseRecruitCrewType();
} }
@@ -1419,99 +1635,96 @@ class GeneralAI{
$nationID, $nationID,
30000 + $general->getLeadership(false) * 100 30000 + $general->getLeadership(false) * 100
); );
if(!$recruitableCityList){ if (!$recruitableCityList) {
//징병 가능한 도시가 없구려 //징병 가능한 도시가 없구려
return ['che_정착장려', null]; return ['che_정착장려', null];
} }
return ['che_NPC능동', [ return ['che_NPC능동', [
'optionText'=>'순간이동', 'optionText' => '순간이동',
'destCityID'=>Util::choiceRandomUsingWeightPair($recruitableCityList), 'destCityID' => Util::choiceRandomUsingWeightPair($recruitableCityList),
]]; ]];
} }
if( if (
$general->getVar('train') >= 90 && $general->getVar('train') >= 90 &&
$general->getVar('atmos') >= 90 $general->getVar('atmos') >= 90
){ ) {
//출병 가능! //출병 가능!
if( if (
$this->attackable && $this->attackable &&
$env['year'] >= $env['startyear'] + 3 && $env['year'] >= $env['startyear'] + 3 &&
$city['front'] >= 2 && $city['front'] >= 2 &&
$nation['war'] == 0 $nation['war'] == 0
){ ) {
return $this->processAttack(); return $this->processAttack();
} }
if($city['front'] > 0){ if ($city['front'] > 0) {
//전방에 훈사까지 완료되어있으면 내정을 해야.. //전방에 훈사까지 완료되어있으면 내정을 해야..
return null; return null;
} }
$frontCities = $db->query('SELECT city, front, officer4 FROM city WHERE nation=%i AND front > 0 AND supply = 1', $nationID); $frontCities = $db->query('SELECT city, front, officer4 FROM city WHERE nation=%i AND front > 0 AND supply = 1', $nationID);
if(!$frontCities){ if (!$frontCities) {
//접경이 아직 없음 //접경이 아직 없음
return null; return null;
} }
$nearCities = []; $nearCities = [];
$attackableCities = []; $attackableCities = [];
foreach($frontCities as $frontCity){ foreach ($frontCities as $frontCity) {
$frontCityID = $frontCity['city']; $frontCityID = $frontCity['city'];
$score = 0.2; $score = 0.2;
if($frontCity['front'] > 1 && $frontCity['officer4']){ if ($frontCity['front'] > 1 && $frontCity['officer4']) {
$score += 3; $score += 3;
} }
$attackableCities[$frontCityID] = $score; $attackableCities[$frontCityID] = $score;
foreach(array_keys(CityConst::byID($frontCityID)->path) as $nearCity){ foreach (array_keys(CityConst::byID($frontCityID)->path) as $nearCity) {
if(!key_exists($nearCity, $nearCities)){ if (!key_exists($nearCity, $nearCities)) {
$nearCities[$nearCity] = [$frontCityID]; $nearCities[$nearCity] = [$frontCityID];
} } else {
else{
$nearCities[$nearCity][] = $frontCityID; $nearCities[$nearCity][] = $frontCityID;
} }
} }
} }
foreach($db->query( foreach ($db->query(
'SELECT city FROM city WHERE nation IN %li AND city IN %li', 'SELECT city FROM city WHERE nation IN %li AND city IN %li',
array_keys($this->warTargetNation), array_keys($this->warTargetNation),
array_keys($nearCities) array_keys($nearCities)
) as $targetCity){ ) as $targetCity) {
$targetCityID = $targetCity['city']; $targetCityID = $targetCity['city'];
foreach($nearCities[$targetCityID] as $attackableCity){ foreach ($nearCities[$targetCityID] as $attackableCity) {
$attackableCities[$attackableCity] += 1; $attackableCities[$attackableCity] += 1;
} }
} }
if($attackableCities){ if ($attackableCities) {
return ['che_NPC능동', [ return ['che_NPC능동', [
'optionText'=>'순간이동', 'optionText' => '순간이동',
'destCityID'=>Util::choiceRandomUsingWeight($attackableCities), 'destCityID' => Util::choiceRandomUsingWeight($attackableCities),
]]; ]];
} }
} }
if($general->getVar('train') < 90){ if ($general->getVar('train') < 90) {
$turnObj = buildGeneralCommandClass('che_훈련', $general, $env, null); $turnObj = buildGeneralCommandClass('che_훈련', $general, $env, null);
[$reqGold, $reqRice] = $turnObj->getCost(); [$reqGold, $reqRice] = $turnObj->getCost();
if($general->getVar('gold') >= $reqGold && $general->getVar('rice') >= $reqRice){ if ($general->getVar('gold') >= $reqGold && $general->getVar('rice') >= $reqRice) {
return ['che_훈련', null]; return ['che_훈련', null];
} }
} }
if($general->getVar('atmos') < 90 ){ if ($general->getVar('atmos') < 90) {
$turnObj = buildGeneralCommandClass('che_사기진작', $general, $env, null); $turnObj = buildGeneralCommandClass('che_사기진작', $general, $env, null);
[$reqGold, $reqRice] = $turnObj->getCost(); [$reqGold, $reqRice] = $turnObj->getCost();
if($general->getVar('gold') >= $reqGold && $general->getVar('rice') >= $reqRice){ if ($general->getVar('gold') >= $reqGold && $general->getVar('rice') >= $reqRice) {
return ['che_사기진작', null]; return ['che_사기진작', null];
} }
} }
@@ -1521,7 +1734,8 @@ class GeneralAI{
return null; return null;
} }
protected function processAttack():array{ protected function processAttack(): array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
@@ -1531,33 +1745,34 @@ class GeneralAI{
$db = DB::db(); $db = DB::db();
if($city['front'] <= 1){ if ($city['front'] <= 1) {
throw new \RuntimeException('출병 불가'.$cityID); throw new \RuntimeException('출병 불가' . $cityID);
} }
$attackableNations = []; $attackableNations = [];
foreach($this->warTargetNation as $targetNationID=>$state){ foreach ($this->warTargetNation as $targetNationID => $state) {
if($state == 1){ if ($state == 1) {
continue; continue;
} }
$attackableNations[] = $targetNationID; $attackableNations[] = $targetNationID;
} }
$nearCities = array_keys(CityConst::byID($cityID)->path); $nearCities = array_keys(CityConst::byID($cityID)->path);
$attackableCities = $db->queryFirstColumn( $attackableCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE nation IN %li AND city IN %li', 'SELECT city FROM city WHERE nation IN %li AND city IN %li',
$attackableNations, $attackableNations,
$nearCities $nearCities
); );
if(count($attackableCities) == 0){ if (count($attackableCities) == 0) {
throw new \RuntimeException('출병 불가'.$cityID.var_export($attackableNations, true).var_export($nearCities, true)); throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true));
} }
return ['che_출병', ['destCityID'=>Util::choiceRandom($attackableCities)]]; return ['che_출병', ['destCityID' => Util::choiceRandom($attackableCities)]];
} }
protected function proceessNeutralLordTurn():array{ protected function proceessNeutralLordTurn(): array
{
$db = DB::db(); $db = DB::db();
@@ -1570,19 +1785,18 @@ class GeneralAI{
$year = $env['year']; $year = $env['year'];
$month = $env['month']; $month = $env['month'];
if($startYear+2 <= $year){ if ($startYear + 2 <= $year) {
return ['che_해산', null]; return ['che_해산', null];
} }
if($city['nation'] == 0 && ($city['level'] == 5 || $city['level'] == 6)) { if ($city['nation'] == 0 && ($city['level'] == 5 || $city['level'] == 6)) {
$nationType = Util::choiceRandom(GameConst::$availableNationType); $nationType = Util::choiceRandom(GameConst::$availableNationType);
$nationColor = Util::choiceRandom(array_keys(GetNationColors())); $nationColor = Util::choiceRandom(array_keys(GetNationColors()));
return ['che_건국', [ return ['che_건국', [
'nationName'=>"".mb_substr($general->getName(), 1), 'nationName' => "" . mb_substr($general->getName(), 1),
'nationType'=>$nationType, 'nationType' => $nationType,
'colorType'=>$nationColor 'colorType' => $nationColor
]]; ]];
} }
//모든 공백지 조사 //모든 공백지 조사
@@ -1590,33 +1804,31 @@ class GeneralAI{
$nationCities = $db->queryFirstColumn('SELECT city FROM city WHERE nation != 0'); $nationCities = $db->queryFirstColumn('SELECT city FROM city WHERE nation != 0');
$occupiedCities = []; $occupiedCities = [];
foreach($lordCities as $tCityId){ foreach ($lordCities as $tCityId) {
$occupiedCities[$tCityId] = 2; $occupiedCities[$tCityId] = 2;
} }
foreach($nationCities as $tCityId){ foreach ($nationCities as $tCityId) {
$occupiedCities[$tCityId] = 1; $occupiedCities[$tCityId] = 1;
} }
$targetCity = []; $targetCity = [];
//NOTE: 최단 거리가 현재 도시에서 '어떻게 가야' 가장 짧은지 알 수가 없으므로, 한칸 간 다음 계산하기로 //NOTE: 최단 거리가 현재 도시에서 '어떻게 가야' 가장 짧은지 알 수가 없으므로, 한칸 간 다음 계산하기로
foreach(array_keys(CityConst::byID($general->getVar('city'))->path) as $nearCityID){ foreach (array_keys(CityConst::byID($general->getVar('city'))->path) as $nearCityID) {
if(CityConst::byID($nearCityID)->level < 4){ if (CityConst::byID($nearCityID)->level < 4) {
$targetCity[$nearCityID] = 0.5; $targetCity[$nearCityID] = 0.5;
} } else if (!key_exists($nearCityID, $occupiedCities)) {
else if(!key_exists($nearCityID, $occupiedCities)){
$targetCity[$nearCityID] = 2; $targetCity[$nearCityID] = 2;
} } else {
else{
$targetCity[$nearCityID] = 0; $targetCity[$nearCityID] = 0;
} }
$distanceFrom = searchDistance($nearCityID, 4, true); $distanceFrom = searchDistance($nearCityID, 4, true);
foreach($distanceFrom as $distance => $distCities){ foreach ($distanceFrom as $distance => $distCities) {
foreach($distCities as $distCity){ foreach ($distCities as $distCity) {
if(key_exists($distCity, $occupiedCities)){ if (key_exists($distCity, $occupiedCities)) {
continue; continue;
} }
if(CityConst::byID($distCity)->level < 4){ if (CityConst::byID($distCity)->level < 4) {
continue; continue;
} }
@@ -1624,11 +1836,12 @@ class GeneralAI{
} }
} }
} }
return ['che_이동', ['destCityID'=>Util::choiceRandomUsingWeight($targetCity)]]; return ['che_이동', ['destCityID' => Util::choiceRandomUsingWeight($targetCity)]];
} }
protected function processLordTurn():?array{ protected function processLordTurn(): ?array
{
$general = $this->getGeneralObj(); $general = $this->getGeneralObj();
$city = $this->city; $city = $this->city;
$nation = $this->nation; $nation = $this->nation;
@@ -1637,38 +1850,36 @@ class GeneralAI{
$year = $env['year']; $year = $env['year'];
$month = $env['month']; $month = $env['month'];
if($general->getVar('npc') == 9 && $this->dipState == self::d평화 && !$this->attackable){ if ($general->getVar('npc') == 9 && $this->dipState == self::d평화 && !$this->attackable) {
if($nation['level'] == 0){ if ($nation['level'] == 0) {
return ['che_해산', null]; return ['che_해산', null];
} } else {
else{
return ['che_방랑', null]; return ['che_방랑', null];
} }
} }
if(in_array($month, [1,4,7,10])){ if (in_array($month, [1, 4, 7, 10])) {
$this->calcPromotion(); $this->calcPromotion();
} } else if ($month == 12) {
else if($month == 12){
$this->calcTexRate(); $this->calcTexRate();
$this->calcGoldBillRate(); $this->calcGoldBillRate();
} } else if ($month == 6) {
else if($month == 6){
$this->calcTexRate(); $this->calcTexRate();
$this->calcRiceBillRate(); $this->calcRiceBillRate();
} }
if($nation['level'] == 0){ if ($nation['level'] == 0) {
return $this->proceessNeutralLordTurn(); return $this->proceessNeutralLordTurn();
} }
return null; return null;
} }
protected function getNationDevelopedRate(){ protected function getNationDevelopedRate()
if($this->devRate !== null){ {
if ($this->devRate !== null) {
return $this->devRate; return $this->devRate;
} }
@@ -1682,7 +1893,8 @@ class GeneralAI{
return $this->devRate; return $this->devRate;
} }
protected function findWarTarget():?int{ protected function findWarTarget(): ?int
{
$db = DB::db(); $db = DB::db();
@@ -1691,29 +1903,30 @@ class GeneralAI{
SetNationFront($nationID); SetNationFront($nationID);
$frontCount = $db->queryFirstField('SELECT count(city) FROM city WHERE nation=%i AND front>0', $nationID); $frontCount = $db->queryFirstField('SELECT count(city) FROM city WHERE nation=%i AND front>0', $nationID);
if($frontCount > 0){ if ($frontCount > 0) {
return null; return null;
} }
$devRate = $this->getNationDevelopedRate(); $devRate = $this->getNationDevelopedRate();
if(($devRate['pop_p'] + $devRate['all_p']) / 2 < 0.8){ if (($devRate['pop_p'] + $devRate['all_p']) / 2 < 0.8) {
return null; return null;
} }
$nations = []; $nations = [];
foreach ($db->queryAllLists('SELECT nation, power FROM nation WHERE level>0') as [$destNationID, $destNationPower]) { foreach ($db->queryAllLists('SELECT nation, power FROM nation WHERE level>0') as [$destNationID, $destNationPower]) {
if(!isNeighbor($nationID, $destNationID)){ if (!isNeighbor($nationID, $destNationID)) {
continue; continue;
} }
$nations[$destNationID] = 1/sqrt($destNationPower+1); $nations[$destNationID] = 1 / sqrt($destNationPower + 1);
} }
if(!$nations){ if (!$nations) {
return null; return null;
} }
return Util::choiceRandomUsingWeight($nations); return Util::choiceRandomUsingWeight($nations);
} }
protected function calcPromotion(){ protected function calcPromotion()
{
$db = DB::db(); $db = DB::db();
$nation = $this->nation; $nation = $this->nation;
@@ -1724,37 +1937,38 @@ class GeneralAI{
$chiefCandidate = []; $chiefCandidate = [];
//이 함수를 부르는건 군주 AI이므로, 군주는 세지 않아도 됨 //이 함수를 부르는건 군주 AI이므로, 군주는 세지 않아도 됨
$userChief = []; $userChief = [];
$db->update('general', [ $db->update('general', [
'permission'=>'ambassador', 'permission' => 'ambassador',
], 'nation=%i AND npc < 2 AND level > 4', $nationID); ], 'nation=%i AND npc < 2 AND level > 4', $nationID);
foreach($db->query( foreach ($db->query(
'SELECT no, npc, level, killturn FROM general WHERE nation = %i AND 12 > level AND level > 4', $nationID 'SELECT no, npc, level, killturn FROM general WHERE nation = %i AND 12 > level AND level > 4',
) as $chief){ $nationID
) as $chief) {
if($chief['npc'] < 2 && $chief['killturn'] < $minKillturn ){ if ($chief['npc'] < 2 && $chief['killturn'] < $minKillturn) {
$chiefCandidate[$chief['level']] = $chief['no']; $chiefCandidate[$chief['level']] = $chief['no'];
$userChief[$chief['no']] = $chief['level']; $userChief[$chief['no']] = $chief['level'];
} }
} }
$db->update('general', [ $db->update('general', [
'level'=>1 'level' => 1
], 'level < 12 AND level > 4 AND nation = %i', $nationID); ], 'level < 12 AND level > 4 AND nation = %i', $nationID);
$maxBelong = $db->queryFirstField('SELECT max(belong) FROM `general` WHERE nation=%i', $nationID); $maxBelong = $db->queryFirstField('SELECT max(belong) FROM `general` WHERE nation=%i', $nationID);
$maxBelong = min($maxBelong - 1, 3); $maxBelong = min($maxBelong - 1, 3);
if(!$userChief){ if (!$userChief) {
$candUserChief = $db->queryFirstField( $candUserChief = $db->queryFirstField(
'SELECT no FROM general WHERE nation = %i AND level = 1 AND killturn > %i AND npc < 2 AND belong >= %i ORDER BY leadership DESC LIMIT 1', 'SELECT no FROM general WHERE nation = %i AND level = 1 AND killturn > %i AND npc < 2 AND belong >= %i ORDER BY leadership DESC LIMIT 1',
$nationID, $nationID,
$minKillturn, $minKillturn,
$maxBelong $maxBelong
); );
if($candUserChief){ if ($candUserChief) {
$userChief[$candUserChief] = 11; $userChief[$candUserChief] = 11;
$chiefCandidate[11] = $candUserChief; $chiefCandidate[11] = $candUserChief;
} }
@@ -1762,25 +1976,26 @@ class GeneralAI{
$promoted = $userChief; $promoted = $userChief;
if(!key_exists(11, $chiefCandidate)){ if (!key_exists(11, $chiefCandidate)) {
$candChiefHead = $db->queryFirstField( $candChiefHead = $db->queryFirstField(
'SELECT no FROM general WHERE nation = %i AND level = 1 AND npc >= 2 AND belong >= %i ORDER BY leadership DESC LIMIT 1', 'SELECT no FROM general WHERE nation = %i AND level = 1 AND npc >= 2 AND belong >= %i ORDER BY leadership DESC LIMIT 1',
$nationID, $nationID,
$maxBelong $maxBelong
); );
if($candChiefHead){ if ($candChiefHead) {
$chiefCandidate[11] = $candChiefHead; $chiefCandidate[11] = $candChiefHead;
$promoted[$candChiefHead] = 11; $promoted[$candChiefHead] = 11;
} }
} }
if($minChiefLevel < 11){ if ($minChiefLevel < 11) {
//무장 수뇌 후보 //무장 수뇌 후보
$candChiefStrength = $db->queryFirstColumn( $candChiefStrength = $db->queryFirstColumn(
'SELECT no FROM general WHERE nation = %i AND strength >= %i AND level = 1 AND belong >= %i ORDER BY strength DESC LIMIT %i', 'SELECT no FROM general WHERE nation = %i AND strength >= %i AND level = 1 AND belong >= %i ORDER BY strength DESC LIMIT %i',
$nationID, $nationID,
GameConst::$chiefStatMin, GameConst::$chiefStatMin,
$maxBelong, 12 - $minChiefLevel $maxBelong,
12 - $minChiefLevel
); );
//지장 수뇌 후보 //지장 수뇌 후보
$candChiefIntel = $db->queryFirstColumn( $candChiefIntel = $db->queryFirstColumn(
@@ -1791,44 +2006,43 @@ class GeneralAI{
12 - $minChiefLevel 12 - $minChiefLevel
); );
//무력, 지력이 모두 높은 장수를 고려하여.. //무력, 지력이 모두 높은 장수를 고려하여..
$iterCandChiefStrength = new \ArrayIterator($candChiefStrength); $iterCandChiefStrength = new \ArrayIterator($candChiefStrength);
$iterCandChiefIntel = new \ArrayIterator($candChiefIntel); $iterCandChiefIntel = new \ArrayIterator($candChiefIntel);
foreach(range(10, $minChiefLevel, -1) as $chiefLevel){ foreach (range(10, $minChiefLevel, -1) as $chiefLevel) {
if(key_exists($chiefLevel, $chiefCandidate)){ if (key_exists($chiefLevel, $chiefCandidate)) {
continue; continue;
} }
/** @var \ArrayIterator $iterCurrentType */ /** @var \ArrayIterator $iterCurrentType */
$iterCurrentType = ($chiefLevel % 2 == 0)?$iterCandChiefStrength:$iterCandChiefIntel; $iterCurrentType = ($chiefLevel % 2 == 0) ? $iterCandChiefStrength : $iterCandChiefIntel;
$candidate = $iterCurrentType->current(); $candidate = $iterCurrentType->current();
while(key_exists($candidate, $promoted)){ while (key_exists($candidate, $promoted)) {
$iterCurrentType->next(); $iterCurrentType->next();
if(!$iterCurrentType->valid()){ if (!$iterCurrentType->valid()) {
break; break;
} }
$candidate = $iterCurrentType->current(); $candidate = $iterCurrentType->current();
} }
if($candidate){ if ($candidate) {
$chiefCandidate[$chiefLevel] = $candidate; $chiefCandidate[$chiefLevel] = $candidate;
$promoted[$candidate] = $chiefLevel; $promoted[$candidate] = $chiefLevel;
} }
} }
foreach($chiefCandidate as $chiefLevel=>$chiefID){ foreach ($chiefCandidate as $chiefLevel => $chiefID) {
$db->update('general', [ $db->update('general', [
'level'=>$chiefLevel 'level' => $chiefLevel
], 'no=%i',$chiefID); ], 'no=%i', $chiefID);
} }
} }
} }
protected function calcTexRate():int{ protected function calcTexRate(): int
{
$db = DB::db(); $db = DB::db();
$nation = $this->nation; $nation = $this->nation;
$env = $this->env; $env = $this->env;
@@ -1838,10 +2052,10 @@ class GeneralAI{
//도시 //도시
$cityCount = $db->queryFirstField('SELECT count(*) FROM city WHERE nation = %i', $nationID); $cityCount = $db->queryFirstField('SELECT count(*) FROM city WHERE nation = %i', $nationID);
if($cityCount == 0) { if ($cityCount == 0) {
$db->update('nation', [ $db->update('nation', [
'war'=>0, 'war' => 0,
'rate'=>15 'rate' => 15
], 'nation=%i', $nationID); ], 'nation=%i', $nationID);
return 15; return 15;
} else { } else {
@@ -1849,20 +2063,21 @@ class GeneralAI{
$avg = ($devRate['pop_p'] + $devRate['all_p']) / 2; $avg = ($devRate['pop_p'] + $devRate['all_p']) / 2;
if($avg > 0.95) $rate = 25; if ($avg > 0.95) $rate = 25;
elseif($avg > 0.70) $rate = 20; elseif ($avg > 0.70) $rate = 20;
elseif($avg > 0.50) $rate = 15; elseif ($avg > 0.50) $rate = 15;
else $rate = 10; else $rate = 10;
$db->update('nation', [ $db->update('nation', [
'war'=>0, 'war' => 0,
'rate'=>$rate 'rate' => $rate
], 'nation=%i', $nationID); ], 'nation=%i', $nationID);
return $rate; return $rate;
} }
} }
protected function calcGoldBillRate():int{ protected function calcGoldBillRate(): int
{
$db = DB::db(); $db = DB::db();
$nation = $this->nation; $nation = $this->nation;
$env = $this->env; $env = $this->env;
@@ -1871,7 +2086,7 @@ class GeneralAI{
$cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID); $cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID);
if(!$cityList){ if (!$cityList) {
return 20; return 20;
} }
@@ -1882,20 +2097,25 @@ class GeneralAI{
$income = $goldIncome + $warIncome; $income = $goldIncome + $warIncome;
$outcome = getOutcome(100, $dedicationList); $outcome = getOutcome(100, $dedicationList);
$bill = intval($income / $outcome * 80); // 수입의 80% 만 지급 $bill = intval($income / $outcome * 80); // 수입의 80% 만 지급
if($bill < 20) { $bill = 20; } if ($bill < 20) {
if($bill > 200) { $bill = 200; } $bill = 20;
}
if ($bill > 200) {
$bill = 200;
}
$db->update('nation', [ $db->update('nation', [
'bill'=>$bill, 'bill' => $bill,
], 'nation=%i', $nationID); ], 'nation=%i', $nationID);
return $bill; return $bill;
} }
protected function calcRiceBillRate():int{ protected function calcRiceBillRate(): int
{
$db = DB::db(); $db = DB::db();
$nation = $this->nation; $nation = $this->nation;
$env = $this->env; $env = $this->env;
@@ -1904,7 +2124,7 @@ class GeneralAI{
$cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID); $cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID);
if(!$cityList){ if (!$cityList) {
return 20; return 20;
} }
@@ -1915,16 +2135,20 @@ class GeneralAI{
$income = $riceIncome + $wallIncome; $income = $riceIncome + $wallIncome;
$outcome = getOutcome(100, $dedicationList); $outcome = getOutcome(100, $dedicationList);
$bill = intval($income / $outcome * 80); // 수입의 80% 만 지급 $bill = intval($income / $outcome * 80); // 수입의 80% 만 지급
if($bill < 20) { $bill = 20; } if ($bill < 20) {
if($bill > 200) { $bill = 200; } $bill = 20;
}
if ($bill > 200) {
$bill = 200;
}
$db->update('nation', [ $db->update('nation', [
'bill'=>$bill, 'bill' => $bill,
], 'nation=%i', $nationID); ], 'nation=%i', $nationID);
return $bill; return $bill;
} }
} }
+14 -15
View File
@@ -65,14 +65,9 @@ class TurnExecutionHelper
return true; return true;
} }
public function processNationCommand(string $commandClassName, ?array $commandArg, LastTurn $commandLast):LastTurn{ public function processNationCommand(Command\NationCommand $commandObj):LastTurn{
$general = $this->getGeneral(); $general = $this->getGeneral();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$commandObj = buildNationCommandClass($commandClassName, $general, $gameStor->getAll(true), $commandLast, $commandArg);
while(true){ while(true){
$failReason = $commandObj->testRunnable(); $failReason = $commandObj->testRunnable();
if($failReason){ if($failReason){
@@ -108,15 +103,13 @@ class TurnExecutionHelper
return $commandObj->getResultTurn(); return $commandObj->getResultTurn();
} }
public function processCommand(string $commandClassName, ?array $commandArg){ public function processCommand(Command\GeneralCommand $commandObj){
$general = $this->getGeneral(); $general = $this->getGeneral();
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$commandObj = buildGeneralCommandClass($commandClassName, $general, $gameStor->getAll(true), $commandArg);
while(true){ while(true){
$failReason = $commandObj->testRunnable(); $failReason = $commandObj->testRunnable();
if($failReason){ if($failReason){
@@ -230,6 +223,8 @@ WHERE turntime < %s ORDER BY turntime ASC, `no` ASC',
$currentTurn = null; $currentTurn = null;
$gameStor = KVStorage::getStorage($db, 'game_env');
foreach($generalsTodo as $rawGeneral){ foreach($generalsTodo as $rawGeneral){
$generalCommand = $rawGeneral['action']; $generalCommand = $rawGeneral['action'];
$generalArg = Json::decode($rawGeneral['arg'])??[]; $generalArg = Json::decode($rawGeneral['arg'])??[];
@@ -244,6 +239,9 @@ WHERE turntime < %s ORDER BY turntime ASC, `no` ASC',
$general = General::createGeneralObjFromDB($rawGeneral['no']); $general = General::createGeneralObjFromDB($rawGeneral['no']);
$turnObj = new static($general); $turnObj = new static($general);
$env = $gameStor->getAll(true);
$generalCommandObj = buildGeneralCommandClass($generalCommand, $general, $env, $generalArg);
$hasNationTurn = false; $hasNationTurn = false;
if($general->getVar('nation') != 0 && $general->getVar('level') >= 5){ if($general->getVar('nation') != 0 && $general->getVar('level') >= 5){
$nationStor = KVStorage::getStorage($db, 'nation_env'); $nationStor = KVStorage::getStorage($db, 'nation_env');
@@ -258,15 +256,18 @@ WHERE turntime < %s ORDER BY turntime ASC, `no` ASC',
$hasNationTurn = true; $hasNationTurn = true;
$nationCommand = $rawNationTurn['action']??null; $nationCommand = $rawNationTurn['action']??null;
$nationArg = Json::decode($rawNationTurn['arg']??null); $nationArg = Json::decode($rawNationTurn['arg']??null);
$lastNationTurn = LastTurn::fromJson($lastNationTurn);
$nationCommandObj = buildNationCommandClass($nationCommand, $general, $env, $lastNationTurn, $nationArg);
} }
if($general->getVar('npc') >= 2){ if($general->getVar('npc') >= 2){
$ai = new GeneralAI($turnObj->getGeneral()); $ai = new GeneralAI($turnObj->getGeneral());
if($hasNationTurn){ if($hasNationTurn){
[$nationCommand, $nationArg] = $ai->chooseNationTurn($nationCommand, $nationArg); $nationCommandObj = $ai->chooseNationTurn($nationCommandObj);
} }
[$generalCommand, $generalArg] = $ai->chooseGeneralTurn($generalCommand, $generalArg); // npc AI 처리 $generalCommandObj = $ai->chooseGeneralTurn($generalCommandObj); // npc AI 처리
} }
@@ -274,13 +275,11 @@ WHERE turntime < %s ORDER BY turntime ASC, `no` ASC',
$turnObj->preprocessCommand(); $turnObj->preprocessCommand();
if($hasNationTurn){ if($hasNationTurn){
$resultNationTurn = $turnObj->processNationCommand( $resultNationTurn = $turnObj->processNationCommand(
$nationCommand, $nationCommandObj
$nationArg,
LastTurn::fromJson($lastNationTurn)
); );
$nationStor->setValue($lastNationTurnKey, $resultNationTurn->toJson()); $nationStor->setValue($lastNationTurnKey, $resultNationTurn->toJson());
} }
$turnObj->processCommand($generalCommand, $generalArg); $turnObj->processCommand($generalCommandObj);
} }
pullNationCommand($general->getVar('nation'), $general->getVar('level')); pullNationCommand($general->getVar('nation'), $general->getVar('level'));
pullGeneralCommand($general->getID()); pullGeneralCommand($general->getID());