Classic, um exemplo comentado
O computador integrado escrito em Lua: os arquivos, a tabela de tarefas, uma tarefa, as notas de peculiaridades, o que fica no motor e como se prova que as ordens são iguais.
Este capítulo resume docs/brains/oabrain-standard.md, docs/brains/authoring.md e docs/playing-with-brains.md. Esses documentos são o texto normativo.
O que é o Classic
Classic é brains/classic. Ele é a documentação do computador integrado: as mesmas ordens, escritas para que você possa lê-las. O jogador integrado continua no motor e continua sendo o padrão. Escolher o Classic põe este pacote no lugar. Deixar o lugar no jogador integrado roda o computador do próprio motor.
O manifesto diz que works-with é um asterisco e que fullAwareness é true. A descrição diz que ele joga exatamente como o computador integrado. A visão total é a flag do manifesto. Ela é permitida numa partida em rede, e o lugar a sinaliza. O ID, o nome e o caminho de instalação não fazem de um pacote o Classic. Os arquivos precisam ser a pasta brains/classic desta compilação, com os mesmos caminhos e os mesmos bytes.
Manifesto ·oabrain.yaml# SPDX-FileCopyrightText: The Open Annihilation Authors; see COPYRIGHT
# SPDX-License-Identifier: GPL-3.0-only
oabrain: 1
id: classic
name: Classic
description: "The engine's own computer player, written in Lua: it plays exactly as the built-in computer player does."
version: "1.0"
author:
name: The Open Annihilation Authors
tags: [classic, teaching]
works-with: ["*"]
requires:
engine: ">= 0.8.6"
levels:
- id: easy
name: Easy
difficulty: easy
- id: medium
name: Medium
difficulty: medium
- id: hard
name: Hard
difficulty: hard
fullAwareness: true
entry: brain.lua
Os arquivos
brain.lua monta uma tarefa para cada esquadrão de 0 a 9. No primeiro tick, onTick atualiza as listas de construção e inicializa as grades. Depois, a cada tick, ele roda a separação de esquadrões na sua contagem regressiva e roda cada tarefa cuja vez chegou.
| Caminho | O que faz |
|---|---|
oabrain.yaml | O manifesto. |
brain.lua | O lugar, a tabela de tarefas e onTick. |
lua/build.lua | Pontua um tipo e escolhe um da lista de um construtor. |
lua/knowledge.lua | Pesos, limites, as grades de construção e os pontos de metal. Lê o registro do motor em me.knowledge. |
lua/place.lua | Procura um local, ampliando a busca até ela ficar do tamanho do mapa. |
lua/profile.lua | Lê o texto do perfil e aplica os pesos. |
lua/report.lua | Escreve o texto que report retorna. |
lua/rules.lua | Lê as regras ai.* que o Classic segue. |
lua/squads.lua | Separa as unidades em esquadrões e dá uma ordem a um esquadrão. |
lua/types.lua | Lê os campos de tipo que o computador usa. |
lua/tasks/ | structures, construction, strike, rally, air_raid e siege. |
README.md | Uma nota. O jogo não a carrega. |
LICENSE | A licença. |
A tabela de tarefas
Os esquadrões 1 e 5 constroem estruturas. O esquadrão 5 só faz isso quando as regras mandam; do contrário, fica ocioso. Os esquadrões 2 e 6 são grupos de ataque. Os esquadrões 3 e 7 se reagrupam neles. O esquadrão 4 é de construção. O esquadrão 8 é a ala aérea. O esquadrão 9 é de cerco. O comentário sobre o esquadrão 9 é uma peculiaridade: a separação nunca preenche esse esquadrão, então a tarefa de cerco só mantém seu horário, a não ser que uma unidade já estivesse nele.
-- …
for index = 0, 9 do
seat.tasks[index] = blank(index)
end
local wave = loaded_rules.attack_wave
seat.tasks[1].kind = "structures"
seat.tasks[1].every = 30
seat.tasks[2].kind = "strike"
seat.tasks[2].min_size = 3
seat.tasks[2].launch = wave
seat.tasks[2].merge_radius = 20000
seat.tasks[2].source = 3
seat.tasks[2].every = 300
seat.tasks[3].kind = "rally"
seat.tasks[3].source = 2
seat.tasks[3].every = 150
seat.tasks[4].kind = "construction"
seat.tasks[4].every = 90
if loaded_rules.squad5_factory_tick then
seat.tasks[5].kind = "structures"
seat.tasks[5].every = 30
else
seat.tasks[5].kind = "idle"
end
seat.tasks[6].kind = "strike"
seat.tasks[6].min_size = 3
seat.tasks[6].launch = wave
seat.tasks[6].merge_radius = 50000
seat.tasks[6].source = 7
seat.tasks[6].every = 300
seat.tasks[7].kind = "rally"
seat.tasks[7].source = 6
seat.tasks[7].every = 150
seat.tasks[8].kind = "air_raid"
seat.tasks[8].every = function()
return 30 + brain.shared_random(900)
end
seat.tasks[8].run = function()
air_raid.run(seat, seat.tasks[8])
end
-- @quirk The sort never fills squad 9, so the siege task only keeps its
-- timetable unless a unit was already in that squad.
local centre = map_centre(game)
seat.tasks[9].kind = "siege"
seat.tasks[9].every = function()
return 30 + brain.shared_random(150)
end
seat.tasks[9].siege_target = point_of(centre)
seat.tasks[9].siege_probe = point_of(centre)
seat.tasks[9].siege_step = point_of(centre)
seat.tasks[9].siege_weight = 0
-- …
No primeiro tick, onTick atualiza as listas de construção e inicializa as grades. A separação em si é a contagem regressiva. Ela começa em 30. Cada tick subtrai um, e quando chega ao fim ela volta a 30 e chama a separação de esquadrões.
-- …
function hooks.onTick(tick)
local seat = hooks.seat
if seat.builds_ready ~= 1 then
types.refresh_builds(seat.types, seat.game)
seat.builds_ready = 1
end
if seat.grids_ready ~= 1 then
knowledge.seed_grids(seat)
end
seat.sort_countdown = seat.sort_countdown - 1
if seat.sort_countdown < 1 then
seat.sort_countdown = 30
brain.section("sort")
squads.sort(seat)
end
for index = 0, 9 do
local task = seat.tasks[index]
if task.kind ~= "none" and task.next <= tick then
if task.kind ~= "idle" then
brain.section("task" .. index)
local interval = task.every
if type(interval) == "function" then
interval = interval()
end
task.next = (tick + interval) & 0xFFFFFFFF
task.run()
end
end
end
end
-- …
squads.lua diz o que a separação faz: unidades selecionáveis no alcance do lugar recebem ordens permanentes, e uma unidade ainda no esquadrão 0 entra no esquadrão em que seu tipo se encaixa. Uma unidade que já está num esquadrão continua nele.
Uma tarefa
lua/tasks/rally.lua é a tarefa de reagrupamento inteira. Quando o esquadrão de reagrupamento e o grupo de ataque têm membros, o esquadrão de reagrupamento vai até o centro do grupo de ataque.
-- SPDX-FileCopyrightText: The Open Annihilation Authors; see COPYRIGHT
-- SPDX-License-Identifier: GPL-3.0-only
-- A waiting squad. When it and the strike group both have members, it moves
-- to the strike group's centre. The timetable is already set.
local squads = require("squads")
local rally = {}
function rally.run(seat, task)
local me = seat.me
if squads.count(me, task.squad) < 1 or squads.count(me, task.source) < 1 then
return
end
local center = squads.centroid(me, task.source)
if center == nil then
return
end
squads.order(me, task.squad, "move", false, nil, squads.fixed_point(center))
end
return rally
As notas de peculiaridades
Uma linha marcada com @quirk registra um comportamento que o computador integrado tem. A nota fica no script para que quem lê a veja ao lado do código. A nota do esquadrão 9 está na tabela de tarefas acima. Outras duas ficam em profile.lua. A palavra any só conta como a primeira palavra depois de plan, e essa mesma palavra é testada de novo em cada palavra seguinte da linha. O ai_weight de um tipo baixável é aplicado uma vez por jogador do computador, então o peso se acumula por esse número.
-- The builders' tick. Each builder places a weighted pick near the base,
-- and an idle one patrols home. A capturing builder builds only while the
-- player is under the withhold count and the hold tick has passed.
-- @quirk ai.builder-withhold-threshold moves only the build limit: an idle
-- capturing builder still circles the base from five build-capable units.
A tarefa de construção retém as obras e mesmo assim patrulha. Uma ala aérea pequena sem base, com ai.patrol-null-enemy-skip, sorteia uma patrulha na borda do mapa, os mesmos três sorteios que uma ala grande faz, e patrulha ali em vez de pôr na fila uma patrulha até o inimigo mais próximo.
O que fica no motor
O computador integrado continua sendo o padrão. Um lugar só é Classic quando seus arquivos correspondem à pasta desta compilação. Esse teste concede dois comportamentos, e um terceiro sorteio da sequência de preparação. brain.shared_random sorteia da sequência compartilhada da partida, a mesma de que os scripts de unidade e a simulação sorteiam, então os intervalos de aeronaves e de cerco do Classic ficam em sincronia com o jogador integrado. unit:stance com direct como true escreve os bits permanentes de movimento e de fogo e não dá nenhuma ordem. Qualquer outro lugar é recusado. brain.setup_random sorteia de uma cópia dessa sequência tirada na preparação integrada do lugar, e só avança a cópia.
Nenhum outro cérebro tem qualquer das duas funções aleatórias, e uma escrita direta de postura é recusada. brain.section é instalado para todos os cérebros. Numa partida, a marcação não faz nada. A visão total não está nesta lista. Ela é a flag do manifesto. O Steady Commander não chama nenhuma delas.
Como se prova
No modo sombra, o Classic é posto ao lado do jogador integrado. As ordens do jogador integrado são as que a partida joga. As ordens do Classic são gravadas e comparadas. Nenhum tick pode ser diferente. Os testes do motor para essa comparação são sim-brains-shadow e sim-brains-shadow-match.
Na substituição, o Classic é posto onde o jogador integrado jogaria. Os testes do motor para isso são sim-brains-classic, sim-brains-classic-data, sim-brains-classic-match, ai-computer-player, match-computer-build, native-trace, native-stage, native-director-render, native-classic-trace, native-classic-stage, native-classic-director-render, native-net-loopback-computer, native-net-loopback-computer-watcher, native-net-loopback-each-ai, native-net-loopback-computer-classic, native-net-loopback-computer-watcher-classic e native-net-loopback-each-ai-classic.
Um teste de resistência joga 1.000 escaramuças sem interface, com sementes definidas e 20.000 ticks cada, sobre o jogo base e os mods publicados. A mesma semente joga a mesma partida. O teste de resistência é a verificação longa de que um lugar rodando o Classic segue as ordens do jogador integrado durante uma partida inteira.
Como ler
Leia o Classic como o comportamento do computador integrado, posto no papel. O computador do próprio motor continua sendo o jogador que um lugar usa quando ninguém escolheu um cérebro. Instalar o Classic, ou empacotá-lo, não muda nada até que um lugar seja definido para ele.