finished game webpage

This commit is contained in:
t510599 2018-06-01 01:44:47 +08:00
parent 152a71ff05
commit f77b019b3d
9 changed files with 1130 additions and 0 deletions

125
web/cards.html Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>災厄之悲歌</title>
<!-- Tocas UICSS 與元件 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/2.3.3/tocas.css">
<!-- Tocas JS模塊與 JavaScript 函式 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/2.3.3/tocas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="./gameUI.css">
</head>
<body>
<div class="ts grid" id="grid">
<!-- Main Container -->
<div class="stretched column">
<!-- Enemy information -->
<div class="ts fluid container" style="height: 40%;" id="enemy">
<div class="ts profile segments" style="width: 30%;">
<div class="ts top attached name segment">
<div class="ts center aligned header">== 等待中 ==</div>
</div>
<div class="ts attached life progress">
<div class="bar" style="width:100%;" data-life="20"><span class="text">--</span></div>
</div>
<div class="ts status segment">
清新
</div>
</div>
<!-- Player Avatar -->
<i class="massive user icon" style="opacity: 0;"></i>
<div class="ts info segments">
<div class="ts segment">
<i class="credit card icon"></i>手牌 <span id="eneHand"> -- </span>
</div>
<div class="ts segment">
<i class="inbox icon"></i>牌組 <span id="eneDeck"> -- </span>
</div>
</div>
</div>
<!-- Game Status -->
<div class="ts fluid container" id="game" style="height: 20%;">
<div class="ts statistic">
<div class="label">房號</div>
<div class="value">00000</div>
</div>
<div class="ts big header" id="turn">Turn 0</div>
<div class="ts inverted button" id="status">等待中</div>
<div class="ts separated buttons" id="actions">
<a class="ts disabled primary button" id="skip">跳過回合</a>
<a class="ts negative button" id="giveup">放棄人生</a>
</div>
</div>
<!-- Current Player information -->
<div class="ts fluid container" style="height: 40%;" id="myself">
<div class="ts profile segments" style="width: 30%;">
<div class="ts name top attached segment">
<div class="ts center aligned header">===</div>
</div>
<div class="ts attached life progress">
<div class="bar" style="width:100%" data-life="20"><span class="text">--</span></div>
</div>
<div class="ts status segment">
清新
</div>
</div>
<!-- Player Avatar -->
<i class="massive user icon"></i>
<div class="ts info segments">
<div class="ts segment">
<i class="credit card icon"></i>手牌 <span id="selfHand">--</span>
</div>
<div class="ts segment">
<i class="inbox icon"></i>牌組 <span id="selfDeck">--</span>
</div>
</div>
</div>
</div>
<!-- Log -->
<div class="four wide column">
<label>Log</label>
<div class="ts speeches" id="log">
</div>
</div>
</div>
<!-- Card Container -->
<div class="ts fluid container" style="position: absolute; bottom:0;">
<label>你的手牌</label>
<div class="cards container">
<a class="ts card"><div class="content"><div class="header">Waiting</div><div class="meta">0</div><div class="description">等待中</div></div></a>
</div>
</div>
<!-- Anchor -->
<!-- snackbar -->
<div class="ts bottom right snackbar">
<div class="content"></div>
<a class="action"></a>
</div>
<!-- modal -->
<div class="ts modals dimmer">
<dialog id="modal" class="ts modal">
<div class="header"></div>
<div class="content"></div>
<div class="actions"></div>
</dialog>
</div>
</body>
<!-- websocket util -->
<script src="./ws.js"></script>
<!-- string formating util -->
<script src="./stringFormat.js"></script>
<!-- game core -->
<script src="./gameCore.js"></script>
</html>

696
web/gameCore.js Normal file
View File

@ -0,0 +1,696 @@
/* System Variable */
var time,timer;
var MouseOn = false;
var now = null; // to store whose turn is this. value: "player" or "enemy"
var socketReady = false;
var lock = true;
var debugMode = true;
var dialogDisplay = false;
var curName = "";
var eneName = "";
var drawID = "";
var poisonDamage = "";
var tradeChoose = "";
var handCards = [];
/* DOM Objects */
var cardContainer = $('.cards.container')[0];
var dialog = $('#modal');
var grid = $('#grid')[0];
var column = $('.column');
var log = $('#log')[0];
var gameStatus = $('#status');
var turn = $('#turn')[0];
var roomNum = $('.statistic > .value')[0];
var enemy = $('#enemy');
var myself = $('#myself');
/* text */
// cards
var cards = {'1': '攻擊', '2': '防禦', '3': '治癒', '4': '補給', '5': '強奪', '6': '奇襲', '7': '交易', '8': '洞悉', '9': '妙策', '10': '掃射', '11': '加護', '12': '劇毒', '13': '詛咒', '14': '反制', '15': '狂亂', '16': '逆轉'};
var cardsDescription = {"1":"對敵方造成兩點傷害","2":"回復一點生命<br>被動:抵擋攻擊類卡片","3":"回復兩點生命","4":"抽取兩張手牌","5":"從敵方手牌中選擇一張加入自己的手牌","6":"對敵方造成兩點傷害,並使其隨機損失一張手牌","7":"選取一張手牌與敵方交換","8":"抽取三張手牌<br>被動:抵擋攻擊類卡片,並抽取一張手牌、抵擋強奪的效果","9":"從牌庫中隨機挑出三張卡片,選擇一張加入手牌","10":"對敵方造成零~五點傷害","11":"回復三點生命,並解除中毒","12":"使敵方中毒:每個回合,玩家會損失一點生命","13":"使其損失四點生命,並隨機損失一張手牌","14":"使敵方生命減半<br>被動:抵擋攻擊類卡片,並反彈其傷害和效果","15":"回復三點生命,並對敵方造成三點傷害","16":"使自己與敵方的生命交換"}
var characters = {'1': '安', '2': '圭月', '3': '梅', '4': '小兔', '5': '銀', '6': '正作', '7': 'W', '8': '桑德', '9': '海爾', '10': '雪村'};
// templates
var cardTemplate = `<a class="ts card" data-id="{{ id }}"><div class="content"><div class="header">{{ name }}</div><div class="meta">{{ id }}</div><div class="description">{{ description }}</div></div></a>`;
var listCardTemplate = `<div class="disabled item" data-id="{{ id }}"><div class="ts header">{{ name }}<div class="sub header">{{ description }}</div></div></div>`;
var logTemplate = `<div class="{{ isSelf }} speech"><div class="content">{{ content }}</div></div>`;
var logDivider = `<div class="ts horizontal divider">{{ player }} Turn {{ turn }}</div>`;
// 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('<p class="poison"><i class="theme icon"></i> 中毒 lv.'+data['poison']+'</p>');
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('<p class="poison"><i class="theme icon"></i> 中毒 lv.'+data['poison']+'</p>');
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 = `<div class="ts selection segmented 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+='</div>';
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 = "<p>對手選擇了"+cards[tradeID]+"</p>";
} else {
var text = "<p>選擇一張卡與對手交換</p>";
}
var list = `<div class="ts selection segmented 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+='</div>';
dialog.children(".header").html('交易')
dialog.children(".content").html(text+list);
dialog.children(".actions").html('');
setModalCardListListener();
modalOpen();
tradeChoose = ""; // reset value
}
function choosePlan(data) {
var text = `<p>請從三張卡中選擇一張加入手牌</p>`;
var list = `<div class="ts selection segmented 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+='</div>';
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 = `<div class="ts selection segmented list">`;
handCards.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='</div>';
dialog.children(".header").html('防禦');
dialog.children(".content").html(text+list);
dialog.children(".actions").html('<button id="modalSkipBtn" class="ts primary button">不使用卡片</button>');
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 = `<div class="ts selection segmented list">`;
handCards.forEach((id) => {
var tmp = listCardTemplate;
tmp = tmp.replace('{{ id }}',id).replace("{{ name }}",cards[id]).replace("{{ description }}",cardsDescription[id]);
list+=tmp;
});
list+='</div>';
dialog.children(".header").html('防禦強奪');
dialog.children(".content").html(list);
dialog.children(".actions").html('<button id="modalSkipBtn" class="ts primary button">不使用卡片</button>');
setModalCardListListener();
setModalSkipButtonListener();
$('#modal .ts.list .item').each((i,e) => {
if (e.dataset.id == "8") {
$(e).removeClass('disabled');
}
});
modalOpen();
}
function playerWin() {
dialog.children(".header").html('你贏了!');
dialog.children(".content").html('<img src="./won.png"><p class="result enemy name">'+eneName+'</p><p class="result player name">'+curName+'</p>');
dialog.children(".actions").html('<button id="close" class="ts button">關閉視窗</button><button id="restart" class="ts primary button">重啟對戰</button><button id="returnIndex" class="ts positive button">返回主畫面</button>');
timerInitialize(); // also remember to stop the timer
modalOpen();
resultListener();
}
function playerLose() {
dialog.children(".header").html('你輸爆了SAD');
dialog.children(".content").html('<img src="./lose.png"><p class="result enemy name">'+eneName+'</p><p class="result player name">'+curName+'</p>');
dialog.children(".actions").html('<button id="close" class="ts button">關閉視窗</button><button id="restart" class="ts primary button">重啟對戰</button><button id="returnIndex" class="ts positive button">返回主畫面</button>');
timerInitialize(); // also remember to stop the timer
modalOpen();
resultListener();
}
function modalClose() {
if (dialogDisplay) {
ts('#modal').modal('hide');
console.log('close');
dialogDisplay = false;
}
}
function modalOpen() {
if (!dialogDisplay) {
ts('#modal').modal('show');
console.log('open');
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 = "1"; // default charactor
}
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

88
web/gameUI.css Normal file
View File

@ -0,0 +1,88 @@
.cards.container > .ts.card {
width: 220px !important;
display: inline-flex !important;
white-space: normal;
margin-left: 8px;
margin-top: 5px;
margin-bottom: 0;
}
.cards.container > .ts.card:first-child {
margin-left: 0;
}
.cards.container {
overflow-x: scroll;
white-space: nowrap;
min-height: 140px;
width: 100%;
padding : 0 8px;
background-color: rgb(250,250,250);
}
body,html {
height: 100%;
width: 100%;
overflow: hidden;
}
label {
background-color: black;
color: white;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
padding: 5px 10px;
z-index: 10;
}
.grid,.column {
height: unset;
padding: 0 !important;
margin: 0 !important;
}
.speeches {
overflow-y: scroll;
height: 100%;
padding: 10px 5px;
box-sizing: border-box;
border-left: 1px solid rgba(0,0,0,0.2);
border-bottom: 1px solid rgba(0,0,0,0.2);
background-color: #efeef1;
}
#game,#myself,#enemy {
display: flex;
justify-content: center;
align-items: center;
}
#game > *,#enemy > *,#myself > * {
margin: auto;
}
.ts.progress .bar {
min-width: 20px;
}
.poison {
color: purple !important;
}
.poison.bar {
background-color: purple !important;
}
.bar {
transition: width .5s ease-in-out;
}
.modal .list{
max-height: 60vh;
overflow-y: scroll;
}
.modal .content img {
width: 100%;
}
p.result.name {
position: absolute;
font-size:50px;
top: calc(50% - 25px);
margin: 0;
line-height: 50px;
}
p.result.enemy.name {
left: 1.5em;
}
p.result.player.name {
right: 1.5em;
}

81
web/index.html Normal file
View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>災厄之悲歌</title>
<!-- Tocas UICSS 與元件 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/2.3.3/tocas.css">
<!-- Tocas JS模塊與 JavaScript 函式 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/2.3.3/tocas.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
<style>
body {
margin: 0;
width: 100vw;
height: 100vh;
}
body > .container {
height: 100%;
}
.title.container {
display: flex !important;
align-items: center;
justify-content: center;
flex-direction: column;
}
.dialog.container {
display: flex !important;
flex-direction: column;
}
.dialog.container > .ts.segment {
flex: 1;
}
</style>
</head>
<body>
<div class="ts title fluid center aligned container">
<h1 style="margin-left: auto; margin-right: auto;display: inline-flex;">災厄之悲歌</h1>
<button class="ts primary animated fadeInDown button" style="animation-delay: .8s;">進入遊戲</button>
<div class="ts steps" style="display: none;">
<a class="step" id="firstStep">
<i class="user icon"></i>
<div class="content">
<div class="title">角色</div>
<div class="description">選擇你的鬥士</div>
</div>
</a>
<a class="step" id="secondStep">
<i class="cube icon"></i>
<div class="content">
<div class="title">房號</div>
<div class="description">輸入房號,或是隨機加入遊戲</div>
</div>
</a>
</div>
<div class="ts segment" id="character" style="display: none;">
<div class="ts big centered menu">
<a class="item" data-id="1"></a>
<a class="item" data-id="2">圭月</a>
<a class="item" data-id="3"></a>
<a class="item" data-id="4">小兔</a>
<a class="item" data-id="5"></a>
<a class="item" data-id="6">正作</a>
<a class="item" data-id="7">W</a>
<a class="item" data-id="8">桑德</a>
<a class="item" data-id="9">海爾</a>
<a class="item" data-id="10">雪村</a>
</div>
<button id="next" class="ts positive button">確認</button>
</div>
<div class="ts segment" id="room" style="display: none;">
<div class="ts action input">
<input id="roomNum" type="text" placeholder="0~99999">
<button id="roomBtn" class="ts positive button">Join!</button>
</div>
<div class="ts horizontal divider">or</div>
<button id="random" class="ts primary button">隨機配對</button>
</div>
<script src="./menu.js"></script>
</body>
</html>

BIN
web/lose.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

92
web/menu.js Normal file
View File

@ -0,0 +1,92 @@
// value setter
var roomID = null,
characterID = null;
function setter() {
if(characterID && roomID){
localStorage.setItem('character',characterID);
localStorage.setItem('room',roomID);
}
}
// listener
var btn = document.querySelector('.ts.primary.button');
btn.addEventListener('click',() => {
btn.classList.remove('fadeInDown');
btn.style.animationDelay = "0s";
btn.classList.add('fadeOutUp');
setTimeout(() => {displayCharacter();},800)
});
var characterMenu = document.querySelector('.ts.centered.menu');
var characters = Array.from(characterMenu.children);
characterMenu.addEventListener('click', (e) => {
var id = e.target.dataset.id;
console.log("charactor:"+id);
if(id) {
characters.forEach(e => {
e.classList.remove('active');
});
characterID = id;
e.target.classList.add('active');
}
});
var nextBtn = document.querySelector('#next');
nextBtn.addEventListener('click',() => { displayRoom() });
var firstStep = document.querySelector('#firstStep');
firstStep.addEventListener('click',() => { displayCharacter() });
var roomNum = document.querySelector('#roomNum');
var roomBtn = document.querySelector('#roomBtn');
roomBtn.addEventListener('click',() => {
if(roomNum.value != null && 0 <= parseInt(roomNum.value) && 99999 >= parseInt(roomNum.value)){
console.log('room:'+roomNum.value)
roomID = roomNum.value;
setter();
location.href = './cards.html';
} else {
console.log("room number error");
}
});
var randomBtn = document.querySelector('#random');
randomBtn.addEventListener('click',() => {
roomID = "n"
setter();
location.href = './cards.html';
});
// animation
var steps = document.querySelector('.ts.steps');
var step = document.querySelectorAll('.ts.steps .step');
var container = document.querySelector('.ts.fluid.container');
var title = document.querySelector('h1');
var characterDialog = document.querySelector('#character');
var roomDialog = document.querySelector('#room');
function displayCharacter() {
btn.style.display = "none";
steps.style.display = "flex";
container.classList.remove('title');
container.classList.remove('fluid');
container.classList.add('dialog');
characterDialog.style.display = "block";
roomDialog.style.display = "none";
characterDialog.classList.add('animated');
characterDialog.classList.add('fadeIn');
title.style.marginTop = "1em";
step[0].classList.add('active');
step[0].classList.remove('completed');
step[1].classList.remove('active');
}
function displayRoom() {
step[0].classList.remove('active');
step[0].classList.add('completed');
step[1].classList.add('active');
characterDialog.style.display = "none";
roomDialog.style.display = "block";
roomDialog.classList.add('animated');
roomDialog.classList.add('fadeIn');
}

4
web/stringFormat.js Normal file
View File

@ -0,0 +1,4 @@
/* https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format */
String.prototype.format = function(array) {
return array.reduce((p,c) => p.replace('{}',c), this);
};

BIN
web/won.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

44
web/ws.js Normal file
View File

@ -0,0 +1,44 @@
var socket; // create object
function init(host="wss://api.stoneapp.tech:8787"){
socket = new WebSocket(host);
console.log("Initializing connection...");
socket.onopen = function() {
wsOnOpen(this.readyState);
}
socket.onclose = function() {
wsOnClose();
}
socket.onmessage = function(msg) {
var dataJson;
try {
dataJson = JSON.parse(msg.data);
} catch(except) {
console.log(msg.data);
return null;
}
wsHandler(dataJson);
}
socket.onerror = function(except) {
wsOnError(except);
}
}
function quit() {
if(socket != null){
socket.close();
socket = null;
}
}
function send(msg) {
try {
socket.send(msg);
} catch(ex) {
console.log(ex);
}
}