feat: add GeneralTurn and NationTurn models with CRUD operations

- Introduced GeneralTurn and NationTurn models in Prisma schema.
- Implemented reserved turns management in the game API, including loading, setting, and shifting turns for generals and nations.
- Created unit tests for reserved turns functionality to ensure correctness.
- Developed reserved turn handler to process actions based on reserved turns.
- Established in-memory storage for reserved turns with persistence to the database.
This commit is contained in:
2025-12-30 04:45:07 +00:00
parent e4c57e2467
commit 6fe6d54432
13 changed files with 1949 additions and 15 deletions
+60 -8
View File
@@ -5,6 +5,7 @@ import { finalizeLogEntry, type LogEntryDraft } from '@sammo-ts/logic';
import type { TurnDaemonHooks } from '../lifecycle/types.js';
import type { InMemoryTurnWorld } from './inMemoryWorld.js';
import type { InMemoryReservedTurnStore } from './reservedTurnStore.js';
export interface DatabaseTurnHooks {
hooks: TurnDaemonHooks;
@@ -50,6 +51,40 @@ const buildGeneralUpdate = (
recentWarTime: general.recentWarTime ?? null,
});
const buildGeneralCreate = (
general: ReturnType<InMemoryTurnWorld['consumeDirtyState']>['generals'][number]
): Prisma.GeneralCreateManyInput => ({
id: general.id,
name: general.name,
nationId: general.nationId,
cityId: general.cityId,
troopId: general.troopId,
npcState: general.npcState,
leadership: general.stats.leadership,
strength: general.stats.strength,
intel: general.stats.intelligence,
experience: general.experience,
dedication: general.dedication,
officerLevel: general.officerLevel,
injury: general.injury,
gold: general.gold,
rice: general.rice,
crew: general.crew,
crewTypeId: general.crewTypeId,
train: general.train,
age: general.age,
horseCode: toCode(general.role.items.horse),
weaponCode: toCode(general.role.items.weapon),
bookCode: toCode(general.role.items.book),
itemCode: toCode(general.role.items.item),
personalCode: toCode(general.role.personality),
specialCode: toCode(general.role.specialDomestic),
special2Code: toCode(general.role.specialWar),
meta: asJson(general.meta),
turnTime: general.turnTime,
recentWarTime: general.recentWarTime ?? null,
});
const buildCityUpdate = (
city: ReturnType<InMemoryTurnWorld['consumeDirtyState']>['cities'][number]
): Prisma.CityUpdateInput => ({
@@ -116,7 +151,8 @@ const buildLogCreateData = (
export const createDatabaseTurnHooks = async (
databaseUrl: string,
world: InMemoryTurnWorld
world: InMemoryTurnWorld,
options?: { reservedTurns?: InMemoryReservedTurnStore }
): Promise<DatabaseTurnHooks> => {
// 턴 처리 결과를 DB에 반영하는 훅을 만든다.
const connector = createPostgresConnector({ url: databaseUrl });
@@ -125,7 +161,8 @@ export const createDatabaseTurnHooks = async (
const hooks: TurnDaemonHooks = {
flushChanges: async () => {
const state = world.getState();
const { generals, cities, nations, logs } = world.consumeDirtyState();
const { generals, cities, nations, logs, createdGenerals } =
world.consumeDirtyState();
await connector.prisma.worldState.update({
where: { id: state.id },
@@ -137,13 +174,25 @@ export const createDatabaseTurnHooks = async (
},
});
const createdIds = new Set(
createdGenerals.map((general) => general.id)
);
if (createdGenerals.length > 0) {
await connector.prisma.general.createMany({
data: createdGenerals.map(buildGeneralCreate),
});
}
await Promise.all([
...generals.map((general) =>
connector.prisma.general.update({
where: { id: general.id },
data: buildGeneralUpdate(general),
})
),
...generals
.filter((general) => !createdIds.has(general.id))
.map((general) =>
connector.prisma.general.update({
where: { id: general.id },
data: buildGeneralUpdate(general),
})
),
...cities.map((city) =>
connector.prisma.city.update({
where: { id: city.id },
@@ -176,6 +225,9 @@ export const createDatabaseTurnHooks = async (
});
}
}
if (options?.reservedTurns) {
await options.reservedTurns.flushChanges();
}
},
};