Classic, ein ausgearbeitetes Beispiel
Der eingebaute Computerspieler, in Lua geschrieben: seine Dateien, die Aufgabentabelle, eine Aufgabe, die Notizen zu den Eigenheiten, was in der Engine bleibt, und wie dieselben Befehle nachgewiesen werden.
Dieses Kapitel fasst docs/brains/oabrain-standard.md, docs/brains/authoring.md und docs/playing-with-brains.md zusammen. Diese Dokumente sind der maßgebliche Text.
Was Classic ist
Classic ist brains/classic. Es ist die Dokumentation des eingebauten Computerspielers: dieselben Befehle, so geschrieben, dass du sie lesen kannst. Der eingebaute Spieler bleibt in der Engine und bleibt der Standard. Classic zu wählen setzt dieses Paket ein. Den Sitz auf dem eingebauten Spieler zu lassen führt den eigenen Computerspieler der Engine aus.
Das Manifest sagt, works-with ist ein Stern, und fullAwareness ist true. Seine Beschreibung sagt, es spielt genau so wie der eingebaute Computerspieler. Volle Sicht ist das Flag des Manifests. Sie ist in einem Netzwerkspiel erlaubt, und der Sitz kennzeichnet sie. Die Kennung, der Name und der Installationspfad machen ein Paket nicht zu Classic. Die Dateien müssen der Ordner brains/classic dieses Builds sein, dieselben Pfade und dieselben Bytes.
Manifest ·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
Die Dateien
brain.lua baut eine Aufgabe für jeden Trupp von 0 bis 9. Beim ersten Tick frischt onTick die Baulisten auf und setzt die Gitter an. Jeder Tick führt dann die Trupp-Sortierung an ihrem Countdown aus und führt jede Aufgabe aus, deren Zug gekommen ist.
| Pfad | Wirkung |
|---|---|
oabrain.yaml | Das Manifest. |
brain.lua | Der Sitz, die Aufgabentabelle und onTick. |
lua/build.lua | Bewertet einen Typ und wählt einen aus der Liste eines Bauers. |
lua/knowledge.lua | Gewichte, Limits, die Baugitter und die Metallstellen. Liest den Eintrag der Engine aus me.knowledge. |
lua/place.lua | Sucht einen Bauplatz und weitet die Suche, bis sie so breit ist wie die Karte. |
lua/profile.lua | Liest den Profiltext und wendet Gewichte an. |
lua/report.lua | Schreibt den Text, den report zurückgibt. |
lua/rules.lua | Liest die ai.*-Regeln, nach denen Classic spielt. |
lua/squads.lua | Sortiert Einheiten in Trupps und gibt einem Trupp einen Befehl. |
lua/types.lua | Liest die Typfelder, die der Computerspieler nutzt. |
lua/tasks/ | structures, construction, strike, rally, air_raid und siege. |
README.md | Eine Notiz. Das Spiel lädt sie nicht. |
LICENSE | Die Lizenz. |
Die Aufgabentabelle
Die Trupps 1 und 5 bauen Gebäude. Trupp 5 tut das nur, wenn die Regeln es sagen, und bleibt sonst untätig. Die Trupps 2 und 6 sind Angriffsgruppen. Die Trupps 3 und 7 sammeln sich auf sie. Trupp 4 ist der Bau. Trupp 8 ist die Flugzeugstaffel. Trupp 9 ist die Belagerung. Der Kommentar zu Trupp 9 ist eine Eigenheit: die Sortierung füllt diesen Trupp nie, darum hält die Belagerungsaufgabe nur ihren Zeitplan, außer eine Einheit war schon darin.
-- …
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
-- …
Beim ersten Tick frischt onTick die Baulisten auf und setzt die Gitter an. Die Sortierung selbst ist der Countdown. Er beginnt bei 30. Jeder Tick zieht eins ab, und unten kehrt er auf 30 zurück und ruft die Trupp-Sortierung.
-- …
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 sagt, was die Sortierung tut: wählbare Einheiten in der Reichweite des Sitzes nehmen Dauerbefehle an, und eine Einheit, die noch in Trupp 0 ist, geht in den Trupp, in den ihr Typ sie einsortiert. Eine Einheit, die schon in einem Trupp ist, behält diesen Trupp.
Eine Aufgabe
lua/tasks/rally.lua ist die ganze Sammelaufgabe. Wenn der Sammeltrupp und die Angriffsgruppe beide Mitglieder haben, bewegt sich der Sammeltrupp zur Mitte der Angriffsgruppe.
-- 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
Die Notizen zu den Eigenheiten
Eine Zeile mit @quirk hält ein Verhalten fest, das der eingebaute Computerspieler hat. Die Notiz steht im Skript, damit ein Leser sie neben dem Code sieht. Die Notiz zu Trupp 9 steht in der Aufgabentabelle oben. Zwei weitere stehen in profile.lua. Das Wort any zählt nur als das erste Wort nach plan, und dasselbe Wort wird an jedem späteren Wort der Zeile erneut geprüft. Der ai_weight eines herunterladbaren Typs wird einmal je Computerspieler angewandt, darum vervielfacht sich das Gewicht mit dieser Anzahl.
-- 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.
Der Bau hält Bauten zurück und patrouilliert trotzdem. Eine kleine Flugzeugstaffel ohne Basis zieht unter ai.patrol-null-enemy-skip eine Patrouille am Kartenrand, dieselben drei Ziehungen, die eine große Staffel nimmt, und patrouilliert dort, statt eine Patrouille auf den nächsten Gegner einzureihen.
Was in der Engine bleibt
Der eingebaute Computerspieler bleibt der Standard. Ein Sitz ist nur dann Classic, wenn seine Dateien zum Ordner dieses Builds passen. Diese Prüfung gewährt zwei Verhaltensweisen und eine dritte Ziehung aus dem Setup-Strom. brain.shared_random zieht aus dem gemeinsamen Strom des Matches, aus dem auch Einheitenskripte und die Simulation ziehen, damit die Intervalle von Flugzeugen und Belagerung bei Classic im Takt mit dem eingebauten Spieler bleiben. unit:stance mit direct auf true schreibt die Bits für Bewegungs- und Feuerhaltung und gibt keinen Befehl. Jeder andere Sitz wird abgelehnt. brain.setup_random zieht aus einer Kopie dieses Stroms, die beim eingebauten Setup des Sitzes genommen wurde, und rückt nur die Kopie voran.
Jedes andere Gehirn hat keine der beiden Zufallsfunktionen, und ein direktes Schreiben der Haltung wird abgelehnt. brain.section ist für jedes Gehirn eingerichtet. In einem Spiel tut die Markierung nichts. Volle Sicht steht nicht in dieser Liste. Sie ist das Flag des Manifests. Steady Commander ruft keine davon auf.
Wie es nachgewiesen wird
Im Schatten wird Classic neben den eingebauten Spieler gesetzt. Die Befehle des eingebauten Spielers sind die, die das Match spielt. Die Befehle von Classic werden aufgezeichnet und verglichen. Kein Tick darf abweichen. Die Tests der Engine für diesen Vergleich sind sim-brains-shadow und sim-brains-shadow-match.
Im Austausch wird Classic dort eingesetzt, wo der eingebaute Spieler spielen würde. Die Tests der Engine dafür sind 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 und native-net-loopback-each-ai-classic.
Ein Dauertest spielt 1.000 gesäte Gefechte ohne Oberfläche, je 20.000 Ticks, über das Grundspiel und die veröffentlichten Mods. Derselbe Seed spielt dasselbe Match. Der Dauertest ist die lange Prüfung, dass ein Sitz, der Classic ausführt, über ein ganzes Spiel bei den Befehlen des eingebauten Spielers bleibt.
Wie man es liest
Lies Classic als das aufgeschriebene Verhalten des eingebauten Computerspielers. Der eigene Computerspieler der Engine ist weiter der Spieler, den ein Sitz nutzt, wenn niemand ein Gehirn gewählt hat. Classic zu installieren oder zu packen ändert nichts, bis ein Sitz darauf gesetzt ist.