ElegyOfDisaster/web/gameCore.js
2018-06-03 00:41:16 +08:00

699 lines
25 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 = false;
var dialogDisplay = false;
var playerChooseStatus = 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":
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('<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();
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 on 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('<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) {
if (data.length == 0) {
send(0);
}
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');
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