返回列表

1st place solution

548. Lux AI Season 2 | lux-ai-season-2

开始: 2023-01-25 结束: 2023-05-08 游戏AI 数据算法赛
第一名解决方案 - Lux AI 赛季2

Lux AI 赛季2:第一名解决方案

作者: Ryan Anderson (Kaggle ID: ryandy22)
发布日期: 2023年5月8日
比赛排名: 第1名

首先,我必须感谢 @ttigga@danmctree 共同打造了我所能想象的最紧张刺激、充满不确定性的冠军争夺战。向你们两位致以最诚挚的祝贺!

背景

Lux 游戏规格可以在 这里 找到。

整体策略

在这个比赛开始前,我参加了 Lux Beta 版本,但在beta后规则变更公布时,我丢弃了大部分策略代码。主要的变更是地衣现在可以产生能量。当时我决定要大力推动早期和频繁的地衣生长,以产生尽可能多的能量。这在比赛中基本保持不变,即使机器人产生的太阳能被宣传为一种可行的替代方案。我只是没有必要的纪律让机器人长时间静止不动——总有太多其他事情需要它去做!

我的先进可视化工具

可视化工具截图

前向模拟

我会为所有单位和工厂确定行动,更新状态,然后重复这个过程。每次调用大约运行2.9秒,根据单位数量和寻路复杂度,这给了我5到50多步的规划量。我模拟了几乎所有事情,包括我未来的单位。我尝试在有限的战斗情况下猜测对手单位的移动。我从未添加处理自碰撞或工厂爆炸的逻辑——可能是乐观地认为当真正到达那时情况会有所不同!我假设对手的地衣值是静态的,这从来都不正确,但也不会相差太远。

工厂放置

基本原则:邻近冰矿,靠近矿石,靠近无/少碎石区域。我反复权衡附近矿石与附近平坦地形的相对价值。平坦地形在游戏早期可能非常宝贵。与其挖掘(昂贵),我的轻型机器人可以去骚扰附近的对手轻型机器人(廉价),打乱他们早期的计划。但我不能只是避开矿石,否则到游戏中期会完全被数量压制。我最终添加了一些逻辑来测量矿石稀缺性,并比较我当前工厂与对手工厂的矿石情况,以确定矿石邻近性的动态加成。还有冰的脆弱性/安全性问题(即是否可以轻易剥夺该位置对冰的访问权),但这个问题我稍后会详细说明。

资源开采与机器人建造

我的策略主要依赖于种植地衣,直到我的能量收入大于当前(估算的)能量消耗。然后我会建造一些单位,然后回到开采冰和种植地衣——直到我的能量收入准备好支持更多单位。这绝对不是一个完美的策略,但效果还可以。我倾向于少建单位,这样我总有充足的能量可以分配。我从来不确定如何决定是建造轻型机器人还是节省金属建造重型机器人。我尝试为每个工厂维持最低数量的轻型机器人,然后在此基础上建造重型机器人。

关于链式系统

我在beta期间大量使用链式系统,但效果有限。我的代码容易出错,而且似乎只需一个轻型机器人就能轻易扰乱整个活动链。我早期就决定除了工厂邻近的简单情况外,我会避免使用链式系统。显然Tigga和Siesta最终证明我错了。他们基于链式的经济系统 definitely 更优越。我很少能长时间有效干扰链式系统。使用链式系统还允许更灵活的工厂生成位置——冰不再 necessarily 需要与工厂相邻。所有资源都只需一条链的距离!非常强大。

角色与目标

在每次模拟步骤中,我都会为每个单位设置或更新"角色"和"目标"。角色是一个长期但非永久的工作/责任。目标是一个单一的多步骤目标,通常涉及移动和/或达到某些资源或能量阈值。每个角色控制不同的目标状态机。当前角色和目标会在调用之间持久保存。每一步我都会:

  1. 检查每个单位,确认当前角色是否仍然有效
  2. 检查每个单位的特殊角色转换情况
  3. 遍历所有单位,为没有角色的单位分配角色,直到所有单位都有角色
  4. 更新所有单位的目标

我最终得到了大约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 排行榜上看到你们所有人。

编辑:

我开源的赛季2代码(Python)可以在 这里 找到。赛季2 NeurIPS版(C++)在 这里

同比赛其他方案