feat: obchod, černé můry, netopýr, pavouk a denní úkoly u chytání motýlků
CI / Generate TypeScript types (push) Successful in 15s
CI / Server unit tests (push) Successful in 23s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 1m2s
CI / Playwright E2E tests (push) Successful in 1m33s
CI / Build and push Docker image (push) Successful in 49s
CI / Notify (push) Successful in 1s
CI / Generate TypeScript types (push) Successful in 15s
CI / Server unit tests (push) Successful in 23s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 1m2s
CI / Playwright E2E tests (push) Successful in 1m33s
CI / Build and push Docker image (push) Successful in 49s
CI / Notify (push) Successful in 1s
- obchod s pomůckami a trvalými vylepšeními (větší síťka, strašák, zpevněná síťka) – mince mají trvalý smysl - černé můry se míchají mezi motýly; chycení (i magnetem) ubere hodně úlovků - ptáci silnější, plašič dražší a kratší, zloději chodí častěji podle bohatství - denní úkol s odměnou + achievementy/odznaky - netopýr v noci loví motýly, pavouk zamotá síťku pavučinou – oba na zaklikání - nákupní hlášky rozlišují nedostatek mincí od jiné chyby; herní smyčka se pozastaví při skryté záložce Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e3f439bde0
commit
252d105408
@@ -11,6 +11,13 @@ import {
|
||||
defeatThief,
|
||||
caterpillarAte,
|
||||
defeatCaterpillar,
|
||||
mothHit,
|
||||
buyPremium,
|
||||
waspSpray,
|
||||
buyInsurance,
|
||||
buyUpgrade,
|
||||
getAchievements,
|
||||
upgradeCost,
|
||||
getLeaderboard,
|
||||
growPests,
|
||||
levelForCaught,
|
||||
@@ -37,6 +44,14 @@ import {
|
||||
CATERPILLAR_EATS,
|
||||
CATERPILLAR_REWARD,
|
||||
AUTO_REPAIR_MS,
|
||||
MOTH_PENALTY,
|
||||
PREMIUM_BUY_COST,
|
||||
WASP_SPRAY_COST,
|
||||
INSURANCE_COST,
|
||||
INSURANCE_DURATION_MS,
|
||||
ROBBERY_FRACTION_INSURED,
|
||||
UPGRADE_MAX,
|
||||
DAILY_TASK_REWARD,
|
||||
} from '../butterflies';
|
||||
import { formatDate } from '../utils';
|
||||
|
||||
@@ -366,6 +381,95 @@ describe('housenka', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('černé můry', () => {
|
||||
test('chycení můry ubere úlovky (nikdy pod nulu) a zvýší statistiku', async () => {
|
||||
await seed(USER, { caught: MOTH_PENALTY + 4, coins: 0, goldenCaught: 0 });
|
||||
const stats = await mothHit(USER);
|
||||
expect(stats.caught).toBe(4);
|
||||
expect(stats.mothsHit).toBe(1);
|
||||
});
|
||||
test('nesnese caught pod nulu', async () => {
|
||||
await seed(USER, { caught: 2, coins: 0, goldenCaught: 0 });
|
||||
const stats = await mothHit(USER);
|
||||
expect(stats.caught).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('obchod – spotřební', () => {
|
||||
test('buyPremium odečte cenu', async () => {
|
||||
await seed(USER, { caught: 0, coins: PREMIUM_BUY_COST + 5, goldenCaught: 0 });
|
||||
const stats = await buyPremium(USER);
|
||||
expect(stats.coins).toBe(5);
|
||||
});
|
||||
test('buyPremium bez mincí selže', async () => {
|
||||
await seed(USER, { caught: 0, coins: PREMIUM_BUY_COST - 1, goldenCaught: 0 });
|
||||
await expect(buyPremium(USER)).rejects.toThrow(InsufficientCoinsError);
|
||||
});
|
||||
test('waspSpray vyhubí všechny vosy a odečte cenu', async () => {
|
||||
await seed(USER, { caught: 0, coins: WASP_SPRAY_COST, goldenCaught: 0, wasps: 4, lastWaspAt: Date.now() });
|
||||
const stats = await waspSpray(USER);
|
||||
expect(stats.coins).toBe(0);
|
||||
expect(stats.wasps).toBe(0);
|
||||
});
|
||||
test('pojistka sníží ztrátu při okradení a spotřebuje se', async () => {
|
||||
const now = 3_000_000;
|
||||
await seed(USER, { caught: 0, coins: INSURANCE_COST + 100, goldenCaught: 0 });
|
||||
await buyInsurance(USER, now);
|
||||
const robbed = await robbery(USER, now + 1000);
|
||||
// po koupi zbývá 100 mincí; pojistka → ztráta jen ROBBERY_FRACTION_INSURED
|
||||
expect(robbed.coins).toBe(Math.round(100 * (1 - ROBBERY_FRACTION_INSURED)));
|
||||
expect(robbed.insuranceUntil).toBe(0); // spotřebováno
|
||||
});
|
||||
});
|
||||
|
||||
describe('obchod – trvalá vylepšení', () => {
|
||||
test('cena roste s úrovní a vylepšení se aplikuje', async () => {
|
||||
await seed(USER, { caught: 0, coins: upgradeCost('net', 0) + upgradeCost('net', 1) + 5, goldenCaught: 0 });
|
||||
const a = await buyUpgrade(USER, 'net');
|
||||
expect(a.upgrades.net).toBe(1);
|
||||
const b = await buyUpgrade(USER, 'net');
|
||||
expect(b.upgrades.net).toBe(2);
|
||||
expect(b.coins).toBe(5);
|
||||
});
|
||||
test('nelze koupit nad maximum', async () => {
|
||||
await seed(USER, { caught: 0, coins: 1_000_000, goldenCaught: 0, upgrades: { net: UPGRADE_MAX.net, scarecrow: 0, reinforced: 0 } });
|
||||
await expect(buyUpgrade(USER, 'net')).rejects.toThrow();
|
||||
});
|
||||
test('bez mincí koupě selže', async () => {
|
||||
await seed(USER, { caught: 0, coins: 0, goldenCaught: 0 });
|
||||
await expect(buyUpgrade(USER, 'net')).rejects.toThrow(InsufficientCoinsError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('denní úkol', () => {
|
||||
test('progres se plní a po splnění se vyplatí odměna jednou', async () => {
|
||||
const now = 4_000_000;
|
||||
const day = formatDate(new Date(now));
|
||||
await seed(USER, {
|
||||
caught: 0, coins: 0, goldenCaught: 0, wasps: 5, lastWaspAt: now,
|
||||
daily: { day, taskId: 'wasp', progress: 0, target: 2, done: false },
|
||||
});
|
||||
await killWasp(USER, now);
|
||||
let stats = await killWasp(USER, now);
|
||||
expect(stats.daily.done).toBe(true);
|
||||
expect(stats.coins).toBe(DAILY_TASK_REWARD);
|
||||
// další zabití už odměnu nepřidá
|
||||
stats = await killWasp(USER, now);
|
||||
expect(stats.coins).toBe(DAILY_TASK_REWARD);
|
||||
});
|
||||
});
|
||||
|
||||
describe('achievementy', () => {
|
||||
test('odemykají se podle statistik', async () => {
|
||||
await seed(USER, { caught: 600, coins: 0, goldenCaught: 1 });
|
||||
const list = await getAchievements(USER);
|
||||
const byId = Object.fromEntries(list.map(a => [a.id, a.unlocked]));
|
||||
expect(byId['first-golden']).toBe(true);
|
||||
expect(byId['catch-500']).toBe(true);
|
||||
expect(byId['catch-2000']).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLeaderboard', () => {
|
||||
test('řadí sestupně dle počtu chycených a respektuje limit', async () => {
|
||||
await seed(USER, { caught: 100, coins: 0, goldenCaught: 2 });
|
||||
|
||||
Reference in New Issue
Block a user