Compose Planetologist movement with Ornithopter flights
This commit is contained in:
1 parent
90c50736f2
commit
4af90e547c
24 files changed
+1607
-50
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import type { Action, GameView } from './engine';
|
||||
import { location, splitLocation } from './board';
|
||||
import { botGroundMoveAllowed } from './bot-mobility';
|
||||
import { planetologistFlightSelection, type PlanetologistFlight } from './planetologist-flight';
|
||||
import { planetologistLeader, planetologistMovementModeSupported,
|
||||
planetologistGatherAdvisorEntry, type PlanetologistMoveMode } from './planetologist-movement';
|
||||
import { validateCohortSelection } from './ornithopter';
|
||||
import { nexusGuildMovementAvailable } from './nexus-guild-cunning-options';
|
||||
|
||||
type Source = { from: string; elite: number; marker: boolean };
|
||||
|
||||
/** Minimal legal participation using only current projected transport and stock.
|
||||
* One counter per origin leaves distinct residual groups where available. */
|
||||
export function botPlanetologistFlightActions(game: GameView,
|
||||
targets: readonly { territory: string; sector: number }[],
|
||||
transports: readonly PlanetologistFlight[]): Action[] {
|
||||
const me = game.players.find(player => player.id === game.me);
|
||||
if (!me || game.status !== 'playing' || game.phase !== 5 || game.active !== me.id ||
|
||||
game.response || game.decision || game.phaseOpening || game.truthtrance ||
|
||||
game.automaticContinuationPending || game.roomControl?.paused || game.roomControl?.closed ||
|
||||
!planetologistLeader(game, me.id) || !planetologistMovementModeSupported(game) ||
|
||||
(!game.ornithopter?.active && !nexusGuildMovementAvailable(game))) return [];
|
||||
const info = game.richeseNoField?.owner === me.id ? game.richeseNoField : null;
|
||||
const marker = info?.private?.deployed;
|
||||
const result: Action[] = [];
|
||||
for (const transport of transports) {
|
||||
const flight = planetologistFlightSelection(game, transport);
|
||||
if (flight.blocked) continue;
|
||||
const sources: Source[] = Object.entries(me.forces).flatMap(([from, total]) => {
|
||||
const elite = me.elites?.forces[from] ?? 0;
|
||||
const availableElite = Math.min(elite, flight.cohort?.elites[from] ?? elite);
|
||||
const availableNormal = Math.min(total - elite, flight.cohort
|
||||
? (flight.cohort.forces[from] ?? 0) - (flight.cohort.elites[from] ?? 0) : total - elite);
|
||||
return availableNormal > 0 ? [{ from, elite: 0, marker: false }]
|
||||
: availableElite > 0 ? [{ from, elite: 1, marker: false }] : [];
|
||||
}).slice(0, 12);
|
||||
if (marker?.controller === me.id && info?.event && !game.homeworldMobility?.noFieldMovementBlocked) {
|
||||
const from = location(marker.location.territory, marker.location.sector);
|
||||
if (!flight.cohort || (flight.cohort.noField?.tokenId === marker.tokenId &&
|
||||
flight.cohort.noField.event === info.event && flight.cohort.noField.from === from))
|
||||
sources.push({ from, elite: 0, marker: true });
|
||||
}
|
||||
for (const mode of ['range', 'gather'] as PlanetologistMoveMode[]) {
|
||||
const groups: Source[][] = mode === 'range' ? sources.map(source => [source])
|
||||
: sources.flatMap((first, index) => sources.slice(index + 1)
|
||||
.filter(second => splitLocation(first.from).territory !== splitLocation(second.from).territory)
|
||||
.map(second => [first, second]));
|
||||
let accepted = 0;
|
||||
choices: for (const target of targets) for (const group of groups) {
|
||||
const to = location(target.territory, target.sector);
|
||||
if (!group.every(source => botGroundMoveAllowed(game, me, source.from, to,
|
||||
source.elite, mode, flight.rangeOverride))) continue;
|
||||
const entry = planetologistGatherAdvisorEntry(game, me,
|
||||
group.map(source => splitLocation(source.from).territory), target.territory);
|
||||
if (entry.blocked) continue;
|
||||
const forces = Object.fromEntries(group.filter(source => !source.marker).map(source => [source.from, 1]));
|
||||
const elites = Object.fromEntries(group.filter(source => !source.marker).map(source => [source.from, source.elite]));
|
||||
const selectedMarker = group.some(source => source.marker) && marker && info?.event
|
||||
? { tokenId: marker.tokenId, event: info.event,
|
||||
from: location(marker.location.territory, marker.location.sector) } : undefined;
|
||||
if (flight.cohort) {
|
||||
try { validateCohortSelection(flight.cohort, me.forces, me.elites?.forces ?? {}, forces, elites, selectedMarker); }
|
||||
catch { continue; }
|
||||
}
|
||||
result.push({ type: 'move', planetologist: mode, forces,
|
||||
...(me.elites ? { eliteForces: elites } : {}),
|
||||
...(selectedMarker ? { noField: selectedMarker.tokenId, event: selectedMarker.event } : {}),
|
||||
...(entry.requiresFlip ? { fighters: true } : {}),
|
||||
...flight.fields, territory: target.territory, sector: target.sector });
|
||||
if (++accepted === 24) break choices;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user