feat: integrate erqiwang rooms and use spec-compliant Web compilation
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
|
||||
export type VipCode = string | number;
|
||||
/** min_ary_indexof in the legacy source compares short codes with ==. */
|
||||
export const sameVipCode=(left:VipCode,right:VipCode):boolean=>left==right;
|
||||
@@ -5,7 +6,7 @@ export interface VipRoom {
|
||||
readonly shortCode: VipCode;
|
||||
readonly title: string;
|
||||
readonly owner: string;
|
||||
readonly roomcodes: { readonly roomtype: readonly unknown[]; readonly roomlist: readonly VipCode[] } | null;
|
||||
readonly roomcodes: { readonly roomtype: Roomtype; readonly roomlist: readonly VipCode[] } | null;
|
||||
readonly playerList: readonly (readonly unknown[])[];
|
||||
readonly deskState: readonly unknown[];
|
||||
readonly seatCount: readonly VipCode[];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../sdk/contracts/roomtype.ts';
|
||||
import { APP } from '../core/constants.ts';
|
||||
import type { OutboundEnvelope } from '../core/types/envelope.ts';
|
||||
import type {
|
||||
@@ -48,7 +49,7 @@ type SubscriptionReleaseResult =
|
||||
|
||||
interface BoundRoomGeneration {
|
||||
readonly roomcode: string;
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly selfSeat: number;
|
||||
readonly seatCount: number;
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ export class PlatformRuntime {
|
||||
this.wireIsOpen = false;
|
||||
|
||||
const errors: unknown[] = [];
|
||||
for (const off of [...this.stateObservers]) {
|
||||
for (const off of [...this.stateObservers]) {
|
||||
this.captureCleanupError(errors, off);
|
||||
}
|
||||
this.captureCleanupError(errors, () => { this.clearLoginGuard(); });
|
||||
@@ -556,7 +556,7 @@ export class PlatformRuntime {
|
||||
this.connectionIntent = { type: 'none' };
|
||||
this.wireIsOpen = false;
|
||||
const errors: unknown[] = [];
|
||||
for (const off of [...this.stateObservers]) {
|
||||
for (const off of [...this.stateObservers]) {
|
||||
this.captureCleanupError(errors, off);
|
||||
}
|
||||
this.captureCleanupError(errors, () => { this.store.setConnectionPhase('kicked'); });
|
||||
@@ -722,7 +722,7 @@ export class PlatformRuntime {
|
||||
this.wireIsOpen = false;
|
||||
|
||||
const cleanupErrors: unknown[] = [];
|
||||
for (const off of [...this.stateObservers]) {
|
||||
for (const off of [...this.stateObservers]) {
|
||||
this.captureCleanupError(cleanupErrors, off);
|
||||
}
|
||||
this.captureCleanupError(cleanupErrors, () => { this.clearLoginGuard(); });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
|
||||
import type { Unsubscribe } from '../../core/reactive.ts';
|
||||
import {parseVipRooms} from '../../protocol/contracts/vip-room.ts';
|
||||
import {parseRankList,parseVipRankList} from '../../protocol/contracts/rank-list.ts';
|
||||
@@ -6,7 +7,8 @@ import type {LoginAccountIdentity} from '../../protocol/contracts/login-contract
|
||||
import {requireStringOrFiniteNumber} from '../../protocol/contracts/validation.ts';
|
||||
import {
|
||||
hasOwn,
|
||||
requireArray,
|
||||
requireArray,
|
||||
requireRoomtype,
|
||||
requireBoolean,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
@@ -152,7 +154,7 @@ function verifyRoomtypeSourceGraph(graph: readonly RoomtypeObjectSnapshot[]): vo
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneAndFreezeRoomtype(value: readonly unknown[]): readonly unknown[] {
|
||||
export function cloneAndFreezeRoomtype(value: Roomtype): Roomtype {
|
||||
const copies = new WeakMap<object, object>();
|
||||
const active = new WeakSet<object>();
|
||||
const ownedGraph: object[] = [];
|
||||
@@ -241,7 +243,7 @@ export function cloneAndFreezeRoomtype(value: readonly unknown[]): readonly unkn
|
||||
return target;
|
||||
};
|
||||
|
||||
const canonical = copy(value, 'roomtype') as readonly unknown[];
|
||||
const canonical = copy(value, 'roomtype') as Roomtype;
|
||||
verifyRoomtypeSourceGraph(sourceGraph);
|
||||
for (let index = ownedGraph.length - 1; index >= 0; index -= 1) {
|
||||
Object.freeze(ownedGraph[index]);
|
||||
@@ -287,7 +289,7 @@ function buildRoom(
|
||||
const raw = requireRecord(rawValue, '$.raw');
|
||||
const roomcode = requireString(room.roomcode, '$.room.roomcode');
|
||||
const selfSeat = requireInteger(room.seat, '$.room.seat');
|
||||
const roomtype = requireArray(room.roomtype, '$.room.roomtype');
|
||||
const roomtype = requireRoomtype(room.roomtype, '$.room.roomtype');
|
||||
const sourcePlayers = requireArray(room.players, '$.room.players');
|
||||
if (requireString(raw.roomcode, '$.raw.roomcode') !== roomcode) {
|
||||
throw new RangeError('$.room.roomcode: expected value parsed from raw roomcode');
|
||||
@@ -569,7 +571,7 @@ export class PlatformStore {
|
||||
this.transitions.push({
|
||||
state,
|
||||
previous,
|
||||
listeners: [...this.listeners],
|
||||
listeners: [...this.listeners],
|
||||
});
|
||||
if (this.publishing) return state;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
|
||||
import type { PlatformGameConnectionPhase } from '../../sdk/contracts/snapshots.ts';
|
||||
import type { TaskData } from '../../core/types/task.ts';
|
||||
|
||||
@@ -46,7 +47,7 @@ export interface InsideRoomState {
|
||||
readonly seatPlayerIds: readonly (number | null)[];
|
||||
readonly readySeats: readonly number[];
|
||||
readonly offlineSeats: readonly number[];
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly stage: number;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireArray,
|
||||
requireRoomtype,
|
||||
requireBoolean,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
|
||||
+9
-7
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
|
||||
import {
|
||||
buildFirstSliceEnvelope,
|
||||
type FirstSliceEnvelope,
|
||||
@@ -5,7 +6,8 @@ import {
|
||||
import {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireArray,
|
||||
requireRoomtype,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
requireRecord,
|
||||
@@ -22,7 +24,7 @@ export type ReconnectSnapshot =
|
||||
export interface ParsedRoomSnapshot {
|
||||
readonly roomcode: string;
|
||||
readonly seat: number;
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly players: readonly unknown[];
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ export function reconnectFrom(raw: JsonRecord): ReconnectSnapshot {
|
||||
export function parseRoomEntryFields(raw: JsonRecord): ParsedRoomSnapshot {
|
||||
const roomcode = requireString(raw.roomcode, '$.data.roomcode');
|
||||
const seat = requireInteger(raw.seat, '$.data.seat');
|
||||
const roomtype = requireArray(raw.roomtype, '$.data.roomtype');
|
||||
const roomtype = requireRoomtype(raw.roomtype, '$.data.roomtype');
|
||||
const players = requireArray(raw.players, '$.data.players');
|
||||
if (seat < 0 || seat >= players.length) {
|
||||
throw new RangeError(`$.data.seat: expected server seat index within players, received number`);
|
||||
@@ -246,7 +248,7 @@ export function parseKickServerPayload(value: unknown): ParsedKickPayload {
|
||||
|
||||
|
||||
export interface CreateRoomEnvironment {
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
}
|
||||
@@ -267,7 +269,7 @@ export function buildCreateRoomRequest(
|
||||
agentid: requireStringOrFiniteNumber(identity.agentid, '$.identity.agentid'),
|
||||
playerid: requireInteger(playerid, '$.playerid'),
|
||||
gameid: requireStringOrFiniteNumber(identity.gameid, '$.identity.gameid'),
|
||||
roomtype: requireArray(raw.roomtype, '$.command.roomtype'),
|
||||
roomtype: requireRoomtype(raw.roomtype, '$.command.roomtype'),
|
||||
ip: requireString(raw.ip, '$.command.ip'),
|
||||
location: raw.location,
|
||||
});
|
||||
@@ -275,7 +277,7 @@ export function buildCreateRoomRequest(
|
||||
|
||||
export interface ParsedCreateRoomResponse {
|
||||
readonly state: number;
|
||||
readonly room: { readonly roomcode: string; readonly seat: number; readonly roomtype: readonly unknown[] } | null;
|
||||
readonly room: { readonly roomcode: string; readonly seat: number; readonly roomtype: Roomtype } | null;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
readonly raw: JsonRecord;
|
||||
@@ -296,7 +298,7 @@ export function parseCreateRoomResponse(value: unknown): ParsedCreateRoomRespons
|
||||
state,
|
||||
room: {roomcode: requireString(raw.roomcode, '$.data.roomcode'),
|
||||
seat: requireInteger(raw.seat, '$.data.seat'),
|
||||
roomtype: requireArray(raw.roomtype, '$.data.roomtype')},
|
||||
roomtype: requireRoomtype(raw.roomtype, '$.data.roomtype')},
|
||||
needprepare: flag('needprepare'), infinite: flag('infinite'), raw,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
|
||||
export type JsonRecord = Readonly<Record<string, unknown>>;
|
||||
|
||||
function receivedType(value: unknown): string {
|
||||
@@ -66,3 +67,9 @@ export function optionalField<T>(
|
||||
if (!hasOwn(record, key)) return undefined;
|
||||
return validate(record[key], `${path}.${key}`);
|
||||
}
|
||||
|
||||
/** Validate the transport shape without interpreting game-specific options. */
|
||||
export function requireRoomtype(value: unknown, path: string): Roomtype {
|
||||
if (typeof value === 'string' || Array.isArray(value)) return value;
|
||||
return invalid(path, 'string or array', value);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {requireArray,requireRecord,requireString} from './validation.ts';
|
||||
import {requireArray,requireRoomtype,requireRecord,requireString} from './validation.ts';
|
||||
import type {VipCode,VipRoom} from '../../core/types/vip-room.ts';
|
||||
export function vipCode(value:unknown,path:string):VipCode {
|
||||
if(typeof value==='string'&&value.length>0)return value;
|
||||
@@ -12,7 +12,8 @@ export function parseVipRooms(payload:unknown):readonly VipRoom[]{
|
||||
const p=`$.data.roomInfo[${index}]`,r=requireRecord(value,p);
|
||||
const playerList=requireArray(r.playerList,p+'.playerList').map((v,i)=>Object.freeze([...requireArray(v,`${p}.playerList[${i}]`)]));
|
||||
const codes=r.roomcodes?requireRecord(r.roomcodes,p+'.roomcodes'):null;
|
||||
const roomcodes=codes?Object.freeze({roomtype:Object.freeze([...requireArray(codes.roomtype,p+'.roomcodes.roomtype')]),roomlist:Object.freeze(requireArray(codes.roomlist,p+'.roomcodes.roomlist').map((v,i)=>vipCode(v,`${p}.roomcodes.roomlist[${i}]`)))}):null;
|
||||
const rawType=codes?requireRoomtype(codes.roomtype,p+'.roomcodes.roomtype'):null;
|
||||
const roomcodes=codes?Object.freeze({roomtype:typeof rawType==='string'?rawType:Object.freeze([...(rawType as readonly unknown[])]),roomlist:Object.freeze(requireArray(codes.roomlist,p+'.roomcodes.roomlist').map((v,i)=>vipCode(v,`${p}.roomcodes.roomlist[${i}]`)))}):null;
|
||||
const deskState=roomcodes?requireArray(r.deskState,p+'.deskState'):[];
|
||||
const seatCount=roomcodes?requireArray(r.seatCount,p+'.seatCount').map((v,i)=>vipCode(v,`${p}.seatCount[${i}]`)):[];
|
||||
if(roomcodes&&[roomcodes.roomlist.length,deskState.length,seatCount.length].some(n=>n!==playerList.length))throw TypeError(p+': desk arrays must have matching lengths');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {Roomtype} from './roomtype.ts';
|
||||
/** Pre-room capability: a game owns its page and validated, opaque wire configuration. */
|
||||
export interface CreateRoomPageContext {
|
||||
readonly previousRoomtype:unknown;
|
||||
submit(roomtype:readonly unknown[]):void;
|
||||
submit(roomtype: Roomtype):void;
|
||||
cancel():void;
|
||||
reportError(message:string):void;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from './roomtype.ts';
|
||||
import type { GameModule } from './game-module.ts';
|
||||
|
||||
/** The one compile-time game identity and module factory for an app. */
|
||||
@@ -6,6 +7,6 @@ export interface GameEntry {
|
||||
readonly gameId: string | number;
|
||||
readonly route: string;
|
||||
/** Interprets this game's actual server roomtype; no synthetic/default input is required. */
|
||||
resolveSeatCount(roomtype: readonly unknown[]): number;
|
||||
resolveSeatCount(roomtype: Roomtype): number;
|
||||
createModule(): GameModule;
|
||||
}
|
||||
|
||||
@@ -5,3 +5,5 @@ export * from './game-module.ts';
|
||||
export * from './game-seat-mapper.ts';
|
||||
export * from './platform-events.ts';
|
||||
export * from './snapshots.ts';
|
||||
|
||||
export type {Roomtype} from './roomtype.ts';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {Roomtype} from './roomtype.ts';
|
||||
/** Platform lifecycle events delivered to the current game module. */
|
||||
export type PlatformToGameEvent =
|
||||
| { readonly type: 'room.entered'; readonly roomtype: readonly unknown[] }
|
||||
| { readonly type: 'room.entered'; readonly roomtype: Roomtype }
|
||||
| { readonly type: 'room.player-joined'; readonly seat: number }
|
||||
| { readonly type: 'room.player-left'; readonly seat: number }
|
||||
| { readonly type: 'room.player-ready'; readonly seat: number }
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Opaque subgame wire configuration. The game defines its contents. */
|
||||
export type Roomtype = string | readonly unknown[];
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "69bfa9e3-15e3-4ed5-a26a-1fda48ffc906",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from './roomtype.ts';
|
||||
/** Public connection phases available to a game. */
|
||||
export type PlatformGameConnectionPhase =
|
||||
| 'connected'
|
||||
@@ -20,7 +21,7 @@ export interface PlatformGameSelfSnapshot {
|
||||
/** Plain public room projection. `roomtype` remains opaque server data. */
|
||||
export interface PlatformGameRoomSnapshot {
|
||||
readonly roomcode: string;
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly stage: number;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import type {Roomtype} from '../contracts/roomtype.ts';
|
||||
import type { strict } from 'node:assert';
|
||||
import type {
|
||||
GameEntry,
|
||||
@@ -16,7 +17,7 @@ export type GameContractHostFactory = () => GameContractHost;
|
||||
|
||||
/** The game owns each room configuration, its expected count and matching Host fixture. */
|
||||
export interface GameContractSample {
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly roomtype: Roomtype;
|
||||
readonly expectedSeatCount: number;
|
||||
readonly makeHost: GameContractHostFactory;
|
||||
}
|
||||
|
||||
+6
-6
@@ -701,7 +701,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -229.49841260344616,
|
||||
"x": -231.99801937299284,
|
||||
"y": 178,
|
||||
"z": 0
|
||||
},
|
||||
@@ -742,8 +742,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 19.996825206892293,
|
||||
"height": 25.200000000000003
|
||||
"width": 24.996038745985672,
|
||||
"height": 31.500000000000004
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -781,10 +781,10 @@
|
||||
"_string": "消",
|
||||
"_horizontalAlign": 0,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 21.083333333333336,
|
||||
"_fontSize": 20,
|
||||
"_actualFontSize": 26.354166666666668,
|
||||
"_fontSize": 25,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 20,
|
||||
"_lineHeight": 25,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
|
||||
Reference in New Issue
Block a user