`;
// messages
var messages = {
"attack": "{} 攻擊 {}",
"damaged": "{} 受到{}點傷害",
"defended": "{} 防禦成功",
"defend": "{} 沒什麼可以防禦的,回復一點生命",
"heal": "{} 回復兩點生命",
"supply": "{} 增加兩張手牌",
"rob": "{} 正在對 {} 行搶",
"cantRob": "{}沒有搶到任何東西",
"robbed": "{} 搶到了 {}",
"surprise": "{} 發動奇襲",
"surprised": "{} 受到{}點傷害,而且掉了一張手牌",
"surNoCard": "{} 受到{}點傷害",
"trade": "{} 想與 {} 進行交易",
"tradeChoose": "{} 選擇了 {}",
"tradeNoCard": "{} 沒有卡片可以交易",
"awared": "{} 洞悉了 {} 的{},並抽取了一張手牌",
"aware": "{} 增加三張手牌",
"plan": "{} 有個妙策",
"sweep": "{} 對 {} 進行掃射,威力是 {}",
"bless": "{} 獲得加護,身上的毒素一掃而空,並回復三點生命,還抽取了兩張手牌",
"poison": "{} 在食物下毒,{}中毒了",
"curse": "{} 詛咒了 {},使其損失四點生命,並掉了一張手牌",
"curseNoCard": "{} 詛咒了 {},使其損失四點生命",
"countered": "{} 反制了 {} 的攻擊,反彈了{}點傷害",
"counteredSur": "{} 反制了 {} 的攻擊,反彈了{}點傷害,並使其掉了一張手牌",
"counter": "{} 反制了敵手,使 {} 生命值減半了!",
"chaos": "{} 進入狂亂模式,回復三點生命,並對 {} 造成三點傷害",
"reverse": "{} 一口氣逆轉了情勢",
"noCard": "{} 抽到了死神",
"poisonDamaged": "{} 受到了劇毒的侵蝕,損失{}點生命",
"surrender": "{}投降",
"firstAttack": "{}先攻",
"win": "{}獲勝",
"draw": "{}抽到了{}",
"drawEne": "{}抽了一張卡片",
"use": "{}使用了{}",
"eneDisconn": "因敵方斷線,所以{}獲勝"
};
/* text end */
// handler
function wsHandler(dataJson) {
if (debugMode) { // debugging data
rawLog(dataJson);
}
// define the current player and update player status
if (dataJson.now === "player") {
now = "player";
gameUpdate(dataJson);
$('#skip').removeClass('disabled');
} else if (dataJson.now === "enemy") {
now = "enemy";
gameUpdate(dataJson);
$('#skip').addClass('disabled');
}
/* special cases */
if (dataJson.toString().match(/^[0-9]{1,5}$/)) { // contains only number => room id
id = dataJson.toString();
gameUpdate({ "room": id });
return null;
}
if (dataJson.room) { // game start
curName = dataJson['cur'];
eneName = dataJson['ene'];
setPlayerName(curName);
setEnemyName(eneName);
$('#enemy .icon').css('opacity','1'); // show enemy avatar
return null;
}
if (dataJson.action) { // time for users to do more action!
// Received: {"msg": "rob", "data": ["W", "\u96ea\u6751"], "action": "toRob", "value": {"enemy_card": ["5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5"]}}
statusInitialize();
lock = false;
switch (dataJson.action) {
case "toDefend":
askGuard(dataJson.value);
break;
case "toRob":
chooseRob(dataJson.value['enemy_card']);
break;
case "toBeRobbed":
askDefendRob();
break;
case "toTrade":
if (dataJson.value) {
chooseTrade(dataJson.value['hand'])
} else {
chooseTrade(handCards,tradeChoose);
}
break;
case "toAdd":
choosePlan(dataJson.value['cards']);
break;
default:
break;
}
}
// normal situation
if (dataJson.msg) {
switch (dataJson.msg) {
case "draw":
drawID = dataJson.data[1]; // card ID
break;
case "use":
useID = dataJson.data[1]; // card ID
dataJson.data[1] = cards[useID]; // edit data
Log(dataJson);
break;
case "robbed":
LogPlayerChoose("robbed",dataJson.data);
break;
case "tradeChoose":
tradeChoose = dataJson.data[1] // set system variable, cardID
LogPlayerChoose("tradeChoose",dataJson.data);
break;
case "poisonDamaged":
poisonDamage = dataJson.data[1]; // posion level
break;
case "win":
if (dataJson.data[0] === "player") {
playerWin();
dataJson.data[0] = '你';
Log(dataJson);
} else if (dataJson.data[0] === "enemy") {
playerLose();
}
break;
case "eneDisconn":
playerWin();
Log(dataJson);
break;
default:
Log(dataJson);
break;
}
}
}
function wsOnClose() {
console.log("Disconnected");
ts('.snackbar').snackbar({
content: '已中斷連線',
});
statusInitialize();
gameStatus.html('連線已中斷');
gameStatus.addClass('warning');
$('#skip').addClass('disabled');
$('#giveup').addClass('disabled');
unsetCardListener();
socketReady = false;
}
function wsOnOpen(status) {
console.log("Connected! Status:" + status);
ts('.snackbar').snackbar({
content: '連線成功!'
});
socketReady = true;
}
function wsOnError(except) {
ts('.snackbar').snackbar({
content: '連線失敗!',
action: '重試',
actionEmphasis: 'negative',
onAction: () => {
init();
}
});
console.log(except);
} // https://stackoverflow.com/questions/25779831/how-to-catch-websocket-connection-to-ws-xxxnn-failed-connection-closed-be
// listener
function setCardListener() {
$('.cards.container a.ts.card').each((i,e) => {
$(e).click(function() {
useCard(this.dataset.id);
});
});
}
function unsetCardListener() {
$('.cards.container a.ts.card').each((i,e) => {
$(e).off("click");
});
}
function setModalCardListListener() {
$('#modal .ts.list .item').each((i,e) => {
$(e).click(function() {
useCard(this.dataset.id);
modalClose();
});
});
}
function setModalSkipButtonListener() {
$('#modal #modalSkipBtn').click(function() {
send(0);
modalClose();
});
}
$('#skip').click(function() {
if (!$(this).hasClass('disabled')) {
send(0);
}
});
$('#giveup').click(function() {
dialog.children(".header").html('你放棄人生了,SAD');
dialog.children(".content").html('
');
dialog.children(".actions").html('');
timerInitialize(); // also remember to stop the timer
modalOpen();
resultListener();
quit();
});
function resultListener() {
$('#returnIndex').click(function() {
location.href = './index.html';
});
$('#restart').click(function() {
location.reload();
});
$('#close').click(function() {
modalClose();
});
}
// log
log.addEventListener("mouseover", function() { MouseOn = true; }); // if mouse is over the div, don't scroll to bottom
log.addEventListener("mouseleave", function() { MouseOn = false; });
function Log(msgJson) {
var node = logTemplate;
node = (now === "player") ? node.replace("{{ isSelf }}","right") : node.replace("{{ isSelf }}",""); // to distinguish if this is self log or enemy log
node = node.replace("{{ content }}",messages[msgJson['msg']].format(msgJson['data']));
log.insertAdjacentHTML("beforeend",node); // insert to log
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
}
function LogPlayerDraw() {
var node = logTemplate.replace("{{ isSelf }}","right");
node = node.replace("{{ content }}",messages['draw'].format([curName,cards[drawID]])); // msgJson['data'][0] player name, msgJson['data'][1] card id
log.insertAdjacentHTML("beforeend",node); // insert to log
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
drawID = "";
}
function LogPlayerChoose(type,data) {
var node = logTemplate;
var name = data[0];
var chooseID = data[1];
if (playerChooseStatus || type === "robbed") {
node = (now === "player") ? node.replace("{{ isSelf }}","right") : node.replace("{{ isSelf }}",""); // to distinguish if this is self log or enemy log
} else {
node = (now === "player") ? node.replace("{{ isSelf }}","") : node.replace("{{ isSelf }}","right"); // if this is the second one playerChoose message, then this is another one's choice
}
node = node.replace("{{ content }}",messages[type].format([name,cards[chooseID]])); // msgJson['data'][0] player name, msgJson['data'][1] card id
log.insertAdjacentHTML("beforeend",node); // insert to log
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
playerChooseStatus = (playerChooseStatus && type !== "robbed") ? false : true; // to count this is first playerChoose message or second one in the turn
}
function LogPlayerPoisonDamaged() {
var node = logTemplate;
var tmpName = "";
if(now === "player") { // to distinguish if this is self log or enemy log
node = node.replace("{{ isSelf }}","right");
tmpName = curName;
} else if (now === "enemy") {
node = node.replace("{{ isSelf }}","");
tmpName = eneName;
}
node = node.replace("{{ content }}",messages['poisonDamaged'].format([tmpName,poisonDamage])); // msgJson['data'][0] player name, msgJson['data'][1] card id
log.insertAdjacentHTML("beforeend",node); // insert to log
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
poisonDamage = "";
}
function rawLog(msg) {
console.log(JSON.stringify(msg));
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
}
function logTurn(playerName,playerTurn) {
var node = logDivider;
node = node.replace("{{ player }}",playerName);
node = node.replace("{{ turn }}",playerTurn);
log.insertAdjacentHTML("beforeend",node); // insert to log
if (!MouseOn) { // if mouse isn't over the div, scroll to bottom
log.scrollTop = log.scrollHeight; // scroll to bottom
}
}
// game status updater
function gameUpdate(data) {
if (data.room) {
roomNum.innerHTML = data.room.padStart(5,'0');
} else if (data.now) {
if (data.now === "player") {
turn.innerHTML = curName+" Turn "+data.player.turn;
logTurn(curName,data.player.turn);
if (drawID !== "") {
LogPlayerDraw(data);
}
statusInitialize();
lock = false; // unlock the cards
gameStatus.addClass('primary');
gameStatus.html('輪到你出牌');
timerSetup(28);
} else if (data.now === "enemy") {
turn.innerHTML = eneName+" Turn "+data.enemy.turn;
logTurn(eneName,data.enemy.turn);
statusInitialize();
gameStatus.html('等待對手出牌');
}
if (poisonDamage !== "") {
LogPlayerPoisonDamaged();
}
playerUpdate(data['player']);
enemyUpdate(data['enemy']);
modalClose();
}
}
function statusInitialize() {
gameStatus.removeClass('info negative warning pulsing primary inverted'); // initailize the status
timerInitialize();
}
// player status updater
function setPlayerName(name) {
var selfNameHeader = $('#myself > .profile > .name > .header');
selfNameHeader.html(name);
}
function setEnemyName(name) {
var eneNameHeader = $('#enemy > .profile > .name > .header');
eneNameHeader.html(name);
}
function playerUpdate(data) {
handCards = data['hand'];
setCard(data['hand']);
var selfHand = $('#selfHand'); // 手牌數
var selfDeck = $('#selfDeck');
var selfLifeBar = $('#myself > .profile > .life.progress > .bar');
var selfLifeText = selfLifeBar.children();
var selfStatus = $('#myself > .profile > .status');
var barWidth = (parseInt(data['life'])/20)*100 // %
selfHand.html(data['hand'].length);
selfDeck.html(data['deck_left']);
selfLifeBar.css('width',barWidth+'%');
selfLifeBar.attr('data-life',data['life']);
selfLifeText.html(data['life']);
if (parseInt(data['poison']) > 0){
selfStatus.html('
中毒 lv.'+data['poison']+'
');
selfLifeBar.addClass('poison');
} else if (parseInt(data['poison']) == 0) {
if (selfLifeBar.hasClass('poison')) {
selfLifeBar.removeClass('poison');
}
selfStatus.html('清新');
}
if (parseInt(data['life']) <= 0) { // dead
if (selfLifeBar.hasClass('poison')) {
selfLifeBar.removeClass('poison');
}
selfLifeBar.addClass('negative');
}
}
function enemyUpdate(data) {
var eneHand = $('#eneHand'); // 手牌數
var eneDeck = $('#eneDeck');
var eneLifeBar = $('#enemy > .profile > .life.progress > .bar');
var eneLifeText = eneLifeBar.children();
var eneStatus = $('#enemy > .profile > .status');
var barWidth = (parseInt(data['life'])/20)*100 // %
eneHand.html(data['hand']);
eneDeck.html(data['deck_left']);
eneLifeBar.css('width',barWidth+'%');
eneLifeBar.attr('data-life',data['life']);
eneLifeText.html(data['life']);
if (parseInt(data['poison']) > 0){
eneStatus.html('
中毒 lv.'+data['poison']+'
');
eneLifeBar.addClass('poison');
} else if (parseInt(data['poison']) == 0) {
if (eneLifeBar.hasClass('poison')) {
eneLifeBar.removeClass('poison');
}
eneStatus.html('清新');
}
if (parseInt(data['life']) <= 0) { // dead
if (eneLifeBar.hasClass('poison')) {
eneLifeBar.removeClass('poison');
}
eneLifeBar.addClass('negative');
}
}
// card
function useCard(id) {
if (!lock) {
statusInitialize();
send(id);
lock = true;
}
}
function setCard(cardsArray) {
$(cardContainer).empty();
var node = "";
cardsArray.forEach((id) => {
var tmp = cardTemplate;
tmp = tmp.replace(/{{ id }}/g,id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
node += tmp;
});
$(cardContainer).append(node);
setCardListener();
resize(); // special case
}
// modals
function chooseRob(data) {
var list = `
`;
data.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('disabled','').replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='
';
dialog.children(".header").html('請問要搶哪張?');
dialog.children(".content").html(list);
dialog.children(".actions").html('');
setModalCardListListener();
modalOpen();
}
function chooseTrade(data,tradeID=null) {
if (data.length == 0) {
send(0);
}
if (tradeID) {
var text = "
對手選擇了"+cards[tradeID]+"
";
} else {
var text = "
選擇一張卡與對手交換
";
}
var list = `
`;
data.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('disabled','').replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='
';
dialog.children(".header").html('交易')
dialog.children(".content").html(text+list);
dialog.children(".actions").html('');
setModalCardListListener();
modalOpen();
tradeChoose = ""; // reset value
}
function choosePlan(data) {
var text = `
請從三張卡中選擇一張加入手牌
`;
var list = `
`;
data.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('disabled','').replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]); // not need to be disabled
list+=tmp;
});
list+='
';
dialog.children(".header").html('妙策');
dialog.children(".content").html(text+list);
dialog.children(".actions").html('');
setModalCardListListener();
modalOpen();
}
function askGuard(data) {
var attackType = data['type'];
var attackName = {"attack": "攻擊","surprise": "奇襲","sweep": "掃射"};
var damage = data['damage'];
var text = `對手使用了 `+attackName[attackType]+`,傷害為 `+damage;
var list = `
`;
handCards.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='
';
dialog.children(".header").html('防禦');
dialog.children(".content").html(text+list);
dialog.children(".actions").html('');
setModalCardListListener();
setModalSkipButtonListener();
$('#modal .ts.list .item').each((i,e) => {
if (e.dataset.id == "2" || e.dataset.id == "8" || e.dataset.id == "14") {
$(e).removeClass('disabled');
}
});
modalOpen();
}
function askDefendRob() {
var list = `
`;
handCards.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='
');
dialog.children(".actions").html('');
timerInitialize(); // also remember to stop the timer
modalOpen();
resultListener();
}
function playerLose() {
dialog.children(".header").html('你輸爆了,SAD');
dialog.children(".content").html('
'+eneName+'
'+curName+'
');
dialog.children(".actions").html('');
timerInitialize(); // also remember to stop the timer
modalOpen();
resultListener();
}
function modalClose() {
if (dialogDisplay) {
ts('#modal').modal('hide');
dialogDisplay = false;
}
}
function modalOpen() {
if (!dialogDisplay) {
ts('#modal').modal('show');
dialogDisplay = true;
}
}
// countdown timer for each turn
function timing() {
if (time == 0) {
clearInterval(timer); // unset the timer
gameStatus.removeClass('warning pulsing');
gameStatus.addClass('negative');
gameStatus.text('時間到!');
return null;
}
if (time <= 5) {
if(!gameStatus.hasClass('pulsing')) {
gameStatus.addClass('pulsing'); // pulsing animation
}
gameStatus.removeClass('info');
gameStatus.addClass('warning');
gameStatus.text('輪到你出牌 '+time);
} else if (time <= 10) {
if(!gameStatus.hasClass('pulsing')) {
gameStatus.addClass('pulsing'); // pulsing animation
}
gameStatus.text('輪到你出牌 '+time);
} else {
gameStatus.removeClass('negative pulsing');
gameStatus.addClass('info');
gameStatus.text('輪到你出牌');
}
time--;
}
function timerSetup(t) {
time = t;
timer = setInterval(timing,1000);
}
function timerInitialize() {
clearInterval(timer);
gameStatus.addClass('inverted');
gameStatus.html('等待中');
}
// resize
function resize() {
var h = cardContainer.offsetHeight;
grid.style.height='calc(100vh - ' + h + 'px)';
column.css('height','calc(100vh - ' + h + 'px)');
}
resize();
window.addEventListener("resize",resize);
// game initailize
$(document).ready(() => {
var roomID = localStorage.getItem('room');
var characterID = localStorage.getItem('character');
if(!roomID || !characterID){
roomID = "n";
characterID = Math.floor(Math.random()*10 + 1).toString(); // randomly choose a player
}
curName = characters[characterID];
setPlayerName(characters[characterID]);
init();
var retry = setInterval(() => {
if(socketReady) { // send the charactor id and room id until the connected
send(characterID);
send(roomID);
clearInterval(retry);
}
}, 300); // try every 0.3s
});
/* https://stackoverflow.com/questions/11700927/horizontal-scrolling-with-mouse-wheel-in-a-div */
function scrollHorizontally(e) {
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
cardContainer.scrollLeft -= (delta*40); // Multiplied by 40
e.preventDefault();
}
cardContainer.addEventListener("mousewheel", scrollHorizontally, false);
cardContainer.addEventListener("DOMMouseScroll", scrollHorizontally, false); //Firefox