const assert = require("assert"); const storage = {}; global.wx = { getStorageSync(key) { return storage[key]; }, setStorageSync(key, value) { storage[key] = JSON.parse(JSON.stringify(value)); }, removeStorageSync(key) { delete storage[key]; } }; const service = require("../services/game-service"); function testLegacyRoomMigration() { storage.zmc_rooms = { 123456: { code: "123456", name: "旧房间", players: [{ id: "legacy-host", name: "旧玩家", ready: true }] }, broken: "invalid" }; service.ensureSeedData(); const room = service.getRoom("123456"); assert.strictEqual(room.schemaVersion, 2); assert.strictEqual(room.status, "waiting"); assert.strictEqual(room.settings.mode, "infection"); assert.strictEqual(room.players[0].status, "ready"); assert.deepStrictEqual(room.events, []); assert.strictEqual(storage.zmc_rooms.broken, undefined); assert.ok(service.getRoom("731204")); } function testBrokenTopLevelStorage() { storage.zmc_rooms = []; assert.doesNotThrow(() => service.ensureSeedData()); assert.ok(service.getRoom("731204")); } function testMainFlowAndReplay() { const joined = service.joinRoom("731204", "测试玩家"); assert.strictEqual(joined.players.length, 4); const preparing = service.startGame("731204"); assert.strictEqual(preparing.status, "preparing"); assert.strictEqual( preparing.players.filter((player) => player.role === "seeker").length, preparing.settings.seekerCount ); let room = service.beginPlaying("731204"); assert.strictEqual(room.status, "playing"); while (room.status === "playing") { const target = room.players.find( (player) => player.role === "hider" && player.status === "hiding" ); const captureCode = service.getCaptureCode(room.code, target.id); assert.strictEqual(captureCode.length, 4); room = service.capture(room.code, target.id, captureCode); } assert.strictEqual(room.status, "finished"); assert.strictEqual(room.winner, "seekers"); const replay = service.resetRoom(room.code); assert.strictEqual(replay.status, "waiting"); assert.ok(replay.players.every((player) => player.role === "")); assert.ok(replay.players.every((player) => player.captures === 0)); assert.strictEqual(replay.resultSnapshot, null); assert.strictEqual(replay.nextClueAt, null); assert.strictEqual(replay.captureTokensUsed.length, 0); } function validPayload(overrides = {}) { return { name: "第二天测试房间", mode: "classic", durationMinutes: 30, prepareMinutes: 3, maxPlayers: 4, seekerCount: 1, clueIntervalMinutes: 5, radiusMeters: 500, skillsEnabled: true, ...overrides }; } function testCreateValidation() { assert.throws( () => service.createRoom(validPayload({ seekerCount: 4 })), /初始寻找者必须少于房间人数上限/ ); assert.throws( () => service.createRoom(validPayload({ durationMinutes: 10 })), /游戏时长应为 15~90 分钟/ ); assert.throws( () => service.createRoom(validPayload({ name: " " })), /请填写房间名称/ ); const room = service.createRoom(validPayload()); assert.strictEqual(room.name, "第二天测试房间"); assert.strictEqual(room.settings.seekerCount, 1); } function testLobbyPermissionsAndReadiness() { const room = service.createRoom(validPayload({ maxPlayers: 6 })); service.addDemoPlayers(room.code, 4); assert.throws( () => service.startGame(room.code, "not-the-host"), /只有房主可以开始游戏/ ); const rooms = storage.zmc_rooms; rooms[room.code].players[1].ready = false; rooms[room.code].players[1].status = "idle"; storage.zmc_rooms = rooms; assert.throws(() => service.startGame(room.code), /还有玩家尚未准备好/); service.toggleReady(room.code, rooms[room.code].players[1].id); const started = service.startGame(room.code); assert.strictEqual(started.status, "preparing"); assert.throws(() => service.startGame(room.code), /不能重复开始/); } function testJoinStatusMessages() { assert.throws(() => service.joinRoom("12", "玩家"), /六位房间码/); assert.throws(() => service.joinRoom("999999", "玩家"), /没有找到/); const room = service.createRoom(validPayload()); storage.zmc_rooms[room.code].status = "finished"; assert.throws(() => service.joinRoom(room.code, "玩家"), /已经结束/); storage.zmc_rooms[room.code].status = "cancelled"; assert.throws(() => service.joinRoom(room.code, "玩家"), /房主取消/); } function prepareRoom(overrides = {}) { const room = service.createRoom(validPayload({ maxPlayers: 6, ...overrides })); service.addDemoPlayers(room.code, 4); return room.code; } function testStateTransitionsAndDemoTiming() { const code = prepareRoom(); assert.throws(() => service.beginPlaying(code), /不能进入追逃阶段/); assert.throws(() => service.revealClue(code), /不能广播线索/); assert.throws(() => service.useSkill(code, "radar"), /不能使用技能/); const preparing = service.startGame(code); assert.strictEqual( preparing.phaseEndsAt - preparing.phaseStartedAt, preparing.settings.prepareMinutes * 1000 ); const scheduledStartAt = preparing.phaseEndsAt; const playing = service.beginPlaying(code); assert.strictEqual(playing.phaseStartedAt, scheduledStartAt); assert.strictEqual(playing.phaseEndsAt - playing.phaseStartedAt, 180000); assert.strictEqual(playing.nextClueAt - playing.phaseStartedAt, 15000); assert.throws(() => service.beginPlaying(code), /不能进入追逃阶段/); const finished = service.finishGame(code, "hiders"); assert.strictEqual(finished.status, "finished"); assert.throws(() => service.finishGame(code), /不能结束游戏/); } function testConfiguredProductionTiming() { const code = prepareRoom({ durationMinutes: 20, prepareMinutes: 5, clueIntervalMinutes: 8 }); storage.zmc_rooms[code].runtime.demoAccelerated = false; const preparing = service.startGame(code); assert.strictEqual(preparing.phaseEndsAt - preparing.phaseStartedAt, 5 * 60 * 1000); const playing = service.beginPlaying(code); assert.strictEqual(playing.phaseEndsAt - playing.phaseStartedAt, 20 * 60 * 1000); assert.strictEqual(playing.nextClueAt - playing.phaseStartedAt, 8 * 60 * 1000); } function startPlayableRoom(overrides = {}) { const code = prepareRoom(overrides); const preparing = service.startGame(code); return service.beginPlaying(preparing.code); } function testRoleCountsAndGameModes() { let room = startPlayableRoom({ mode: "classic", maxPlayers: 8, seekerCount: 3 }); assert.strictEqual(room.players.length, 4); assert.strictEqual(room.players.filter((player) => player.role === "seeker").length, 3); let target = room.players.find((player) => player.role === "hider"); let token = service.getCaptureCode(room.code, target.id); room = service.capture(room.code, target.id, token); target = room.players.find((player) => player.id === target.id); assert.strictEqual(target.role, "hider"); assert.strictEqual(target.status, "caught"); assert.strictEqual(room.status, "finished"); room = startPlayableRoom({ mode: "infection", seekerCount: 1 }); target = room.players.find((player) => player.role === "hider"); token = service.getCaptureCode(room.code, target.id); room = service.capture(room.code, target.id, token); target = room.players.find((player) => player.id === target.id); assert.strictEqual(target.role, "seeker"); assert.strictEqual(target.status, "seeking"); } function testPersistedSkills() { let room = startPlayableRoom({ mode: "classic", skillsEnabled: true }); const seeker = room.players.find((player) => player.role === "seeker"); const hider = room.players.find((player) => player.role === "hider"); room = service.useSkill(room.code, "radar", seeker.id); assert.strictEqual( room.players.find((player) => player.id === seeker.id).skillsUsed.radar, 1 ); assert.throws( () => service.useSkill(room.code, "radar", seeker.id), /已经使用过/ ); room = service.useSkill(room.code, "silent", hider.id); assert.strictEqual( room.players.find((player) => player.id === hider.id).silentPending, true ); assert.ok(!room.events[0].message.includes(hider.name)); room = service.revealClue(room.code); assert.strictEqual( room.players.find((player) => player.id === hider.id).silentPending, false ); assert.match(room.events[0].message, /静默已生效/); assert.throws( () => service.useSkill(room.code, "radar", hider.id), /当前身份不能使用/ ); room = startPlayableRoom({ skillsEnabled: false }); assert.throws(() => service.useSkill(room.code, "radar"), /未启用角色技能/); } function testCaptureTokenAndPermissions() { const now = 1800000000000; let room = startPlayableRoom({ mode: "classic" }); const seeker = room.players.find((player) => player.role === "seeker"); const target = room.players.find((player) => player.role === "hider"); const anotherHider = room.players.find( (player) => player.role === "hider" && player.id !== target.id ); const token = service.getCaptureCode(room.code, target.id, now); assert.strictEqual(token.length, 4); assert.notStrictEqual(service.getCaptureCode(room.code, anotherHider.id, now), token); assert.notStrictEqual(service.getCaptureCode(room.code, target.id, now + 30000), token); assert.throws( () => service.capture(room.code, target.id, token, anotherHider.id, now), /只有正在游戏的寻找者/ ); assert.throws( () => service.capture(room.code, target.id, token, seeker.id, now + 30000), /口令不正确或已经失效/ ); room = service.capture(room.code, target.id, token, seeker.id, now); assert.strictEqual(room.players.find((player) => player.id === seeker.id).captures, 1); assert.throws( () => service.capture(room.code, target.id, token, seeker.id, now), /口令已经使用过/ ); } function testPauseResumeAndHostFinish() { let room = startPlayableRoom({ mode: "classic" }); assert.throws(() => service.pauseGame(room.code, "not-host"), /只有房主/); const phaseEndsAt = room.phaseEndsAt; room = service.pauseGame(room.code); assert.strictEqual(room.status, "paused"); assert.ok(room.pausedRemainingMs > 0); assert.ok(room.pausedRemainingMs <= phaseEndsAt - room.pausedAt + 5); assert.throws(() => service.pauseGame(room.code), /只有追逃阶段可以暂停/); room = service.resumeGame(room.code); assert.strictEqual(room.status, "playing"); assert.strictEqual(room.pausedRemainingMs, 0); assert.throws(() => service.resumeGame(room.code), /没有暂停/); assert.throws( () => service.finishGame(room.code, "", "not-host", "host_ended"), /只有房主可以提前结束/ ); room = service.finishGame(room.code, "", service.USER_ID, "host_ended"); assert.strictEqual(room.status, "finished"); assert.strictEqual(room.finishReason, "host_ended"); } function testSafetyQuitAndStableResult() { let room = startPlayableRoom({ mode: "classic", seekerCount: 1 }); const hiders = room.players.filter((player) => player.role === "hider"); room = service.quitGame(room.code, hiders[0].id); assert.strictEqual( room.players.find((player) => player.id === hiders[0].id).status, "quit" ); assert.strictEqual(room.status, "playing"); for (let index = 1; index < hiders.length; index += 1) { room = service.quitGame(room.code, hiders[index].id); } assert.strictEqual(room.status, "finished"); assert.strictEqual(room.finishReason, "all_hiders_left"); assert.ok(room.resultSnapshot); assert.strictEqual(room.resultSnapshot.players.length, room.players.length); assert.deepStrictEqual( room.resultSnapshot.players.map((player) => player.id), service.getRoom(room.code).resultSnapshot.players.map((player) => player.id) ); assert.throws(() => service.quitGame(room.code, hiders[0].id), /当前不能退出/); } testLegacyRoomMigration(); testBrokenTopLevelStorage(); testMainFlowAndReplay(); testCreateValidation(); testLobbyPermissionsAndReadiness(); testJoinStatusMessages(); testStateTransitionsAndDemoTiming(); testConfiguredProductionTiming(); testRoleCountsAndGameModes(); testPersistedSkills(); testCaptureTokenAndPermissions(); testPauseResumeAndHostFinish(); testSafetyQuitAndStableResult(); console.log("game-service tests passed");