JCMGO.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. export module JCMGO {
  2. /**
  3. * @name Socket客户端
  4. * @description 包含了前后端所有的交互接口。(建议继承该类来使用)
  5. */
  6. export class SocketClient {
  7. private _url: string = null;
  8. private _ws: WebSocket = null;
  9. /**
  10. * @name 连接服务端
  11. * @param {string} url 服务端ws地址
  12. */
  13. public connect(url: string) {
  14. if (!url) throw "url can not be null";
  15. this._url = url;
  16. this._ws = new WebSocket(url);
  17. this._ws.onclose = this.handleOnClose.bind(this);
  18. this._ws.onmessage = (e) => this.invoke(JSON.parse(e.data));
  19. }
  20. /**
  21. * @name 重新连接
  22. * @deprecated 可在onDestroy\onMiss时调用
  23. */
  24. public reconnect() {
  25. if (this._url != null && this._ws == null) {
  26. this.connect(this._url);
  27. }
  28. }
  29. /**
  30. * @name 关闭连接
  31. */
  32. public close()
  33. {
  34. if (this._ws != null) {
  35. this._ws.close();
  36. this._ws = null;
  37. }
  38. }
  39. private _id: number = null;
  40. /**ID,由服务端发放 */
  41. public get id() {
  42. return this._id;
  43. }
  44. private _isValid = false;
  45. /** 是否有效(可通信) */
  46. public get isValid() {
  47. return this._isValid;
  48. }
  49. private _loaded = false;
  50. /** 是否已经载入过 */
  51. public get loaded() {
  52. return this._loaded;
  53. }
  54. /**监听-载入 */
  55. protected onLoad() {}
  56. /**监听-重载 */
  57. protected onReload() {}
  58. /**监听-销毁(连上后断开) */
  59. protected onDestroy() {}
  60. /**监听-迷失(请求连接但连接失败)*/
  61. protected onMiss() {}
  62. /**调用远程函数 */
  63. private call(func: string, ...args: any[]) {
  64. if (args == undefined) args = [];
  65. let data: SocketDataPack = {func: func, args: args};
  66. this._ws.send(JSON.stringify(data));
  67. }
  68. /**调用本地函数 */
  69. private invoke(dataPack: SocketDataPack) {
  70. let theFunc = this[dataPack.func] as Function;
  71. if (theFunc instanceof Function) theFunc.apply(this, dataPack.args);
  72. else console.error(`invoke fail: can not find func [${dataPack.func}]`);
  73. }
  74. /**初始化函数-被服务端远程调用 */
  75. private initByServer(id: number) {
  76. this._id = id;
  77. this._isValid = true;
  78. try {
  79. if (this._loaded) {
  80. this.onReload();
  81. } else {
  82. this.onLoad();
  83. }
  84. } catch (e) { console.error(e); }
  85. this._loaded = true;
  86. }
  87. /**监听处理-WebSocket关闭 */
  88. private handleOnClose() {
  89. this._ws = null;
  90. try {
  91. if (this._isValid) {
  92. this._isValid = false;
  93. this.onDestroy();
  94. } else {
  95. this.onMiss();
  96. }
  97. } catch (e) { console.error(e); }
  98. }
  99. //========================API========================
  100. /**单人随机匹配 */
  101. public singlePersonRandomMatching(param: MatchParam) {
  102. this.call("SinglePersonRandomMatching", JSON.stringify(param));
  103. }
  104. /**开始帧同步 */
  105. public startFrameSync() {
  106. this.call("StartFrameSync");
  107. }
  108. private _cachedInputs: object[] = [];
  109. /**缓存帧数据输入 */
  110. public cacheInputs(...inputs: object[]) {
  111. this._cachedInputs.push(...inputs);
  112. }
  113. /**上传帧数据输入*/
  114. public uploadInputs(sign: string = "") {
  115. if (this._cachedInputs.length > 0) {
  116. this.call("UploadInputs", this._cachedInputs, sign);
  117. this._cachedInputs = [];
  118. }
  119. }
  120. //========================Broadcast========================
  121. /**当房间加入玩家 */
  122. protected onRoomAddPlayer(e: BroadcastEvent<RoomAddPlayerBst>) {}
  123. /**当房间移除玩家 */
  124. protected onRoomRemovePlayer(e: BroadcastEvent<RoomRemovePlayerBst>) {}
  125. /**当房间结束匹配 */
  126. protected onRoomEndMatching(e: BroadcastEvent<RoomEndMatchingBst>) {}
  127. /**当帧同步开始 */
  128. protected onFrameSyncStart(e: BroadcastEvent<null>) {}
  129. /**当接收帧同步数据 */
  130. protected onRecvFrame(e: BroadcastEvent<Frame>) {}
  131. }
  132. //========================传输协议========================
  133. /**匹配参数 */
  134. export interface MatchParam {
  135. /**玩家基本信息 */
  136. playerInfo: object;
  137. /**匹配房间配置 */
  138. matchRoomConfig: MatchRoomConfig
  139. }
  140. /**匹配房间配置 */
  141. export interface MatchRoomConfig {
  142. /**同一类型的房间才会匹配到一起 */
  143. type: string;
  144. /**最多容纳多少人 */
  145. maxMembers: number;
  146. /**服务端下发同步帧的频率 */
  147. syncFrameRate: number;
  148. /**匹配多久结束 */
  149. maxMatchingTime: number;
  150. }
  151. /**同步帧 */
  152. export interface Frame {
  153. /**帧序列号 */
  154. id: number;
  155. /**帧操作(房间内所有玩家上传的操作会合并在内) */
  156. inputs: object[];
  157. /**时间间隔(毫秒) */
  158. dt: number;
  159. /**服务端返回的最新记号(需要在上传帧数据输入时,填入参数sign) */
  160. sign: string;
  161. }
  162. /**房间信息 */
  163. export interface RoomInfo {
  164. id: number;
  165. playerInfos: Map<number, object>;
  166. }
  167. /**RPC协议 */
  168. interface SocketDataPack {
  169. func: string;
  170. args: any[];
  171. }
  172. //========================广播事件========================
  173. /**房间结束匹配 */
  174. export interface RoomEndMatchingBst {
  175. roomInfo: RoomInfo;
  176. timestamp: number;
  177. }
  178. /**房间加入玩家 */
  179. export interface RoomAddPlayerBst {
  180. /**最新的房间信息 */
  181. roomInfo: RoomInfo;
  182. playerIndex: number;
  183. isSelf: boolean;
  184. }
  185. /**房间移除玩家 */
  186. export interface RoomRemovePlayerBst {
  187. /**最新的房间信息(目标已离开房间,所以里面没目标的信息) */
  188. roomInfo: RoomInfo;
  189. playerIndex: number;
  190. isSelf: boolean;
  191. }
  192. /**广播事件 */
  193. export interface BroadcastEvent<T> {
  194. /**序列号 */
  195. seq: number;
  196. /**数据 */
  197. data: T;
  198. }
  199. //========================Object========================
  200. /**对象处理工具 */
  201. export class ObjectUtils {
  202. /**
  203. * 将sources合并到target,该合并全部是深拷贝
  204. * @param target
  205. * @param sources
  206. * @returns {object}
  207. */
  208. public static merge(target: object, ...sources: object[]): object {
  209. for (let i = 0; i < sources.length; ++i) {
  210. let source = sources[i];
  211. if (typeof source != 'object' || source == null) {
  212. continue;
  213. }
  214. for (let skey in source) {
  215. //只处理自身的key 这里可能来自于外部prototype的扩展
  216. if (!source.hasOwnProperty(skey)) {
  217. continue;
  218. }
  219. if (source[skey] instanceof Date) {
  220. //Date类型 要克隆一份 保证深拷贝
  221. target[skey] = new Date(source[skey]);
  222. continue;
  223. }
  224. else if (typeof (target[skey]) == 'object' && target[skey] != null && typeof (source[skey]) == 'object' && source[skey] != null) {
  225. // 两个都是Object 递归merge之
  226. this.merge(target[skey], source[skey]);
  227. }
  228. else {
  229. if (Array.isArray(source[skey])) {
  230. // 数组merge后还是数组
  231. target[skey] = this.merge([], source[skey]);
  232. }
  233. else if (typeof (source[skey]) == 'object' && source[skey] !== null) {
  234. // Object要克隆一份以确保深拷贝
  235. target[skey] = this.merge({}, source[skey]);
  236. }
  237. else {
  238. // 基本类型 直接赋值即可
  239. target[skey] = source[skey];
  240. }
  241. }
  242. }
  243. }
  244. return target;
  245. }
  246. public static values(obj: object): any[] {
  247. let output = [];
  248. for (let k in obj) {
  249. obj.hasOwnProperty(k) && output.push(obj[k]);
  250. }
  251. return output;
  252. }
  253. public static entries(obj: object): any[][] {
  254. let output = [];
  255. for (let key in obj) {
  256. if (!obj.hasOwnProperty(key)) {
  257. continue;
  258. }
  259. output.push([key, obj[key]]);
  260. }
  261. return output;
  262. }
  263. public static forEach(obj: object, handler: (value: any, key: string, obj: object) => void): void {
  264. for (let key in obj) {
  265. if (!obj.hasOwnProperty(key)) {
  266. return;
  267. }
  268. handler(obj[key], key, obj);
  269. }
  270. }
  271. }
  272. //========================Math========================
  273. /**
  274. * 多平台一致精确计算
  275. * [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的]
  276. */
  277. export class ExactMath {
  278. // 计算精度
  279. // sin、cos、tan方法的误差小数点后16位
  280. public static readonly ACCURACY_SIN_ERROR = 1e-16;
  281. public static readonly ACCURACY_COS_ERROR = 1e-16;
  282. public static readonly ACCURACY_TAN_ERROR = 1e-16;
  283. // 角度弧度常量
  284. public static readonly DEG = 57.29577951308232;
  285. public static readonly RAD = 0.017453292519943295;
  286. public static readonly PI = 3.141592653589793;
  287. public static readonly E = 2.718281828459045;
  288. public static readonly LN2 = 0.6931471805599453;
  289. public static readonly LN10 = 2.302585092994046;
  290. public static readonly LOG2E = 1.4426950408889634;
  291. public static readonly LOG10E = 0.4342944819032518;
  292. public static readonly SQRT1_2 = 0.7071067811865476;
  293. public static readonly SQRT2 = 1.4142135623730951;
  294. /****************************************************基础****************************************************/
  295. /**
  296. * 获得小数位数
  297. * @param {Number} num 浮点数
  298. * @returns {Number}
  299. */
  300. public static getDecimalPlace(num: number) {
  301. if (num && num !== Math.floor(num)) {
  302. for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
  303. temp = num * m;
  304. if (temp == Math.floor(temp)) return n;
  305. }
  306. return 20;
  307. } else {
  308. return 0;
  309. }
  310. }
  311. /**
  312. * 保留n为小数,并四舍五入
  313. * @param {Number} num 浮点数
  314. * @param {Number} n 整数
  315. * @returns {Number}
  316. */
  317. public static toFixed(num: number, n: number = 0) {
  318. if (n == 0) {
  319. return Math.round(num);
  320. } else {
  321. const m = Math.pow(10, n);
  322. return Math.round(num * (m * 10) / 10) / m;
  323. }
  324. }
  325. public static abs(x: number) {
  326. return Math.abs(x);
  327. }
  328. public static round(x: number) {
  329. return Math.round(x);
  330. }
  331. public static ceil(x: number) {
  332. return Math.ceil(x)
  333. }
  334. public static floor(x: number) {
  335. return Math.floor(x)
  336. }
  337. public static min(...args: number[]) {
  338. return Math.min(...args);
  339. }
  340. public static max(...args: number[]) {
  341. return Math.max(...args);
  342. }
  343. /**
  344. * 小数相加
  345. * @param {Number} num1 浮点数
  346. * @param {Number} num2 浮点数
  347. * @returns {Number}
  348. */
  349. public static add(...args: number[]) {
  350. if (args.length === 2) {
  351. const num1 = args[0];
  352. const num2 = args[1];
  353. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  354. return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
  355. } else {
  356. return args.reduce((a, b) => this.add(a, b))
  357. }
  358. };
  359. /**
  360. * 小数相减
  361. * @param {Number} num1 浮点数
  362. * @param {Number} num2 浮点数
  363. * @returns {Number}
  364. */
  365. public static sub(...args: number[]) {
  366. if (args.length === 2) {
  367. const num1 = args[0];
  368. const num2 = args[1];
  369. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  370. return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
  371. } else {
  372. return args.reduce((a, b) => this.sub(a, b))
  373. }
  374. };
  375. /**
  376. * 小数相乘
  377. * @param {Number} num1 浮点数
  378. * @param {Number} num2 浮点数
  379. * @returns {Number}
  380. */
  381. public static mul(...args: number[]) {
  382. if (args.length === 2) {
  383. let num1 = args[0];
  384. let num2 = args[1];
  385. // 方案1:
  386. // 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
  387. // const n1 = this.getDecimalPlace(num1);
  388. // const n2 = this.getDecimalPlace(num2);
  389. // const m1 = Math.pow(10, n1);
  390. // const m2 = Math.pow(10, n2);
  391. // return (n1 * m1) * (n2 * m2) / (m1 * m2);
  392. // 方案2:
  393. // 用除法实现乘法,不会存在过大中间值
  394. let n1 = this.getDecimalPlace(num1);
  395. let n2 = this.getDecimalPlace(num2);
  396. let m = Math.pow(10, n2);
  397. num2 = m / this.toFixed(num2 * m);
  398. m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
  399. m = this.toFixed(num1 * m) / this.toFixed(num2 * m);
  400. let n = Math.min(this.getDecimalPlace(m), n1 + n2);
  401. return this.toFixed(m, n);
  402. } else {
  403. return args.reduce((a, b) => this.mul(a, b))
  404. }
  405. };
  406. /**
  407. * 小数相除法
  408. * @param {Number} num1 浮点数
  409. * @param {Number} num2 浮点数
  410. * @returns {Number}
  411. */
  412. public static div(...args: number[]) {
  413. if (args.length === 2) {
  414. const num1 = args[0];
  415. const num2 = args[1];
  416. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  417. return this.toFixed(num1 * m) / this.toFixed(num2 * m);
  418. } else {
  419. return args.reduce((a, b) => this.div(a, b))
  420. }
  421. };
  422. /**
  423. * 取余
  424. * @param {Number} num1 浮点数
  425. * @param {Number} num2 浮点数
  426. * @returns {Number}
  427. */
  428. public static rem(...args: number[]) {
  429. if (args.length === 2) {
  430. const num1 = args[0];
  431. const num2 = args[1];
  432. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  433. return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
  434. } else {
  435. return args.reduce((a, b) => this.rem(a, b))
  436. }
  437. };
  438. /**
  439. * n次方,仅支持整数次方(正负都可以)
  440. * @param {Number} num 浮点数
  441. * @param {Number} n 整数
  442. */
  443. public static pow(num: number, n: number) {
  444. if (num == 0 && n == 0) {
  445. return 1;
  446. }
  447. if (num == 0 && n > 0) {
  448. return 0
  449. }
  450. if (num == 0 && n < 0) {
  451. return Infinity;
  452. }
  453. // num为负数,n为负小数,返回NaN
  454. if (num < 0 && n < 0 && Math.round(n) != n) {
  455. return NaN;
  456. }
  457. if (Math.round(n) != n) {
  458. throw new Error('n must be an integer');
  459. }
  460. let result = 1;
  461. if (n > 0) {
  462. for (let index = 0; index < n; index++) {
  463. result = this.mul(result, num);
  464. }
  465. } else if (n < 0) {
  466. for (let index = 0, len = Math.abs(n); index < len; index++) {
  467. result = this.div(result, num);
  468. }
  469. }
  470. return result;
  471. };
  472. /**
  473. * 开方运算【牛顿迭代法】
  474. *
  475. * @param {Number} n
  476. * @returns
  477. */
  478. public static sqrt(n: number) {
  479. if (n < 0) return NaN;
  480. if (n === 0) return 0;
  481. if (n === 1) return 1;
  482. let last = 0;
  483. let res = 1;
  484. let c = 50;
  485. while (res != last && --c >= 0) {
  486. last = res;
  487. res = this.div(this.add(res, this.div(n, res)), 2)
  488. }
  489. return res;
  490. // float InvSqrt(float x)
  491. // {
  492. // float xhalf = 0.5f * x;
  493. // int i = * (int *) & x; // get bits for floating VALUE
  494. // i = 0x5f375a86 - (i >> 1); // gives initial guess y0
  495. // x = * (float *) & i; // convert bits BACK to float
  496. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  497. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  498. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  499. // return 1 / x;
  500. // }
  501. };
  502. /****************************************************随机****************************************************/
  503. private static getSeed(seed: number) {
  504. if (typeof seed !== "number" || isNaN(seed)) throw "seed invalid";
  505. seed = Math.floor(seed % 233280);
  506. return seed;
  507. }
  508. private static randomSeed: number;
  509. /**
  510. * 设置随机种子
  511. */
  512. public static setSeed(seed: number) {
  513. this.randomSeed = this.getSeed(seed);
  514. };
  515. /**
  516. * 随机
  517. */
  518. public static random() {
  519. if (typeof this.randomSeed !== "number" || isNaN(this.randomSeed)) throw "seed invalid";
  520. this.randomSeed = (this.randomSeed * 9301 + 49297) % 233280;
  521. return this.randomSeed / 233280.0;
  522. };
  523. /**
  524. * 根据随机种子随机
  525. * @param {number} seed
  526. */
  527. public static randomBySeed(seed: number) {
  528. seed = this.getSeed(seed);
  529. seed = (seed * 9301 + 49297) % 233280;
  530. return seed / 233280.0;
  531. };
  532. /****************************************************角度弧度转换****************************************************/
  533. /**
  534. * 弧度数转角度数
  535. * @param {Number} radians 浮点数
  536. * @returns {Numbe} 浮点数
  537. */
  538. public static radiansToDegrees(radians: number) {
  539. return this.div(radians, this.RAD);
  540. };
  541. /**
  542. * 角度数转弧度数
  543. * @param {Number} degrees 浮点数
  544. * @returns {Numbe} 浮点数
  545. */
  546. public static degreesToRadians(degrees: number) {
  547. return this.div(degrees, this.DEG);
  548. };
  549. /**
  550. * 将角度值转换到[0, 360)范围内
  551. * @param {Number} angle 浮点数
  552. * @returns {Number} 整数
  553. */
  554. public static get0To360Angle(angle: number) {
  555. if (angle === 0) {
  556. return 0;
  557. } else if (angle < 0) {
  558. return this.add(this.rem(angle, 360), 360);
  559. } else {
  560. return this.rem(angle, 360);
  561. }
  562. };
  563. /****************************************************三角函数****************************************************/
  564. /**
  565. * 查表
  566. */
  567. private static _sin = {};
  568. private static _cos = {};
  569. private static _tan = {};
  570. /**
  571. * 3个三角函数,根据需求自行添加
  572. * 为了效率,应该尽量使用查表法
  573. * 表内查不到的,目前使用系统方法的结果并取前4位小数
  574. */
  575. public static sin(x: number) {
  576. // if (x == 0) {
  577. // return 0;
  578. // } else if (x == 90) {
  579. // return 1;
  580. // }
  581. // let n = x, sum = 0, i = 1;
  582. // do {
  583. // i++;
  584. // sum = this.add(sum, n);
  585. // // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
  586. // n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
  587. // } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
  588. // return sum;
  589. if (this._sin.hasOwnProperty(x)) {
  590. return this._sin[x];
  591. }
  592. return this.toFixed(Math.sin(x), 4);
  593. };
  594. public static cos(x: number) {
  595. if (this._cos.hasOwnProperty(x)) {
  596. return this._cos[x];
  597. }
  598. return this.toFixed(Math.cos(x), 4);
  599. };
  600. public static tan(x: number) {
  601. if (this._tan.hasOwnProperty(x)) {
  602. return this._tan[x];
  603. }
  604. return this.toFixed(Math.tan(x), 4);
  605. };
  606. }
  607. }