@littlepartytime/dev-kit 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/dev.d.ts +7 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +30 -13
- package/dist/commands/dev.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/testing/game-preview.d.ts +53 -0
- package/dist/testing/game-preview.d.ts.map +1 -0
- package/dist/testing/game-preview.js +158 -0
- package/dist/testing/game-preview.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/webapp/pages/Preview.tsx +195 -42
- package/package.json +19 -1
package/dist/commands/dev.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export interface DevOptions {
|
|
2
2
|
port?: number;
|
|
3
3
|
socketPort?: number;
|
|
4
|
+
silent?: boolean;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
+
export interface DevServerHandle {
|
|
7
|
+
stop(): Promise<void>;
|
|
8
|
+
port: number;
|
|
9
|
+
socketPort: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function devCommand(projectDir: string, options?: DevOptions): Promise<DevServerHandle>;
|
|
6
12
|
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAwEvG"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -12,12 +12,14 @@ const chokidar_1 = __importDefault(require("chokidar"));
|
|
|
12
12
|
async function devCommand(projectDir, options = {}) {
|
|
13
13
|
const port = options.port || 4000;
|
|
14
14
|
const socketPort = options.socketPort || 4001;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
if (!options.silent) {
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(' Little Party Time Dev Kit');
|
|
18
|
+
console.log(' =========================');
|
|
19
|
+
console.log('');
|
|
20
|
+
}
|
|
19
21
|
// Start Socket.IO server
|
|
20
|
-
const { reloadEngine } = (0, socket_server_1.createSocketServer)({
|
|
22
|
+
const { server, io, reloadEngine } = (0, socket_server_1.createSocketServer)({
|
|
21
23
|
port: socketPort,
|
|
22
24
|
projectDir,
|
|
23
25
|
});
|
|
@@ -25,7 +27,9 @@ async function devCommand(projectDir, options = {}) {
|
|
|
25
27
|
const enginePath = path_1.default.join(projectDir, 'dist', 'engine.cjs');
|
|
26
28
|
const watcher = chokidar_1.default.watch(enginePath, { ignoreInitial: true });
|
|
27
29
|
watcher.on('change', () => {
|
|
28
|
-
|
|
30
|
+
if (!options.silent) {
|
|
31
|
+
console.log('[Dev] Engine changed, reloading...');
|
|
32
|
+
}
|
|
29
33
|
reloadEngine();
|
|
30
34
|
});
|
|
31
35
|
// Start Vite dev server
|
|
@@ -48,12 +52,25 @@ async function devCommand(projectDir, options = {}) {
|
|
|
48
52
|
},
|
|
49
53
|
});
|
|
50
54
|
await vite.listen();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (!options.silent) {
|
|
56
|
+
console.log(` Preview: http://localhost:${port}/preview`);
|
|
57
|
+
console.log(` Multiplayer: http://localhost:${port}/play`);
|
|
58
|
+
console.log(` Debug Panel: http://localhost:${port}/debug`);
|
|
59
|
+
console.log(` Socket.IO: ws://localhost:${socketPort}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(' Press Ctrl+C to stop');
|
|
62
|
+
console.log('');
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
port,
|
|
66
|
+
socketPort,
|
|
67
|
+
async stop() {
|
|
68
|
+
await watcher.close();
|
|
69
|
+
await vite.close();
|
|
70
|
+
io.disconnectSockets(true);
|
|
71
|
+
io.close();
|
|
72
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
73
|
+
},
|
|
74
|
+
};
|
|
58
75
|
}
|
|
59
76
|
//# sourceMappingURL=dev.js.map
|
package/dist/commands/dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { validateBundle } from './utils/validate-bundle';
|
|
2
2
|
export type { ValidationResult } from './utils/validate-bundle';
|
|
3
|
+
export { devCommand } from './commands/dev';
|
|
4
|
+
export type { DevOptions, DevServerHandle } from './commands/dev';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateBundle = void 0;
|
|
3
|
+
exports.devCommand = exports.validateBundle = void 0;
|
|
4
4
|
var validate_bundle_1 = require("./utils/validate-bundle");
|
|
5
5
|
Object.defineProperty(exports, "validateBundle", { enumerable: true, get: function () { return validate_bundle_1.validateBundle; } });
|
|
6
|
+
var dev_1 = require("./commands/dev");
|
|
7
|
+
Object.defineProperty(exports, "devCommand", { enumerable: true, get: function () { return dev_1.devCommand; } });
|
|
6
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2DAAyD;AAAhD,iHAAA,cAAc,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2DAAyD;AAAhD,iHAAA,cAAc,OAAA;AAEvB,sCAA4C;AAAnC,iGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface GamePreviewOptions {
|
|
2
|
+
/** Absolute path to the game project directory */
|
|
3
|
+
projectDir: string;
|
|
4
|
+
/** Number of players to simulate */
|
|
5
|
+
playerCount: number;
|
|
6
|
+
/** Vite dev server port (default: 4100) */
|
|
7
|
+
port?: number;
|
|
8
|
+
/** Socket.IO server port (default: 4101) */
|
|
9
|
+
socketPort?: number;
|
|
10
|
+
/** Run browser in headless mode (default: true) */
|
|
11
|
+
headless?: boolean;
|
|
12
|
+
/** Browser type to use (default: 'chromium') */
|
|
13
|
+
browserType?: 'chromium' | 'firefox' | 'webkit';
|
|
14
|
+
}
|
|
15
|
+
export declare class GamePreview {
|
|
16
|
+
private options;
|
|
17
|
+
private serverHandle;
|
|
18
|
+
private browser;
|
|
19
|
+
private context;
|
|
20
|
+
private playerPages;
|
|
21
|
+
constructor(options: GamePreviewOptions);
|
|
22
|
+
/**
|
|
23
|
+
* Start the dev server and launch browser pages for each player.
|
|
24
|
+
* Each player automatically joins the game lobby.
|
|
25
|
+
*/
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Get the Playwright Page object for a specific player by index.
|
|
29
|
+
* Player 0 is the host.
|
|
30
|
+
*/
|
|
31
|
+
getPlayerPage(playerIndex: number): any;
|
|
32
|
+
/**
|
|
33
|
+
* Get all Playwright Page objects.
|
|
34
|
+
*/
|
|
35
|
+
getPlayerPages(): any[];
|
|
36
|
+
/**
|
|
37
|
+
* Get the number of players.
|
|
38
|
+
*/
|
|
39
|
+
get playerCount(): number;
|
|
40
|
+
/**
|
|
41
|
+
* Click "Ready" for all players.
|
|
42
|
+
*/
|
|
43
|
+
readyAll(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Have the host start the game. Requires all players to be ready.
|
|
46
|
+
*/
|
|
47
|
+
startGame(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Stop the browser and dev server, cleaning up all resources.
|
|
50
|
+
*/
|
|
51
|
+
stop(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=game-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-preview.d.ts","sourceRoot":"","sources":["../../src/testing/game-preview.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CACjD;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,WAAW,CAAyC;gBAEhD,OAAO,EAAE,kBAAkB;IAUvC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD5B;;;OAGG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG;IASvC;;OAEG;IACH,cAAc,IAAI,GAAG,EAAE;IAIvB;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAS/B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAYhC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAe5B"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GamePreview = void 0;
|
|
37
|
+
const PLAYER_NAMES = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi'];
|
|
38
|
+
class GamePreview {
|
|
39
|
+
options;
|
|
40
|
+
serverHandle = null;
|
|
41
|
+
browser = null;
|
|
42
|
+
context = null;
|
|
43
|
+
playerPages = [];
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.options = {
|
|
46
|
+
port: 4100,
|
|
47
|
+
socketPort: 4101,
|
|
48
|
+
headless: true,
|
|
49
|
+
browserType: 'chromium',
|
|
50
|
+
...options,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Start the dev server and launch browser pages for each player.
|
|
55
|
+
* Each player automatically joins the game lobby.
|
|
56
|
+
*/
|
|
57
|
+
async start() {
|
|
58
|
+
// 1. Start dev server programmatically
|
|
59
|
+
const { devCommand } = await Promise.resolve().then(() => __importStar(require('../commands/dev')));
|
|
60
|
+
this.serverHandle = await devCommand(this.options.projectDir, {
|
|
61
|
+
port: this.options.port,
|
|
62
|
+
socketPort: this.options.socketPort,
|
|
63
|
+
silent: true,
|
|
64
|
+
});
|
|
65
|
+
// 2. Launch browser (dynamic import to handle optional dependency)
|
|
66
|
+
let playwright;
|
|
67
|
+
try {
|
|
68
|
+
// Use variable to prevent TypeScript from resolving the module at compile time
|
|
69
|
+
const moduleName = 'playwright';
|
|
70
|
+
playwright = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new Error('GamePreview requires playwright. Install it with: npm install -D playwright');
|
|
74
|
+
}
|
|
75
|
+
const browserType = playwright[this.options.browserType];
|
|
76
|
+
if (!browserType) {
|
|
77
|
+
throw new Error(`Unknown browser type: ${this.options.browserType}`);
|
|
78
|
+
}
|
|
79
|
+
this.browser = await browserType.launch({
|
|
80
|
+
headless: this.options.headless,
|
|
81
|
+
});
|
|
82
|
+
this.context = await this.browser.newContext();
|
|
83
|
+
// 3. Open pages and join lobby for each player
|
|
84
|
+
for (let i = 0; i < this.options.playerCount; i++) {
|
|
85
|
+
const nickname = PLAYER_NAMES[i] || `Player ${i + 1}`;
|
|
86
|
+
const page = await this.context.newPage();
|
|
87
|
+
await page.goto(`http://localhost:${this.options.port}/play`);
|
|
88
|
+
// Fill nickname and join
|
|
89
|
+
await page.fill('input[placeholder="Your nickname"]', nickname);
|
|
90
|
+
await page.click('button:has-text("Join")');
|
|
91
|
+
// Wait for lobby to appear
|
|
92
|
+
await page.waitForSelector('text=Lobby', { timeout: 10000 });
|
|
93
|
+
this.playerPages.push({ page, nickname });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the Playwright Page object for a specific player by index.
|
|
98
|
+
* Player 0 is the host.
|
|
99
|
+
*/
|
|
100
|
+
getPlayerPage(playerIndex) {
|
|
101
|
+
if (playerIndex < 0 || playerIndex >= this.playerPages.length) {
|
|
102
|
+
throw new Error(`Invalid playerIndex: ${playerIndex}. Valid range: 0-${this.playerPages.length - 1}`);
|
|
103
|
+
}
|
|
104
|
+
return this.playerPages[playerIndex].page;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get all Playwright Page objects.
|
|
108
|
+
*/
|
|
109
|
+
getPlayerPages() {
|
|
110
|
+
return this.playerPages.map(pp => pp.page);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the number of players.
|
|
114
|
+
*/
|
|
115
|
+
get playerCount() {
|
|
116
|
+
return this.playerPages.length;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Click "Ready" for all players.
|
|
120
|
+
*/
|
|
121
|
+
async readyAll() {
|
|
122
|
+
for (const pp of this.playerPages) {
|
|
123
|
+
const readyButton = pp.page.locator('button:has-text("Ready")');
|
|
124
|
+
await readyButton.click();
|
|
125
|
+
// Small delay to let Socket.IO propagate
|
|
126
|
+
await pp.page.waitForTimeout(100);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Have the host start the game. Requires all players to be ready.
|
|
131
|
+
*/
|
|
132
|
+
async startGame() {
|
|
133
|
+
const hostPage = this.playerPages[0].page;
|
|
134
|
+
await hostPage.click('button:has-text("Start Game")');
|
|
135
|
+
// Wait for game state to load on all pages
|
|
136
|
+
await Promise.all(this.playerPages.map(pp => pp.page.waitForTimeout(500)));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Stop the browser and dev server, cleaning up all resources.
|
|
140
|
+
*/
|
|
141
|
+
async stop() {
|
|
142
|
+
if (this.context) {
|
|
143
|
+
await this.context.close().catch(() => { });
|
|
144
|
+
this.context = null;
|
|
145
|
+
}
|
|
146
|
+
if (this.browser) {
|
|
147
|
+
await this.browser.close().catch(() => { });
|
|
148
|
+
this.browser = null;
|
|
149
|
+
}
|
|
150
|
+
if (this.serverHandle) {
|
|
151
|
+
await this.serverHandle.stop();
|
|
152
|
+
this.serverHandle = null;
|
|
153
|
+
}
|
|
154
|
+
this.playerPages = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.GamePreview = GamePreview;
|
|
158
|
+
//# sourceMappingURL=game-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-preview.js","sourceRoot":"","sources":["../../src/testing/game-preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GamePreview = void 0;
|
|
4
|
+
var game_preview_1 = require("./game-preview");
|
|
5
|
+
Object.defineProperty(exports, "GamePreview", { enumerable: true, get: function () { return game_preview_1.GamePreview; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AAAA,+CAA6C;AAApC,2GAAA,WAAW,OAAA"}
|
|
@@ -1,90 +1,243 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
const PLAYER_NAMES = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi'];
|
|
2
4
|
|
|
3
5
|
export default function Preview() {
|
|
4
|
-
const [
|
|
5
|
-
const [stateJson, setStateJson] = useState('');
|
|
6
|
+
const [playerCount, setPlayerCount] = useState(3);
|
|
6
7
|
const [playerIndex, setPlayerIndex] = useState(0);
|
|
7
8
|
const [actions, setActions] = useState<any[]>([]);
|
|
8
9
|
const [GameRenderer, setGameRenderer] = useState<React.ComponentType<any> | null>(null);
|
|
10
|
+
const [engine, setEngine] = useState<any>(null);
|
|
11
|
+
const [fullState, setFullState] = useState<any>(null);
|
|
12
|
+
const [viewState, setViewState] = useState<any>(null);
|
|
13
|
+
const [gameOver, setGameOver] = useState(false);
|
|
14
|
+
const [gameResult, setGameResult] = useState<any>(null);
|
|
15
|
+
const [stateJson, setStateJson] = useState('');
|
|
16
|
+
|
|
17
|
+
// Refs to avoid recreating platform on every state change
|
|
18
|
+
const fullStateRef = useRef(fullState);
|
|
19
|
+
fullStateRef.current = fullState;
|
|
20
|
+
const playerIndexRef = useRef(playerIndex);
|
|
21
|
+
playerIndexRef.current = playerIndex;
|
|
22
|
+
const stateUpdateListeners = useRef<Set<(...args: unknown[]) => void>>(new Set());
|
|
9
23
|
|
|
10
|
-
//
|
|
24
|
+
// Generate mock players
|
|
25
|
+
const mockPlayers = useMemo(() => {
|
|
26
|
+
return Array.from({ length: playerCount }, (_, i) => ({
|
|
27
|
+
id: `player-${i + 1}`,
|
|
28
|
+
nickname: PLAYER_NAMES[i] || `Player ${i + 1}`,
|
|
29
|
+
avatarUrl: null,
|
|
30
|
+
isHost: i === 0,
|
|
31
|
+
}));
|
|
32
|
+
}, [playerCount]);
|
|
33
|
+
|
|
34
|
+
const mockPlayersRef = useRef(mockPlayers);
|
|
35
|
+
mockPlayersRef.current = mockPlayers;
|
|
36
|
+
|
|
37
|
+
// Load renderer and engine dynamically
|
|
11
38
|
useEffect(() => {
|
|
12
|
-
import('/src/
|
|
13
|
-
setGameRenderer(() => mod.
|
|
39
|
+
import('/src/index.ts').then((mod) => {
|
|
40
|
+
setGameRenderer(() => mod.Renderer || mod.default);
|
|
41
|
+
if (mod.engine) {
|
|
42
|
+
setEngine(mod.engine);
|
|
43
|
+
}
|
|
14
44
|
}).catch((err) => {
|
|
15
|
-
console.error('Failed to load
|
|
45
|
+
console.error('Failed to load game module:', err);
|
|
46
|
+
// Fallback: try loading renderer directly
|
|
47
|
+
import('/src/renderer.tsx').then((mod) => {
|
|
48
|
+
setGameRenderer(() => mod.default || mod.Renderer);
|
|
49
|
+
}).catch((err2) => {
|
|
50
|
+
console.error('Failed to load renderer:', err2);
|
|
51
|
+
});
|
|
16
52
|
});
|
|
17
53
|
}, []);
|
|
18
54
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
// Initialize game when engine loads or player count changes
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!engine) return;
|
|
58
|
+
const initialState = engine.init(mockPlayers);
|
|
59
|
+
setFullState(initialState);
|
|
60
|
+
setGameOver(false);
|
|
61
|
+
setGameResult(null);
|
|
62
|
+
setActions([]);
|
|
63
|
+
}, [engine, mockPlayers]);
|
|
64
|
+
|
|
65
|
+
// Compute view state whenever fullState or playerIndex changes
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!engine || !fullState) return;
|
|
68
|
+
const view = engine.getPlayerView(fullState, mockPlayers[playerIndex].id);
|
|
69
|
+
setViewState(view);
|
|
70
|
+
// Notify renderer of new view
|
|
71
|
+
stateUpdateListeners.current.forEach(handler => handler(view));
|
|
72
|
+
}, [fullState, playerIndex, engine, mockPlayers]);
|
|
73
|
+
|
|
74
|
+
// Sync state JSON editor
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (fullState) {
|
|
77
|
+
setStateJson(JSON.stringify(fullState, null, 2));
|
|
78
|
+
}
|
|
79
|
+
}, [fullState]);
|
|
80
|
+
|
|
81
|
+
// Platform object — stable reference via useMemo on engine + mockPlayers
|
|
82
|
+
const platform = useMemo(() => {
|
|
83
|
+
if (!engine) return null;
|
|
84
|
+
return {
|
|
85
|
+
getPlayers: () => mockPlayersRef.current,
|
|
86
|
+
getLocalPlayer: () => mockPlayersRef.current[playerIndexRef.current],
|
|
87
|
+
send: (action: any) => {
|
|
88
|
+
const currentState = fullStateRef.current;
|
|
89
|
+
if (!currentState) return;
|
|
90
|
+
|
|
91
|
+
const playerId = mockPlayersRef.current[playerIndexRef.current].id;
|
|
92
|
+
|
|
93
|
+
// Log the action
|
|
94
|
+
setActions(prev => [...prev, {
|
|
95
|
+
time: new Date().toISOString(),
|
|
96
|
+
player: mockPlayersRef.current[playerIndexRef.current].nickname,
|
|
97
|
+
action,
|
|
98
|
+
}]);
|
|
99
|
+
|
|
100
|
+
// Run through engine
|
|
101
|
+
const newState = engine.handleAction(currentState, playerId, action);
|
|
102
|
+
setFullState(newState);
|
|
103
|
+
|
|
104
|
+
// Check game over
|
|
105
|
+
if (engine.isGameOver(newState)) {
|
|
106
|
+
setGameOver(true);
|
|
107
|
+
setGameResult(engine.getResult(newState));
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
on: (event: string, handler: (...args: unknown[]) => void) => {
|
|
111
|
+
if (event === 'stateUpdate') {
|
|
112
|
+
stateUpdateListeners.current.add(handler);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
off: (event: string, handler: (...args: unknown[]) => void) => {
|
|
116
|
+
if (event === 'stateUpdate') {
|
|
117
|
+
stateUpdateListeners.current.delete(handler);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
reportResult: (result: any) => {
|
|
121
|
+
console.log('Game result reported:', result);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}, [engine]);
|
|
125
|
+
|
|
126
|
+
// Apply manual state override from JSON editor
|
|
127
|
+
const applyState = useCallback(() => {
|
|
39
128
|
try {
|
|
40
129
|
const parsed = JSON.parse(stateJson);
|
|
41
|
-
|
|
130
|
+
setFullState(parsed);
|
|
131
|
+
if (engine) {
|
|
132
|
+
setGameOver(engine.isGameOver(parsed));
|
|
133
|
+
if (engine.isGameOver(parsed)) {
|
|
134
|
+
setGameResult(engine.getResult(parsed));
|
|
135
|
+
} else {
|
|
136
|
+
setGameResult(null);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
42
139
|
} catch (e) {
|
|
43
|
-
// ignore
|
|
140
|
+
// ignore invalid JSON
|
|
44
141
|
}
|
|
45
|
-
}, [stateJson]);
|
|
142
|
+
}, [stateJson, engine]);
|
|
143
|
+
|
|
144
|
+
// Reset game
|
|
145
|
+
const resetGame = useCallback(() => {
|
|
146
|
+
if (!engine) return;
|
|
147
|
+
const newState = engine.init(mockPlayersRef.current);
|
|
148
|
+
setFullState(newState);
|
|
149
|
+
setGameOver(false);
|
|
150
|
+
setGameResult(null);
|
|
151
|
+
setActions([]);
|
|
152
|
+
}, [engine]);
|
|
46
153
|
|
|
154
|
+
// Clamp playerIndex when playerCount decreases
|
|
47
155
|
useEffect(() => {
|
|
48
|
-
|
|
49
|
-
|
|
156
|
+
if (playerIndex >= playerCount) {
|
|
157
|
+
setPlayerIndex(0);
|
|
158
|
+
}
|
|
159
|
+
}, [playerCount, playerIndex]);
|
|
50
160
|
|
|
51
161
|
return (
|
|
52
162
|
<div className="flex gap-4 h-[calc(100vh-80px)]">
|
|
53
163
|
{/* Renderer */}
|
|
54
164
|
<div className="flex-1 bg-zinc-900 rounded-lg overflow-auto">
|
|
55
|
-
{GameRenderer ? (
|
|
56
|
-
<GameRenderer platform={platform} state={
|
|
165
|
+
{GameRenderer && platform && viewState ? (
|
|
166
|
+
<GameRenderer platform={platform} state={viewState} />
|
|
57
167
|
) : (
|
|
58
|
-
<div className="p-4 text-zinc-500">
|
|
168
|
+
<div className="p-4 text-zinc-500">
|
|
169
|
+
{!engine ? 'Loading engine...' : 'Initializing game...'}
|
|
170
|
+
</div>
|
|
59
171
|
)}
|
|
60
172
|
</div>
|
|
61
173
|
|
|
62
174
|
{/* Control Panel */}
|
|
63
|
-
<div className="w-80 flex flex-col gap-4">
|
|
175
|
+
<div className="w-80 flex flex-col gap-4 overflow-auto">
|
|
176
|
+
{/* Player Count */}
|
|
177
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
178
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Player Count</h3>
|
|
179
|
+
<input
|
|
180
|
+
type="number"
|
|
181
|
+
min={2}
|
|
182
|
+
max={8}
|
|
183
|
+
value={playerCount}
|
|
184
|
+
onChange={(e) => setPlayerCount(Math.max(2, Math.min(8, Number(e.target.value))))}
|
|
185
|
+
className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
64
189
|
{/* Player Switcher */}
|
|
65
190
|
<div className="bg-zinc-900 rounded-lg p-3">
|
|
66
191
|
<h3 className="text-sm font-bold text-zinc-400 mb-2">Current Player</h3>
|
|
67
192
|
<select
|
|
68
193
|
value={playerIndex}
|
|
69
194
|
onChange={(e) => setPlayerIndex(Number(e.target.value))}
|
|
70
|
-
className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1"
|
|
195
|
+
className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
|
|
71
196
|
>
|
|
72
197
|
{mockPlayers.map((p, i) => (
|
|
73
|
-
<option key={p.id} value={i}>{p.nickname}</option>
|
|
198
|
+
<option key={p.id} value={i}>{p.nickname}{p.isHost ? ' (Host)' : ''}</option>
|
|
74
199
|
))}
|
|
75
200
|
</select>
|
|
76
201
|
</div>
|
|
77
202
|
|
|
203
|
+
{/* Game Result */}
|
|
204
|
+
{gameOver && gameResult && (
|
|
205
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
206
|
+
<h3 className="text-sm font-bold text-green-400 mb-2">Game Over</h3>
|
|
207
|
+
<pre className="text-xs font-mono bg-zinc-800 rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap">
|
|
208
|
+
{JSON.stringify(gameResult, null, 2)}
|
|
209
|
+
</pre>
|
|
210
|
+
<button
|
|
211
|
+
onClick={resetGame}
|
|
212
|
+
className="mt-2 w-full bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
|
|
213
|
+
>
|
|
214
|
+
Reset Game
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{/* Reset button (when game is not over) */}
|
|
220
|
+
{!gameOver && engine && (
|
|
221
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
222
|
+
<button
|
|
223
|
+
onClick={resetGame}
|
|
224
|
+
className="w-full bg-zinc-700 hover:bg-zinc-600 text-white px-3 py-1 rounded text-sm"
|
|
225
|
+
>
|
|
226
|
+
Reset Game
|
|
227
|
+
</button>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
78
231
|
{/* State Editor */}
|
|
79
|
-
<div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col">
|
|
80
|
-
<h3 className="text-sm font-bold text-zinc-400 mb-2">Game State</h3>
|
|
232
|
+
<div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
|
|
233
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
|
|
81
234
|
<textarea
|
|
82
235
|
value={stateJson}
|
|
83
236
|
onChange={(e) => setStateJson(e.target.value)}
|
|
84
|
-
className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none"
|
|
237
|
+
className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none min-h-[120px]"
|
|
85
238
|
/>
|
|
86
239
|
<button
|
|
87
|
-
onClick={
|
|
240
|
+
onClick={applyState}
|
|
88
241
|
className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
|
|
89
242
|
>
|
|
90
243
|
Apply State
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlepartytime/dev-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Development toolkit CLI for Little Party Time game developers",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./testing": {
|
|
13
|
+
"types": "./dist/testing/index.d.ts",
|
|
14
|
+
"default": "./dist/testing/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"bin": {
|
|
8
18
|
"lpt-dev-kit": "./dist/cli.js"
|
|
9
19
|
},
|
|
@@ -21,6 +31,14 @@
|
|
|
21
31
|
"url": "https://github.com/chesterli710/littlepartytime-sdk.git",
|
|
22
32
|
"directory": "packages/dev-kit"
|
|
23
33
|
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"playwright": ">=1.40.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"playwright": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
24
42
|
"dependencies": {
|
|
25
43
|
"@vitejs/plugin-react": "^5",
|
|
26
44
|
"archiver": "^7",
|