Skip to content

Commit

Permalink
Add a Dinosaur skill power.
Browse files Browse the repository at this point in the history
GitOrigin-RevId: e7952375c4c4e99761899ad8e915803dc334d4d5
  • Loading branch information
cpojer committed Feb 26, 2025
1 parent 075ceb4 commit f6f5763
Show file tree
Hide file tree
Showing 22 changed files with 333 additions and 312 deletions.
11 changes: 8 additions & 3 deletions apollo/Action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -919,9 +919,9 @@ function buySkill(map: MapData, { from, skill }: BuySkillAction) {
return null;
}

function activatePower(map: MapData, { skill }: ActivatePowerAction) {
function activatePower(map: MapData, { from, skill }: ActivatePowerAction) {
const player = map.getCurrentPlayer();
const { charges, requiresCrystal } = getSkillConfig(skill);
const { charges, requiresCrystal, requiresTarget } = getSkillConfig(skill);

if (
player &&
Expand All @@ -932,7 +932,12 @@ function activatePower(map: MapData, { skill }: ActivatePowerAction) {
player.charge >= charges * Charge &&
(!requiresCrystal || (player.isHumanPlayer() && player.crystal != null))
) {
return getActivatePowerActionResponse(map, player.id, skill, false);
const target = requiresTarget && from ? from : null;
if (requiresTarget && (!target || !map.contains(target))) {
return null;
}

return getActivatePowerActionResponse(map, player.id, skill, target, false);
}

return null;
Expand Down
23 changes: 6 additions & 17 deletions apollo/ActionResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,9 @@ export type CreateTracksActionResponse = Readonly<{
type: 'CreateTracks';
}>;

export type FoldActionResponse = Readonly<{
from: Vector;
type: 'Fold';
}>;
export type FoldActionResponse = Readonly<{ from: Vector; type: 'Fold' }>;

export type UnfoldActionResponse = Readonly<{
from: Vector;
type: 'Unfold';
}>;
export type UnfoldActionResponse = Readonly<{ from: Vector; type: 'Unfold' }>;

export type CompleteUnitActionResponse = Readonly<{
from: Vector;
Expand Down Expand Up @@ -174,17 +168,11 @@ export type MoveUnitActionResponse = Readonly<{
type: 'MoveUnit';
}>;

export type StartActionResponse = Readonly<{
type: 'Start';
}>;
export type StartActionResponse = Readonly<{ type: 'Start' }>;

export type BeginGameActionResponse = Readonly<{
type: 'BeginGame';
}>;
export type BeginGameActionResponse = Readonly<{ type: 'BeginGame' }>;

export type SetViewerActionResponse = Readonly<{
type: 'SetViewer';
}>;
export type SetViewerActionResponse = Readonly<{ type: 'SetViewer' }>;

export type SetPlayerActionResponse = Readonly<{
player: PlayerID;
Expand All @@ -207,6 +195,7 @@ export type BuySkillActionResponse = Readonly<{

export type ActivatePowerActionResponse = Readonly<{
free?: boolean;
from?: Vector;
skill: Skill;
type: 'ActivatePower';
units?: ImmutableMap<Vector, Unit>;
Expand Down
1 change: 1 addition & 0 deletions apollo/Effects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const handleDefaultEffects = (
activeMap,
currentPlayer.id,
skill,
null,
true,
);
activeMap = applyActionResponse(
Expand Down
97 changes: 46 additions & 51 deletions apollo/__tests__/Skill.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,8 @@ const fromB = vec(1, 2);
const toB = vec(2, 2);

test('status effects from leaders are applied', async () => {
const leader = {
name: generateUnitName(true),
};
const regular = {
name: generateUnitName(false),
};
const leader = { name: generateUnitName(true) };
const regular = { name: generateUnitName(false) };
const [, state1] = execute(
map.copy({
units: map.units
Expand Down Expand Up @@ -160,9 +156,7 @@ test('status effects from research labs are applied', async () => {
});

test('status effects from skills are applied', async () => {
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };
const [, state1] = execute(
map.copy({
teams: updatePlayer(
Expand Down Expand Up @@ -215,9 +209,7 @@ test('status effects from skills are applied', async () => {
});

test('status effects from skills can increase and decrease attack or defense', async () => {
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };
const initialMap = map.copy({
units: map.units
.set(fromA, SmallTank.create(1, options))
Expand All @@ -238,9 +230,7 @@ test('status effects from skills can increase and decrease attack or defense', a
AttackUnitAction(fromA, toA),
)!;
const [, defaultAttackBtoA] = execute(
initialMap.copy({
currentPlayer: 2,
}),
initialMap.copy({ currentPlayer: 2 }),
vision,
AttackUnitAction(toA, fromA),
)!;
Expand All @@ -251,9 +241,7 @@ test('status effects from skills can increase and decrease attack or defense', a
AttackUnitAction(fromA, toA),
)!;
const [, skillAttackBtoA] = execute(
initialMapWithSkills.copy({
currentPlayer: 2,
}),
initialMapWithSkills.copy({ currentPlayer: 2 }),
vision,
AttackUnitAction(toA, fromA),
)!;
Expand All @@ -270,9 +258,7 @@ test('status effects from skills can increase and decrease attack or defense', a
});

test('status effects can increase defense on specific tiles', async () => {
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };

const newMapA = map.map.slice();
const newMapB = map.map.slice();
Expand Down Expand Up @@ -332,9 +318,7 @@ test('status effects can increase defense on specific tiles', async () => {
});

test('status effects can increase attack on specific tiles', async () => {
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };
const newMap = map.map.slice();
newMap[map.getTileIndex(fromA)] = RailTrack.id;

Expand Down Expand Up @@ -435,13 +419,8 @@ test('sniper leader units can capture with a skill', async () => {
test('skills can extend the range of units', async () => {
const skills = new Set([Skill.MovementIncreaseGroundUnitDefenseDecrease]);
const player = map.getPlayer(1);
const playerWithSkill = player.copy({
skills,
});
const playerWithActiveSkill = player.copy({
activeSkills: skills,
skills,
});
const playerWithSkill = player.copy({ skills });
const playerWithActiveSkill = player.copy({ activeSkills: skills, skills });

expect(SmallTank.getRadiusFor(player1)).toBeLessThan(
SmallTank.getRadiusFor(playerWithSkill),
Expand All @@ -458,9 +437,7 @@ test('skills can extend the range of units', async () => {

test('activating a power adds the active status effect of a power', () => {
const skills = new Set([Skill.AttackIncreaseMinor]);
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };
const [, state1] = execute(
map.copy({
teams: updatePlayer(
Expand Down Expand Up @@ -504,13 +481,13 @@ test(`cannot activate a skill that the player doesn't own or if the player doesn
execute(
mapA,
vision,
ActivatePowerAction(Skill.AttackIncreaseMajorDefenseDecreaseMajor),
ActivatePowerAction(Skill.AttackIncreaseMajorDefenseDecreaseMajor, null),
),
).toBe(null);

// Player does not have enough charges.
expect(
execute(mapA, vision, ActivatePowerAction(Skill.AttackIncreaseMinor)),
execute(mapA, vision, ActivatePowerAction(Skill.AttackIncreaseMinor, null)),
).toBe(null);

// Skill is already activated.
Expand All @@ -525,7 +502,7 @@ test(`cannot activate a skill that the player doesn't own or if the player doesn
),
}),
vision,
ActivatePowerAction(Skill.AttackIncreaseMinor),
ActivatePowerAction(Skill.AttackIncreaseMinor, null),
),
).toBe(null);

Expand All @@ -538,7 +515,7 @@ test(`cannot activate a skill that the player doesn't own or if the player doesn
),
}),
vision,
ActivatePowerAction(Skill.AttackIncreaseMinor),
ActivatePowerAction(Skill.AttackIncreaseMinor, null),
)!;

expect(actionResponse.type).toBe('ActivatePower');
Expand Down Expand Up @@ -567,13 +544,8 @@ test('can enable some units and disable others', async () => {
test('can modify the range of units using a skill', async () => {
const player = map.getPlayer(1);
const skills = new Set([Skill.BuyUnitBazookaBear]);
const playerWithSkill = player.copy({
skills,
});
const playerWithActiveSkill = player.copy({
activeSkills: skills,
skills,
});
const playerWithSkill = player.copy({ skills });
const playerWithActiveSkill = player.copy({ activeSkills: skills, skills });

expect(BazookaBear.getRangeFor(player)).toMatchInlineSnapshot(`
[
Expand Down Expand Up @@ -602,9 +574,7 @@ test('can modify the range of units using a skill', async () => {

test('the counter attack skill makes counter attacks more powerful', () => {
const skills = new Set([Skill.CounterAttackPower]);
const options = {
name: generateUnitName(false),
};
const options = { name: generateUnitName(false) };
const [, state1] = execute(map, vision, AttackUnitAction(fromA, toA))!;
const [, state2] = execute(
map.copy({
Expand All @@ -630,9 +600,7 @@ test('the counter attack skill makes counter attacks more powerful', () => {

test('the commander skill makes leader units stronger', () => {
const skills = new Set([Skill.BuyUnitCommander]);
const options = {
name: generateUnitName(true),
};
const options = { name: generateUnitName(true) };
const mapA = map.copy({
teams: updatePlayer(map.teams, map.getPlayer(1).copy({ skills })),
units: map.units.set(fromA, SmallTank.create(1, options)),
Expand Down Expand Up @@ -722,3 +690,30 @@ test('adds a shield when healing units with the shield skill', async () => {
const [, resultMapB] = execute(mapB, vision, HealAction(fromA, toA))!;
expect(resultMapB.units.get(toA)!.shield).toBe(true);
});

test(`some skills require a target to be provided`, () => {
const skills = new Set([Skill.BuyUnitDinosaur]);
const from = vec(1, 1);
const mapA = map.copy({
teams: updatePlayer(
map.teams,
map.getPlayer(1).copy({ charge: Charge * MaxCharges, skills }),
),
});

expect(
execute(mapA, vision, ActivatePowerAction(Skill.BuyUnitDinosaur, null)),
).toBe(null);

const [actionResponse] = execute(
mapA,
vision,
ActivatePowerAction(Skill.BuyUnitDinosaur, from),
)!;

expect(actionResponse.type).toBe('ActivatePower');
if (actionResponse.type !== 'ActivatePower') {
throw new Error(`Expected 'actionResponse' type to be 'ActivatePower'.`);
}
expect(actionResponse.from).toBe(from);
});
6 changes: 4 additions & 2 deletions apollo/action-mutators/ActionMutators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ export const StartAction = () => ({ type: 'Start' }) as const;
export const BuySkillAction = (from: Vector, skill: Skill) =>
({ from, skill, type: 'BuySkill' }) as const;

export const ActivatePowerAction = (skill: Skill, from?: Vector) =>
({ from, skill, type: 'ActivatePower' }) as const;
export const ActivatePowerAction = (skill: Skill, from: Vector | null) =>
from
? ({ from, skill, type: 'ActivatePower' } as const)
: ({ skill, type: 'ActivatePower' } as const);

export const ActivateCrystalAction = (crystal: Crystal) =>
({ crystal, type: 'ActivateCrystal' }) as const;
Loading

0 comments on commit f6f5763

Please sign in to comment.