Skip to content

Commit

Permalink
🆙 Add leveling mechanic
Browse files Browse the repository at this point in the history
  • Loading branch information
ferdodo committed Feb 10, 2025
1 parent dac2009 commit 2b6662c
Show file tree
Hide file tree
Showing 15 changed files with 113 additions and 30 deletions.
9 changes: 9 additions & 0 deletions core/api/transpose-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ export function transposeHandle(context: BackContext): Observable<void> {

game.playerHeroes[publicKey].push(grabbedHero);
benchHeroes[grabPiece.benchPosition] = ungrabbedHero;

if (
game.playerHeroes[publicKey].length >
game.playerLevel[publicKey]
) {
await abort();
return;
}

await commit(game);
return;
}
Expand Down
3 changes: 2 additions & 1 deletion core/api/transpose.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import { withTwoPiecesOnBoard } from "../fixtures/with-two-pieces-on-board.js";
import { getGame } from "../utils/get-game.js";
import { withTwoPlayersInCombat } from "../fixtures/with-two-players-in-combat.js";
import { asPlayerShopBuy } from "../automations/as-player-shop-buy.js";
import { withOneBoughtHeroAndLevelTwo } from "../fixtures/with-one-bought-hero-and-level-two.js";

test("Transpose shall move piece to an empty cell", async () => {
const testContext = await withTwoPlayerGameStarted();
await asPlayerTransposeBoardToBoard(testContext);
});

test("Transpose from bench to board", async () => {
const testContext = await withOneBoughtHero();
const testContext = await withOneBoughtHeroAndLevelTwo();
await asPlayerTransposeBenchToBoard(testContext);
const game = await getGame(testContext);
const publicKey = testContext.frontContexts[0].publicKey || "Error";
Expand Down
2 changes: 0 additions & 2 deletions core/automations/go-to-next-combat-phase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { TestContext } from "../types/test-context.js";
import { firstValueFrom, filter } from "rxjs";
import { getPhaseDuration } from "../utils/get-phase-duration.js";
import { getGame } from "../utils/get-game.js";
import { Phase } from "../types/phase.js";
import { goToNextPhase } from "./go-to-next-phase.js";
Expand Down
26 changes: 26 additions & 0 deletions core/automations/go-to-next-planning-phase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { TestContext } from "../types/test-context.js";
import { getGame } from "../utils/get-game.js";
import { Phase } from "../types/phase.js";
import { goToNextPhase } from "./go-to-next-phase.js";

export async function goToNextPlanningPhase(
testContext: TestContext,
playerIndex = 0,
) {
const frontContext = testContext.frontContexts[playerIndex];

if (!frontContext) {
throw new Error(`Player ${playerIndex} not found !`);
}

if (!frontContext.playsig) {
throw new Error(`Player ${playerIndex} has not initiated the game !`);
}

let game = await getGame(testContext);

do {
await goToNextPhase(testContext);
game = await getGame(testContext);
} while (game?.phase !== Phase.Planning);
}
14 changes: 6 additions & 8 deletions core/fixtures/with-game-over-with-p1-advantage-against-bot.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { observeGame } from "../api/observe-game.js";
import { asNewPlayerConnect } from "../automations/as-new-player-connect.js";
import { asPlayerInitiateGame } from "../automations/as-player-initiate-game.js";
import { asPlayerLevelUp } from "../automations/as-player-level-up.js";
import { asPlayerShopBuy } from "../automations/as-player-shop-buy.js";
import { asPlayerTransposeBenchToBoard } from "../automations/as-player-transpose-bench-to-board.js";
import { goToNextPhase } from "../automations/go-to-next-phase.js";
import { goToNextPlanningPhase } from "../automations/go-to-next-planning-phase.js";
import type { TestContext } from "../types/test-context.js";
import { connectBot } from "../utils/connect-bot.js";
import { getGame } from "../utils/get-game.js";
import { isBotOutOfMoves } from "../utils/is-bot-out-of-moves.js";
import { isGameInProgress } from "../utils/is-game-in-progress.js";
import { withServerStarted } from "./with-server-started.js";
import { firstValueFrom, filter, timeout } from "rxjs";
Expand All @@ -27,7 +30,9 @@ export async function withGameOverWithP1AdvantageAgainstBot(): Promise<TestConte
),
]);

await goToNextPlanningPhase(testContext);
await asPlayerShopBuy(testContext);
await asPlayerLevelUp(testContext);
await asPlayerTransposeBenchToBoard(testContext);

const frontContext = testContext.frontContexts[1];
Expand All @@ -43,14 +48,7 @@ export async function withGameOverWithP1AdvantageAgainstBot(): Promise<TestConte

await firstValueFrom(
observeGame(frontContext).pipe(
filter((game) => {
return (
game.playerMoney[frontContext.publicKey] < 3 &&
Object.values(game.playerBenches[frontContext.publicKey]).filter(
Boolean,
).length === 0
);
}),
filter((game) => isBotOutOfMoves(game, frontContext.publicKey)),
timeout(1000),
),
);
Expand Down
4 changes: 4 additions & 0 deletions core/fixtures/with-game-over-with-p1-advantage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { asPlayerLevelUp } from "../automations/as-player-level-up.js";
import { asPlayerShopBuy } from "../automations/as-player-shop-buy.js";
import { asPlayerTransposeBenchToBoard } from "../automations/as-player-transpose-bench-to-board.js";
import { goToNextPhase } from "../automations/go-to-next-phase.js";
import { goToNextPlanningPhase } from "../automations/go-to-next-planning-phase.js";
import type { TestContext } from "../types/test-context.js";
import { getGame } from "../utils/get-game.js";
import { isGameInProgress } from "../utils/is-game-in-progress.js";
Expand All @@ -9,6 +11,8 @@ import { withTwoPlayerGameStarted } from "./with-two-player-game-started.js";
export async function withGameOverWithP1Advantage(): Promise<TestContext> {
const testContext = await withTwoPlayerGameStarted();
await asPlayerShopBuy(testContext);
await goToNextPlanningPhase(testContext);
await asPlayerLevelUp(testContext);
await asPlayerTransposeBenchToBoard(testContext);

while (isGameInProgress(await getGame(testContext))) {
Expand Down
11 changes: 11 additions & 0 deletions core/fixtures/with-one-bought-hero-and-level-two.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { asPlayerLevelUp } from "../automations/as-player-level-up.js";
import { goToNextPlanningPhase } from "../automations/go-to-next-planning-phase.js";
import type { TestContext } from "../types/test-context.js";
import { withOneBoughtHero } from "./with-one-bought-hero.js";

export async function withOneBoughtHeroAndLevelTwo(): Promise<TestContext> {
const testContext = await withOneBoughtHero();
await goToNextPlanningPhase(testContext);
await asPlayerLevelUp(testContext);
return testContext;
}
20 changes: 3 additions & 17 deletions core/fixtures/with-two-bot-game-fighting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { withTwoBotGameStarted } from "./with-two-bot-game-started.js";
import { observeGame } from "../api/observe-game.js";
import { firstValueFrom, filter, timeout } from "rxjs";
import { Phase } from "../types/phase.js";
import { isBotOutOfMoves } from "../utils/is-bot-out-of-moves.js";

export async function withTwoBotGameFighting(): Promise<TestContext> {
const testContext = await withTwoBotGameStarted();
Expand All @@ -19,35 +20,20 @@ export async function withTwoBotGameFighting(): Promise<TestContext> {
while (getHeroCountA() < 5 || getHeroCountB() < 5) {
await firstValueFrom(
observeGame(frontContextA).pipe(
filter((game) => {
return (
game.playerMoney[frontContextA.publicKey] < 3 &&
Object.values(game.playerBenches[frontContextA.publicKey]).filter(
Boolean,
).length === 0
);
}),
filter((game) => isBotOutOfMoves(game, frontContextA.publicKey)),
timeout(1000),
),
);

await firstValueFrom(
observeGame(frontContextB).pipe(
filter((game) => {
return (
game.playerMoney[frontContextB.publicKey] < 3 &&
Object.values(game.playerBenches[frontContextB.publicKey]).filter(
Boolean,
).length === 0
);
}),
filter((game) => isBotOutOfMoves(game, frontContextB.publicKey)),
timeout(1000),
),
);

await goToNextPhase(testContext);
await goToNextPhase(testContext);

game = await getGame(testContext);
}

Expand Down
4 changes: 4 additions & 0 deletions core/fixtures/with-two-pieces-on-board.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import type { TestContext } from "../types/test-context.js";
import { withOneBoughtHero } from "./with-one-bought-hero.js";
import { asPlayerTransposeBenchToBoard } from "../automations/as-player-transpose-bench-to-board.js";
import { asPlayerLevelUp } from "../automations/as-player-level-up.js";
import { goToNextPlanningPhase } from "../automations/go-to-next-planning-phase.js";

export async function withTwoPiecesOnBoard(): Promise<TestContext> {
const testContext = await withOneBoughtHero();
await goToNextPlanningPhase(testContext);
await asPlayerLevelUp(testContext);

await asPlayerTransposeBenchToBoard(testContext, 0, {
positionX: 1,
Expand Down
1 change: 1 addition & 0 deletions core/types/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Appellation } from "./appellation.js";

export interface Display {
pieces: Piece[];
level: number;
players: PlayerDisplay[];
shop: Appellation[];
bench: Record<number, Piece>;
Expand Down
16 changes: 15 additions & 1 deletion core/utils/connect-bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getHeroCost } from "./get-hero-cost.js";
import { shopBuy } from "../api/shop-buy.js";
import { transpose } from "../api/transpose.js";
import { debounceTime } from "rxjs/operators";
import { getLevelUpCost } from "./get-level-up-cost.js";
import { levelUp } from "../api/level-up.js";

export async function connectBot(frontContext: FrontContext, debounce = 0) {
const initiateGameResponse = await initiateGame(frontContext);
Expand All @@ -18,11 +20,23 @@ export async function connectBot(frontContext: FrontContext, debounce = 0) {
if (game.phase === Phase.Planning) {
const bench = game.playerBenches[frontContext.publicKey] || {};
const benchEntries = Object.entries(bench);
const levelUpCost = getLevelUpCost(game, frontContext.publicKey);

if (game.playerMoney[frontContext.publicKey] >= levelUpCost) {
await levelUp(frontContext);
return;
}

const board = game.playerHeroes[frontContext.publicKey];
const boardSize = board.filter(Boolean).length;

const availableBoardSlot =
boardSize < game.playerLevel[frontContext.publicKey];

for (const [_benchPosition, hero] of benchEntries) {
const benchPosition = Number.parseInt(_benchPosition);

if (hero) {
if (hero && availableBoardSlot) {
const grab = { benchPosition };
const positionX = Math.floor(Math.random() * 5);
const positionY = Math.floor(Math.random() * 10);
Expand Down
21 changes: 21 additions & 0 deletions core/utils/is-bot-out-of-moves.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Game } from "../types/game.js";
import type { PublicKey } from "../types/public-key.js";
import { getHeroCost } from "./get-hero-cost.js";
import { getLevelUpCost } from "./get-level-up-cost.js";

export function isBotOutOfMoves(game: Game, publicKey: PublicKey): boolean {
const money = game.playerMoney[publicKey];
const levelUpCost = getLevelUpCost(game, publicKey);
const cantLevelUp = money < levelUpCost;
const shop = game.playerShops[publicKey] || [];
const minShopEntryPrice = Math.min(...shop.map(getHeroCost));
const bench = game.playerBenches[publicKey] || {};
const benchSize = Object.values(bench).filter(Boolean).length;
const benchHasFreeSlots = benchSize < 6;
const cantShopBuy = money < minShopEntryPrice || !benchHasFreeSlots;
const board = game.playerHeroes[publicKey] || [];
const level = game.playerLevel[publicKey] || 1;
const boardHasFreeSlots = board.length < level;
const cantTransposeToBoard = !benchSize || !boardHasFreeSlots;
return cantLevelUp && cantShopBuy && cantTransposeToBoard;
}
1 change: 1 addition & 0 deletions interface/utils/portray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function portray(
right: false,
})) ||
[],
level: game.playerLevel[publicKey] || 1,
players: game.publicKeys.map((p) => ({
name: game.nicknames[p],
health: game.playerHealths[p] || 0,
Expand Down
8 changes: 7 additions & 1 deletion interface/utils/render-piece-highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ export function renderPieceHighlight(
highlightMesh.position.y = 0.501;
}

highlightMesh.visible = true;
highlightMesh.visible =
display.pieces.length < display.level ||
display.pieces.some((piece) => piece.transposed) ||
(Object.values(display.bench)
.filter(Boolean)
.some((piece) => piece.transposed) &&
boardSlotHasPiece);

if (
highlightMesh.material !== threeContext.pieceHighlightInactiveMaterial
Expand Down
3 changes: 3 additions & 0 deletions sandbox/utils/display-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class DisplayFactory implements Subscribable<Display> {
level,
isMe,
})),
level: 1,
bench: [
{
hero: {
Expand Down Expand Up @@ -211,6 +212,7 @@ export class DisplayFactory implements Subscribable<Display> {
(handle) => handle.id !== piece.hero.id,
);

this.display.level--;
return this;
},
setTransposed: () => {
Expand Down Expand Up @@ -240,6 +242,7 @@ export class DisplayFactory implements Subscribable<Display> {
gui.add(pieceHandle, "removePiece");

this.pieceHandles.push(pieceHandle);
this.display.level++;
return this;
}

Expand Down

0 comments on commit 2b6662c

Please sign in to comment.