diff --git a/src/api.server.ts b/src/api.server.ts index 69c5c0c..1db870c 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -19,6 +19,7 @@ const fs = require('fs'); const join = require('path').join; let config: Config = require('../config/config.json'); +require('./common/Extend'); export class ApiServer { server: FastifyInstance; diff --git a/src/common/Extend.ts b/src/common/Extend.ts new file mode 100644 index 0000000..b385726 --- /dev/null +++ b/src/common/Extend.ts @@ -0,0 +1,863 @@ + +/** + * 对数字进行补0操作 + * @param value 要补0的数值 + * @param length 要补的总长度 + * @return 补0之后的字符串 + */ +function zeroize(value: number | string, length: number = 2): string { + let str = "" + value; + let zeros = ""; + for (let i = 0, len = length - str.length; i < len; i++) { + zeros += "0"; + } + return zeros + str; +} + +/****************************************扩展Object****************************************/ +interface Object { + /** + * 返回一个浅副本的对象 + * 此对象会拷贝key value + * + * @memberOf Object + */ + clone?(): Object; + /** + * 将数据拷贝到 to + * @param to 目标 + */ + copyto?(to: Object):void; + /** + * 获取指定属性的描述,会查找当前数据和原型数据 + * @param property 指定的属性名字 + */ + getPropertyDescriptor?(property: string): PropertyDescriptor; + + + zssign?(target: any): any; +} + +Object.defineProperties(Object.prototype, { + clone: { + value: function () { + let o = {}; + for (let n in this) { + // @ts-ignore + o[n] = this[n]; + } + return o; + }, + writable: true + }, + getPropertyDescriptor: { + value: function (property: string): any { + let data = Object.getOwnPropertyDescriptor(this, property); + if (data) { + return data; + } + let prototype = Object.getPrototypeOf(this); + if (prototype) { + return prototype.getPropertyDescriptor(property); + } + return; + }, + writable: true + }, + copyto: { + value: function (to: Object) { + for (let p in this) { + if (!(p in to)) { // 本身没有这个属性 + // @ts-ignore + to[p] = this[p]; + } else { + let data: PropertyDescriptor = to.getPropertyDescriptor(p); + if (data) { + if (data.set || data.writable) {// 可进行赋值 + // @ts-ignore + to[p] = this[p]; + } + } + } + } + }, + writable: true + }, + zssign: { + value: function (target: Object) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + let output = Object(target); + for (let nextKey in this) { + if (!(nextKey in output)) { // 本身没有这个属性 + output[nextKey] = this[nextKey]; + } + // else { + // let data: PropertyDescriptor = output.getPropertyDescriptor(nextKey); + // if (data) { + // if (data.set || data.writable) {// 可进行赋值 + // output[nextKey] = this[nextKey]; + // } + // } + // } + } + return output; + }, + writable: true + } +}); + +/****************************************扩展Math****************************************/ +interface Math { + /** + * 让数值处于指定的最大值和最小值之间,低于最小值取最小值,高于最大值取最大值 + * @param value 要处理的数值 + * @param min 最小值 + * @param max 最大值 + */ + clamp?(value: number, min: number, max: number): number; + + /** + * 从最小值到最大值之间随机[min,max) + */ + random2?(min: number, max: number): number; + /** + * 角度转弧度的乘数 + * Math.PI / 180 + * @type {number} + * @memberOf Math + */ + DEG_TO_RAD: number; + /** + * 弧度转角度的乘数 + * 180 / Math.PI + */ + RAD_TO_DEG: number; + /** + * 整圆的弧度 + */ + PI2: number; + /** + * 90°的弧度 + * + * @type {number} + * @memberOf Math + */ + PI_1_2: number; +} + +Math.DEG_TO_RAD = Math.PI / 180; + +Math.RAD_TO_DEG = 180 / Math.PI; + +Math.PI2 = 2 * Math.PI; + +Math.PI_1_2 = Math.PI * .5; + +Math.clamp = (value, min, max) => { + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +Math.random2 = (min, max) => { + return min + Math.random() * (max - min); +} + +/****************************************扩展Number********************************************/ +interface Number { + /** + * 对数字进行补0操作 + * @param length 要补的总长度 + * @return 补0之后的字符串 + */ + zeroize?(length: number): string; + + /** + * 数值介于,`min` `max`直接,包含min,max + * 即:[min,max] + * + * @param {number} min + * @param {number} max + * @returns {boolean} + * @memberof Number + */ + between?(min: number, max: number): boolean; +} + +Object.defineProperties(Number.prototype, { + zeroize: { + value: function (this: number, length: number) { return zeroize(this, length) }, + writable: true + }, + between: { + value: function (this: number, min: number, max: number) { return min <= this && max >= this }, + writable: true + } +}); + +/****************************************扩展String****************************************/ +interface String { + /** + * 替换字符串中{0}{1}{2}{a} {b}这样的数据,用obj对应key替换,或者是数组中对应key的数据替换 + */ + substitute?(...args: any): string; + substitute?(args: any[]): string; + /** + * 对数字进行补0操作 + * @param length 要补的总长度 + * @return 补0之后的字符串 + */ + zeroize?(length: number): string; + /** + * 将一个字符串转换成一个很小几率重复的数值 + * 此方法hash的字符串并不一定唯一,慎用 + */ + hash?(): number; + + /** + * 获取字符串长度,中文方块字符算两个长度 + */ + trueLength?(): number; + /** + * 中文字符个数 + * */ + cnLength?(): number; + /** + * 比较版本号 + * */ + versionCompare?(target: string): number; +} + + +Object.defineProperties(String.prototype, { + zeroize: { + value: function (length: number) { return zeroize(this, length) }, + writable: true + }, + substitute: { + value: function (this: string) { + let len = arguments.length; + if (len > 0) { + let obj: IArguments; + if (len == 1) { + obj = arguments[0]; + if (typeof obj !== "object") { + obj = arguments; + } + } else { + obj = arguments; + } + if ((obj instanceof Object) && !(obj instanceof RegExp)) { + return this.replace(/\{(?:%([^{}]+)%)?([^{}]+)\}/g, function (match: string, handler: string, key: string) { + //检查key中,是否为%开头,如果是,则尝试按方法处理 + // @ts-ignore + let value = obj[key]; + if (handler) {//如果有处理器,拆分处理器 + let func = String.subHandler[handler]; + if (func) { + value = func(value); + } + } + return (value !== undefined) ? '' + value : match; + }); + } + } + return this.toString();//防止生成String对象,ios反射String对象会当成一个NSDictionary处理 + }, + writable: true + }, + hash: { + value: function () { + let len = this.length; + let hash = 5381; + for (let i = 0; i < len; i++) { + hash += (hash << 5) + this.charCodeAt(i); + } + return hash & 0x7fffffff; + }, + writable: true + }, + trueLength: { + value: function () { + let arr: string[] = this.match(/[\u2E80-\u9FBF]/ig); + return this.length + (arr ? arr.length : 0); + }, + writable: true + }, + cnLength: { + value: function () { + let arr: string[] = this.match(/[\u2E80-\u9FBF]/ig); + return arr ? arr.length : 0; + }, + writable: true + }, + versionCompare: { + value: function (target: string): number { + const GTR = 1; //大于 + const LSS = -1; //小于 + const EQU = 0; //等于 + if (!target) { + return GTR; + } + let v1arr = String(this).split(".").map(function (a) { + return parseInt(a); + }); + let v2arr = String(target).split(".").map(function (a) { + return parseInt(a); + }); + let arrLen = Math.max(v1arr.length, v2arr.length); + let result; + + //排除错误调用 + if (this == undefined || target == undefined) { + throw new Error(); + } + + //检查空字符串,任何非空字符串都大于空字符串 + if (this.length == 0 && target.length == 0) { + return EQU; + } else if (this.length == 0) { + return LSS; + } else if (target.length == 0) { + return GTR; + } + //循环比较版本号 + for (let i = 0; i < arrLen; i++) { + result = versionComp(v1arr[i], v2arr[i]); + if (result == EQU) { + continue; + } else { + break; + } + } + return result; + + function versionComp(n1: number, n2: number) { + if (typeof n1 != "number") { + n1 = 0; + } + if (typeof n2 != "number") { + n2 = 0; + } + if (n1 > n2) { + return GTR; + } else if (n1 < n2) { + return LSS; + } else { + return EQU; + } + } + }, + writable: true + } +}); +interface StringConstructor { + /** + * 对数字进行补0操作 + * @param value 要补0的数值 + * @param length 要补的总长度 + * @return 补0之后的字符串 + */ + zeroize?: (value: number, length: number) => string; + + + /** + * substitute的回调函数 + * + * @type {Readonly<{ [index: string]: { (input: any): string } }>} + * @memberOf StringConstructor + */ + subHandler?: Readonly<{ [index: string]: { (input: any): string } }>; +} + +String.zeroize = zeroize; + + + +/****************************************扩展Date****************************************/ + + +interface Date { + + /** + * 格式化日期 + * + * @param {string} mask 时间字符串 + * @param {boolean} [local] 是否基于本地时间显示,目前项目,除了报错信息,其他时间都用UTC时间显示 + * @returns {string} 格式化后的时间 + */ + format(mask: string, local?: boolean): string; +} + +Object.defineProperties(Date.prototype, { + format: { + value: function (mask: string, local?: boolean) { + let d: Date = this; + // @ts-ignore + return mask.replace(/"[^"]*"|'[^']*'|(?:d{1,2}|m{1,2}|yy(?:yy)?|([hHMs])\1?)/g, function ($0: string) { + switch ($0) { + case "d": return gd(); + case "dd": return zeroize(gd()); + case "M": return gM() + 1; + case "MM": return zeroize(gM() + 1); + case "yy": return (gy() + "").substr(2); + case "yyyy": return gy(); + case "h": return gH() % 12 || 12; + case "hh": return zeroize(gH() % 12 || 12); + case "H": return gH(); + case "HH": return zeroize(gH()); + case "m": return gm(); + case "mm": return zeroize(gm()); + case "s": return gs(); + case "ss": return zeroize(gs()); + default: return $0.substr(1, $0.length - 2); + } + }); + function gd() { return local ? d.getDate() : d.getUTCDate() } + function gM() { return local ? d.getMonth() : d.getUTCMonth() } + function gy() { return local ? d.getFullYear() : d.getUTCFullYear() } + function gH() { return local ? d.getHours() : d.getUTCHours() } + function gm() { return local ? d.getMinutes() : d.getUTCMinutes() } + function gs() { return local ? d.getSeconds() : d.getUTCSeconds() } + }, + writable: true + } +}); + +/****************************************扩展Array****************************************/ +const enum ArraySort { + /** + * 升序 + */ + ASC = 0, + /** + * 降序 + */ + DESC = 1 +} + + + +interface ArrayConstructor { + // binaryInsert(partArr: T[], item: T, filter: { (tester: T, ...args): boolean }, ...args); + SORT_DEFAULT: { number: 0, string: "", boolean: false }; +} + +/** + * 用于对Array排序时,处理undefined + */ +Array.SORT_DEFAULT = { + number: 0, + string: "", + boolean: false +} +Object.freeze(Array.SORT_DEFAULT); + +interface Array { + /** + * 如果数组中没有要放入的对象,则将对象放入数组 + * + * @param {T} t 要放入的对象 + * @returns {number} 放入的对象,在数组中的索引 + * + * @member Array + */ + pushOnce?(t: T): number; + + /** + * + * 删除某个数据 + * @param {T} t + * @returns {boolean} true 有这个数据并且删除成功 + * false 没有这个数据 + */ + remove?(t: T): boolean; + + /** + * 排序 支持多重排序 + * 降序, 升序 + * @param {(keyof T)[]} kArr 参数属性列表 + * @param {(boolean[] | ArraySort[])} [dArr] 是否降序,默认升序 + * @returns {this} + * + * @member Array + */ + multiSort?(kArr: (keyof T)[], dArr?: boolean[] | ArraySort[]): this; + + /** + * 默认排序 + * + * @param {string} [key] + * @param {boolean} [descend] + * + * @member Array + */ + doSort?(key?: keyof T, descend?: boolean | ArraySort): this; + doSort?(descend?: boolean | ArraySort, key?: keyof T): this; + + /** + * 将数组克隆到to + * to的数组长度会和当前数组一致 + * + * @template T + * @param {Array} to + */ + cloneTo?(to: Array):void; + + /** + * 将数组附加到to中 + * + * @template T + * @param {Array} to + * + * @member ArrayConstructor + */ + appendTo?(to: Array):void; + + /** + * 移除数组index位置的元素, 比slice效率高 + * @param index + */ + spliceOne?(index: number): boolean; + + /** + * 随机排序 + */ + randomSort?():void; + + /** + * 检查数组中是否含有另外一个object + * @param obj 与数组同类型的obj | 同类型的数组 | 指定child字段的值 | 指定child字段的数组 + * @param child 比较字段 + */ + contains?(obj: T | T[] | {} | {}[], child?: string): boolean; + + /** + * 将数组随机插入当前数组中 + * @param arr + */ + randomInsert?(arr: Array):void; + /** + * 随机获取n个元素 + * @param count + */ + randomGet?(count?: number):T[]; + /** + * 随机获取1个元素 + */ + randomOne?():T; + /** + * 随机移除n个元素 + * @param count + */ + randomRemove?(count?: number):T[]; + /** + * 数组移动n位 + * @param n n > 0 右移, n<0 左移 + */ + moveElement?(n: number): T[]; + + /** + * 两个数组并集 + * @param arr + */ + union?(arr: T[]): T[]; + + /** + * 两个数组交集 + * @param arr + */ + intersect?(arr: T[]): T[]; + /** + * 相对于arr的差集 + * @param arr + */ + difference?(arr: T[]): T[]; +} + +Object.defineProperties(Array.prototype, { + cloneTo: { + value: function (this: T[], b: any[]) { + b.length = this.length; + let len = this.length; + b.length = len; + for (let i = 0; i < len; i++) { + b[i] = this[i]; + } + }, + writable: true + }, + appendTo: { + value: function (this: T[], b: any[]) { + let len = this.length; + for (let i = 0; i < len; i++) { + b.push(this[i]); + } + }, + writable: true + }, + pushOnce: { + value: function (this: T[], t: T) { + let idx = this.indexOf(t); + if (!~idx) { + idx = this.length; + this[idx] = t; + } + return idx; + }, + writable: true + }, + remove: { + value: function (this: T[], t: T) { + let idx = this.indexOf(t); + if (~idx) { + this.splice(idx, 1); + return true; + } + return false; + }, + writable: true + }, + doSort: { + value: function () { + let key: string, descend: boolean; + let len = arguments.length; + + for (let i = 0; i < len; i++) { + let arg = arguments[i]; + let t = typeof arg; + if (t === "string") { + key = arg; + } else { + descend = !!arg; + } + } + if (key) { + return this.sort((a: any, b: any) => descend ? b[key] - a[key] : a[key] - b[key]); + } else { + return this.sort((a: any, b: any) => descend ? b - a : a - b); + } + }, + writable: true + }, + multiSort: { + value: function (kArr: string[], dArr?: boolean[] | boolean) { + let isArr = Array.isArray(dArr); + return this.sort((a: any, b: any): number => { + const def = Array.SORT_DEFAULT; + for (let idx = 0, len = kArr.length; idx < len; idx++) { + let key = kArr[idx]; + // @ts-ignore + let mode = isArr ? !!dArr[idx] : !!dArr; + let av = a[key]; + let bv = b[key]; + let typea = typeof av; + let typeb = typeof bv; + if (typea == "object" || typeb == "object") { + return 0; + } + else if (typea != typeb) { + if (typea == "undefined") { + // @ts-ignore + bv = def[typeb]; + } else if (typeb == "undefined") { + // @ts-ignore + av = def[typea]; + } + else { + return 0; + } + } + if (av < bv) { + return mode ? 1 : -1; + } else if (av > bv) { + return mode ? -1 : 1; + } else { + continue; + } + } + return 0; + }); + }, + writable: true + }, + spliceOne: { + value: function (index: number): boolean { + if (index === -1 || index >= this.length) { + return false; + } + const len = this.length - 1; + for (let i = index; i < len; i++) { + this[i] = this[i + 1]; + } + this.length = len; + return true; + }, + writable: true + }, + + randomSort: { + value: function () { + for (let j, x, i = this.length; i; j = (Math.random() * i) | 0, x = this[--i], this[i] = this[j], this[j] = x) { + ; + } + }, + writable: true + }, + contains: { + value: function (obj: T | T[] | {} | {}[], child?: string): boolean { + let result = false; + if (child) { + const isArr = Array.isArray(obj); + if (isArr) { + // @ts-ignore + if (obj[0].hasOwnProperty(child)) { + let set0 = new Set(); + // @ts-ignore + for (let s of obj) { + // @ts-ignore + set0.add(s[child]); + } + // @ts-ignore + let set1 = new Set(this.filter (x => set0.has(x))); + return set0.size === set1.size; + } else { + // @ts-ignore + let set0 = new Set(obj); + let set1 = new Set(this.filter ((x: {}) => set0.has(x))); + return set1.size === set0.size; + } + + } else { + if (obj.hasOwnProperty(child)) { + for (let sub of this) { + if (sub.hasOwnProperty(child)) { + // @ts-ignore + if (sub[child] === obj[child]) { + result = true; + break; + } + } + } + } else { + for (let sub of this) { + if (sub.hasOwnProperty(child)) { + // @ts-ignore + if (sub[child] === obj) { + result = true; + break; + } + } + } + } + + } + } else { + // 不指定 比较字段 的话, 只处理2种情况 + // 1: obj 为数组 + // 2: obj 不是数组 + if (Array.isArray(obj)) { + let set0 = new Set(obj); + // @ts-ignore + let set1 = new Set(this.filter (x => set0.has(x))); + return set1.size === set0.size; + } else { + let idx = this.indexOf(obj); + return !!(~idx); + } + + } + return result; + }, + writable: true + }, + randomInsert: { + value: function(arr: Array) { + const length = this.length; + arr.forEach(value => { + this.splice(Math.random() * length, 0 , value); + }) + }, + writable: true + }, + randomGet: { + value: function(count: number = 1):T[] { + let shuffled: T[] = this.slice(0), i = this.length, min = i - count, temp, index; + if (min < 0) { + return shuffled; + } + while (i-- > min) { + index = Math.floor((i + 1) * Math.random()); + temp = shuffled[index]; + shuffled[index] = shuffled[i]; + shuffled[i] = temp; + } + return shuffled.slice(min); + }, + writable: true + }, + randomOne: { + value: function(): T { + let results = this.randomGet(1); + if (results.length > 0) { + return results[0]; + } else { + return null; + } + }, + writable: true + }, + randomRemove: { + value: function(count: number = 1):T[] { + let result = []; + while (count -- > 0 && this.length > 0) { + let index = Math.random() * this.length | 0; + result.push(...this.splice(index, 1)); + } + return result; + }, + writable: true + }, + + moveElement: { + value: function (n: number): T[] { + if (Math.abs(n) > this.length) n = n % this.length; + return this.slice(-n).concat(this.slice(0, -n)); + }, + writable: true + }, + + union: { + value: function (this: T[], b: any[]): T[] { + let a = this.concat(b); + return [...new Set(a)]; + }, + writable: true + }, + + intersect: { + value: function (this: T[], b: any[]): T[] { + let set0 = new Set(b); + let set1 = new Set(this.filter (x => set0.has(x))); + return [...set1]; + }, + writable: true + }, + + difference: { + value: function (this: T[], b: any[]): T[] { + let set0 = new Set(b); + let set1 = new Set(this.filter(x => !set0.has(x))); + return [...set1]; + }, + writable: true + } + +}); diff --git a/src/controllers/CardController.ts b/src/controllers/CardController.ts index 9800b74..1ebcd6f 100644 --- a/src/controllers/CardController.ts +++ b/src/controllers/CardController.ts @@ -4,6 +4,7 @@ import {CardGroup} from "../models/CardGroup"; import {ZError} from "../common/ZError"; import {Card} from "../models/subdoc/Card"; import {MoneyTypeConst} from "../constants/MoneyTypeConst"; +import {BaseConst} from "../constants/BaseConst"; export default class CardController extends BaseController { @router('post /api/:accountid/card_group/:heroid') @@ -102,8 +103,37 @@ export default class CardController extends BaseController { } account.moneys.set(MoneyTypeConst.CARD_SCROLL, money - count); - // TODO: 随机取count张卡牌, 并与当前已有的卡比较, 将新卡添加进用户卡组 + // TODO: 根据配置的概率获取卡 + let cardMap = global.$cfg.get(BaseConst.UNIT); + let cards: any[] = []; + for (let [id, card] of cardMap) { + if (card.unittypei_id == 2) { + cards.push(card); + } + } + let cardsgetd: number[] = cards.randomGet(count).map((o: any) => o.id); + let totalCards = account.cards.union(cardsgetd); + let dulpSet = new Set(account.cards.intersect(cardsgetd)); + let results: any = []; + let expHero = account.moneys.get(MoneyTypeConst.HERO_EXP) || 0; + //TODO: 根据配置设置每张卡分解后得到的经验 + const expPreCard = 10; + for (let id of cardsgetd) { + let data: any = {id}; + if (dulpSet.has(id)) { + data.isnew = false; + data.expdust = MoneyTypeConst.HERO_EXP; + data.dustcount = expPreCard; + expHero += expPreCard; + } else { + data.isnew = true; + } + results.push(data); + } + + account.moneys.set(MoneyTypeConst.HERO_EXP, expHero); + account.cards = totalCards; await account.save(); - return {}; + return results; } }