| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650 |
- export module JCMGO {
- /**
- * @name Socket客户端
- * @description 包含了前后端所有的交互接口。(建议继承该类来使用)
- */
- export class SocketClient {
- private _url: string = null;
- private _ws: WebSocket = null;
-
- /**
- * @name 连接服务端
- * @param {string} url 服务端ws地址
- */
- public connect(url: string) {
- if (!url) throw "url can not be null";
- this._url = url;
- this._ws = new WebSocket(url);
- this._ws.onclose = this.handleOnClose.bind(this);
- this._ws.onmessage = (e) => this.invoke(JSON.parse(e.data));
- }
-
- /**
- * @name 重新连接
- * @deprecated 可在onDestroy\onMiss时调用
- */
- public reconnect() {
- if (this._url != null && this._ws == null) {
- this.connect(this._url);
- }
- }
-
- /**
- * @name 关闭连接
- */
- public close()
- {
- if (this._ws != null) {
- this._ws.close();
- this._ws = null;
- }
- }
-
- private _id: number = null;
- /**ID,由服务端发放 */
- public get id() {
- return this._id;
- }
- private _isValid = false;
- /** 是否有效(可通信) */
- public get isValid() {
- return this._isValid;
- }
- private _loaded = false;
- /** 是否已经载入过 */
- public get loaded() {
- return this._loaded;
- }
-
- /**监听-载入 */
- protected onLoad() {}
- /**监听-重载 */
- protected onReload() {}
- /**监听-销毁(连上后断开) */
- protected onDestroy() {}
- /**监听-迷失(请求连接但连接失败)*/
- protected onMiss() {}
-
- /**调用远程函数 */
- private call(func: string, ...args: any[]) {
- if (args == undefined) args = [];
- let data: SocketDataPack = {func: func, args: args};
- this._ws.send(JSON.stringify(data));
- }
-
- /**调用本地函数 */
- private invoke(dataPack: SocketDataPack) {
- let theFunc = this[dataPack.func] as Function;
- if (theFunc instanceof Function) theFunc.apply(this, dataPack.args);
- else console.error(`invoke fail: can not find func [${dataPack.func}]`);
- }
- /**初始化函数-被服务端远程调用 */
- private initByServer(id: number) {
- this._id = id;
- this._isValid = true;
- try {
- if (this._loaded) {
- this.onReload();
- } else {
- this.onLoad();
- }
- } catch (e) { console.error(e); }
- this._loaded = true;
- }
-
- /**监听处理-WebSocket关闭 */
- private handleOnClose() {
- this._ws = null;
- try {
- if (this._isValid) {
- this._isValid = false;
- this.onDestroy();
- } else {
- this.onMiss();
- }
- } catch (e) { console.error(e); }
- }
- //========================API========================
- /**单人随机匹配 */
- public singlePersonRandomMatching(param: MatchParam) {
- this.call("SinglePersonRandomMatching", JSON.stringify(param));
- }
- /**开始帧同步 */
- public startFrameSync() {
- this.call("StartFrameSync");
- }
- private _cachedInputs: object[] = [];
- /**缓存帧数据输入 */
- public cacheInputs(...inputs: object[]) {
- this._cachedInputs.push(...inputs);
- }
- /**上传帧数据输入*/
- public uploadInputs(sign: string = "") {
- if (this._cachedInputs.length > 0) {
- this.call("UploadInputs", this._cachedInputs, sign);
- this._cachedInputs = [];
- }
- }
- //========================Broadcast========================
- /**当房间加入玩家 */
- protected onRoomAddPlayer(e: BroadcastEvent<RoomAddPlayerBst>) {}
- /**当房间移除玩家 */
- protected onRoomRemovePlayer(e: BroadcastEvent<RoomRemovePlayerBst>) {}
- /**当房间结束匹配 */
- protected onRoomEndMatching(e: BroadcastEvent<RoomEndMatchingBst>) {}
- /**当帧同步开始 */
- protected onFrameSyncStart(e: BroadcastEvent<null>) {}
- /**当接收帧同步数据 */
- protected onRecvFrame(e: BroadcastEvent<Frame>) {}
- }
-
- //========================传输协议========================
- /**匹配参数 */
- export interface MatchParam {
- /**玩家基本信息 */
- playerInfo: object;
- /**匹配房间配置 */
- matchRoomConfig: MatchRoomConfig
- }
- /**匹配房间配置 */
- export interface MatchRoomConfig {
- /**同一类型的房间才会匹配到一起 */
- type: string;
- /**最多容纳多少人 */
- maxMembers: number;
- /**服务端下发同步帧的频率 */
- syncFrameRate: number;
- /**匹配多久结束 */
- maxMatchingTime: number;
- }
- /**同步帧 */
- export interface Frame {
- /**帧序列号 */
- id: number;
- /**帧操作(房间内所有玩家上传的操作会合并在内) */
- inputs: object[];
- /**时间间隔(毫秒) */
- dt: number;
- /**服务端返回的最新记号(需要在上传帧数据输入时,填入参数sign) */
- sign: string;
- }
- /**房间信息 */
- export interface RoomInfo {
- id: number;
- playerInfos: Map<number, object>;
- }
- /**RPC协议 */
- interface SocketDataPack {
- func: string;
- args: any[];
- }
- //========================广播事件========================
- /**房间结束匹配 */
- export interface RoomEndMatchingBst {
- roomInfo: RoomInfo;
- timestamp: number;
- }
- /**房间加入玩家 */
- export interface RoomAddPlayerBst {
- /**最新的房间信息 */
- roomInfo: RoomInfo;
- playerIndex: number;
- isSelf: boolean;
- }
- /**房间移除玩家 */
- export interface RoomRemovePlayerBst {
- /**最新的房间信息(目标已离开房间,所以里面没目标的信息) */
- roomInfo: RoomInfo;
- playerIndex: number;
- isSelf: boolean;
- }
- /**广播事件 */
- export interface BroadcastEvent<T> {
- /**序列号 */
- seq: number;
- /**数据 */
- data: T;
- }
- //========================Object========================
- /**对象处理工具 */
- export class ObjectUtils {
- /**
- * 将sources合并到target,该合并全部是深拷贝
- * @param target
- * @param sources
- * @returns {object}
- */
- public static merge(target: object, ...sources: object[]): object {
- for (let i = 0; i < sources.length; ++i) {
- let source = sources[i];
- if (typeof source != 'object' || source == null) {
- continue;
- }
- for (let skey in source) {
- //只处理自身的key 这里可能来自于外部prototype的扩展
- if (!source.hasOwnProperty(skey)) {
- continue;
- }
- if (source[skey] instanceof Date) {
- //Date类型 要克隆一份 保证深拷贝
- target[skey] = new Date(source[skey]);
- continue;
- }
- else if (typeof (target[skey]) == 'object' && target[skey] != null && typeof (source[skey]) == 'object' && source[skey] != null) {
- // 两个都是Object 递归merge之
- this.merge(target[skey], source[skey]);
- }
- else {
- if (Array.isArray(source[skey])) {
- // 数组merge后还是数组
- target[skey] = this.merge([], source[skey]);
- }
- else if (typeof (source[skey]) == 'object' && source[skey] !== null) {
- // Object要克隆一份以确保深拷贝
- target[skey] = this.merge({}, source[skey]);
- }
- else {
- // 基本类型 直接赋值即可
- target[skey] = source[skey];
- }
- }
- }
- }
- return target;
- }
- public static values(obj: object): any[] {
- let output = [];
- for (let k in obj) {
- obj.hasOwnProperty(k) && output.push(obj[k]);
- }
- return output;
- }
- public static entries(obj: object): any[][] {
- let output = [];
- for (let key in obj) {
- if (!obj.hasOwnProperty(key)) {
- continue;
- }
- output.push([key, obj[key]]);
- }
- return output;
- }
- public static forEach(obj: object, handler: (value: any, key: string, obj: object) => void): void {
- for (let key in obj) {
- if (!obj.hasOwnProperty(key)) {
- return;
- }
- handler(obj[key], key, obj);
- }
- }
- }
- //========================Math========================
- /**
- * 多平台一致精确计算
- * [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的]
- */
- export class ExactMath {
- // 计算精度
- // sin、cos、tan方法的误差小数点后16位
- public static readonly ACCURACY_SIN_ERROR = 1e-16;
- public static readonly ACCURACY_COS_ERROR = 1e-16;
- public static readonly ACCURACY_TAN_ERROR = 1e-16;
- // 角度弧度常量
- public static readonly DEG = 57.29577951308232;
- public static readonly RAD = 0.017453292519943295;
- public static readonly PI = 3.141592653589793;
- public static readonly E = 2.718281828459045;
- public static readonly LN2 = 0.6931471805599453;
- public static readonly LN10 = 2.302585092994046;
- public static readonly LOG2E = 1.4426950408889634;
- public static readonly LOG10E = 0.4342944819032518;
- public static readonly SQRT1_2 = 0.7071067811865476;
- public static readonly SQRT2 = 1.4142135623730951;
- /****************************************************基础****************************************************/
- /**
- * 获得小数位数
- * @param {Number} num 浮点数
- * @returns {Number}
- */
- public static getDecimalPlace(num: number) {
- if (num && num !== Math.floor(num)) {
- for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
- temp = num * m;
- if (temp == Math.floor(temp)) return n;
- }
- return 20;
- } else {
- return 0;
- }
- }
- /**
- * 保留n为小数,并四舍五入
- * @param {Number} num 浮点数
- * @param {Number} n 整数
- * @returns {Number}
- */
- public static toFixed(num: number, n: number = 0) {
- if (n == 0) {
- return Math.round(num);
- } else {
- const m = Math.pow(10, n);
- return Math.round(num * (m * 10) / 10) / m;
- }
- }
- public static abs(x: number) {
- return Math.abs(x);
- }
- public static round(x: number) {
- return Math.round(x);
- }
- public static ceil(x: number) {
- return Math.ceil(x)
- }
- public static floor(x: number) {
- return Math.floor(x)
- }
- public static min(...args: number[]) {
- return Math.min(...args);
- }
- public static max(...args: number[]) {
- return Math.max(...args);
- }
- /**
- * 小数相加
- * @param {Number} num1 浮点数
- * @param {Number} num2 浮点数
- * @returns {Number}
- */
- public static add(...args: number[]) {
- if (args.length === 2) {
- const num1 = args[0];
- const num2 = args[1];
- const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
- return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
- } else {
- return args.reduce((a, b) => this.add(a, b))
- }
- };
- /**
- * 小数相减
- * @param {Number} num1 浮点数
- * @param {Number} num2 浮点数
- * @returns {Number}
- */
- public static sub(...args: number[]) {
- if (args.length === 2) {
- const num1 = args[0];
- const num2 = args[1];
- const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
- return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
- } else {
- return args.reduce((a, b) => this.sub(a, b))
- }
- };
- /**
- * 小数相乘
- * @param {Number} num1 浮点数
- * @param {Number} num2 浮点数
- * @returns {Number}
- */
- public static mul(...args: number[]) {
- if (args.length === 2) {
- let num1 = args[0];
- let num2 = args[1];
- // 方案1:
- // 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
- // const n1 = this.getDecimalPlace(num1);
- // const n2 = this.getDecimalPlace(num2);
- // const m1 = Math.pow(10, n1);
- // const m2 = Math.pow(10, n2);
- // return (n1 * m1) * (n2 * m2) / (m1 * m2);
- // 方案2:
- // 用除法实现乘法,不会存在过大中间值
- let n1 = this.getDecimalPlace(num1);
- let n2 = this.getDecimalPlace(num2);
- let m = Math.pow(10, n2);
- num2 = m / this.toFixed(num2 * m);
- m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
- m = this.toFixed(num1 * m) / this.toFixed(num2 * m);
- let n = Math.min(this.getDecimalPlace(m), n1 + n2);
- return this.toFixed(m, n);
- } else {
- return args.reduce((a, b) => this.mul(a, b))
- }
- };
- /**
- * 小数相除法
- * @param {Number} num1 浮点数
- * @param {Number} num2 浮点数
- * @returns {Number}
- */
- public static div(...args: number[]) {
- if (args.length === 2) {
- const num1 = args[0];
- const num2 = args[1];
- const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
- return this.toFixed(num1 * m) / this.toFixed(num2 * m);
- } else {
- return args.reduce((a, b) => this.div(a, b))
- }
- };
- /**
- * 取余
- * @param {Number} num1 浮点数
- * @param {Number} num2 浮点数
- * @returns {Number}
- */
- public static rem(...args: number[]) {
- if (args.length === 2) {
- const num1 = args[0];
- const num2 = args[1];
- const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
- return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
- } else {
- return args.reduce((a, b) => this.rem(a, b))
- }
- };
- /**
- * n次方,仅支持整数次方(正负都可以)
- * @param {Number} num 浮点数
- * @param {Number} n 整数
- */
- public static pow(num: number, n: number) {
- if (num == 0 && n == 0) {
- return 1;
- }
- if (num == 0 && n > 0) {
- return 0
- }
- if (num == 0 && n < 0) {
- return Infinity;
- }
- // num为负数,n为负小数,返回NaN
- if (num < 0 && n < 0 && Math.round(n) != n) {
- return NaN;
- }
- if (Math.round(n) != n) {
- throw new Error('n must be an integer');
- }
- let result = 1;
- if (n > 0) {
- for (let index = 0; index < n; index++) {
- result = this.mul(result, num);
- }
- } else if (n < 0) {
- for (let index = 0, len = Math.abs(n); index < len; index++) {
- result = this.div(result, num);
- }
- }
- return result;
- };
- /**
- * 开方运算【牛顿迭代法】
- *
- * @param {Number} n
- * @returns
- */
- public static sqrt(n: number) {
- if (n < 0) return NaN;
- if (n === 0) return 0;
- if (n === 1) return 1;
- let last = 0;
- let res = 1;
- let c = 50;
- while (res != last && --c >= 0) {
- last = res;
- res = this.div(this.add(res, this.div(n, res)), 2)
- }
- return res;
- // float InvSqrt(float x)
- // {
- // float xhalf = 0.5f * x;
- // int i = * (int *) & x; // get bits for floating VALUE
- // i = 0x5f375a86 - (i >> 1); // gives initial guess y0
- // x = * (float *) & i; // convert bits BACK to float
- // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
- // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
- // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
- // return 1 / x;
- // }
- };
- /****************************************************随机****************************************************/
- private static getSeed(seed: number) {
- if (typeof seed !== "number" || isNaN(seed)) throw "seed invalid";
- seed = Math.floor(seed % 233280);
- return seed;
- }
- private static randomSeed: number;
- /**
- * 设置随机种子
- */
- public static setSeed(seed: number) {
- this.randomSeed = this.getSeed(seed);
- };
- /**
- * 随机
- */
- public static random() {
- if (typeof this.randomSeed !== "number" || isNaN(this.randomSeed)) throw "seed invalid";
- this.randomSeed = (this.randomSeed * 9301 + 49297) % 233280;
- return this.randomSeed / 233280.0;
- };
- /**
- * 根据随机种子随机
- * @param {number} seed
- */
- public static randomBySeed(seed: number) {
- seed = this.getSeed(seed);
- seed = (seed * 9301 + 49297) % 233280;
- return seed / 233280.0;
- };
- /****************************************************角度弧度转换****************************************************/
- /**
- * 弧度数转角度数
- * @param {Number} radians 浮点数
- * @returns {Numbe} 浮点数
- */
- public static radiansToDegrees(radians: number) {
- return this.div(radians, this.RAD);
- };
- /**
- * 角度数转弧度数
- * @param {Number} degrees 浮点数
- * @returns {Numbe} 浮点数
- */
- public static degreesToRadians(degrees: number) {
- return this.div(degrees, this.DEG);
- };
- /**
- * 将角度值转换到[0, 360)范围内
- * @param {Number} angle 浮点数
- * @returns {Number} 整数
- */
- public static get0To360Angle(angle: number) {
- if (angle === 0) {
- return 0;
- } else if (angle < 0) {
- return this.add(this.rem(angle, 360), 360);
- } else {
- return this.rem(angle, 360);
- }
- };
- /****************************************************三角函数****************************************************/
- /**
- * 查表
- */
- private static _sin = {};
- private static _cos = {};
- private static _tan = {};
- /**
- * 3个三角函数,根据需求自行添加
- * 为了效率,应该尽量使用查表法
- * 表内查不到的,目前使用系统方法的结果并取前4位小数
- */
- public static sin(x: number) {
- // if (x == 0) {
- // return 0;
- // } else if (x == 90) {
- // return 1;
- // }
- // let n = x, sum = 0, i = 1;
- // do {
- // i++;
- // sum = this.add(sum, n);
- // // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
- // n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
- // } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
- // return sum;
- if (this._sin.hasOwnProperty(x)) {
- return this._sin[x];
- }
- return this.toFixed(Math.sin(x), 4);
- };
- public static cos(x: number) {
- if (this._cos.hasOwnProperty(x)) {
- return this._cos[x];
- }
- return this.toFixed(Math.cos(x), 4);
- };
- public static tan(x: number) {
- if (this._tan.hasOwnProperty(x)) {
- return this._tan[x];
- }
- return this.toFixed(Math.tan(x), 4);
- };
- }
- }
|