`;
+
+// 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":
+ robbedCard = dataJson.data[1]; // card ID
+ LogPlayerChoose("robbed",robbedCard);
+ break;
+ case "tradeChoose":
+ tradeChoose = dataJson.data[1]; // card ID
+ LogPlayerChoose("tradeChoose",tradeChoose);
+ 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() {
+ 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;
+ if(now === "player") { // to distinguish if this is self log or enemy log
+ node = node.replace("{{ isSelf }}","right");
+ } else if (now === "enemy") {
+ node = node.replace("{{ isSelf }}","");
+ }
+
+ 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,dataID) {
+ var node = logTemplate.replace("{{ isSelf }}","right");
+ 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[type].format([tmpName,cards[dataID]])); // 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
+ }
+}
+
+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) {
+ console.log(tradeChoose);
+ 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+='