Skip to content

Commit

Permalink
add optional win condition feature
Browse files Browse the repository at this point in the history
  • Loading branch information
sookmax committed May 27, 2024
1 parent 9fc0b4f commit 3d7d4f0
Show file tree
Hide file tree
Showing 21 changed files with 1,652 additions and 94 deletions.
3 changes: 2 additions & 1 deletion apollo/ActionMap.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,6 @@
["BuySkill", [39, ["type", "from", "skill", "player"]]],
["ActivatePower", [40, ["type", "skill"]]],
["PreviousTurnGameOver", [41, ["type", "fromPlayer"]]],
["SecretDiscovered", [42, ["type", "condition"]]]
["SecretDiscovered", [42, ["type", "condition"]]],
["OptionalWin", [43, ["type", "condition", "conditionId", "toPlayer"]]]
]
89 changes: 73 additions & 16 deletions apollo/GameOver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,20 @@ export type GameEndActionResponse = Readonly<{
type: 'GameEnd';
}>;

export type OptionalWinActionResponse = Readonly<{
condition: WinCondition;
conditionId: number;
toPlayer: PlayerID;
type: 'OptionalWin';
}>;

export type GameOverActionResponses =
| AttackUnitGameOverActionResponse
| BeginTurnGameOverActionResponse
| CaptureGameOverActionResponse
| GameEndActionResponse
| PreviousTurnGameOverActionResponse;
| PreviousTurnGameOverActionResponse
| OptionalWinActionResponse;

function check(
previousMap: MapData,
Expand Down Expand Up @@ -90,9 +98,12 @@ const pickWinningPlayer = (
if (condition.type === WinCriteria.DefeatAmount) {
return (
condition.players?.length ? condition.players : activeMap.active
).find(
(playerID) =>
activeMap.getPlayer(playerID).stats.destroyedUnits >= condition.amount,
).find((playerID) =>
!condition.optional
? activeMap.getPlayer(playerID).stats.destroyedUnits >= condition.amount
: !condition.completed?.has(playerID) &&
activeMap.getPlayer(playerID).stats.destroyedUnits >=
condition.amount,
);
}

Expand Down Expand Up @@ -138,19 +149,20 @@ export function checkGameOverConditions(
const gameState: MutableGameState = actionResponse
? [[actionResponse, map]]
: [];
const gameEndResponse = condition
? ({
condition,
conditionId: activeMap.config.winConditions.indexOf(condition),
toPlayer: pickWinningPlayer(
previousMap,
activeMap,
lastActionResponse,

const winningPlayer = condition
? pickWinningPlayer(previousMap, activeMap, lastActionResponse, condition)
: undefined;

const gameEndResponse =
condition?.optional === false
? ({
condition,
),
type: 'GameEnd',
} as const)
: checkGameEnd(map);
conditionId: activeMap.config.winConditions.indexOf(condition),
toPlayer: winningPlayer,
type: 'GameEnd',
} as const)
: checkGameEnd(map);

if (gameEndResponse) {
let newGameState: GameState = [];
Expand All @@ -162,6 +174,36 @@ export function checkGameOverConditions(
];
}

const optionalWinResponse =
condition?.optional === true &&
winningPlayer &&
!condition.completed?.has(winningPlayer)
? ({
condition,
conditionId: activeMap.config.winConditions.indexOf(condition),
toPlayer: winningPlayer,
type: 'OptionalWin',
} as const)
: null;

if (optionalWinResponse) {
let newGameState: GameState = [];
[newGameState, map] = processRewards(map, optionalWinResponse);
map = applyGameOverActionResponse(map, optionalWinResponse);
return [
...gameState,
...newGameState,
[
// update `optionalWinResponse.condition` with the new `map.config` updated in `applyGameOverActionResponse()`
{
...optionalWinResponse,
condition: map.config.winConditions[optionalWinResponse.conditionId],
},
map,
],
];
}

if (
actionResponse?.type === 'AttackUnitGameOver' ||
actionResponse?.type === 'BeginTurnGameOver'
Expand Down Expand Up @@ -231,6 +273,21 @@ export function applyGameOverActionResponse(
}
case 'GameEnd':
return map;
case 'OptionalWin': {
const { condition, conditionId, toPlayer } = actionResponse;
const winConditions = Array.from(map.config.winConditions);
winConditions[conditionId] = {
...condition,
completed: condition.completed
? new Set([...condition.completed, toPlayer])
: new Set([toPlayer]),
};
return map.copy({
config: map.config.copy({
winConditions,
}),
});
}
default: {
actionResponse satisfies never;
throw new UnknownTypeError('applyGameOverActionResponse', type);
Expand Down
1 change: 1 addition & 0 deletions apollo/actions/applyActionResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ export default function applyActionResponse(
}
case 'BeginGame':
case 'SecretDiscovered':
case 'OptionalWin':
case 'Start':
return map;
default: {
Expand Down
1 change: 1 addition & 0 deletions apollo/lib/computeVisibleActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ const VisibleActionModifiers: Record<
MoveUnit: {
Source: true,
},
OptionalWin: true,
PreviousTurnGameOver: true,
ReceiveReward: true,
Rescue: {
Expand Down
1 change: 1 addition & 0 deletions apollo/lib/dropLabelsFromActionResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default function dropLabelsFromActionResponse(
case 'PreviousTurnGameOver':
case 'ReceiveReward':
case 'SecretDiscovered':
case 'OptionalWin':
case 'SetViewer':
case 'Start':
return actionResponse;
Expand Down
1 change: 1 addition & 0 deletions apollo/lib/getActionResponseVectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default function getActionResponseVectors(
case 'PreviousTurnGameOver':
case 'ReceiveReward':
case 'SecretDiscovered':
case 'OptionalWin':
case 'SetViewer':
case 'Start':
break;
Expand Down
7 changes: 5 additions & 2 deletions apollo/lib/getWinningTeam.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { PlayerID } from '@deities/athena/map/Player.tsx';
import MapData from '@deities/athena/MapData.tsx';
import { GameEndActionResponse } from '../GameOver.tsx';
import {
GameEndActionResponse,
OptionalWinActionResponse,
} from '../GameOver.tsx';

export default function getWinningTeam(
map: MapData,
actionResponse: GameEndActionResponse,
actionResponse: GameEndActionResponse | OptionalWinActionResponse,
): 'draw' | PlayerID {
const isDraw = !actionResponse.toPlayer;
return isDraw
Expand Down
13 changes: 7 additions & 6 deletions apollo/lib/processRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ import MapData from '@deities/athena/MapData.tsx';
import { WinCriteria } from '@deities/athena/WinConditions.tsx';
import isPresent from '@deities/hephaestus/isPresent.tsx';
import applyActionResponse from '../actions/applyActionResponse.tsx';
import { GameEndActionResponse } from '../GameOver.tsx';
import {
GameEndActionResponse,
OptionalWinActionResponse,
} from '../GameOver.tsx';
import { GameState, MutableGameState } from '../Types.tsx';
import getWinningTeam from './getWinningTeam.tsx';

export function processRewards(
map: MapData,
gameEndResponse: GameEndActionResponse,
actionResponse: GameEndActionResponse | OptionalWinActionResponse,
): [GameState, MapData] {
const gameState: MutableGameState = [];
const winningTeam = getWinningTeam(map, gameEndResponse);
const winningTeam = getWinningTeam(map, actionResponse);
if (winningTeam !== 'draw') {
const rewards = new Set(
[
'condition' in gameEndResponse
? gameEndResponse.condition?.reward
: null,
'condition' in actionResponse ? actionResponse.condition?.reward : null,
map.config.winConditions.find(
(condition) => condition.type === WinCriteria.Default,
)?.reward,
Expand Down
4 changes: 3 additions & 1 deletion athena/MapData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,9 @@ export default class MapData {
data.config.biome,
(data.config.winConditions
? decodeWinConditions(data.config.winConditions)
: null) || [{ hidden: false, type: WinCriteria.Default }],
: null) || [
{ hidden: false, optional: false, type: WinCriteria.Default },
],
),
size,
toPlayerID(data.currentPlayer),
Expand Down
Loading

0 comments on commit 3d7d4f0

Please sign in to comment.