快乐的3个露营者-PHP
一群胆小鬼,喜欢用令人衰弱的咒语向对手撒谎,看着他们腐烂。
编辑:鲁比先生受到了严厉的指责,不再说坏话了
便利
Grass - atk:50 def:99 spd:81 Confuse Poison Heal
一个有毒的无臂海狸,喜欢将人与有问题的握手混淆
软盘
Water - atk:50 def:99 spd:81 Confuse Burn Heal
公告板资深人士,对轻率的谈话和火焰战争情有独钟。
坚果
Fire - atk:50 def:99 spd:81 Burn Poison Heal
大规模杀伤性武器是他最喜欢的糖果。
块状
Php - lines:500 clarity:05 spd:01 Gather Guess Store
凭借其近2位数的智商和惊人的记忆力,Lumpy可以猜出敌人的举动。好吧,主要是。
战略
策略是让对手尽快中毒,燃烧和困惑。
没有使用睡眠,因为它似乎不如上面的三个咒语强大。
从长远来看,混乱是致命的,因为它可以减少30%的攻击(造成伤害和施法),阻止治疗者治愈自己并严重伤害重击者(50点防御力/ 100点攻击力的怪物会对其自身造成20点伤害)。
一旦敌人被彻底粘贴,我的营员们只会看着他嘶嘶作响,腐烂并猛烈地把自己撞死。
高防御力和治疗能力可以减轻痛苦中的传入伤害。
当我的三个营员在战斗时,魔鹿驼峰会注视着敌人的一举一动,有时还能识别出它们。这些信息将反馈给我们的战斗机,他们会尽力利用这些信息。
在防守之后,速度是最重要的数据。
主动性对于在下一次打击发生之前进行康复至关重要。
根本不使用攻击。
咒语是终极武器吗?
诸如毒药,燃烧和迷惑之类的法术可以逃脱其他攻击的整体石头/剪刀布/剪刀逻辑。
一旦怪物受到影响,即使施法者死了,它也会继续失去生命值。好像施法者的幽灵一直在攻击他。
此外,毒药变得比完全疲惫的大规模攻击(5回合后超过50点)更强大。
被毒害和燃烧的怪物的预期寿命即使经过3次治疗也不会超过8回合。
就像马丁的机器人似乎指出的那样,游戏的平衡性非常好。
从根本上说,这将是在纯净施法者和纯净攻击者之间取得平衡的主动性。
编码
调用 php campers.php
这是一个丑陋的混乱局面,但坦率地说,该界面也无济于事。
既然出现了适当的侵略性比赛,我就实施了我长期计划中的敌人举动猜测。
分析攻击需要各种特技演绎和最后转弯状态的持久存储,这意味着使用偏执控制器接口进行全面战争。
它既不是漂亮的东西,也不是六足的兔子,但是做得很好。
<?php
// ============================================================================
// Game
// ============================================================================
class G {
static $code_type = array ("Normal", "Psychic", "Fire", "Water", "Grass", "?", "self");
static $code_move = array ("Punch", "Heal", "Slow", "Pain", "Sleep", "Weaken", "Fireball", "Burn", "Sharpen", "Watergun", "Confuse", "Shield", "Vine", "Poison", "Sap", "?", "self", "pass");
static $move_uses = array (1000,3,5,1000,3,5,1000,3,5,1000,3,5,1000,3,5, 2000,2000);
static $move_type = array (0,0,0,1,1,1,2,2,2,3,3,3,4,4,4, 5,5,5);
static $move_dmg = array (20,0,10,20,0,10,20,0,10,20,0,10,20,0,10, 20,10,0);
static $move_forbidden = array (1,1,1,0,0,0,4,4,4,2,2,2,3,3,3);
static $code_effect = array ("N", "Poison", "Confuse", "Burn", "Sleep", "H", "Sharpen", "Weaken", "Shield", "Sap", "Slow");
static $decode_type, $decode_move, $decode_effect;
static $damage_multiplier = array (
array (2, 0, 1, 1, 1, 0),
array (0, 2, 1, 1, 1, 0),
array (1, 1,.5, 2,.5, 0),
array (1, 1,.5,.5, 2, 0),
array (1, 1, 2,.5,.5, 0),
array (2, 2, 2, 2, 2,-1),
array (9, 9, 9, 9, 9, 9, 1));
static $atk_score = array ("Poison"=> 1002, "Confuse"=>1001, "Burn"=>1000);
static $status_field = "atk:def:spd:hp:type:Pturns";
static $all_moves, $strong_moves, $medium_moves, $effect_moves, $possible_moves;
function init()
{
self::$status_field = explode (":", self::$status_field);
foreach (array ("type", "move", "effect") as $table) self::${"decode_$table"} = array_flip (self::${"code_$table"});
foreach (self::$code_move as $c=>$m)
{
if ($m == "?") break;
self::$all_moves[] = new Move($m);
if (self::$move_uses[$c] > 5) self::$strong_moves[] = $m;
if (self::$move_uses[$c] == 5) self::$medium_moves[] = $m;
if (self::$move_uses[$c] == 3) self::$effect_moves[] = $m;
for ($type = 0 ; $type != 5 ; $type++) if ((self::$move_uses[$c] > 5) && (self::$move_forbidden[$c] != $type)) self::$possible_moves[$type][] = $m;
}
}
function __construct ($name, $team)
{
$this->turn = 0;
$this->name = $name;
$this->team = $team;
$this->results_pending = false;
}
function parse_team ($tpack, $own_team)
{
$pack = explode ("|", $tpack);
list ($name,$active) = explode (":", array_shift($pack));
if ($own_team)
{
$team = $this->team;
}
else
{
if (!isset($this->enemies[$name])) $this->enemies[$name] = new Team(array (new Monster (), new Monster (), new Monster ()));
$team = $this->foes = $this->enemies[$name];
}
$team->active = $active;
foreach ($pack as $i=>$mpack) $team->monster[$i]->parse_monster ($own_team, $mpack);
}
function choose_active ()
{
// detect start of round
$team = $this->team;
$foes = $this->foes;
foreach ($team->monster as $i=>$m) if ($m->hp > 0) $candidate[$i] = $m;
if (count ($candidate) == 3)
{
$this->results_pending = false;
$this->round++;
// reinitialize all monsters
foreach (array($team, $foes) as $t)
foreach ($t->monster as $m)
$m->start_round();
// guess initial opponent
$opponent = $foes->initial_opponent();
}
else
{
$this->analyze_last_round();
$opponent = $foes->active();
}
return $this->do_switch ($opponent);
}
function choose_attacker ($foe)
{
foreach ($this->team->monster as $i=>$m) if ($m->can_attack($foe)) $candidate[$i] = $m;
if (isset($candidate))
{
uasort ($candidate, function ($a,$b) use ($foe) { return ($a->atk_score != $b->atk_score) ? $b->atk_score - $a->atk_score : $b->life_expectancy($foe) - $a->life_expectancy($foe); });
return key($candidate);
}
return -1;
}
function do_switch ($foe)
{
$replacement = $this->choose_attacker ($foe);
if ($replacement < 0)
{
$candidate = $this->team->monster;
uasort ($candidate, function ($a,$b) use ($foe) { return $b->life_expectancy($foe) - $a->life_expectancy($foe); });
$replacement = key($candidate);
}
$this->old_own = $this->team->monster[$replacement];
$this->old_own->attack = "pass";
return $replacement;
}
function choose_action ()
{
$this->analyze_last_round();
$own = $this->team->active();
$foe = $this->foes->active();
$this->old_own = $own;
if ($own->hp <= $own->max_damage($foe) && $own->can_do ("Heal")) return $own->execute("Heal");
if ($attack = $own->can_attack($foe)) return $own->execute($attack);
if ($own->hp <= 50 && $own->can_do ("Heal")) return $own->execute("Heal");
return 10 + $this->do_switch ($foe);
}
function choose_bonus()
{
foreach ($this->team->monster as $m)
{
if ($m->spd_b == 0) { $m->spd_b++; $res[] = 2; }
else { $m->def_b++; $res[] = 1; }
}
return implode (":", $res);
}
function parse ($parts)
{
self::parse_team ($parts[1], true);
self::parse_team ($parts[2], false);
}
function analyze_last_round()
{
if ($this->results_pending)
{
$this->results_pending = false;
$foes = $this->foes;
$foe = null;
foreach ($foes->monster as $m) if ($m->hp != $m->old->hp) $foe = $m;
if ($foe === null) $foe = $foes->monster[$foes->active];
$this->old_own->guess_attack($foe);
}
}
function process ($line)
{
$parts = explode ("#", $line);
switch ($parts[0])
{
case "T": // register for tournament
echo "$this->name|$this->team";
break;
case "C": // designate active monster
$this->parse ($parts);
echo $this->choose_active();
break;
case "A": // choose round action
$this->parse ($parts);
echo $this->choose_action();
// save current state
foreach (array($this->team, $this->foes) as $t)
foreach ($t->monster as $m)
{
unset ($m->old);
$m->old = clone ($m);
}
$this->results_pending = true;
break;
case "B": // distribute stat bonus
echo $this->choose_bonus();
break;
}
}
}
G::init();
// ============================================================================
// Move
// ============================================================================
class Move {
function __construct ($move)
{
$this->register($move);
}
function register ($move)
{
$this->type = G::$decode_move[$move];
$this->reinit();
}
function reinit()
{
$this->uses = G::$move_uses[$this->type];
}
function __tostring() { return G::$code_move[$this->type]."($this->uses)"; }
}
// ============================================================================
// Monster
// ============================================================================
class Monster {
function __construct ($name="?", $type="?", $atk=100, $def=100, $spd=100, $m0="?", $m1="?", $m2="?")
{
$this->name = $name;
$this->type = G::$decode_type[$type];
$this->atk = $atk;
$this->def = $def;
$this->spd = $spd;
$this->hp = 100;
$this->move = array (new Move($m0), new Move($m1), new Move($m2));
$this->atk_b = 0;
$this->def_b = 0;
$this->spd_b = 0;
foreach (G::$code_effect as $e) $this->$e = 0;
}
function __tostring ()
{
return implode (":", array (
$this->name,
$this->type,
$this->atk,
$this->def,
$this->spd,
$this->move[0]->type,
$this->move[1]->type,
$this->move[2]->type));
}
function start_round()
{
foreach ($this->move as $m) $m->reinit();
}
function parse_monster ($own_team, $spack)
{
$pack = explode (":", $spack);
$name = array_shift ($pack); // get name
array_shift ($pack); // skip id
if ($this->name == "?") $this->name = $name; // get paranoid
else if ($this->name != $name) die ("expected $this->name, got $name");
// store updated values
foreach (G::$status_field as $var) $this->$var = array_shift ($pack);
if ($own_team)
{
foreach ($this->move as $m) $m->new_count = array_shift($pack);
$pack = array_slice ($pack, 3); // these are maintained internally
}
$var = array();
foreach ($pack as $e) @$var[G::$code_effect[$e]]++;
foreach (G::$code_effect as $e) $this->$e = @$var[$e]+0;
}
function damage_recieved ($attack, $foe=null)
{
if ($attack == "self") $foe = $this;
$a = G::$decode_move[$attack];
$type = G::$move_type[$a];
$dmg = g::$move_dmg[$a];
if ($dmg == 0) return 0;
$atk = ($foe ->atk+$foe ->atk_b) * pow (.8, ($foe ->Weaken - $foe ->Sharpen));
$def = ($this->def+$this->def_b) * pow (.8, ($this->Sap - $this->Shield ));
$boost = ($foe->type == $type) ? 1.2 : 1;
return max (floor ($dmg * $atk / $def * $boost * G::$damage_multiplier[$this->type][$type]), 1);
}
function guess_attack_from_effect ($attacks)
{
foreach ($attacks as $status) if ($this->$status != $this->old->$status) return $status;
return "?";
}
function guess_attack_from_damage ($foe, $damages)
{
$select = array();
foreach (G::$possible_moves[$foe->type] as $attack)
{
$dmg = $this->damage_recieved ($attack, $foe);
foreach ($damages as $damage) if ($damage != 0 && abs ($dmg/$damage-1) < 0.1) $select[$attack] = 1;
}
$res = array();
foreach ($select as $a=>$x) $res[] = $a;
return $res;
}
function guess_attack ($foe)
{
$attempt = G::$decode_move[$this->old->attack];
$success = ($this->old->attack == "pass");
foreach ($this->move as $m)
{
if ($m->type == $attempt)
{
if ($m->new_count == $m->uses-1)
{
$m->uses--;
$success = true;
}
break;
}
}
$possible = array();
$attack = $this->guess_attack_from_effect (array("Burn", "Confuse", "Poison", "Sleep", "Slow", "Weaken", "Sap"));
if ($attack == "?") $attack = $foe->guess_attack_from_effect (array("Sharpen", "Shield"));
if ($attack == "?")
{
$foe_damage = $this->old->hp - $this->hp - (10 * $this->Burn + 5 * $this->Pturns*$this->Poison);
if ($this->old->attack == "Heal" && $success) $foe_damage += 50;
$possible_dmg[] = $foe_damage;
//;!;if ($this->Confuse) $possible_dmg[] = $foe_damage + $this->damage_recieved ("self");
$possible = $this->guess_attack_from_damage ($foe, $possible_dmg);
if (count ($possible) == 1) $attack = $possible[0];
}
if ($attack == "?")
{
$own_damage = $foe->old->hp - $foe->hp
- (10 * $foe->Burn + 5 * $foe->Pturns*$foe->Poison)
+ $foe->damage_recieved ($this->attack);
if (abs ($own_damage/50+1) < 0.1) $attack = "Heal";
}
if ($attack != "?")
{
$type = G::$decode_move[$attack];
if ($attack != "?")
{
foreach ($foe->move as $m) if ($m->type == $type) goto found_old;
foreach ($foe->move as $m) if ($m->type == 15) { $m->register($attack); goto found_new; }
}
found_new:
found_old:
}
}
function max_damage($foe)
{
$dmg = 0;
foreach ($foe->move as $m) $dmg = max ($dmg, $this->damage_recieved (G::$code_move[$m->type], $foe));
return $dmg;
}
function expected_damage ($foe)
{
return $this->max_damage($foe) + 10 * $this->Burn + 5 * ($this->Pturns+1);
}
function life_expectancy ($foe)
{
$hp = $this->hp;
$poison = $this->Pturns;
$heal = $this->can_do ("Heal");
$dmg = $this->max_damage($foe);
for ($turn = 0 ; $hp > 0 && $turn < 10; $turn++)
{
$hp -= 10 * $this->Burn + 5 * $poison;
if ($poison > 0) $poison++;
$hp -= $dmg;
if ($hp <= 0 && $heal > 0) { $hp+=50; $heal--; }
}
return 100 * $turn + $this->hp;
}
function can_attack ($foe)
{
$attack = false;
if ($this->hp > 0)
{
if (!$foe->Poison && $this->can_do ("Poison" )) $attack = "Poison";
else if (!$foe->Confuse && $this->can_do ("Confuse")) $attack = "Confuse";
else if (!$foe->Burn && $this->can_do ("Burn" )) $attack = "Burn";
}
$this->atk_score = ($attack === false) ? 0 : G::$atk_score[$attack];
return $attack;
}
function can_do($move)
{
$type = G::$decode_move[$move];
foreach ($this->move as $m) if ($m->type == $type && $m->uses > 0) return $m->uses;
return false;
}
function execute($move)
{
$type = G::$decode_move[$move];
foreach ($this->move as $i=>$m) if ($m->type == $type)
{
if ($m->uses > 0)
{
//;!; $m->uses--;
$this->attack = $move;
}
else $this->attack = "pass";
return $i;
}
die ("$this asked to perform $move, available ".implode(",", $this->move));
}
}
// ============================================================================
// Team
// ============================================================================
class Team {
function __construct ($members)
{
$this->monster = $members;
}
function __tostring()
{
return implode ("|", $this->monster);
}
function active ()
{
return $this->monster[$this->active];
}
function initial_opponent()
{
return $this->monster[0];
}
}
// ============================================================================
// main
// ============================================================================
$input = $argv[1];
$team_name = "H3C";
$mem_file = "$team_name/memory.txt";
$trc_file = "$team_name/trace.txt";
if (!file_exists($team_name)) mkdir($team_name, 0777, true) or die ("could not create storage directory '$team_name'");
if ($input == "T") array_map('unlink', glob("$team_name/*.txt"));
if (file_exists($mem_file)) $game = unserialize (file_get_contents ($mem_file));
else
{
$team = new Team (
array (
new Monster ("Handy" , "Grass" , 50, 99, 81, "Confuse", "Poison", "Heal"),
new Monster ("Nutty" , "Fire" , 50, 99, 81, "Burn" , "Poison", "Heal"),
new Monster ("Flippy", "Water" , 50, 99, 81, "Confuse" , "Burn" , "Heal")));
$game = new G($team_name,$team);
}
$game->process ($input);
file_put_contents ($mem_file, serialize($game));
结果
LittleKid仍然令人生畏,但我的三人组以一定优势击败了他的毒蛇怪。
马丁的机器人由于缺乏主动性而注定了失败,提高速度将要求降低攻击力,这将削弱其在破坏能力上的优势。
来自JavaScript星球的新竞争者与团队是一对一的,但与其他竞争对手的竞争则更糟。它们实际上有助于降低LittleKid的得分:)。
所以看来我可爱的朋友们仍然是山丘之王-现在...
170 H3C
158 Nodemon
145 LittleKid
55 InsideYourHead
42 HardenedTrio
30 BitterRivals