Classic, um exemplo comentado
O computador integrado escrito em Lua: os seus ficheiros, a tabela de tarefas, uma tarefa, as notas sobre peculiaridades, o que fica no motor e como se prova que as ordens são as mesmas.
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
O Classic é brains/classic. É documentação do computador integrado: as mesmas ordens, escritas para que as possa ler. O jogador integrado continua no motor e continua a ser a predefinição. Escolher o Classic coloca este pacote no lugar. Deixar o lugar no jogador integrado corre o computador do próprio motor.
O manifesto diz que works-with é um asterisco e que fullAwareness é true. A sua descrição diz que joga exatamente como o computador integrado. A visão total é a marca do manifesto. É permitida numa partida em rede, e o lugar assinala-a. O id, o nome e o caminho de instalação não fazem de um pacote o Classic. Os ficheiros têm de 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 ficheiros
brain.lua constrói uma tarefa para cada esquadrão, de 0 a 9. No primeiro tick, onTick atualiza as listas de construção e inicializa as grelhas. Depois, em cada tick, corre a ordenação dos esquadrões segundo a sua contagem decrescente e corre cada tarefa cuja vez tenha chegado.
| 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 grelhas de construção e os pontos de metal. Lê o registo do motor a partir de me.knowledge. |
lua/place.lua | Procura um local e alarga a procura até esta ser tão larga como o mapa. |
lua/profile.lua | Lê o texto do perfil e aplica pesos. |
lua/report.lua | Escreve o texto que report devolve. |
lua/rules.lua | Lê as regras ai.* pelas quais o Classic joga. |
lua/squads.lua | Distribui as unidades por 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ó o faz quando as regras o indicam, e de resto fica inativo. Os esquadrões 2 e 6 são grupos de ataque. Os esquadrões 3 e 7 reúnem-se com eles. O esquadrão 4 é a construção. O esquadrão 8 é a esquadrilha aérea. O esquadrão 9 é o cerco. O comentário sobre o esquadrão 9 é uma peculiaridade: a ordenação nunca preenche esse esquadrão, por isso a tarefa de cerco só mantém o seu horário, a não ser que já lá estivesse uma unidade.
-- …
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 grelhas. A própria ordenação é a contagem decrescente. Começa em 30. Cada tick subtrai um e, ao chegar ao fim, volta a 30 e chama a ordenação dos 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 ordenação faz: as unidades selecionáveis no alcance do lugar recebem ordens permanentes, e uma unidade que ainda esteja no esquadrão 0 junta-se ao esquadrão em que o seu tipo se ordena. Uma unidade que já esteja num esquadrão mantém-se nele.
Uma tarefa
lua/tasks/rally.lua é a tarefa de reunião completa. Quando o esquadrão de reunião e o grupo de ataque têm ambos membros, o esquadrão de reunião desloca-se para 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 sobre peculiaridades
Uma linha marcada com @quirk regista um comportamento que o computador integrado tem. A nota está no script para que quem o lê a veja ao lado do código. A nota do esquadrão 9 está na tabela de tarefas acima. Há mais duas 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 transferível é aplicado uma vez por cada computador, por isso o peso acumula-se 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 construção retém construções e continua a patrulhar. Uma pequena esquadrilha aérea sem base, com ai.patrol-null-enemy-skip, sorteia uma patrulha junto ao limite do mapa, com os mesmos três sorteios de uma esquadrilha grande, e patrulha aí em vez de pôr em fila uma patrulha sobre o inimigo mais próximo.
O que fica no motor
O computador integrado continua a ser a predefinição. Um lugar só é o Classic quando os seus ficheiros coincidem com a pasta desta compilação. Esse teste concede dois comportamentos, e um terceiro sorteio a partir do fluxo de preparação. brain.shared_random sorteia a partir do fluxo partilhado da partida, o mesmo de que os scripts das unidades e a simulação sorteiam, para que os intervalos aéreos e de cerco do Classic acompanhem os do jogador integrado. unit:stance com direct definido 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 a partir de uma cópia desse fluxo feita na preparação integrada do lugar, e só faz avançar a cópia.
Nenhum outro cérebro tem qualquer destas funções aleatórias, e uma escrita direta de postura é recusada. brain.section é instalado para todos os cérebros. Numa partida, a marca não faz nada. A visão total não está nesta lista. É a marca do manifesto. O Steady Commander não chama nenhuma delas.
Como se prova
Em sombra, o Classic é colocado 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 diferir. Os testes do motor para essa comparação são sim-brains-shadow e sim-brains-shadow-match.
Em substituição, o Classic é colocado 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 1000 escaramuças sem interface, com semente, de 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 que corre o Classic se mantém nas ordens do jogador integrado durante uma partida inteira.
Como o ler
Leia o Classic como o comportamento do computador integrado, posto por escrito. O computador do próprio motor continua a ser o jogador que um lugar usa quando ninguém escolheu um cérebro. Instalar o Classic, ou empacotá-lo, não altera nada até um lugar lhe ser atribuído.