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:
@@ -0,0 +1,192 @@
|
||||
import type {
|
||||
DatabaseClient,
|
||||
GeneralTurnRow,
|
||||
NationTurnRow,
|
||||
} from '../context.js';
|
||||
|
||||
export const DEFAULT_TURN_ACTION = '휴식';
|
||||
export const MAX_GENERAL_TURNS = 30;
|
||||
export const MAX_NATION_TURNS = 12;
|
||||
|
||||
export interface ReservedTurnEntry {
|
||||
action: string;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ReservedTurnView {
|
||||
index: number;
|
||||
action: string;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const normalizeAction = (action: string | null | undefined): string =>
|
||||
action && action.length > 0 ? action : DEFAULT_TURN_ACTION;
|
||||
|
||||
const normalizeArgs = (args: unknown): Record<string, unknown> =>
|
||||
isRecord(args) ? args : {};
|
||||
|
||||
const createDefaultEntry = (): ReservedTurnEntry => ({
|
||||
action: DEFAULT_TURN_ACTION,
|
||||
args: {},
|
||||
});
|
||||
|
||||
const buildDefaultTurns = (length: number): ReservedTurnEntry[] =>
|
||||
Array.from({ length }, () => createDefaultEntry());
|
||||
|
||||
const applyShift = (
|
||||
turns: ReservedTurnEntry[],
|
||||
amount: number
|
||||
): ReservedTurnEntry[] => {
|
||||
if (amount === 0) {
|
||||
return turns.slice();
|
||||
}
|
||||
if (amount > 0) {
|
||||
const padding = Array.from({ length: amount }, () => createDefaultEntry());
|
||||
const sliced = turns.slice(0, Math.max(0, turns.length - amount));
|
||||
return padding.concat(sliced);
|
||||
}
|
||||
const shift = Math.min(turns.length, Math.abs(amount));
|
||||
const padding = Array.from({ length: shift }, () => createDefaultEntry());
|
||||
const sliced = turns.slice(shift);
|
||||
return sliced.concat(padding);
|
||||
};
|
||||
|
||||
const buildTurnListFromRows = (
|
||||
rows: Array<GeneralTurnRow | NationTurnRow>,
|
||||
maxTurns: number
|
||||
): ReservedTurnEntry[] => {
|
||||
const result = buildDefaultTurns(maxTurns);
|
||||
for (const row of rows) {
|
||||
if (row.turnIdx < 0 || row.turnIdx >= maxTurns) {
|
||||
continue;
|
||||
}
|
||||
result[row.turnIdx] = {
|
||||
action: normalizeAction(row.actionCode),
|
||||
args: normalizeArgs(row.arg),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const serializeTurnList = (turns: ReservedTurnEntry[]): ReservedTurnView[] =>
|
||||
turns.map((entry, index) => ({
|
||||
index,
|
||||
action: entry.action,
|
||||
args: entry.args,
|
||||
}));
|
||||
|
||||
const persistGeneralTurns = async (
|
||||
db: DatabaseClient,
|
||||
generalId: number,
|
||||
turns: ReservedTurnEntry[]
|
||||
): Promise<void> => {
|
||||
await db.generalTurn.deleteMany({ where: { generalId } });
|
||||
await db.generalTurn.createMany({
|
||||
data: turns.map((entry, turnIdx) => ({
|
||||
generalId,
|
||||
turnIdx,
|
||||
actionCode: normalizeAction(entry.action),
|
||||
arg: normalizeArgs(entry.args),
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
const persistNationTurns = async (
|
||||
db: DatabaseClient,
|
||||
nationId: number,
|
||||
officerLevel: number,
|
||||
turns: ReservedTurnEntry[]
|
||||
): Promise<void> => {
|
||||
await db.nationTurn.deleteMany({ where: { nationId, officerLevel } });
|
||||
await db.nationTurn.createMany({
|
||||
data: turns.map((entry, turnIdx) => ({
|
||||
nationId,
|
||||
officerLevel,
|
||||
turnIdx,
|
||||
actionCode: normalizeAction(entry.action),
|
||||
arg: normalizeArgs(entry.args),
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
export const loadGeneralTurns = async (
|
||||
db: DatabaseClient,
|
||||
generalId: number
|
||||
): Promise<ReservedTurnEntry[]> => {
|
||||
const rows = await db.generalTurn.findMany({
|
||||
where: { generalId },
|
||||
orderBy: [{ turnIdx: 'asc' }],
|
||||
});
|
||||
return buildTurnListFromRows(rows, MAX_GENERAL_TURNS);
|
||||
};
|
||||
|
||||
export const loadNationTurns = async (
|
||||
db: DatabaseClient,
|
||||
nationId: number,
|
||||
officerLevel: number
|
||||
): Promise<ReservedTurnEntry[]> => {
|
||||
const rows = await db.nationTurn.findMany({
|
||||
where: { nationId, officerLevel },
|
||||
orderBy: [{ turnIdx: 'asc' }],
|
||||
});
|
||||
return buildTurnListFromRows(rows, MAX_NATION_TURNS);
|
||||
};
|
||||
|
||||
export const setGeneralTurn = async (
|
||||
db: DatabaseClient,
|
||||
generalId: number,
|
||||
turnIndex: number,
|
||||
action: string,
|
||||
args: unknown
|
||||
): Promise<ReservedTurnView[]> => {
|
||||
const turns = await loadGeneralTurns(db, generalId);
|
||||
turns[turnIndex] = {
|
||||
action: normalizeAction(action),
|
||||
args: normalizeArgs(args),
|
||||
};
|
||||
await persistGeneralTurns(db, generalId, turns);
|
||||
return serializeTurnList(turns);
|
||||
};
|
||||
|
||||
export const shiftGeneralTurns = async (
|
||||
db: DatabaseClient,
|
||||
generalId: number,
|
||||
amount: number
|
||||
): Promise<ReservedTurnView[]> => {
|
||||
const turns = await loadGeneralTurns(db, generalId);
|
||||
const shifted = applyShift(turns, amount);
|
||||
await persistGeneralTurns(db, generalId, shifted);
|
||||
return serializeTurnList(shifted);
|
||||
};
|
||||
|
||||
export const setNationTurn = async (
|
||||
db: DatabaseClient,
|
||||
nationId: number,
|
||||
officerLevel: number,
|
||||
turnIndex: number,
|
||||
action: string,
|
||||
args: unknown
|
||||
): Promise<ReservedTurnView[]> => {
|
||||
const turns = await loadNationTurns(db, nationId, officerLevel);
|
||||
turns[turnIndex] = {
|
||||
action: normalizeAction(action),
|
||||
args: normalizeArgs(args),
|
||||
};
|
||||
await persistNationTurns(db, nationId, officerLevel, turns);
|
||||
return serializeTurnList(turns);
|
||||
};
|
||||
|
||||
export const shiftNationTurns = async (
|
||||
db: DatabaseClient,
|
||||
nationId: number,
|
||||
officerLevel: number,
|
||||
amount: number
|
||||
): Promise<ReservedTurnView[]> => {
|
||||
const turns = await loadNationTurns(db, nationId, officerLevel);
|
||||
const shifted = applyShift(turns, amount);
|
||||
await persistNationTurns(db, nationId, officerLevel, shifted);
|
||||
return serializeTurnList(shifted);
|
||||
};
|
||||
Reference in New Issue
Block a user