Skip to content

Commit 1119898

Browse files
sookmaxcpojer
andauthored
Add optional win condition feature (#34)
Co-authored-by: cpojer <christoph.pojer@gmail.com>
1 parent 17c7727 commit 1119898

22 files changed

+1746
-139
lines changed

apollo/ActionMap.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,6 @@
171171
["BuySkill", [39, ["type", "from", "skill", "player"]]],
172172
["ActivatePower", [40, ["type", "skill"]]],
173173
["PreviousTurnGameOver", [41, ["type", "fromPlayer"]]],
174-
["SecretDiscovered", [42, ["type", "condition"]]]
174+
["SecretDiscovered", [42, ["type", "condition"]]],
175+
["OptionalCondition", [43, ["type", "condition", "conditionId", "toPlayer"]]]
175176
]

apollo/GameOver.tsx

+73-13
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,20 @@ export type GameEndActionResponse = Readonly<{
5252
type: 'GameEnd';
5353
}>;
5454

55+
export type OptionalConditionActionResponse = Readonly<{
56+
condition: WinCondition;
57+
conditionId: number;
58+
toPlayer: PlayerID;
59+
type: 'OptionalCondition';
60+
}>;
61+
5562
export type GameOverActionResponses =
5663
| AttackUnitGameOverActionResponse
5764
| BeginTurnGameOverActionResponse
5865
| CaptureGameOverActionResponse
5966
| GameEndActionResponse
60-
| PreviousTurnGameOverActionResponse;
67+
| PreviousTurnGameOverActionResponse
68+
| OptionalConditionActionResponse;
6169

6270
function check(
6371
previousMap: MapData,
@@ -92,6 +100,7 @@ const pickWinningPlayer = (
92100
condition.players?.length ? condition.players : activeMap.active
93101
).find(
94102
(playerID) =>
103+
(!condition.optional || !condition.completed?.has(playerID)) &&
95104
activeMap.getPlayer(playerID).stats.destroyedUnits >= condition.amount,
96105
);
97106
}
@@ -138,19 +147,20 @@ export function checkGameOverConditions(
138147
const gameState: MutableGameState = actionResponse
139148
? [[actionResponse, map]]
140149
: [];
141-
const gameEndResponse = condition
142-
? ({
143-
condition,
144-
conditionId: activeMap.config.winConditions.indexOf(condition),
145-
toPlayer: pickWinningPlayer(
146-
previousMap,
147-
activeMap,
148-
lastActionResponse,
150+
151+
const winningPlayer = condition
152+
? pickWinningPlayer(previousMap, activeMap, lastActionResponse, condition)
153+
: undefined;
154+
155+
const gameEndResponse =
156+
condition?.type === WinCriteria.Default || condition?.optional === false
157+
? ({
149158
condition,
150-
),
151-
type: 'GameEnd',
152-
} as const)
153-
: checkGameEnd(map);
159+
conditionId: activeMap.config.winConditions.indexOf(condition),
160+
toPlayer: winningPlayer,
161+
type: 'GameEnd',
162+
} as const)
163+
: checkGameEnd(map);
154164

155165
if (gameEndResponse) {
156166
let newGameState: GameState = [];
@@ -162,6 +172,38 @@ export function checkGameOverConditions(
162172
];
163173
}
164174

175+
const optionalConditionResponse =
176+
condition?.type !== WinCriteria.Default &&
177+
condition?.optional === true &&
178+
winningPlayer &&
179+
!condition.completed?.has(winningPlayer)
180+
? ({
181+
condition,
182+
conditionId: activeMap.config.winConditions.indexOf(condition),
183+
toPlayer: winningPlayer,
184+
type: 'OptionalCondition',
185+
} as const)
186+
: null;
187+
188+
if (optionalConditionResponse) {
189+
let newGameState: GameState = [];
190+
[newGameState, map] = processRewards(map, optionalConditionResponse);
191+
map = applyGameOverActionResponse(map, optionalConditionResponse);
192+
return [
193+
...gameState,
194+
...newGameState,
195+
[
196+
// update `optionalConditionResponse.condition` with the new `map.config` updated in `applyGameOverActionResponse()`
197+
{
198+
...optionalConditionResponse,
199+
condition:
200+
map.config.winConditions[optionalConditionResponse.conditionId],
201+
},
202+
map,
203+
],
204+
];
205+
}
206+
165207
if (
166208
actionResponse?.type === 'AttackUnitGameOver' ||
167209
actionResponse?.type === 'BeginTurnGameOver'
@@ -231,6 +273,24 @@ export function applyGameOverActionResponse(
231273
}
232274
case 'GameEnd':
233275
return map;
276+
case 'OptionalCondition': {
277+
const { condition, conditionId, toPlayer } = actionResponse;
278+
if (condition.type === WinCriteria.Default) {
279+
return map;
280+
}
281+
const winConditions = Array.from(map.config.winConditions);
282+
winConditions[conditionId] = {
283+
...condition,
284+
completed: condition.completed
285+
? new Set([...condition.completed, toPlayer])
286+
: new Set([toPlayer]),
287+
};
288+
return map.copy({
289+
config: map.config.copy({
290+
winConditions,
291+
}),
292+
});
293+
}
234294
default: {
235295
actionResponse satisfies never;
236296
throw new UnknownTypeError('applyGameOverActionResponse', type);

apollo/actions/applyActionResponse.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,12 @@ export default function applyActionResponse(
480480
case 'HiddenTargetAttackBuilding':
481481
case 'HiddenTargetAttackUnit':
482482
return applyHiddenActionResponse(map, vision, actionResponse);
483+
case 'OptionalCondition':
483484
case 'AttackUnitGameOver':
484485
case 'BeginTurnGameOver':
485486
case 'CaptureGameOver':
486-
case 'PreviousTurnGameOver':
487487
case 'GameEnd':
488+
case 'PreviousTurnGameOver':
488489
return applyGameOverActionResponse(map, actionResponse);
489490
case 'SetViewer': {
490491
const currentPlayer = map.maybeGetPlayer(vision.currentViewer)?.id;

apollo/lib/computeVisibleActions.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ const VisibleActionModifiers: Record<
372372
MoveUnit: {
373373
Source: true,
374374
},
375+
OptionalCondition: true,
375376
PreviousTurnGameOver: true,
376377
ReceiveReward: true,
377378
Rescue: {

apollo/lib/dropLabelsFromActionResponse.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default function dropLabelsFromActionResponse(
6262
case 'GameEnd':
6363
case 'HiddenFundAdjustment':
6464
case 'Message':
65+
case 'OptionalCondition':
6566
case 'PreviousTurnGameOver':
6667
case 'ReceiveReward':
6768
case 'SecretDiscovered':

apollo/lib/getActionResponseVectors.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export default function getActionResponseVectors(
8787
case 'GameEnd':
8888
case 'HiddenFundAdjustment':
8989
case 'Message':
90+
case 'OptionalCondition':
9091
case 'PreviousTurnGameOver':
9192
case 'ReceiveReward':
9293
case 'SecretDiscovered':

apollo/lib/getWinningTeam.tsx apollo/lib/getMatchingTeam.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { PlayerID } from '@deities/athena/map/Player.tsx';
22
import MapData from '@deities/athena/MapData.tsx';
3-
import { GameEndActionResponse } from '../GameOver.tsx';
3+
import {
4+
GameEndActionResponse,
5+
OptionalConditionActionResponse,
6+
} from '../GameOver.tsx';
47

5-
export default function getWinningTeam(
8+
export default function getMatchingTeam(
69
map: MapData,
7-
actionResponse: GameEndActionResponse,
10+
actionResponse: GameEndActionResponse | OptionalConditionActionResponse,
811
): 'draw' | PlayerID {
912
const isDraw = !actionResponse.toPlayer;
1013
return isDraw

apollo/lib/processRewards.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ import MapData from '@deities/athena/MapData.tsx';
22
import { WinCriteria } from '@deities/athena/WinConditions.tsx';
33
import isPresent from '@deities/hephaestus/isPresent.tsx';
44
import applyActionResponse from '../actions/applyActionResponse.tsx';
5-
import { GameEndActionResponse } from '../GameOver.tsx';
5+
import {
6+
GameEndActionResponse,
7+
OptionalConditionActionResponse,
8+
} from '../GameOver.tsx';
69
import { GameState, MutableGameState } from '../Types.tsx';
7-
import getWinningTeam from './getWinningTeam.tsx';
10+
import getMatchingTeam from './getMatchingTeam.tsx';
811

912
export function processRewards(
1013
map: MapData,
11-
gameEndResponse: GameEndActionResponse,
14+
actionResponse: GameEndActionResponse | OptionalConditionActionResponse,
1215
): [GameState, MapData] {
1316
const gameState: MutableGameState = [];
14-
const winningTeam = getWinningTeam(map, gameEndResponse);
17+
const winningTeam = getMatchingTeam(map, actionResponse);
1518
if (winningTeam !== 'draw') {
1619
const rewards = new Set(
1720
[
18-
'condition' in gameEndResponse
19-
? gameEndResponse.condition?.reward
20-
: null,
21+
'condition' in actionResponse ? actionResponse.condition?.reward : null,
2122
map.config.winConditions.find(
2223
(condition) => condition.type === WinCriteria.Default,
2324
)?.reward,

0 commit comments

Comments
 (0)