feat: 게임 엔진에 Clock 인터페이스 및 관련 클래스 추가, 테스트 케이스 작성

This commit is contained in:
2025-12-28 12:48:13 +00:00
parent 4f69d31fb1
commit cbea41ffbc
9 changed files with 121 additions and 83 deletions
+4 -2
View File
@@ -9,10 +9,12 @@
"build": "tsc -p tsconfig.json",
"dev": "node -e \"console.log('dev not configured')\"",
"lint": "node -e \"console.log('lint not configured')\"",
"test": "node -e \"console.log('test not configured')\""
"test": "vitest run --config vitest.config.ts"
},
"dependencies": {
"js-sha512": "^0.9.0"
},
"devDependencies": {}
"devDependencies": {
"vitest": "^4.0.16"
}
}
+1
View File
@@ -1,4 +1,5 @@
export * from './rng.js';
export * from './time/Clock.js';
export * from './util/BytesLike.js';
export * from './util/convertBytesLikeToArrayBuffer.js';
export * from './util/convertBytesLikeToUint8Array.js';
+86
View File
@@ -0,0 +1,86 @@
export interface Clock {
nowMs(): number;
sleepMs(ms: number): Promise<void>;
}
export class SystemClock implements Clock {
// 시스템 시간을 기준으로 동작하는 기본 시계.
nowMs(): number {
return Date.now();
}
async sleepMs(ms: number): Promise<void> {
if (ms <= 0) {
return;
}
await new Promise((resolve) => setTimeout(resolve, ms));
}
}
export class ManualClock implements Clock {
// 테스트에서 시간을 직접 이동시키는 수동 시계.
private currentMs: number;
constructor(initialMs = 0) {
this.currentMs = initialMs;
}
nowMs(): number {
return this.currentMs;
}
async sleepMs(ms: number): Promise<void> {
if (ms <= 0) {
return;
}
this.currentMs += ms;
}
advanceMs(ms: number): void {
if (ms <= 0) {
return;
}
this.currentMs += ms;
}
setMs(ms: number): void {
this.currentMs = ms;
}
}
export class StepClock implements Clock {
// 호출마다 일정 간격씩 시간이 진행되는 시계.
private currentMs: number;
private readonly stepMs: number;
constructor(stepMs: number, initialMs = 0) {
if (stepMs <= 0) {
throw new Error('stepMs must be positive');
}
this.stepMs = stepMs;
this.currentMs = initialMs;
}
nowMs(): number {
this.currentMs += this.stepMs;
return this.currentMs;
}
async sleepMs(ms: number): Promise<void> {
if (ms <= 0) {
return;
}
this.currentMs += ms;
}
advanceMs(ms: number): void {
if (ms <= 0) {
return;
}
this.currentMs += ms;
}
setMs(ms: number): void {
this.currentMs = ms;
}
}
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import { ManualClock, StepClock } from '../src/time/Clock.js';
describe('ManualClock', () => {
it('returns current time without advancing', () => {
const clock = new ManualClock(1000);
expect(clock.nowMs()).toBe(1000);
expect(clock.nowMs()).toBe(1000);
});
it('advances with sleep and manual advance', async () => {
const clock = new ManualClock(0);
await clock.sleepMs(250);
expect(clock.nowMs()).toBe(250);
clock.advanceMs(750);
expect(clock.nowMs()).toBe(1000);
});
it('can set time explicitly', () => {
const clock = new ManualClock(10);
clock.setMs(5000);
expect(clock.nowMs()).toBe(5000);
});
});
describe('StepClock', () => {
it('advances on each nowMs call', () => {
const clock = new StepClock(100, 0);
expect(clock.nowMs()).toBe(100);
expect(clock.nowMs()).toBe(200);
expect(clock.nowMs()).toBe(300);
});
it('advances with sleep', async () => {
const clock = new StepClock(50, 1000);
await clock.sleepMs(200);
expect(clock.nowMs()).toBe(1250);
});
it('advances manually', () => {
const clock = new StepClock(10, 0);
clock.advanceMs(50);
expect(clock.nowMs()).toBe(60);
});
it('rejects non-positive step', () => {
expect(() => new StepClock(0)).toThrow('stepMs must be positive');
expect(() => new StepClock(-5)).toThrow('stepMs must be positive');
});
});
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true,
include: ['test/**/*.test.ts'],
},
});