Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional win condition feature #34

Merged
merged 11 commits into from
May 30, 2024
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"]]],
["OptionalCondition", [43, ["type", "condition", "conditionId", "toPlayer"]]]
]
86 changes: 73 additions & 13 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 OptionalConditionActionResponse = Readonly<{
condition: WinCondition;
conditionId: number;
toPlayer: PlayerID;
type: 'OptionalCondition';
}>;

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

function check(
previousMap: MapData,
Expand Down Expand Up @@ -92,6 +100,7 @@ const pickWinningPlayer = (
condition.players?.length ? condition.players : activeMap.active
).find(
(playerID) =>
(!condition.optional || !condition.completed?.has(playerID)) &&
activeMap.getPlayer(playerID).stats.destroyedUnits >= condition.amount,
);
}
Expand Down Expand Up @@ -138,19 +147,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;
cpojer marked this conversation as resolved.
Show resolved Hide resolved

const gameEndResponse =
condition?.type === WinCriteria.Default || 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 +172,38 @@ export function checkGameOverConditions(
];
}

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

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

if (
actionResponse?.type === 'AttackUnitGameOver' ||
actionResponse?.type === 'BeginTurnGameOver'
Expand Down Expand Up @@ -231,6 +273,24 @@ export function applyGameOverActionResponse(
}
case 'GameEnd':
return map;
case 'OptionalCondition': {
const { condition, conditionId, toPlayer } = actionResponse;
if (condition.type === WinCriteria.Default) {
return map;
}
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 'OptionalCondition':
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,
},
OptionalCondition: 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 'OptionalCondition':
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 'OptionalCondition':
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,
OptionalConditionActionResponse,
} from '../GameOver.tsx';

export default function getWinningTeam(
map: MapData,
actionResponse: GameEndActionResponse,
actionResponse: GameEndActionResponse | OptionalConditionActionResponse,
): '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,
OptionalConditionActionResponse,
} from '../GameOver.tsx';
import { GameState, MutableGameState } from '../Types.tsx';
import getWinningTeam from './getWinningTeam.tsx';

export function processRewards(
map: MapData,
gameEndResponse: GameEndActionResponse,
actionResponse: GameEndActionResponse | OptionalConditionActionResponse,
): [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
Loading
Loading