JCMGO.ts 23 KB

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