1
0
Fork 0

init before WX13

This commit is contained in:
WEBXOSS 2016-10-23 13:56:45 +08:00
commit 32979b97c4
30 changed files with 130257 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
trash
.DS_Store
*.sublime-*

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "webxoss-client"]
path = webxoss-client
url = https://github.com/webxoss/webxoss-client.git

175
Callback.js Normal file
View file

@ -0,0 +1,175 @@
'use strict';
/**
* 创建一个Callback对象.
* @class
* @example
* 使用Callback实现的计时器:
* function wait (t) {
* return new Callback(function (callback) {
* setTimeout(callback,t*1000);
* });
* }
* @param {Callback~executor} fn - 用于初始化Callback对象的执行函数.
*/
/**
* 执行函数.
* @callback Callback~executor
* @param {function} 操作完成声明函数,该函数被调用后,
* 由此执行函数创建的Callback对象的状态将变为`Done`,
* 其回调函数队列将被调用,参数将传入第一个回调函数.
*/
function Callback (fn) {
/**
* 回调函数队列.
* @private {function[]}
*/
this._callbacks = [];
/**
* 回调函数的上下文对象队列.
* @private {Object[]}
*/
this._thisArray = [];
/**
* Callback的状态.
* `_done` 为false时表示该Callback处于pending状态.
* `_done` 为true时表示该Callback处于done状态.
* @private {boolean}
*/
this._done = false;
/**
* 调用首个回调函数时的参数.
* @private
*/
this._arg = [];
fn(this._handleCallbacks.bind(this));
}
/**
* 创建一个永远处于pending状态的Callback.
* @returns {Callback} 永远处于pending状态的Callback.
*/
Callback.never = function () {
return new Callback(function () {});
};
/**
* 创建一个立即回调的Callback.
* 通常用于同步操作和异步操作混合的场合,提供语法糖功能.
* @returns {Callback} 立即回调的Callback,调用参数会传递到回调函数.
*/
Callback.immediately = function () {
var arg = arguments;
return new Callback(function (callback) {
callback.apply(this,arg);
});
};
/**
* 异步的循环`forEach`,语法类似于`Array.prototype.forEach`.
* @example
* `arr`为一个数据数组,`doSomethingAsynWith`是一个返回Callback对象的异步处理函数.
* 现在要遍历`arr`,对每一个元素执行异步处理:
* Callback.forEach(arr,function (item,index,arr) {
* return doSomethingAsynWith(item);
* },this).callback(this,function () {
* // all done;
* });
* @returns {Callback}.
*/
Callback.forEach = function (arr,fn,thisp) {
return arr.reduce(function (chain,item,i,array) {
return chain.callback(thisp,fn.bind(thisp,item,i,array));
},Callback.immediately());
};
/**
* 异步的循环`for`.
* @example
* `doSomethingAsyn`是一个返回Callback对象的异步处理函数.
* 现在循环执行10次`doSomethingAsyn`:
* Callback.for(this,1,10,function (i) {
* return doSomethingAsyn(i);
* },this).callback(this,function () {
* // all done;
* });
* @returns {Callback}.
*/
Callback.for = function (thisp,min,max,fn) {
var chain = Callback.immediately();
for (var i = min; i <= max; i++) {
chain.callback(thisp,fn.bind(thisp,i));
}
return chain;
};
/**
* 异步的循环`loop`.
* @example
* `doSomethingAsyn`是一个返回Callback对象的异步处理函数.
* 现在循环执行10次`doSomethingAsyn`:
* Callback.loop(this,10,function () {
* return doSomethingAsyn();
* },this).callback(this,function () {
* // all done;
* });
* @returns {Callback}.
*/
Callback.loop = function (thisp,n,fn) {
var chain = Callback.immediately();
while (n--) {
chain.callback(thisp,fn.bind(thisp));
}
return chain;
}
/**
* 处理回调函数队列. 该方法总是在Callback对象的状态变为done时调用.
* @private
*/
Callback.prototype._handleCallbacks = function () {
this._done = true;
if (this._callbacks.length) {
var callback = this._callbacks.shift();
var thisp = this._thisArray.shift();
var returnValue = callback.apply(thisp,arguments);
if (isObj(returnValue) && isFunc(returnValue.callback)) {
this._done = false;
returnValue.callback(this,this._handleCallbacks);
} else {
this._handleCallbacks(returnValue);
}
} else {
this._arg = arguments;
}
};
/**
* 向Callback对象添加回调函数. 可以链式调用.
* @public
* @param thisp - 绑定的上下文对象.
* @param {function} func - 回调函数.
* 回调函数的返回值为Callback对象时,其后续的回调函数会在Callback的状态变为done时才执行,
* 否则该回调函数执行后,后续的回调函数立即执行,并把返回值作为参数传入.
*/
Callback.prototype.callback = function (thisp,func) {
if (arguments.length !== 2) {
debugger;
console.warn('thisp is not specified!');
func = thisp;
thisp = this;
}
this._thisArray.push(thisp);
this._callbacks.push(func);
if (this._done) this._handleCallbacks.apply(this,this._arg);
return this;
};
global.Callback = Callback;

1395
Card.js Normal file

File diff suppressed because it is too large Load diff

115244
CardInfo.js Normal file

File diff suppressed because it is too large Load diff

248
Client.js Normal file
View file

@ -0,0 +1,248 @@
'use strict';
function Client (manager,socket) {
this.manager = manager;
this.socket = socket;
this.room = null;
this.cfg = null;
this.nickname = '';
this.id = Math.random();
// this.ip = '';
this.onSocketUpdate = null;
socket.emit('client id',this.id);
}
Client.prototype.emit = function () {
return this.socket.emit.apply(this.socket,arguments);
};
Client.prototype.reset = function () {
this.room = null;
this.cfg = null;
this.nickname = '';
};
Client.prototype.updateSocket = function (socket) {
this.socket = socket;
if (this.onSocketUpdate) {
this.onSocketUpdate();
}
};
// Client.prototype.createRoom = function (roomName) {
// this.manager.createRoom(this,roomName);
// };
// Client.prototype.joinRoom = function (roomName) {
// this.manager.joinRoom(this,roomName);
// };
Client.prototype.ready = function (cfg) {
var errMsg;
if (!this.room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (this !== this.room.guest) {
errMsg = 'YOU_ARE_NOT_THE_GUEST';
} else if (this.cfg) {
errMsg = 'YOU_ARE_READY';
} else if (this.room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if (!Game.checkDeck(cfg,this.room.mayusRoom)) {
errMsg = 'INVALID_CONFIG';
}
if (errMsg) {
this.socket.emit('error message',errMsg);
return;
}
this.cfg = cfg;
// this.room.host.socket.emit('ready');
this.room.update();
};
Client.prototype.unready = function () {
var errMsg;
if (!this.room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (this !== this.room.guest) {
errMsg = 'YOU_ARE_NOT_THE_GUEST';
} else if (!this.cfg) {
errMsg = 'YOU_ARE_NOT_READY';
} else if (this.room.game) {
errMsg = 'GAME_ALREADY_STARTED';
}
if (errMsg) {
this.socket.emit('error message',errMsg);
return;
}
this.cfg = null;
// this.room.host.socket.emit('unready');
this.room.update();
};
Client.prototype.startGame = function (cfg) {
var errMsg;
if (!this.room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (this !== this.room.host) {
errMsg = 'YOU_ARE_NOT_THE_HOST';
} else if (!this.room.guest || !this.room.guest.cfg) {
errMsg = 'YOUR_OPPONENT_IS_NOT_READY';
} else if (this.cfg || this.room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if (!Game.checkDeck(cfg,this.room.mayusRoom)) {
errMsg = 'INVALID_CONFIG';
}
if (errMsg) {
this.socket.emit('error message',errMsg);
return;
}
this.cfg = cfg;
var room = this.room;
room.live = !!cfg.live;
var cfg = {
seed: Math.random() * 0xFFFFFFFF >>> 0,
hostMainDeck: room.host.cfg.mainDeck,
hostLrigDeck: room.host.cfg.lrigDeck,
guestMainDeck: room.guest.cfg.mainDeck,
guestLrigDeck: room.guest.cfg.lrigDeck,
hostIO: room.createHostIO(),
guestIO: room.createGuestIO(),
onGameover: this.manager.gameover.bind(this.manager,room)
};
room.game = new Game(cfg);
room.emit('game start');
// 统计数据
var date = new Date();
var day = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate();
if (!this.manager.gameCountMap) this.manager.gameCountMap = {};
if (!this.manager.gameCountMap[day]) this.manager.gameCountMap[day] = 0;
this.manager.gameCountMap[day]++;
// console.log(day);
// var time = new Date().toISOString().replace('T',' ').substr(0,19);
// var count = this.manager.rooms.filter(function (room) {
// return room.game;
// }).length;
// console.log('%s "%s" starts. Count: %s',time,room.name,count);
room.game.start();
this.manager.updateRoomList();
};
Client.prototype.chat = function (msg) {
if (!msg) return;
if (!isStr(msg)) return;
if (msg.length > 256) return;
var room = this.room;
if (!room) return;
if (this.getPosition() === 'live-spectator') {
return;
}
// if (!room.host || !room.guest) return;
// this.socket.emit('chat feedback',msg);
// if (this === room.host) {
// room.guest.socket.emit('chat',msg);
// } else {
// room.host.socket.emit('chat',msg);
// }
var msgObj = {
nickname: this.nickname,
position: this.getPosition(),
content: msg
};
// room.emit('chat',msgObj);
room.getRoomMembers().forEach(function (client) {
client.emit('chat',msgObj);
},this);
};
Client.prototype.surrender = function () {
var errMsg;
var room = this.room;
if (!room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (!room.game || ((this !== room.host) && (this !== room.guest))) {
errMsg = 'YOU_ARE_NOT_BATTLING';
} else if (room.reconnecting) {
errMsg = 'WAITING_FOR_RECONNECT';
}
if (errMsg) {
this.socket.emit('error message',errMsg);
return;
}
if (this === room.host) {
room.emitTo(['guest','guest-spectator'],'opponent surrendered');
room.emitTo(['host','host-spectator','live-spectator'],'surrendered');
} else {
room.emitTo(['host','host-spectator','live-spectator'],'opponent surrendered');
room.emitTo(['guest','guest-spectator'],'surrendered');
}
var arg = {
surrender: (this === room.host)? 'host' : 'guest'
};
room.game.gameover(arg);
};
Client.prototype.drop = function () {
var errMsg;
var room = this.room;
if (!room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (!room.game || ((this !== room.host) && (this !== room.guest))) {
errMsg = 'YOU_ARE_NOT_BATTLING';
}
if (errMsg) {
this.socket.emit('error message',errMsg);
return;
}
if (!room.reconnecting) {
// 发送drop请求后,服务器收到之前,
// 对方可能已经重连,故不发送错误信息.
return;
}
if (this === room.host) {
room.emit('guest disconnected');
} else {
room.emit('host disconnected');
}
this.manager.removeRoom(room);
};
Client.prototype.getPosition = function () {
if (!this.room) return 'none';
if (this.room.host === this) return 'host';
if (this.room.guest === this) return 'guest';
if (inArr(this,this.room.hostSpectatorList)) return 'host-spectator';
if (inArr(this,this.room.guestSpectatorList)) return 'guest-spectator';
return 'live-spectator';
};
Client.prototype.gameover = function () {
this.cfg = null;
if (this.getPosition() === 'live-spectator') {
this.reset();
}
};
Client.prototype.tick = function () {
this.socket.emit('tock');
// var socket = this.socket;
// setTimeout(function() {
// socket.emit('tock');
// }, 300);
};
global.Client = Client;

159
ConstEffect.js Normal file
View file

@ -0,0 +1,159 @@
'use strict';
function ConstEffect (constEffectManager,cfg) {
this.constEffectManager = constEffectManager;
this.source = cfg.source;
this.createTimming = cfg.createTimming;
this.destroyTimmings = concat(cfg.destroyTimming || []);
// this.triggerTimmings = concat(cfg.triggerTimming || []);
this.cross = cfg.cross;
this.condition = cfg.condition;
this.action = cfg.action;
this.fixed = !!cfg.fixed; // 表示只进行一次计算,此后不发生变化
this.computed = false;
this.continuous = !!cfg.continuous; // 表示不会因失去能力而无效,如<黑幻虫 蝎>
this.reregister = false; // 针对卢浮宫的重注册机制,可改变注册时间(在效果队列中的序号)
this.masksSet = [];
this.masksAdd = [];
this.masksEffectFilters = [];
if (!this.createTimming) {
this.create();
} else {
this.createTimming.addFunction(this.create.bind(this),cfg.once);
}
}
// 创建并添加常时效果
ConstEffect.prototype.create = function () {
this.constEffectManager.addConstEffect(this);
this.computed = false;
this.destroyTimmings.forEach(function (timming) {
timming.addConstEffectDestroy(this);
},this);
// this.triggerTimmings.forEach(function (timming) {
// timming.addConstEffect(this);
// },this);
// this.trigger();
};
// 销毁并移除常时效果
ConstEffect.prototype.destroy = function () {
this.clear();
this.destroyTimmings.forEach(function (timming) {
timming.removeConstEffectDestroy(this);
},this);
// this.triggerTimmings.forEach(function (timming) {
// timming.removeConstEffect(this);
// },this);
this.constEffectManager.removeConstEffect(this);
};
// 触发(清除并重新记录数据,但不计算)
// 计算仅在ConstEffectManager.compute()处执行.
// ConstEffect.prototype.trigger = function () {
// this.clear();
// };
ConstEffect.prototype.compute = function () {
if (this.fixed && this.computed) return;
this.clear();
if (this.cross && !this.source.crossed) return;
if (!this.condition || this.condition.call(this.source)) {
var control = {
reregister: false
};
this.action.call(this.source,this.set.bind(this),this.add.bind(this),control);
this.reregister = control.reregister;
}
this.computed = true;
};
// 清除数据
ConstEffect.prototype.clear = function () {
this.masksSet.length = 0;
this.masksAdd.length = 0;
this.masksEffectFilters.length = 0;
// [this.tableSet,this.tableAdd].forEach(function (table) {
// for (var hash in table) {
// var mask = table[hash];
// delete table[hash];
// this.constEffectManager.compute(mask.target,mask.prop);
// }
// },this);
};
// 设置(target 的 prop 属性的值改变为value)
ConstEffect.prototype.set = function (target,prop,value,arg) {
this._setAdd(this.masksSet,target,prop,value,arg);
};
// 增加(target 的 prop 属性的值增加value, value 可为负数)
ConstEffect.prototype.add = function (target,prop,value,arg) {
// <幻兽神 狮王> & <幻兽 苍龙>
if (!arg || !arg.asCost) { // <幻兽 骆驼>
if (prop === 'power') {
if (this.source.player.powerChangeBanned) return;
if (target.powerAddProtected && (this.source.player !== target.player)) return;
}
}
// <VIER=维克斯>
var flag = this.source.player._VierVX &&
(prop === 'power') &&
(value > 0) &&
// (this.source.type === 'SIGNI') &&
(target.type === 'SIGNI') &&
(target.player === this.source.player);
if (flag) {
value = -value;
}
// <暴力飞溅>
var count = this.source.player._ViolenceSplashCount;
flag = (count > 0) &&
(prop === 'power') &&
(value < 0) &&
(this.source.type === 'SIGNI') &&
(target.type === 'SIGNI') &&
(target.player !== this.source.player);
if (flag) {
value *= Math.pow(2,count);
}
// <DREI=恶英娘>
count = this.source.player._DreiDioDaughter;
flag = (count > 0) &&
(prop === 'power') &&
(value < 0) &&
(target.type === 'SIGNI') &&
(target.player !== this.source.player);
if (flag) {
value *= Math.pow(2,count);
}
if (prop === 'effectFilters') {
this._setAdd(this.masksEffectFilters,target,prop,value,arg)
} else if (isObj(value) && (value.constructor === Effect)) {
this._setAdd(this.masksSet,target,prop,value,arg);
} else {
this._setAdd(this.masksAdd,target,prop,value,arg);
}
};
ConstEffect.prototype._setAdd = function (masks,target,prop,value,arg) {
if (!arg) arg = {};
var mask = new Mask(target,prop,value,!!arg.forced);
if (!mask.checkFilter(this.source)) return;
this.constEffectManager.setBase(target,prop);
masks.push(mask);
};
ConstEffect.prototype.checkFilter = function (mask) {
if (this.fixed && this.computed) return true;
return mask.checkFilter(this.source);
};
global.ConstEffect = ConstEffect;

211
ConstEffectManager.js Normal file
View file

@ -0,0 +1,211 @@
'use strict';
function ConstEffectManager (game) {
this.game = game;
this.constEffects = [];
this.tableBase = {};
}
ConstEffectManager.prototype.addConstEffect = function (constEffect) {
this.constEffects.unshift(constEffect);
};
ConstEffectManager.prototype.removeConstEffect = function (constEffect) {
removeFromArr(constEffect,this.constEffects);
};
ConstEffectManager.prototype.setBase = function (target,prop) {
var hash = target.gid + prop;
if (hash in this.tableBase) return;
var value = target[prop];
if (value === undefined) value = 0;
// if (isArr(value)) {
// value = [];
// }
if (isObj(value) && (value.constructor === Timming)) {
value = null;
}
this.tableBase[hash] = new Mask(target,prop,value);
};
ConstEffectManager.prototype.compute = function () {
// console.log('ConstEffectManager.compute();');
// <DREI=漆料>
var signis = concat(this.game.turnPlayer.signis,this.game.turnPlayer.opponent.signis);
var powers = signis.map(function (signi) {
return signi.power;
},this);
// 预计算
// 针对卢浮宫,加入重注册机制,重注册的效果在效果队列中移至队头
var sorted = [];
this.constEffects.forEach(function (constEffect) {
constEffect.compute();
if (constEffect.reregister) {
constEffect.reregister = false;
sorted.unshift(constEffect);
} else {
sorted.push(constEffect);
}
},this);
this.constEffects = sorted;
// 恢复原始数值 (并记录 abilityLost)
var oldAbilityLostMap = {};
for (var hash in this.tableBase) {
var mask = this.tableBase[hash];
if (mask.prop === 'abilityLost') {
oldAbilityLostMap[mask.target.gid] = mask.target.abilityLost;
}
mask.set(true);
}
this.game.cards.forEach(function (card) {
card.registeredEffects.length = 0;
},this);
// this.tableBase = {};
// 设置 effectFilters (具有最高优先级)
var cardsEffectFilters = [];
this.constEffects.forEach(function (constEffect) {
constEffect.masksEffectFilters.forEach(function (mask) {
mask.add();
cardsEffectFilters.push(mask.target);
},this);
},this);
// 获得"失去能力"的卡.
var cardsAbilityLost = [];
for (var i = 0; i < this.constEffects.length; i++) {
var constEffect = this.constEffects[i];
var source = constEffect.source;
if (inArr(source,cardsAbilityLost)) continue;
constEffect.masksSet.forEach(function (mask) {
if (mask.prop !== 'abilityLost') return;
if (!constEffect.checkFilter(mask)) return;
cardsAbilityLost.push(mask.target);
});
};
// 清除 effectFilters
cardsEffectFilters.forEach(function (card) {
card.effectFilters.length = 0;
},this);
// 销毁一次性的,以"失去能力"的卡为对象的 mask.
// TODO...
// 过滤 source 为"失去能力"的卡 的效果
var constEffects = this.constEffects.filter(function (constEffect) {
if (constEffect.continuous) return true; // "失去能力"仍有效的,如<黑幻虫 蝎>
if (constEffect.fixed && constEffect.computed) return true;
return !inArr(constEffect.source,cardsAbilityLost);
},this);
// 设置 effectFilters
constEffects.forEach(function (constEffect) {
constEffect.masksEffectFilters.forEach(function (mask) {
if (!constEffect.checkFilter(mask)) return;
mask.add();
},this);
},this);
constEffects.reverse();
// 设置变化值
var hashes = [];
constEffects.forEach(function (constEffect) {
constEffect.masksSet.forEach(function (mask) {
// var hash = mask.target.gid + mask.prop;
// if (inArr(hash,hashes)) return; // 相当于后来的覆盖先前的
if (!constEffect.checkFilter(mask)) return;
// hashes.push(hash);
mask.set();
},this);
},this);
// 设置增加值
constEffects.forEach(function (constEffect) {
constEffect.masksAdd.forEach(function (mask) {
if (!constEffect.checkFilter(mask)) return;
mask.add();
},this);
},this);
// 处理一些非负属性
for (var hash in this.tableBase) {
var mask = this.tableBase[hash];
var prop = mask.prop;
var target = mask.target;
var nonNegativeProps = [
'costColorless',
'costWhite',
'costBlack',
'costRed',
'costBlue',
'costGreen',
'limit',
'level'
];
if (inArr(prop,nonNegativeProps)) {
if (target[prop] < 0) {
target[prop] = 0;
}
}
}
// 移除已触发,但效果源"失去能力"的效果
cardsAbilityLost.forEach(function (card) {
if (!oldAbilityLostMap[card.gid]) {
// 从"不失去能力"变为"失去能力"
this.game.effectManager.removeTriggeredEffectBySource(card);
}
},this);
signis.forEach(function (signi,i) {
var newPower = signi.power
if (newPower < 0) signi.power = 0;
if (signi.power !== powers[i]) {
var event = {
card: signi,
oldPower: powers[i],
newPower: newPower,
power: signi.power,
};
signi.onPowerChange.trigger(event,true);
}
// <花音>和<七草>的处理
if ((signi.cid === 305) || (signi.cid === 1183)) {
signi.onPowerUpdate.trigger(null,true);
}
},this);
// 向 UI 输出卡片状态(LRIG 和 SIGNI 的力量,冻结,枪兵等)
// this.game.informPower();
this.game.outputCardStates();
};
// ConstEffectManager.prototype.computeTimming = function (hash,timming) {
// timming.effects.length = 0;
// this.constEffects.forEach(function (constEffect) {
// if (hash in constEffect.tableAdd) {
// var cfg = constEffect.tableAdd[hash].value;
// var effect = new Effect(this.game.effectManager,cfg);
// timming.effects.push(effect);
// }
// },this);
// };
ConstEffectManager.prototype.getOriginalValue = function (target,prop) {
var hash = target.gid + prop;
if (hash in this.tableBase) {
return this.tableBase[hash].value;
} else {
return target[prop];
}
};
global.ConstEffectManager = ConstEffectManager;

141
Effect.js Normal file
View file

@ -0,0 +1,141 @@
'use strict';
/*
Effect 对象注册在 Timming ,
触发时以自己为原型创建一个拷贝,
在拷贝上附加 proto,event,triggerZone 等属性.
这个拷贝注册到 EffectManager .
*/
function Effect (effectManager,cfg) {
this.effectManager = effectManager;
this.source = cfg.source;
this.description = cfg.description;
this.optional = cfg.optional;
this.once = !!cfg.once; // 1回合只能发动一次.
this.isBurst = cfg.isBurst; // 用于"迸发效果发动时"时点.
// 以及"发动和解决要在相同场所"的规则例外
this.triggerCondition = cfg.triggerCondition;
this.costWhite = cfg.costWhite;
this.costBlack = cfg.costBlack;
this.costRed = cfg.costRed;
this.costBlue = cfg.costBlue;
this.costGreen = cfg.costGreen;
this.costColorless = cfg.costColorless;
this.costDown = cfg.costDown;
this.costExceed = cfg.costExceed;
this.costCondition = cfg.costCondition;
this.costAsyn = cfg.costAsyn;
this.costChange = cfg.costChange;
this.condition = cfg.condition;
this.actionAsyn = cfg.actionAsyn;
this.disabled = false; // 失去能力时设置为 true .
}
Effect.prototype.trigger = function (event) {
if (this.disabled) return;
if (this.once && inArr(this,this.effectManager.triggeredEffects)) {
return;
}
if (!this.triggerCondition || this.triggerCondition.call(this.source,event)) {
this.source.activate();
var effect = Object.create(this);
effect.proto = this;
effect.event = event;
effect.triggerZone = this.source.zone;
this.effectManager.addTriggeredEffect(effect);
}
};
Effect.prototype.triggerAndHandleAsyn = function (event) {
if (this.disabled) return Callback.immediately();
if (!this.triggerCondition || this.triggerCondition.call(this.source,event)) {
// this.source.activate();
var effect = Object.create(this);
effect.event = event;
effect.triggerZone = this.source.zone;
if (!this.checkCondition()) return Callback.immediately();
return effect.handleAsyn(false);
}
return Callback.immediately();
};
Effect.prototype.checkCondition = function () {
// "结束这个回合",如<终结之洞>
var game = this.effectManager.game;
if (game.getData(game,'endThisTurn')) return;
// "1回合1次"
if (this.once && inArr(this.proto,this.effectManager.triggeredEffects)) {
return;
}
// 隐藏规则之"发动和解决的场所必须一致"
if (!this.isBurst && this.triggerZone) { // 排除迸发
if (this.triggerZone.faceup) { // 公开领域
if (this.triggerZone !== this.source.zone) { // 发动和解决场所不一致
return false;
}
}
}
if (this.condition && !this.condition.call(this.source,this.event)) {
return false;
}
return this.source.player.enoughCost(this);
};
Effect.prototype.handleAsyn = function (needConfirm) {
var player = this.source.player;
if (!this.isOptional()) {
return Callback.immediately().callback(this,function () {
if (this.once) {
this.effectManager.triggeredEffects.push(this.proto);
}
return this.actionAsyn.call(this.source,this.event,{});
});
}
if (player.enoughCost(this)) {
return Callback.immediately().callback(this,function () {
if (!needConfirm) return this.source;
return player.selectOptionalAsyn('LAUNCH',[this.source]);
}).callback(this,function (card) {
if (!card) return;
if (this.once) {
this.effectManager.triggeredEffects.push(this.proto);
}
var costArg;
return player.payCostAsyn(this).callback(this,function (arg) {
costArg = arg;
this.source.activate();
if (this.isBurst) {
this.source.player.onBurstTriggered.trigger();
}
this.effectManager.currentEffect = this;
return this.actionAsyn.call(this.source,this.event,costArg);
}).callback(this,function () {
if (this.isBurst && this.source.player.burstTwice) {
// <Burst Rush> 再处理一次.
this.source.activate();
this.source.player.onBurstTriggered.trigger();
return this.actionAsyn.call(this.source,this.event,costArg);
}
}).callback(this,function () {
this.effectManager.currentEffect = null;
});
});
}
return Callback.immediately();
};
Effect.prototype.end = function () {
if (!this.isBurst) return;
var card = this.source;
card.handleBurstEnd(this.event.crossLifeCloth);
};
Effect.prototype.isOptional = function () {
return this.optional || this.source.player.needCost(this);
};
global.Effect = Effect;

120
EffectManager.js Normal file
View file

@ -0,0 +1,120 @@
'use strict';
function EffectManager (game) {
this.game = game;
this.player = null;
this.playerTriggeredEffects = [];
this.opponentTriggeredEffects = [];
this.triggeredEffects = []; // 1回合1次的效果
this.currentEffect = null;
}
// 根据 this.player 返回相应的满足发动条件的 effects .
EffectManager.prototype.getPlayerEffects = function () {
var effects = (this.player === this.game.turnPlayer)? this.playerTriggeredEffects : this.opponentTriggeredEffects;
effects = effects.filter(function (effect) {
return effect.checkCondition();
},this);
return effects;
};
EffectManager.prototype.clearPlayerEffects = function () {
var effects = (this.player === this.game.turnPlayer)? this.playerTriggeredEffects : this.opponentTriggeredEffects;
this.game.pushEffectSource(null);
effects.forEach(function (effect) {
effect.end();
},this);
this.game.popEffectSource();
effects.length = 0;
};
EffectManager.prototype.removePlayerEffect = function (effect) {
var effects = (this.player === this.game.turnPlayer)? this.playerTriggeredEffects : this.opponentTriggeredEffects;
removeFromArr(effect,effects);
// 迸发结束
var flag = effect.isBurst && effects.every(function (eff) {
return !eff.isBurst || (eff.source !== effect.source);
},this);
if (!flag) return;
this.game.pushEffectSource(null);
effect.end();
this.game.popEffectSource();
};
EffectManager.prototype.addTriggeredEffect = function (effect) {
if (effect.source.player === this.game.turnPlayer) {
this.playerTriggeredEffects.push(effect);
} else {
this.opponentTriggeredEffects.push(effect);
}
};
EffectManager.prototype.removeTriggeredEffectBySource = function (source) {
[this.playerTriggeredEffects,this.opponentTriggeredEffects].forEach(function (effects) {
for (var i = 0; i < effects.length; i++) {
var effect = effects[i];
if (effect.source === source) {
effects.splice(i,1);
i--;
}
}
})
};
EffectManager.prototype.handleEffectsAsyn = function () {
// 过滤不满足条件的效果
// this.playerTriggeredEffects = this.playerTriggeredEffects.filter(function (effect) {
// return (!effect.condition || effect.condition.call(effect.source,effect.event));
// },this);
// this.opponentTriggeredEffects = this.opponentTriggeredEffects.filter(function (effect) {
// return (!effect.condition || effect.condition.call(effect.source,effect.event));
// },this);
// 回合玩家具有优先权
// if (!this.player) {
// this.player = this.game.turnPlayer;
// }
this.player = this.game.turnPlayer;
var effects = this.getPlayerEffects();
// 若优先玩家无触发效果,移交优先权
if (!effects.length) {
this.clearPlayerEffects();
this.player = this.player.opponent;
effects = this.getPlayerEffects();
}
// 若双方均无触发效果,结束处理
if (!effects.length) {
// this.player = null;
this.clearPlayerEffects();
return Callback.immediately();
}
// 处理优先玩家的触发效果
var allOptional = effects.every(function (effect) {
return effect.isOptional();
},this);
var needConfirm = true;
return Callback.immediately().callback(this,function () {
if (effects.length === 1) return effects[0];
needConfirm = false;
if (allOptional) {
return this.player.selectOptionalAsyn('EFFECTS',effects);
}
return this.player.selectAsyn('EFFECTS',effects);
}).callback(this,function (effect) {
if (!effect) {
this.clearPlayerEffects();
return Callback.immediately();
}
// removeFromArr(effect,effects);
return this.game.blockAsyn(effect.source,this,function () {
return effect.handleAsyn(needConfirm).callback(this,function () {
this.removePlayerEffect(effect);
});
});
});
};
global.EffectManager = EffectManager;

88
FakeSocket.js Normal file
View file

@ -0,0 +1,88 @@
'use strict';
window.FakeSocket = (function () {
var sockets = [];
window.addEventListener('message',function (event) {
var win = event.source;
var name = event.data.name;
var data = event.data.data;
if ((name !== 'tick') && (name !== 'tock')) console.log(JSON.stringify(event.data));
for (var i = 0; i < sockets.length; i++) {
var socket = sockets[i];
if (socket._win === win) {
socket._doEmit(name,data);
return;
}
}
});
function FakeSocket (win) {
this._win = win;
this._listeners = {};
this.id = '<' + sockets.push(this) + '>';
this.io = {
reconnection: function () {},
opts: {
query: ''
}
}
}
FakeSocket.prototype.on = function (name,handler) {
if (!this._listeners[name]) this._listeners[name] = [];
this._listeners[name].push(handler);
};
FakeSocket.prototype.emit = function (name,data) {
this._win.postMessage({
name: name,
data: data
},'*');
};
FakeSocket.prototype._doEmit = function (name,data) {
var listeners = this._listeners[name];
if (listeners) {
listeners.forEach(function (listener) {
listener(data);
});
}
};
FakeSocket.prototype.disconnect = function () {
this.emit('disconnect');
};
FakeSocket.prototype.removeAllListeners = function (name) {
var listeners = this._listeners[name];
if (listeners) {
listeners.length = 0;
}
};
return FakeSocket;
})();

1074
Game.js Normal file

File diff suppressed because it is too large Load diff

73
IO.js Normal file
View file

@ -0,0 +1,73 @@
'use strict';
/*
服务器 -> 客户端:
msg = {
buffer: [{
id: id,
data: []
}]
}
客户端 -> 服务器
msg = {
id: id,
data: {
label: label,
input: []
}
}
*/
function IO (client,getSpectators) {
this.client = client;
this.getSpectators = getSpectators || function () {
return [];
};
this.listener = null;
this.lastId = 0;
this.id = 0;
this.buffer = [];
this.setSocketListener();
client.onSocketUpdate = function () {
this.setSocketListener();
this.resend();
}.bind(this);
};
IO.prototype.setSocketListener = function () {
this.client.socket.removeAllListeners('gameMessage');
this.client.socket.on('gameMessage',function (msg) {
if (this.lastId === msg.id) {
// console.log('same id.');
return;
};
this.buffer.length = 0;
if (this.listener) {
this.listener(msg.data);
}
}.bind(this));
};
IO.prototype.send = function (data) {
var obj = {
id: this.id,
data: data
};
this.id++;
// 将要发送的数据缓存,以便重新连接时重发.
this.buffer.push(obj);
// 现在发送的信息不包括缓存的数据.
var msg = {buffer: [obj]};
this.client.emit('gameMessage',msg);
this.getSpectators().forEach(function (spectator) {
spectator.emit('gameMessage',msg);
},this);
};
IO.prototype.resend = function () {
var msg = {buffer: this.buffer};
// console.log(msg);
this.client.emit('gameMessage',msg);
};
global.IO = IO;

19
IO_Node.js Normal file
View file

@ -0,0 +1,19 @@
'use strict';
function IO_Socket (socket) {
this.socket = socket;
this.listener = null;
this.socket.on('gameMessage',function (data) {
// TODO:
// check data
if (this.listener) {
this.listener.call(null,data);
}
}.bind(this));
};
IO_Socket.prototype.send = function (data) {
this.socket.emit('gameMessage',data);
};
global.IO = IO;

77
Mask.js Normal file
View file

@ -0,0 +1,77 @@
'use strict';
function Mask (target,prop,value,forced) {
this.target = target;
this.prop = prop;
this.value = value;
this.forced = forced;
if ((prop === 'onBurst') && this.value) {
this.value.isBurst = true;
}
}
Mask.prototype.set = function (reset) {
var target = this.target;
var item = target[this.prop];
if (item === undefined) {
debugger;
console.warn('Mask.set(): target.pid:%s,this.prop:%s,this.value:%s',target.pid,this.prop,this.value);
} else if (isObj(item) && (item.constructor === Timming)) {
// item.effects.length = 0;
if (reset) {
item.effects.length = 0;
return;
}
var timming = item;
var effect = this.value;
var source = effect.source;
if (source.canNotGainAbility || source.player.canNotGainAbility) {
// 不能获得新能力
return;
}
timming.effects.push(effect);
effect.source.registeredEffects.push(effect);
return;
} else if (isArr(item)) {
target[this.prop] = this.value.slice();
} else if (this.prop === 'abilityLost') {
target.abilityLost = this.value;
if (!target.abilityLost) return;
Card.abilityProps.forEach(function (prop) {
target[prop] = false;
},this);
target.registeredEffects.forEach(function (effect) {
effect.disabled = true;
},this);
} else {
if (!reset && inArr(this.prop,Card.abilityProps) && (target.canNotGainAbility || target.player.canNotGainAbility)) {
// 不能获得新能力
return;
}
target[this.prop] = this.value;
}
};
Mask.prototype.add = function (reset) {
var item = this.target[this.prop];
if (isArr(item)) {
var arr = item;
arr.push(this.value);
return;
}
if (isObj(item) && (item.constructor === Timming)) {
return;
}
this.target[this.prop] += this.value;
};
Mask.prototype.checkFilter = function (source) {
if (this.forced) return true;
var target = this.target;
if (!target.isEffectFiltered) return true;
return !target.isEffectFiltered(source);
};
global.Mask = Mask;

375
Phase.js Normal file
View file

@ -0,0 +1,375 @@
'use strict';
function Phase (game) {
// 引用
this.game = game;
// 快捷方式
this.player = game.turnPlayer;
this.opponent = game.turnPlayer.opponent;
// 基本属性
this.firstTurn = true;
this.status = '';
this.additionalTurn = false; // 是否是由于效果追加的回合
// 注册
game.register(this);
// 时点
this.onTurnStart = new Timming(game);
this.onTurnEnd = new Timming(game);
}
// 游戏开始前的"setup"阶段:
// 1. 双方洗牌;
// 2. 双方选择等级0的LRIG背面放置到LRIG区;
// 3. 双方猜拳决定先手;
// 4. 双方抽5张卡;
// 5. 双方选择任意数量手牌放回,洗牌后,抽出相同张数的卡; (换手牌)
// 6. 双方从牌组最上方取7张卡放入生命护甲区;
// 7. 双方大喊"OPEN!!",并将LRIG翻至正面.
// 这之后进入先手玩家的竖置阶段.
Phase.prototype.setup = function () {
this.player.shuffle();
this.opponent.shuffle();
this.game.blockStart();
this.game.frameStart();
this.player.setupLrigAsyn().callback(this,function () {
return this.opponent.setupLrigAsyn();
}).callback(this,function () {
return this.game.decideFirstPlayerAsyn();
}).callback(this,function (player) {
this.game.turnPlayer = player;
this.player = player;
this.opponent = player.opponent;
this.game.packOutputs(function () {
this.player.setupHands();
this.opponent.setupHands();
},this);
return this.player.redrawAsyn();
}).callback(this,function () {
return this.opponent.redrawAsyn();
}).callback(this,function () {
this.game.packOutputs(function () {
this.player.setupLifeCloth();
this.opponent.setupLifeCloth();
},this);
this.game.packOutputs(function () {
this.player.open();
this.opponent.open();
},this);
this.game.outputColor();
this.game.frameEnd();
return this.game.blockEndAsyn();
}).callback(this,function () {
this.upPhase();
});
}
// 竖直阶段:
// 1. 回合玩家将其LRIG和全部SIGNI竖置.
// 这之后进入抽卡阶段.
Phase.prototype.upPhase = function () {
this.status = 'upPhase';
this.game.handleFrameEnd();
this.game.blockStart();
this.player.up();
this.game.blockEndAsyn().callback(this,this.drawPhase);
};
// 抽卡阶段:
// 1. 回合玩家抽2张卡. (若该回合为先手第一回合,则抽1张)
// 这之后进入充能阶段.
Phase.prototype.drawPhase = function () {
this.status = 'drawPhase';
this.game.handleFrameEnd();
this.game.blockStart();
if (this.firstTurn) {
this.player.draw(1);
} else {
this.player.draw(this.player.drawCount);
}
this.game.blockEndAsyn().callback(this,this.enerPhase);
};
// 充能阶段:
// 1. 回合玩家可以进行1次主动充能. (主动充能: 选择自己的一张手牌或场上的一只SIGNI,将其置于能量区)
// 这之后进入成长阶段.
Phase.prototype.enerPhase = function () {
if (this.player.skipEnerPhase) {
this.growPhase();
return;
}
this.status = 'enerPhase';
this.game.handleFrameEnd();
this.player.chargeAsyn().callback(this,this.growPhase);
this.player.endEnerPhaseAsyn().callback(this,this.growPhase);
};
// 成长阶段:
// 1. 回合玩家可以进行主动成长.
// 这之后进入主要阶段.
Phase.prototype.growPhase = function () {
if (this.player.skipGrowPhase) {
this.mainPhase();
return;
}
this.status = 'growPhase';
this.game.handleFrameEnd();
return this.game.blockAsyn(this,function () {
this.player.onGrowPhaseStart.trigger();
}).callback(this,function () {
this.player.growAsyn().callback(this,this.mainPhase);
this.player.endGrowPhaseAsyn().callback(this,this.mainPhase);
});
};
// 主要阶段:
// 回合玩家可以按任意顺序执行任意次以下行动:
// * 召唤SIGNI;
// * 废弃SIGNI;
// * 使用魔法;
// * 使用技艺;
// * 使用起动效果.
// 这之后进入攻击阶段. (若该回合为先手第一回合,则逃过攻击阶段,直接进入结束阶段)
Phase.prototype.mainPhase = function () {
this.status = 'mainPhase';
this.game.handleFrameEnd();
// 由于<白罗星 海王星>的效果,需要在进入主要阶段时重置 SIGNI .
this.game.blockAsyn(this,function () {
this.game.frameStart();
return this.player.resetSignisAsyn().callback(this,function () {
this.game.frameEnd();
});
}).callback(this,function () {
return this.game.blockAsyn(this,function () {
this.player.onMainPhaseStart.trigger();
});
}).callback(this,function () {
function loop () {
if (this.checkForcedEndTurn()) {
this.endPhase();
return;
}
this.player.summonSigniAsyn().callback(this,loop);
this.player.summonResonaSigniAsyn().callback(this,loop);
this.player.trashSigniAsyn().callback(this,loop);
this.player.useSpellAsyn().callback(this,loop);
this.player.useMainPhaseArtsAsyn().callback(this,loop);
this.player.useActionEffectAsyn().callback(this,loop);
this.player.endMainPhaseAsyn().callback(this,function () {
if (this.firstTurn) {
this.endPhase();
} else {
this.attackPhase();
}
});
}
loop.call(this);
});
};
// 攻击阶段:
// 攻击阶段分为3个步骤:
// 1. 技艺使用步骤
// 2. SIGNI攻击步骤
// 3. LRIG攻击步骤
// WIXOSS官方规则有第四个步骤:防御步骤,
// 而这里,防御步骤归入LRIG攻击步骤.
Phase.prototype.attackPhase = function () {
this.status = 'beforeArtsStep';
return this.game.blockAsyn(this,function () {
// <雪月風火 花代・肆>
if (this.player.discardOnAttackPhase) {
this.game.trashCards(this.player.hands);
this.player.discardOnAttackPhase = false;
}
this.player.onAttackPhaseStart.trigger();
}).callback(this,function () {
this.artsStep();
});
};
// 技艺使用步骤:
// 1. 回合玩家可以发动任意次[攻击阶段]的技艺;
// 2. 对方玩家可以发动任意次[攻击阶段]的技艺;
// 这之后进入SIGNI攻击步骤.
Phase.prototype.artsStep = function () {
this.status = 'artsStep';
this.game.handleFrameEnd();
function playerLoop () {
if (this.checkForcedEndTurn()) {
this.endPhase();
return;
}
this.player.useAttackPhaseArtsAsyn().callback(this,playerLoop);
this.player.useAttackPhaseActionEffect().callback(this,playerLoop);
this.player.summonResonaSigniAsyn().callback(this,playerLoop);
this.player.endArtsStepAsyn().callback(this,opponentLoop);
}
function opponentLoop () {
if (this.game.getData(this.game,'endAttackPhase')) {
this.endPhase();
return;
}
this.opponent.useAttackPhaseArtsAsyn().callback(this,opponentLoop);
this.opponent.useAttackPhaseActionEffect().callback(this,opponentLoop);
this.opponent.summonResonaSigniAsyn().callback(this,opponentLoop);
this.opponent.endArtsStepAsyn().callback(this,this.signiAttackStep);
}
playerLoop.call(this);
};
// SIGNI攻击步骤:
// 1. 回合玩家可以执行任意次"SIGNI攻击". ("SIGNI攻击"的流程见 Player.js)
// 这之后进入LRIG攻击步骤.
Phase.prototype.signiAttackStep = function () {
if (this.player.skipSigniAttackStep) {
this.lrigAttackStep();
return;
}
this.status = 'signiAttackStep';
this.game.handleFrameEnd();
function loop () {
if (this.checkForcedEndTurn()) {
this.endPhase();
return;
}
this.player.signiAttackAsyn().callback(this,loop);
this.player.endSigniAttackStepAsyn().callback(this,this.lrigAttackStep);
}
loop.call(this);
};
// LRIG攻击步骤:
// 1.回合玩家可以执行任意次"LRIG攻击". ("LRIG攻击"的流程见 Player.js)
// 这之后进入结束阶段.
Phase.prototype.lrigAttackStep = function () {
if (this.player.skipLrigAttackStep) {
this.endPhase();
return;
}
this.status = 'lrigAttackStep';
this.game.handleFrameEnd();
function loop () {
if (this.checkForcedEndTurn()) {
this.endPhase();
return;
}
this.player.lrigAttackAsyn().callback(this,loop);
this.player.endLrigAttackStepAsyn().callback(this,this.endPhase);
}
loop.call(this);
};
// 结束阶段:
// 1. 回合玩家的手牌数多于6张的场合,回合玩家舍弃任意手牌至手牌数为6.
// 这之后交换回合. (wixoss)
Phase.prototype.endPhase = function () {
this.status = 'endPhase';
this.player.rebuildCount = 0;
this.player.opponent.rebuildCount = 0;
this.player.ignoreGrowCost = false;
this.game.effectManager.triggeredEffects.length = 0; // 1回合1次的效果.
this.game.handleFrameEnd();
Callback.immediately().callback(this,function () {
// 处理"回合结束时,把XXX放到废弃区".
// 同时触发"回合结束时"时点.
var cards = concat(this.player.signis,this.player.opponent.signis).filter(function (signi) {
if (signi._trashWhenTurnEnd) {
signi._trashWhenTurnEnd = false;
return true;
}
},this);
[this.player,this.player.opponent].forEach(function (player) {
if (player._trashLifeClothCount) {
cards = cards.concat(player.lifeClothZone.getTopCards(player._trashLifeClothCount));
player._trashLifeClothCount = 0;
}
if (this.game.getData(player,'trashAllHandsWhenTurnEnd')) {
cards = cards.concat(player.hands);
}
},this);
return this.game.blockAsyn(this,function () {
// 注:
// 根据官方解释,"回合结束时"的触发时点,在弃牌之前,
// 而常时效果的销毁在弃牌之后.
// 这里用两个时点加以区分:
// player.onTurnEnd 指弃牌之后,
// player.onTurnEnd2 指弃牌之前.
this.player.onTurnEnd2.trigger();
this.game.trashCards(cards);
});
}).callback(this,function () {
// 弃牌
var n = this.player.hands.length - 6;
if (n > 0) {
this.game.blockAsyn(this,function () {
return this.player.discardAsyn(n);
}).callback(this,function () {
this.wixoss();
});
} else {
this.wixoss();
}
});
};
// 交换回合
// 1. 触发"回合结束"时点;
// 2. 交换回合;
// 3. 触发"回合开始"时点.
// 这之后进入回合玩家的竖置阶段.
Phase.prototype.wixoss = function () {
this.additionalTurn = !!this.game.getData(this.player,'additionalTurn');
this.game.clearData();
this.status = '';
this.player.usedActionEffects.length = 0;
this.player.opponent.usedActionEffects.length = 0;
this.player.chain = null;
this.player.opponent.chain = null;
this.player.attackCount = 0;
this.player.opponent.attackCount = 0;
this.firstTurn = false;
this.game.blockAsyn(this,function () {
this.game.frameStart();
this.player.onTurnEnd.trigger();
this.onTurnEnd.trigger({
player: this.player
});
this.game.frameEnd();
}).callback(this,function () {
if (!this.additionalTurn) {
var tmp = this.player;
this.player = this.opponent;
this.opponent = tmp;
}
this.game.turnPlayer = this.player;
this.game.frameStart();
this.player.onTurnStart.trigger();
this.onTurnStart.trigger({
player: this.player
});
this.game.frameEnd();
this.upPhase();
});
};
Phase.prototype.isAttackPhase = function () {
return inArr(this.status,['beforeArtsStep','artsStep','signiAttackStep','lrigAttackStep']);
};
Phase.prototype.checkForcedEndTurn = function () {
if (this.player.rebuildCount > 1) return true;
if (this.game.getData(this.game,'endThisTurn')) return true;
return false;
};
global.Phase = Phase;

103
Phase_backup.js Normal file
View file

@ -0,0 +1,103 @@
'use strict';
var player,opponent;
function setup () {
player = game.hostPlayer;
opponent = game.guestPlayer;
player.shuffle();
opponent.shuffle();
player.setupLrigAsyn().callback(function () {
return opponent.setupLrigAsyn();
}).callback(function () {
return game.decideFirstPlayerAsyn();
}).callback(function () {
player.setupHands();
opponent.setupHands();
return player.redrawAsyn();
}).callback(function () {
return opponent.redrawAsyn();
}).callback(function () {
player.setupLifeCloth();
opponent.setupLifeCloth();
player.open();
opponent.open();
upPhase();
});
game.sendMsgQueue();
}
function upPhase () {
player.up();
drawPhase();
}
function drawPhase () {
player.draw(2);
enerPhase();
}
function enerPhase () {
player.chargeAsyn().callback(growPhase);
player.endEnerPhaseAsyn().callback(growPhase);
}
function growPhase () {
player.growAsyn().callback(mainPhase);
player.endGrowPhaseAsyn().callback(mainPhase);
}
function mainPhase () {
function loop () {
player.summonSigniAsyn().callback(loop);
player.trashSigniAsyn().callback(loop);
player.useSpellAsyn().callback(loop);
player.useMainPhaseArtsAsyn().callback(loop);
player.useActionEffectAsyn().callback(loop);
player.endMainPhaseAsyn().callback(attackPhase);
}
loop();
}
function attackPhase () {
artsStep();
}
function artsStep () {
function playerLoop () {
player.useAttackPhaseArtsAsyn().callback(playerLoop);
player.endArtsStepAsyn().callback(opponentLoop);
}
function opponentLoop () {
opponent.useAttackPhaseArtsAsyn().callback(opponentLoop);
opponent.endArtsStepAsyn().callback(signiAttackStep);
}
playerLoop();
}
function signiAttackStep () {
function loop () {
player.signiAttackAsyn().callback(loop);
player.endSigniAttackStepAsyn().callback(lrigAttackStep);
}
loop();
}
function lrigAttackStep () {
function loop () {
player.lrigAttackAsyn().callback(loop);
player.endLrigAttackStepAsyn().callback(endPhase);
}
loop();
}
function endPhase () {
var n = player.hands.length - 7;
if (n > 0) {
player.discardAsyn(n).callback(wixoss);
} else {
wixoss();
}
}
function wixoss () {
var tmp = player;
player = opponent;
opponent = tmp;
upPhase();
}

2198
Player.js Normal file

File diff suppressed because it is too large Load diff

258
Room.js Normal file
View file

@ -0,0 +1,258 @@
'use strict';
function Room (name,host,password,mayusRoom) {
this.name = name;
this.host = host;
this.guest = null;
this.hostSpectatorList = []; // 列表,client或undefined或null,
this.guestSpectatorList = []; // undefined表示禁用,null表示空.
for (var i = 0; i < 5; i++) {
this.hostSpectatorList.push(undefined);
this.guestSpectatorList.push(undefined);
}
// 直播
this.live = false;
this.liveSpectators = [];
this.game = null;
this.password = password;
this.mayusRoom = !!mayusRoom;
this.reconnecting = false;
this.activateTime = Date.now();
}
Room.prototype.toInfo = function () {
var total = 2;
var count = 1;
if (this.guest) count++;
concat(this.hostSpectatorList,this.guestSpectatorList).forEach(function (spectator) {
if (spectator === undefined) return;
total++;
if (spectator) count++;
},this);
var info = {
roomName: this.name,
passwordRequired: !!this.password,
total: total,
count: count,
live: this.live,
mayusRoom: this.mayusRoom
};
return info;
};
Room.prototype.getAllClients = function () {
return this.liveSpectators.concat(this.getRoomMembers());
};
Room.prototype.getRoomMembers = function () { // 除了直播观众
var clients = this.hostSpectatorList.concat(this.guestSpectatorList).filter(function (spectator) {
return spectator;
});
if (this.host) clients.push(this.host);
if (this.guest) clients.push(this.guest);
return clients;
};
Room.prototype.emit = function (name,value) {
var clients = this.getAllClients();
clients.forEach(function (client) {
client.socket.emit(name,value);
},this);
};
Room.prototype.emitTo = function (positions,name,value) {
var clients = this.getAllClients();
clients.forEach(function (client) {
if (!inArr(client.getPosition(),positions)) return;
client.socket.emit(name,value);
},this);
};
Room.prototype.removeSpectator = function (client) {
return this.removeHostSpectator(client) ||
this.removeGuestSpectator(client) ||
this.removeLiveSpectator(client);
};
Room.prototype.removeHostSpectator = function (client) {
var i = this.hostSpectatorList.indexOf(client);
if (i === -1) return false;
this.hostSpectatorList[i] = null;
return true;
};
Room.prototype.removeGuestSpectator = function (client) {
var i = this.guestSpectatorList.indexOf(client);
if (i === -1) return false;
this.guestSpectatorList[i] = null;
return true;
};
Room.prototype.removeClient = function (client) {
if (client === this.guest) {
this.guest = null;
return true;
}
return this.removeSpectator(client);
};
Room.prototype.isHostSpectatorsFull = function () {
return this.hostSpectatorList.every(function (spectator) {
return (spectator !== null);
});
};
Room.prototype.isGuestSpectatorsFull = function () {
return this.guestSpectatorList.every(function (spectator) {
return (spectator !== null);
});
};
Room.prototype.isFull = function () {
if (!this.guest) return false;
if (!this.isHostSpectatorsFull()) return false;
if (!this.isGuestSpectatorsFull()) return false;
return true;
};
Room.prototype.pushHostSpectator = function (client) {
for (var i = 0; i < this.hostSpectatorList.length; i++) {
if (this.hostSpectatorList[i] === null) {
this.hostSpectatorList[i] = client;
return true;
}
}
return false;
};
Room.prototype.pushGuestSpectator = function (client) {
for (var i = 0; i < this.guestSpectatorList.length; i++) {
if (this.guestSpectatorList[i] === null) {
this.guestSpectatorList[i] = client;
return true;
}
}
return false;
};
Room.prototype.setHostSpectator = function (client,i) {
if (this.hostSpectatorList[i] !== null) return false;
client.cfg = null;
this.removeClient(client);
this.hostSpectatorList[i] = client;
return true;
};
Room.prototype.setGuestSpectator = function (client,i) {
if (this.guestSpectatorList[i] !== null) return false;
client.cfg = null;
this.removeClient(client);
this.guestSpectatorList[i] = client;
return true;
};
Room.prototype.update = function () {
this.getRoomMembers().forEach(function (client) {
var msgObj = {
roomName: this.name,
host: this.host.nickname,
guest: this.guest? this.guest.nickname : '',
hostSpectatorList: this.hostSpectatorList.map(function (spectator) {
if (spectator === null) return '';
if (spectator === undefined) return null;
return spectator.nickname;
}),
guestSpectatorList: this.guestSpectatorList.map(function (spectator) {
if (spectator === null) return '';
if (spectator === undefined) return null;
return spectator.nickname;
}),
guestReady: !!(this.guest && this.guest.cfg),
me: client.getPosition(),
mayusRoom: this.mayusRoom
}
client.emit('update room',msgObj);
},this);
};
Room.prototype.getHostSpectators = function () {
var spaectators = [];
this.hostSpectatorList.forEach(function (spectator) {
if (!spectator) return;
spaectators.push(spectator);
});
return spaectators;
};
Room.prototype.getGuestSpectators = function () {
var spaectators = [];
this.guestSpectatorList.forEach(function (spectator) {
if (!spectator) return;
spaectators.push(spectator);
});
return spaectators;
};
Room.prototype.lockHostSpec = function (i) {
var spectator = this.hostSpectatorList[i];
this.hostSpectatorList[i] = undefined;
if (spectator) {
spectator.reset();
spectator.emit('kicked');
}
};
Room.prototype.lockGuestSpec = function (i) {
var spectator = this.guestSpectatorList[i];
this.guestSpectatorList[i] = undefined;
if (spectator) {
spectator.reset();
spectator.emit('kicked');
}
};
Room.prototype.unlockHostSpec = function (i) {
if (this.hostSpectatorList[i]) return;
this.hostSpectatorList[i] = null;
};
Room.prototype.unlockGuestSpec = function (i) {
if (this.guestSpectatorList[i]) return;
this.guestSpectatorList[i] = null;
};
// 直播
Room.prototype.pushLiveSpectator = function (client) {
this.liveSpectators.push(client);
};
Room.prototype.removeLiveSpectator = function (client) {
removeFromArr(client,this.liveSpectators);
};
Room.prototype.gameover = function () {
this.reconnecting = false;
if (this.game) this.game.destroy();
this.game = null;
this.live = false;
this.activateTime = Date.now();
this.getAllClients().forEach(function (client) {
client.gameover();
},this);
this.liveSpectators.length = 0;
this.update();
};
Room.prototype.createHostIO = function () {
return new IO(this.host,function () {
return this.liveSpectators.concat(this.getHostSpectators());
}.bind(this));
};
Room.prototype.createGuestIO = function () {
return new IO(this.guest,this.getGuestSpectators.bind(this));
};
global.Room = Room;

579
RoomManager.js Normal file
View file

@ -0,0 +1,579 @@
'use strict';
function RoomManager (cfg) {
this.VERSION = 63;
this.MAX_ROOMS = cfg.MAX_ROOMS;
this.MAX_CLIENTS = cfg.MAX_CLIENTS;
this.MAX_ROOM_NAME_LENGTH = cfg.MAX_ROOM_NAME_LENGTH;
this.MAX_NICKNAME_LENGTH = cfg.MAX_NICKNAME_LENGTH;
this.MAX_PASSWORD_LENGTH = cfg.MAX_PASSWORD_LENGTH;
this.clients = [];
this.rooms = [];
this.roomMap = {};
this.replayList = [];
this.MAX_REPLAY_LENGTH = 20;
this.maxClientsCount = 0;
this.maxGamesCount = 0;
setInterval(this.cleanUp.bind(this),60*1000);
}
RoomManager.prototype.createClient = function (socket,id) {
if (this.clients.length >= this.MAX_CLIENTS) {
socket.disconnect();
return;
}
var client;
if (id) {
var room;
for (var i = 0; i < this.rooms.length; i++) {
room = this.rooms[i];
// if (!room.reconnecting) continue; // 服务器可能还不知道掉线,所以注释掉.
// console.log('host:%s\nguest:%s\nyou:%s',room.host.id,room.guest.id,id);
if (room.host.id === id) {
client = room.host;
client.updateSocket(socket);
break;
}
if (room.guest && room.guest.id === id) {
client = room.guest;
client.updateSocket(socket);
break;
}
}
if (client) {
room.reconnecting = false;
socket.emit('game reconnect');
room.emit('opponent reconnect');
} else {
socket.emit('game reconnect failed');
}
}
if (!client) {
client = new Client(this,socket);
}
this.clients.push(client);
// if (this.clients.length > this.maxClientsCount) {
// this.maxClientsCount = this.clients.length;
// console.log(new Date().toISOString().replace('T',' ').substr(0,19)+' Max clients count: %s',this.clients.length);
// }
socket.on('error',this.handleError.bind(this,client));
socket.on('disconnect',this.disconnect.bind(this,client));
socket.on('createRoom',this.createRoom.bind(this,client));
socket.on('joinRoom',this.joinRoom.bind(this,client));
socket.on('leaveRoom',this.leaveRoom.bind(this,client));
socket.on('lockSpec',this.lockSpec.bind(this,client));
socket.on('unlockSpec',this.unlockSpec.bind(this,client));
socket.on('changePosition',this.changePosition.bind(this,client));
socket.on('getReplayList',this.getReplayList.bind(this,client));
socket.on('getReplayContent',this.getReplayContent.bind(this,client));
socket.on('watchLive',this.watchLive.bind(this,client));
socket.on('ready',client.ready.bind(client));
socket.on('unready',client.unready.bind(client));
socket.on('startGame',client.startGame.bind(client));
socket.on('chat',client.chat.bind(client));
socket.on('surrender',client.surrender.bind(client));
socket.on('drop',client.drop.bind(client));
socket.on('tick',client.tick.bind(client));
// socket.on('reloadCardInfo',this.reloadCardInfo.bind(this));
socket.emit('version',this.VERSION);
this.updateRoomList();
};
RoomManager.prototype.handleError = function (client,err) {
console.error(err);
console.error(err.stack);
// console.trace();
var errMsg = 'SERVER_ERROR';
var room = client.room;
if (room) {
// if (room.host) {
// room.host.socket.emit('error message',errMsg);
// }
// if (room.guest) {
// room.guest.socket.emit('error message',errMsg);
// }
room.emit('error message',errMsg);
this.removeRoom(client.room);
}
};
RoomManager.prototype.disconnect = function (client) {
removeFromArr(client,this.clients);
var room = client.room;
if (!room) return;
// --- 重新连接开始 ---
if (room.game && !room.reconnecting) {
if ((client === room.host) || (client === room.guest)) {
room.reconnecting = true;
// !here
// room.emit('wait for reconnect');
room.getAllClients().forEach(function (c) {
if (c === client) return;
c.emit('wait for reconnect');
},this);
return;
}
}
// --- 重新连接结束 ---
room.reconnecting = false;
if (client === room.host) {
// 主机掉线
room.emit('host disconnected');
this.removeRoom(room);
} else if (client === room.guest) {
// 客机掉线
if (room.game) {
room.emit('guest disconnected');
this.removeRoom(room);
} else {
room.guest = null;
room.update();
this.updateRoomList();
}
} else {
// 观众掉线
room.removeSpectator(client);
room.update();
this.updateRoomList();
}
};
RoomManager.prototype.removeRoom = function (room) {
if (!room) return;
if (room.game) room.game.destroy();
room.getAllClients().forEach(function (client) {
client.reset();
},this);
delete this.roomMap[room.name];
removeFromArr(room,this.rooms);
this.updateRoomList();
};
RoomManager.prototype.updateRoomList = function () {
this.clients.forEach(function (client) {
var list = [];
this.rooms.forEach(function (room) {
var flag = (room.live && room.game && !this.checkLiveIP(client,room)) ||
(!room.game && !room.isFull());
if (flag) {
list.push(room.toInfo());
}
},this);
if (client.room) return;
client.socket.emit('update room list',list);
client.socket.emit('update online counter',this.clients.length);
},this);
};
RoomManager.prototype.checkRoomName = function (roomName) {
// TODO: 过滤敏感字词
if (!isStr(roomName) || !roomName || roomName.length > this.MAX_ROOM_NAME_LENGTH) {
return 'INVALID_ROOM_NAME';
}
};
RoomManager.prototype.checkNickname = function (nickname) {
// TODO: 过滤敏感字词
if (!isStr(nickname) || !nickname || nickname.length > this.MAX_NICKNAME_LENGTH) {
return 'INVALID_NICKNAME';
}
};
RoomManager.prototype.checkPassword = function (password) {
if (!isStr(password) || password.length > this.MAX_PASSWORD_LENGTH) {
return 'INVALID_PASSWORD';
}
};
RoomManager.prototype.checkClientInRoom = function (client) {
if (client.room) return 'ALREADY_IN_A_ROOM';
};
RoomManager.prototype.checkLiveIP = function (client,room) {
if (!client.socket.handshake) return '';
if (!room.guest) return '';
var address = client.socket.handshake.address;
var guestAddress = room.guest.socket.handshake.address;
if (address === guestAddress) return 'IP_BANNED';
};
RoomManager.prototype.createRoom = function (client,cfg) {
var errMsg;
if (!isObj(cfg) || !isStr(cfg.roomName) || !isStr(cfg.nickname)) {
errMsg = 'INVALID_CONFIG';
}
var roomName = cfg.roomName;
var nickname = cfg.nickname;
var password = cfg.password;
if (!errMsg) {
errMsg =
this.checkRoomName(roomName) ||
this.checkNickname(nickname) ||
this.checkPassword(password) ||
this.checkClientInRoom(client);
}
if (!errMsg) {
if (roomName in this.roomMap) {
errMsg = 'ROOM_ALREADY_EXISTS';
} else if (this.rooms.length >= this.MAX_ROOMS) {
errMsg = 'MAX_ROOMS';
}
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
// console.log('%s creates room: %s',client.socket.id,roomName);
if (password) {
console.log('nickname: %s, roomName: %s, password: %s',nickname,roomName,password);
}
// 双向绑定
var room = new Room(roomName,client,password,!!cfg.mayusRoom);
client.room = room;
this.roomMap[roomName] = room;
this.rooms.push(room);
client.nickname = nickname;
// client.socket.emit('host room',{
// roomName: room.name,
// host: nickname,
// guest: ''
// });
room.update();
this.updateRoomList();
};
RoomManager.prototype.joinRoom = function (client,cfg) {
var errMsg;
if (!isObj(cfg) || !isStr(cfg.roomName) || !isStr(cfg.nickname)) {
errMsg = 'INVALID_CONFIG';
}
var roomName = cfg.roomName;
var nickname = cfg.nickname;
var password = cfg.password;
if (!errMsg) {
errMsg =
this.checkRoomName(roomName) ||
this.checkNickname(nickname) ||
this.checkPassword(password) ||
this.checkClientInRoom(client);
}
var room;
if (!errMsg) {
room = this.roomMap[roomName];
if (!room) {
errMsg = 'ROOM_DOES_NOT_EXIST';
} else if (room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if (room.isFull()) {
errMsg = 'ROOM_IS_FULL';
}
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
if (room.password && (password !== room.password)) {
client.socket.emit('wrong password');
return;
}
client.nickname = nickname;
client.room = room;
if (!room.guest) {
room.guest = client;
} else if (!room.isHostSpectatorsFull()) {
room.pushHostSpectator(client);
} else {
room.pushGuestSpectator(client);
}
room.update();
this.updateRoomList();
};
// RoomManager.prototype.toGuest = function (client,room) {
// room.removeClient(client);
// room.guest = client;
// room.update();
// };
// RoomManager.prototype.toHostSpectator = function (client,room,i) {
// room.removeClient(client);
// room.setHostSpectator(client,i);
// room.update();
// };
// RoomManager.prototype.toGuestSpectator = function (client,room,i) {
// room.removeClient(client);
// room.setGuestSpectator(client,i);
// room.update();
// };
RoomManager.prototype.leaveRoom = function (client) {
var errMsg;
if (!client.room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (client.room.game && ((client === client.room.host) || (client === client.room.guest))) {
errMsg = 'GAME_ALREADY_STARTED';
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
var room = client.room;
if (client === room.host) {
room.emit('host left');
this.removeRoom(room);
} else {
room.removeClient(client);
client.reset();
room.update();
}
// var host = client.room.host;
// var guest = client.room.guest;
// if (client === host) {
// if (guest) {
// guest.socket.emit('host left');
// }
// this.removeRoom(client.room);
// } else {
// host.socket.emit('guest left');
// client.room.guest = null;
// client.reset();
// }
this.updateRoomList();
};
function checkSpectatorIndex (i) {
if (!((i >= 0) && (i < 5))) {
return 'INVALID_INDEX';
}
}
RoomManager.prototype.lockSpec = function (client,i) {
i >>>= 0;
var errMsg;
var room = client.room;
if (!room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (client.room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if ((client !== room.host) && (client !== room.guest)) {
errMsg = 'NO_PERMISSION';
} else {
errMsg = checkSpectatorIndex(i);
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
if (client === room.host) {
room.lockHostSpec(i);
} else {
room.lockGuestSpec(i);
}
room.update();
this.updateRoomList();
};
RoomManager.prototype.unlockSpec = function (client,i) {
i >>>= 0;
var errMsg;
var room = client.room;
if (!room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if ((client !== room.host) && (client !== room.guest)) {
errMsg = 'NO_PERMISSION';
} else {
errMsg = checkSpectatorIndex(i);
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
if (client === room.host) {
room.unlockHostSpec(i);
} else {
room.unlockGuestSpec(i);
}
room.update();
this.updateRoomList();
};
RoomManager.prototype.changePosition = function (client,cfg) {
var errMsg;
var room = client.room;
if (!room) {
errMsg = 'YOU_ARE_NOT_IN_ANY_ROOM';
} else if (room.game) {
errMsg = 'GAME_ALREADY_STARTED';
} else if (client === room.host) {
errMsg = 'NO_PERMISSION';
} else if (!isObj(cfg)) {
errMsg = 'INVALID_CONFIG';
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
if (cfg.position === 'guest') {
if (room.guest) return room.update();
room.removeClient(client);
room.guest = client;
return room.update();
}
var i = cfg.i >>> 0;
if (checkSpectatorIndex(i)) return;
if (cfg.position === 'host-spectator') {
room.setHostSpectator(client,i);
} else if (cfg.position === 'guest-spectator') {
room.setGuestSpectator(client,i);
}
room.update();
};
RoomManager.prototype.gameover = function (room,replay) {
// console.log('%s gameovered',room.name);
if (room) {
room.gameover();
}
if (replay) {
this.pushReplay(replay);
}
this.updateRoomList();
};
RoomManager.prototype.pushReplay = function (replay) {
// if (replay.messagePacks.length < 20) return;
replay.id = Math.random();
this.replayList.unshift(replay);
if (this.replayList.length > this.MAX_REPLAY_LENGTH) {
this.replayList.pop();
}
};
RoomManager.prototype.getReplayList = function (client) {
var list = this.replayList.map(function (replay) {
return {
id: replay.id,
win: replay.win,
surrender: replay.surrender,
selfLrig: replay.selfLrig,
opponentLrig: replay.opponentLrig
};
},this);
client.socket.emit('replayList',list);
};
RoomManager.prototype.getReplayContent = function (client,id) {
if (!isNum(id)) return;
for (var i = 0; i < this.replayList.length; i++) {
var replay = this.replayList[i];
if (replay.id === id) {
client.socket.emit('replayContent',{
clientVersion: this.version,
win: replay.win,
surrender: replay.surrender,
messagePacks: replay.messagePacks
});
return;
}
}
client.socket.emit('replayContent',null);
};
RoomManager.prototype.watchLive = function (client,cfg) {
var errMsg;
if (!isObj(cfg) || !isStr(cfg.roomName)) {
errMsg = 'INVALID_CONFIG';
}
var roomName = cfg.roomName;
if (!errMsg) {
errMsg = this.checkClientInRoom(client);
}
var room;
if (!errMsg) {
room = this.roomMap[roomName];
if (!room) {
errMsg = 'ROOM_DOES_NOT_EXIST';
} else if (!room.live) {
errMsg = 'NOT_IN_LIVE_MODE';
} else if (!room.game) {
errMsg = 'GAME_NOT_IN_PROGRESS';
} else {
errMsg = this.checkLiveIP(client,room);
}
}
if (errMsg) {
client.socket.emit('error message',errMsg);
return;
}
client.room = room;
room.pushLiveSpectator(client);
client.emit('liveData',room.game.getLiveMessagePacks());
};
RoomManager.prototype.cleanUp = function () {
var clients = this.clients.slice();
clients.forEach(function (client) {
var socket = client.socket;
if (socket.disconnected) {
this.disconnect(client);
console.error('cleanUp');
}
},this);
var rooms = this.rooms.slice();
var now = Date.now();
rooms.forEach(function (room) {
var flag =
((now - room.activateTime) >= 3*60*60*1000) ||
(!room.reconnecting && room.host && room.host.socket.disconnected) ||
(!room.reconnecting && room.guest && room.guest.socket.disconnected);
if (!flag) return;
console.log('clean up: ' + room.name);
this.removeRoom(room);
},this);
};
// RoomManager.prototype.reloadCardInfo = function (password) {
// if (password !== 'WEBXOSS') return;
// var path = require('path');
// var filePath = './CardInfo.js';
// delete require.cache[path.resolve(filePath)];
// require(filePath);
// };
global.RoomManager = RoomManager;

86
Timming.js Normal file
View file

@ -0,0 +1,86 @@
'use strict';
// Timming 拼写错误...
// 应该是 Timing ...
// 嘛,改起来挺麻烦,不改了喵
function Timming (game) {
this.game = game;
// this.subTimmings = [];
// this.conditions = [];
this.funcs = [];
this.onceFlags = [];
this.effects = [];
this.constEffects = [];
this.constEffectsDestroy = [];
}
// Timming.prototype.if = function (condition) {
// var timming = new Timming();
// this.subTimmings.push(timming);
// this.conditions.push(condition);
// return timming;
// };
Timming.prototype.addFunction = function (func,once) {
this.funcs.push(func);
this.onceFlags.push(!!once);
};
Timming.prototype.addConstEffect = function (constEffect) {
this.constEffects.push(constEffect);
};
Timming.prototype.removeConstEffect = function (constEffect) {
removeFromArr(constEffect,this.constEffects);
};
Timming.prototype.addConstEffectDestroy = function (constEffect) {
this.constEffectsDestroy.push(constEffect);
};
Timming.prototype.removeConstEffectDestroy = function (constEffect) {
removeFromArr(constEffect,this.constEffectsDestroy);
};
Timming.prototype.trigger = function (event,dontHandleFrameEnd) {
for (var i = 0; i < this.funcs.length; i++) {
var func = this.funcs[i];
var once = this.onceFlags[i];
func();
if (once) {
this.funcs.splice(i,1);
this.onceFlags.splice(i,1);
i--;
}
}
// this.funcs.forEach(function (func) {
// func();
// },this);
this.constEffectsDestroy.slice().forEach(function (constEffect) {
constEffect.destroy();
},this);
// this.constEffects.forEach(function (constEffect) {
// constEffect.trigger();
// },this);
// 注:
// 关于创建/销毁常时效果和触发触发效果的顺序:
// 某时点触发时,会创建/销毁常时效果,但此时不计算,
// 效果触发之后,才计算常时效果.
// 这与WIXOSS中"場から移動したことによって発動する常時能力は、
// 移動する直前の状態を参照して発動の有無を確認します."的规则相符.
this.effects.forEach(function (effect) {
this.game.pushTriggeringEffect(effect,event);
},this);
// this.subTimmings.forEach(function (timming,idx) {
// if (this.conditions[idx](event)) {
// timming.trigger(event);
// }
// },this);
if (!dontHandleFrameEnd) {
this.game.handleFrameEnd();
}
};
global.Timming = Timming;

70
Zone.js Normal file
View file

@ -0,0 +1,70 @@
'use strict';
function Zone (game,player,name,args,pids) {
args = args.split(' ');
// 引用
this.game = game;
this.player = player;
// 基本属性
this.name = name;
this.checkable = inArr('checkable',args); // 表示该区域,玩家可以查看里侧的牌.
this.up = inArr('up',args); // 表示该区域,卡片默认竖置.
this.faceup = inArr('faceup',args); // 表示该区域,卡片默认正面朝上.
this.bottom = inArr('bottom',args); // 表示该区域,卡片放入的时候,默认放进底部.
this.inhand = inArr('inhand',args); // 表示该区域,卡片是拿在玩家手里的,这意味着:
// 1. 该区域的卡,在游戏逻辑中,是里侧表示的,
// 而在UI中,对己方玩家是表侧表示的.
// 2. 该区域的卡,玩家可以随时洗切.
// 注册
game.register(this);
// 卡片
this.cards = [];
if (isArr(pids)) {
pids.forEach(function (pid) {
this.cards.push(new Card(game,player,this,pid));
},this);
}
// 附加的属性
this.disabled = false; // <ワーム・ホール>
this.powerDown = false; // <黒幻蟲 サソリス>
}
Zone.prototype.getTopCards = function (n) {
// var cards = [];
// for (var i = 0; i < n; i++) {
// var card = this.cards[i];
// if (!card) break;
// cards.push(card);
// }
// return cards;
return this.cards.slice(0,n);
};
Zone.prototype.moveCardsToTop = function (cards) {
cards.forEach(function (card) {
removeFromArr(card,this.cards);
},this);
this.cards.unshift.apply(this.cards,cards);
};
Zone.prototype.moveCardsToBottom = function (cards) {
cards.forEach(function (card) {
removeFromArr(card,this.cards);
},this);
this.cards.push.apply(this.cards,cards);
};
Zone.prototype.getStates = function () {
var states = [];
if (this.powerDown) states.push('powerDown');
if (this.disabled) states.push('disabled');
return states;
};
global.Zone = Zone;

40
debug.js Normal file
View file

@ -0,0 +1,40 @@
console.log('------debug start------');
function reload (filePath) {
var path = require('path');
delete require.cache[path.resolve(filePath)];
require(filePath);
}
reload('./CardInfo.js');
// reload('./Card.js');
// reload('./Mask.js');
// reload('./ConstEffect.js');
// reload('./ConstEffectManager.js');
// reload('./Player.js');
// reload('./Client.js');
// var test = require('./test.js');
// var roomManager = test.roomManager;
// console.log(JSON.stringify(roomManager.gameCountMap),null,'\t');
// console.log(roomManager.gameCountMap);
// var room = roomManager.roomMap['【周年祭】F组'];
// if (!room) {
// console.log('no room');
// return;
// }
// console.log('seed:'+room.game.seed);
// console.log('seed:'+room.guest.id);
// var fs = require('fs');
// fs.writeFile('host.txt',JSON.stringify(room.game.hostPlayer.messagePacks),function (err) {
// console.log(err? 'host error' : 'host succ');
// });
// fs.writeFile('guest2.txt',JSON.stringify(room.game.guestPlayer.messagePacks),function (err) {
// console.log(err? 'guest error' : 'guest succ');
// });
// roomManager.rooms.forEach(function (room) {
// console.log(room.name + !!room.game + !!room.live);
// },this);
console.log('------debug end------');

136
nginx.conf Normal file
View file

@ -0,0 +1,136 @@
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# My Settings
##
# server {
# listen 80;
# root /usr/share/nginx/www;
# index index.html;
#
# location /node {
# proxy_pass http://127.0.0.1:2015/;
# }
# location /socket.io {
# proxy_pass http://127.0.0.1:2015/;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# }
# }
## http://fbbear.com/?p=460
server {
listen 80;
resolver 8.8.8.8;
location / {
proxy_pass http://webxoss.com$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1; #必须
proxy_set_header Upgrade $http_upgrade; #必须
proxy_set_header Connection "upgrade"; #必须
proxy_send_timeout 1h; #send 超时时间 记得一定要按需配置这个 否则默认60s就断开了
proxy_read_timeout 1h; #read 超时时间
#allow 127.0.0.1;
#deny all;
}
}
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
##
# nginx-naxsi config
##
# Uncomment it if you installed nginx-naxsi
##
#include /etc/nginx/naxsi_core.rules;
##
# nginx-passenger config
##
# Uncomment it if you installed nginx-passenger
##
#passenger_root /usr;
#passenger_ruby /usr/bin/ruby;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}

1
random.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7000
socket.io.js Normal file

File diff suppressed because it is too large Load diff

186
test.html Normal file
View file

@ -0,0 +1,186 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Server</title>
<script>
var global = window;
// function require (file) {
// return window[file.replace(/^\.[\/\\]/,'').replace(/.js$/,'')];
// }
function handleCardInfo () {
// var defaultValueMap = {
// "rarity": "LR",
// "cardType": "SIGNI",
// "color": "white",
// "level": 0,
// "limit": 0,
// "power": 0,
// "limiting": "",
// "classes": [],
// "costWhite": 0,
// "costBlack": 0,
// "costRed": 0,
// "costBlue": 0,
// "costGreen": 0,
// "costColorless": 0,
// "guardFlag": false,
// "multiEner": false,
// };
for (var x in CardInfo) {
var info = CardInfo[x];
delete info.timestamp;
delete info.kana;
delete info.imgUrl;
delete info.cardText;
delete info.cardText_zh_CN;
delete info.cardText_en;
delete info.constEffects;
delete info.actionEffects;
delete info.startUpEffects;
delete info.spellEffect;
delete info.artsEffect;
delete info.burstEffect
delete info.faqs;
delete info.cardSkills;
delete info.growCondition;
delete info.useCondition;
delete info.costChange;
delete info.costChangeBeforeUseAsyn;
delete info.resonaPhase;
delete info.resonaCondition;
delete info.resonaAsyn;
delete info.encore;
// for (var key in defaultValueMap) {
// if (info[key] === defaultValueMap[key]) {
// delete info[key];
// }
// }
}
// var textarea = document.createElement('textarea');
// textarea.value = JSON.stringify(CardInfo);
// document.body.appendChild(textarea);
// textarea.select();
}
function handleCardInfo_ru () {
var props = [
"name",
"actionEffectTexts",
"constEffectTexts",
"startUpEffectTexts",
"spellEffectTexts",
"artsEffectTexts",
"burstEffectTexts",
"attachedEffectTexts",
"extraTexts",
];
var suffix = [
"",
"_zh_CN",
"_en"
]
var arr = [];
for (var x in CardInfo) {
if (x <= 1762) continue;
var info = CardInfo[x];
var obj = {
pid: info.pid,
wxid: info.wxid,
};
props.forEach(function (rawprop) {
// suffix.forEach(function (suf) {
// var prop = rawprop + suf;
// if (!info[prop]) return;
// obj[prop] = info[prop];
// });
obj[rawprop+"_ru"] = info[rawprop+"_en"];
});
arr.push(obj);
}
window.arr = arr;
}
function fetchAndHandleRuJson (url) {
let CardInfo_ru = {};
fetch(url).then(res => res.json()).then(arr => {
arr.forEach(info => {
let info_ru = {};
for (let prop in info) {
if (!prop.match(/_ru$/)) continue;
info_ru[prop] = info[prop];
}
CardInfo_ru[info.pid] = info_ru;
});
window.ru = JSON.stringify(CardInfo_ru);
});
}
function getPrCards () {
let ids = [];
for (let pid in CardInfo) {
let card = CardInfo[pid];
let id = +card.wxid.replace('PR-','');
if (id) ids.push(id);
}
ids.sort((a,b) => a-b);
let ranges = [];
let start = ids[0];
let end = ids[0];
ids.slice(1).concat(0).forEach(id => {
if ((id - end) === 1) {
end = id;
} else {
let range = `${('000'+start).slice(-3)}-${('000'+end).slice(-3)}`;
if (start === end) range = ('000'+start).slice(-3);
ranges.push(range);
start = end = id;
}
})
return ranges;
}
function getUntestedPr () {
let ids = [];
for (let pid in CardInfo) {
if (pid <= 1762) continue;
let card = CardInfo[pid];
if (card.pid !== card.cid) continue;
if (/^PR-/.test(card.wxid)) ids.push(card.wxid);
}
return ids;
}
function getNewCardNames () {
let names = [];
for (let pid in CardInfo) {
if (pid <= 1762) continue;
let card = CardInfo[pid];
if (card.pid !== card.cid) continue;
names.push(card.name_zh_CN);
}
return names;
}
</script>
<script src="util.js"></script>
<script src="random.min.js"></script>
<script src="Callback.js"></script>
<script src="Game.js"></script>
<script src="Phase.js"></script>
<script src="IO.js"></script>
<script src="Player.js"></script>
<script src="Card.js"></script>
<script src="Zone.js"></script>
<script src="CardInfo.js"></script>
<script src="Timming.js"></script>
<script src="Mask.js"></script>
<script src="ConstEffect.js"></script>
<script src="ConstEffectManager.js"></script>
<script src="Effect.js"></script>
<script src="EffectManager.js"></script>
<script src="FakeSocket.js"></script>
<script src="Client.js"></script>
<script src="Room.js"></script>
<script src="RoomManager.js"></script>
<script src="test.js"></script>
</head>
<body>
<button onclick="newClient();">newClient</button>
</body>
</html>

157
test.js Normal file
View file

@ -0,0 +1,157 @@
'use strict';
if (!global.window) {
global.Random = require("random-js");
require("./util.js");
require("./Callback.js");
require("./Game.js");
require("./Phase.js");
require("./IO.js");
require("./Player.js");
require("./Card.js");
require("./Zone.js");
require("./CardInfo.js");
require("./Timming.js");
require("./Mask.js");
require("./ConstEffect.js");
require("./ConstEffectManager.js");
require("./Effect.js");
require("./EffectManager.js");
require("./Client.js");
require("./Room.js");
require("./RoomManager.js");
}
var io;
if (global.window) {
io = {
on: function (name,handler) {
io._handler = handler;
},
use: function () {}
};
global.window.newClient = function () {
var win = window.open('./webxoss-client/?local=true');
win.addEventListener('load',function () {
var socket = new FakeSocket(win);
win.addEventListener('unload',function () {
socket._doEmit('disconnect');
});
win.document.title = 'Client' + socket.id;
io._handler(socket);
});
}
} else {
var express = require('express');
var compression = require('compression');
var app = express();
var server = require('http').Server(app);
app.use(compression());
app.use('/background',express.static(__dirname + '/webxoss-client/images',{
maxAge: '2h'
}));
app.use('/images',express.static(__dirname + '/webxoss-client/images',{
maxAge: '30d'
}));
app.use(express.static(__dirname + '/webxoss-client'));
io = require('socket.io')(server,{
pingTimeout: 30000,
maxHttpBufferSize: 1024*1024,
});
server.listen(80);
}
var cfg = {
MAX_ROOMS: 100,
MAX_CLIENTS: 500,
MAX_ROOM_NAME_LENGTH: 15,
MAX_NICKNAME_LENGTH: 10,
MAX_PASSWORD_LENGTH: 15
};
var roomManager = new RoomManager(cfg);
var MAX_SOCKETS = 500;
// var MAX_SOCKETS_PER_IP = 50;
// var ipTable = {};
function getSocketCount () {
if (!io.sockets) return 0;
return Object.keys(io.sockets.connected).length;
}
io.use(function (socket,next) {
if (getSocketCount() >= MAX_SOCKETS) {
next(new Error('MAX_SOCKETS'));
return;
}
// var handshake = socket.request;
// var ip = handshake.connection.remoteAddress;
// if (!ip) {
// next();
// return;
// }
// if (ip in ipTable) {
// if (ipTable[ip] >= MAX_SOCKETS_PER_IP) {
// console.log('MAX_SOCKETS_PER_IP: %s',ip);
// next(new Error('MAX_SOCKETS_PER_IP'));
// return;
// } else {
// ipTable[ip]++;
// }
// } else {
// ipTable[ip] = 1;
// }
// socket.on('disconnect',function () {
// console.log('disconnect: %s, count: %s',ip,getSocketCount());
// if (ip in ipTable) {
// if (ipTable[ip] <= 1) {
// delete ipTable[ip];
// } else {
// ipTable[ip]--;
// }
// }
// });
next();
});
io.on('connect',function (socket) {
if (global.window) {
return roomManager.createClient(socket);
}
var req = socket.request;
if (req.connection.destroyed) {
console.log('req.connection.destroyed');
return;
}
var query = require('url').parse(req.url,true).query;
// console.log('connect: %s, count: %s',req.connection.remoteAddress,getSocketCount());
// console.log(query.clientId);
roomManager.createClient(socket,+query.clientId);
// test
// socket.on('force disconnect',function () {
// socket.disconnect();
// });
// for debug
if (typeof process === 'undefined') return;
var password = '';
process.argv.slice(2).forEach(function (arg) {
var match = arg.match(/^debug_password=(\S+)/)
if (match) {
password = match[1];
}
});
if (!password) return;
socket.on('debug',function (psw) {
if (psw !== password) return;
try {
var path = require('path');
var filePath = './debug.js';
delete require.cache[path.resolve(filePath)];
require(filePath);
} catch (e) {
console.log(e);
}
});
});
if (!global.window) {
exports.roomManager = roomManager;
}

36
util.js Normal file
View file

@ -0,0 +1,36 @@
'use strict';
global.concat = Array.prototype.concat.bind([]);
global.toArr = function (obj) {
if (!obj) return [];
if (typeof obj === 'string') return [];
return Array.prototype.slice.call(obj,0);
};
global.isArr = Array.isArray;
global.inArr = function (item,arr) {
return (toArr(arr).indexOf(item) != -1);
};
global.removeFromArr = function (item,arr) {
var idx = arr.indexOf(item);
if (idx < 0) {
return false;
} else {
arr.splice(idx,1);
return true;
}
}
global.isStr = function (v) {
return (typeof v === 'string');
};
global.isObj = function (v) {
return v && (typeof v === 'object') && !isArr(v);
};
global.isNum = function (v) {
return (typeof v === 'number');
};
global.isFunc = function (v) {
return (typeof v === 'function');
};
// function nextTick (callback) {
// setTimeout(callback,0);
// }

1
webxoss-client Submodule

@ -0,0 +1 @@
Subproject commit 1129c71e168857fee9416066f7f64b7fdb8f7491