548. Lux AI Season 2 | lux-ai-season-2
首先,我必须感谢 @ttigga 和 @danmctree 共同打造了我所能想象的最紧张刺激、充满不确定性的冠军争夺战。向你们两位致以最诚挚的祝贺!
Lux 游戏规格可以在 这里 找到。
在这个比赛开始前,我参加了 Lux Beta 版本,但在beta后规则变更公布时,我丢弃了大部分策略代码。主要的变更是地衣现在可以产生能量。当时我决定要大力推动早期和频繁的地衣生长,以产生尽可能多的能量。这在比赛中基本保持不变,即使机器人产生的太阳能被宣传为一种可行的替代方案。我只是没有必要的纪律让机器人长时间静止不动——总有太多其他事情需要它去做!

我会为所有单位和工厂确定行动,更新状态,然后重复这个过程。每次调用大约运行2.9秒,根据单位数量和寻路复杂度,这给了我5到50多步的规划量。我模拟了几乎所有事情,包括我未来的单位。我尝试在有限的战斗情况下猜测对手单位的移动。我从未添加处理自碰撞或工厂爆炸的逻辑——可能是乐观地认为当真正到达那时情况会有所不同!我假设对手的地衣值是静态的,这从来都不正确,但也不会相差太远。
基本原则:邻近冰矿,靠近矿石,靠近无/少碎石区域。我反复权衡附近矿石与附近平坦地形的相对价值。平坦地形在游戏早期可能非常宝贵。与其挖掘(昂贵),我的轻型机器人可以去骚扰附近的对手轻型机器人(廉价),打乱他们早期的计划。但我不能只是避开矿石,否则到游戏中期会完全被数量压制。我最终添加了一些逻辑来测量矿石稀缺性,并比较我当前工厂与对手工厂的矿石情况,以确定矿石邻近性的动态加成。还有冰的脆弱性/安全性问题(即是否可以轻易剥夺该位置对冰的访问权),但这个问题我稍后会详细说明。
我的策略主要依赖于种植地衣,直到我的能量收入大于当前(估算的)能量消耗。然后我会建造一些单位,然后回到开采冰和种植地衣——直到我的能量收入准备好支持更多单位。这绝对不是一个完美的策略,但效果还可以。我倾向于少建单位,这样我总有充足的能量可以分配。我从来不确定如何决定是建造轻型机器人还是节省金属建造重型机器人。我尝试为每个工厂维持最低数量的轻型机器人,然后在此基础上建造重型机器人。
我在beta期间大量使用链式系统,但效果有限。我的代码容易出错,而且似乎只需一个轻型机器人就能轻易扰乱整个活动链。我早期就决定除了工厂邻近的简单情况外,我会避免使用链式系统。显然Tigga和Siesta最终证明我错了。他们基于链式的经济系统 definitely 更优越。我很少能长时间有效干扰链式系统。使用链式系统还允许更灵活的工厂生成位置——冰不再 necessarily 需要与工厂相邻。所有资源都只需一条链的距离!非常强大。
在每次模拟步骤中,我都会为每个单位设置或更新"角色"和"目标"。角色是一个长期但非永久的工作/责任。目标是一个单一的多步骤目标,通常涉及移动和/或达到某些资源或能量阈值。每个角色控制不同的目标状态机。当前角色和目标会在调用之间持久保存。每一步我都会:
我最终得到了大约10个独特角色:antagonizer(骚扰者)、attacker(攻击者)、blockade(封锁者)、cow(奶牛)、miner(矿工)、pillager(掠夺者)、protector(保护者)、recharge(充能者)、relocate(迁移者)、power_transporter(能量运输者)、water_transporter(水运输者)。
角色选择大致如下,取决于单位类型和工厂情况:
new_role = (
None
or RolePillager.from_lichen_cell(step, unit, max_dist=20, max_count=1)
or RoleAntagonizer.from_chain(step, unit, max_dist=20, max_count=1)
or RoleRelocate.from_assist_ice_conflict(step, unit)
or RoleAntagonizer.from_mine(step, unit, max_dist=20, ice=True, max_water=50)
or RoleAntagonizer.from_mine(step, unit, max_dist=20, max_count=1)
or RoleCow.from_lowland_route(step, unit, max_dist=2, min_size=50)
# 等等...
)
并非所有单位都是平等的。我不希望一个低优先级的轻型机器人占据一个重型机器人急需移动到的格子,或者捡起本可以更好利用的能量。我使用了一组优先级情况来确保单位(和工厂)以合理的顺序锁定它们的行动。我写了一些漂亮的Python代码来相对干净地实现这一点。
class EntityGroup:
def __init__(self, step, entities):
self.step = step
self.entities = entities
def __getattr__(self, attr):
if attr not in self.__dict__:
def func(*args, **kwargs):
for e in self.entities:
if e.last_action_step < self.step:
e.action = getattr(e, attr)(self.step, *args, **kwargs)
if e.action is not None:
e.last_action_step = self.step
return func
return super().__getattr__(attr)
group = EntityGroup(step, board.player.units() + board.player.factories())
group.do_pickup_resource_from_exploding_factory()
group.do_move_win_collision()
group.do_protector_transfer(heavy=True)
group.do_protector_pickup(heavy=True)
group.do_protector_move(heavy=True)
group.do_factory_end_phase_water()
group.do_factory_build()
# 等等...
在冲刺2期间,我注意到一些比赛中两个工厂会偶然发生争夺冰的情况。这在当时感觉是个疯狂的想法,但我觉得如果这意味着我能很好地定位以剥夺对手工厂的冰资源,同时使用轻型水运输者耗死他们,那么故意选择一个次优的工厂生成位置可能是值得的。我称这种策略为"冰冲突"。我在冲刺2期间开始这样做,给我的最终工厂放置一个巨大的加成,如果它在对手工厂附近所有冰资源4距离范围内。这使我赢得了冲刺2,并暂时打破了Kaggle的匹配算法,将我的分数推到了36k+(RIP Homeostat)。我以为这种策略会被复制或反制,但事实并非如此。当Bovard修复匹配算法时,我停用了"生产"版本中的冰冲突逻辑,以便留到雨天(即最后一天)。我继续改进这个策略,包括使用一对轻型"封锁"单位来阻挡对手的水运输者。最终,冰冲突生成是唯一让我与Siesta和Tigga改进的经济策略保持竞争力的东西。有趣的是,这对flg完全不起作用,他们倾向于使用重型机器人灵活地在地图上移动冰到需要的地方。这导致了很少的工厂击杀,我不得不接受原本次优的工厂位置。这种弱势对局严重影响了我的最终得分。
在一些情况下,单位和工厂需要相互协调,以确保它们不会试图做/使用相同的东西。
参加这场比赛时我几乎没有期待。我不知道会投入多少时间, mostly 以为RL会像赛季1一样占据主导地位。事实证明这个游戏非常具有挑战性、出人意料的深且平衡良好。我非常享受在这个项目上折腾3个多月。感谢 @stonet2000 和 @bovard 组织这一切。特别致敬那些不断推动游戏/元界限的人们:@harmbuisman、@danmctree 和 @ttigga。也感谢 @ferdinandlimburg 和 @philippkostuch 在过去一个月左右推动了完全不同且具竞争力的解决方案。期待在 future 排行榜上看到你们所有人。
编辑: