diff options
30 files changed, 1373 insertions, 180 deletions
diff --git a/webcards/LICENSE b/webcards/LICENSE new file mode 100644 index 0000000..15d71cd --- /dev/null +++ b/webcards/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Kyle Gunger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/webcards/README.md b/webcards/README.md new file mode 100644 index 0000000..0c6b070 --- /dev/null +++ b/webcards/README.md @@ -0,0 +1,5 @@ +# WebCardsClient + +![WebCards](http://35.11.215.147:3000/CCGKyle/WebCardsClient/raw/branch/master/images/wc-icon-144.png) + +An in-browser client to play WebCards
\ No newline at end of file diff --git a/webcards/assets/standard/diamond.svg b/webcards/assets/standard/diamond.svg new file mode 100644 index 0000000..41f7cc2 --- /dev/null +++ b/webcards/assets/standard/diamond.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + style="enable-background:new" + id="svg8" + version="1.1" + viewBox="0 0 33.866666 33.866668" + height="128" + width="128"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-263.13332)" + style="opacity:1" + id="layer1"> + <path + id="path859" + d="M 16.933333,295.73051 5.3453296,280.06665 16.933332,264.40279 28.521336,280.06665 Z" + style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.49378231;stroke-opacity:1" /> + </g> +</svg> diff --git a/webcards/assets/standard/heart.svg b/webcards/assets/standard/heart.svg new file mode 100644 index 0000000..8cc32e4 --- /dev/null +++ b/webcards/assets/standard/heart.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + width="128" + height="128" + viewBox="0 0 33.866666 33.866668" + version="1.1" + id="svg8" + style="enable-background:new"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <path + transform="matrix(0.26458334,0,0,0.26458334,1.2695285,1.26953)" + style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.3796902;stroke-opacity:1;enable-background:new" + d="M 31.880591,0.16895874 C 14.609272,3.6552705 -0.10970932,24.675453 6.162248e-4,42.266772 0.20206882,74.388447 59.172962,118.40354 59.172962,118.40354 c 0,0 59.020808,-44.003376 59.229928,-76.136768 C 118.51733,24.675491 103.79578,3.6475603 86.522912,0.16895874 76.21487,-1.9070223 59.229844,15.963831 59.229844,15.963831 c 0,0 -17.02625,-17.8786392 -27.349253,-15.79487226 z" + id="path815" /> +</svg> diff --git a/webcards/client.html b/webcards/client.html index 70d7f1d..0b06712 100644 --- a/webcards/client.html +++ b/webcards/client.html @@ -8,37 +8,54 @@ <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700&display=swap" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="styles/icofont.css"> + <link rel="stylesheet" type="text/css" href="styles/input.css"> + <link rel="stylesheet" type="text/css" href="styles/client/base.css"> <link rel="stylesheet" type="text/css" href="styles/client/desktop.css"> <link rel="stylesheet" type="text/css" href="styles/client/tablet.css"> <link rel="stylesheet" type="text/css" href="styles/client/mobile.css"> - - <!--<script src="scripts/libs/discord-rpc.js"></script>--> - <script src="scripts/client/message.js"></script> - <script src="scripts/client/sock.js"></script> - <script src="scripts/client/lobby.js"></script> - <script src="scripts/client/table.js"></script> - <script src="scripts/client/client.js"></script> + <link rel="stylesheet" type="text/css" href="styles/client/card.css"> + + <link rel="icon" sizes="32x32" href="images/wc-icon-32.png"> + <link rel="icon" sizes="48x48" href="images/wc-icon-48.png"> + <link rel="icon" sizes="96x96" href="images/wc-icon-96.png"> + <link rel="icon" sizes="144x144" href="images/wc-icon-144.png"> + <link rel="icon" sizes="288x288" href="images/wc-icon-288.png"> + + <script src="scripts/cards/card.js"></script> + <script src="scripts/cards/deck.js"></script> + <script src="scripts/cards/drag.js"></script> + + <script src="scripts/gui/input.js"></script> + <script src="scripts/gui/lobby.js"></script> + <script src="scripts/gui/table.js"></script> + + <script src="scripts/socket/message.js"></script> + <script src="scripts/socket/sock.js"></script> + + <script src="scripts/client.js"></script> + + <title>WebCards - Client</title> </head> <body> - <div class="table" style="visibility: hidden;"> + <div class="table" state="closed"> </div> <div class="topbar" style="height: auto;"> <div class="top-buttons"> - <button id="newgame" class="top-button" onclick="game.lob.newGameScreen()"></button> - <button id="settings" class="top-button" onclick="game.lob.mobileSettingsScreen()"></button> + <button id="newgame" class="top-button" onclick="game.lob.newGame()"></button> + <button id="settings" class="top-button" onclick="game.lob.mobileSettings()"></button> <button id="reset" class="top-button" onclick="game.reset()"></button> </div> <div class="new-game" style="display: none;"></div> - <div class="mobile-settings" style="display: none;"> - <span><input type="text" id="name" placeholder="Username"/></span> - <span><input type="color" id="usercolor" value="#f00"/></span> + <div class="settings mobile-settings" style="display: none;"> + <span><input type="text" placeholder="Username"/></span> + <span><input type="color" value="#f00"/></span> <button id="set">Accept Settings</button> </div> @@ -82,11 +99,10 @@ </div> </div> - <div class="settings"> - <span><input type="text" id="name" placeholder="Username"/></span> - <span><input type="color" id="usercolor" value="#f00"/></span> - - <button onclick="game.acceptSettings()">Accept Settings</button> + <div class="settings" > + <div class="input-container" type="color" onclick="this.getElementsByTagName('input')[0].click()"> + <input type="color"> + </div> </div> </div> </div> @@ -96,6 +112,36 @@ var params = new URLSearchParams((new URL(window.location)).search); var game = new Client(params.get("s"), params.get("g")); setTimeout(game.init.bind(game), 100); + + + // Live testing purposes only + var d = new MultiDrag(); + var c1 = new Card({ + all: [ + { + type: "image", + image: "assets/standard/diamond.svg" + } + ] + }); + var c2 = new Card({ + all: [ + { + type: "image", + image: "assets/standard/heart.svg" + } + ] + }); + function test() { + c1.e.addEventListener("mousedown", d.startDragging.bind(d)); + c1.e.addEventListener("mouseup", d.stopDraggingAll.bind(d)); + c2.e.addEventListener("mousedown", d.startDragging.bind(d)); + c2.e.addEventListener("mouseup", d.stopDraggingAll.bind(d)); + + game.tab.root.append(c1.e); + game.tab.root.append(c2.e); + game.tab.openTable(); + } </script> </body> diff --git a/webcards/favicon.ico b/webcards/favicon.ico Binary files differnew file mode 100644 index 0000000..91e0742 --- /dev/null +++ b/webcards/favicon.ico diff --git a/webcards/images/wc-icon-144.png b/webcards/images/wc-icon-144.png Binary files differnew file mode 100644 index 0000000..ecc4084 --- /dev/null +++ b/webcards/images/wc-icon-144.png diff --git a/webcards/images/wc-icon-288.png b/webcards/images/wc-icon-288.png Binary files differnew file mode 100644 index 0000000..04f6a07 --- /dev/null +++ b/webcards/images/wc-icon-288.png diff --git a/webcards/images/wc-icon-32.png b/webcards/images/wc-icon-32.png Binary files differnew file mode 100644 index 0000000..ec390c5 --- /dev/null +++ b/webcards/images/wc-icon-32.png diff --git a/webcards/images/wc-icon-48.png b/webcards/images/wc-icon-48.png Binary files differnew file mode 100644 index 0000000..e787102 --- /dev/null +++ b/webcards/images/wc-icon-48.png diff --git a/webcards/images/wc-icon-96.png b/webcards/images/wc-icon-96.png Binary files differnew file mode 100644 index 0000000..a10d0eb --- /dev/null +++ b/webcards/images/wc-icon-96.png diff --git a/webcards/index.html b/webcards/index.html index c7d2f71..c322ec1 100644 --- a/webcards/index.html +++ b/webcards/index.html @@ -8,14 +8,24 @@ <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700&display=swap" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="styles/icofont.css"> + <link rel="icon" sizes="32x32" href="images/wc-icon-32.png"> + <link rel="icon" sizes="48x48" href="images/wc-icon-48.png"> + <link rel="icon" sizes="96x96" href="images/wc-icon-96.png"> + <link rel="icon" sizes="144x144" href="images/wc-icon-144.png"> + <link rel="icon" sizes="288x288" href="images/wc-icon-288.png"> + <link rel="stylesheet" type="text/css" href="styles/home/base.css"> <link rel="stylesheet" type="text/css" href="styles/home/desktop.css"> <link rel="stylesheet" type="text/css" href="styles/home/mobile.css"> + + <link rel="stylesheet" type="text/css" href="styles/input.css"> + + <title>WebCards</title> </head> <body> <div class="content"> - <p style="font-size: 30px; font-weight: bold;"><span style="color: dodgerblue; font-weight: normal;">Web</span>Cards</p> + <p style="font-size: 30px; font-weight: normal;">Web<span style="color: #0084ff; font-weight: bold;">Cards</span></p> <div> <select id="type"> <option value="ws://" selected>ws</option> @@ -27,7 +37,7 @@ <input id="port" type="number" value="4040"> </div> - <a id="conn" href="client.html?s=ws://127.0.0.1:4040&g=-1">Connect</a> + <button id="conn" onclick="connect()">Connect</a> </div> <script> @@ -40,9 +50,10 @@ a.onchange = updateLink; p.onchange = updateLink; - function updateLink() { - var url = "client.html?s=" + t.value + a.value + ":" + p.value + "&g=-1"; - c.setAttribute("href", url); + function connect() { + var url = "./client.html?s=" + t.value + a.value + ":" + p.value + "&g=-1"; + //c.setAttribute("href", url); + window.location = url; } </script> </body> diff --git a/webcards/scripts/cards/card.js b/webcards/scripts/cards/card.js new file mode 100644 index 0000000..015995d --- /dev/null +++ b/webcards/scripts/cards/card.js @@ -0,0 +1,97 @@ +var CardPos = ["top", "topl", "topr", "mid", "midt", "midb", "bot", "botl", "botr", "all"]; + +// Card class represents one card. +// Every card should have a deck. +// Use deck.appendCard or deck.prependCard to make a card visible +function Card (data) { + this.e = this.generateElements(data); + this.e.style.left = "0px"; + this.e.style.top = "0px"; +} + +// Internal +Card.prototype = { + // Main generation func + generateElements: function (data) { + switch (typeof data) { + case "object": + return this.generateObjectCard(data); + case "string": + return this.generateBasicCard(data); + } + let e = document.createElement("card"); + let t = document.createElement("carea"); + t.className = "mid"; + t.innerText = "Card Error: data"; + e.append(t); + return e; + }, + + // Generate a card with basic text only + generateBasicCard: function (data) { + let e = document.createElement("card"); + let t = document.createElement("carea"); + t.className = "mid"; + t.innerText = data; + e.appendChild(t); + return e; + }, + + // Generate a card with rich visuals + generateObjectCard: function (data) { + let e = document.createElement("card"); + + // Check for an asset URL + if (typeof data.assetURL != "string") { + data.assetURL = ""; + } + + // Set card styles + for (let i in data.style) { + e.style[i] = data.style[i]; + } + + // Generate card areas. + for (let i in CardPos) { + if (typeof data[CardPos[i]] == "object") + e.appendChild(this.generateCArea(data[CardPos[i]], CardPos[i], data.assetURL)); + } + + return e; + }, + + generateCArea: function (data, carea, assetURL) { + // Create and set area + let area = document.createElement("carea"); + area.className = carea; + + // Create inner area text and images + for (let i in data) { + if (i == "style") + for (j in data.style) + area.style[j] = data.style[j]; + + if (data[i].type == "text") { + let e = document.createElement("ctext"); + + e.innerText = data[i].text; + + for (let j in data[i].style) { + e.style[j] = data[i].style[j]; + } + + area.appendChild(e); + + } else if (data[i].type == "image") { + let e = document.createElement("cimage"); + + e.style.backgroundImage = "url(\"" + assetURL + data[i].image + "\")"; + + area.appendChild(e); + } + } + + return area; + } + +};
\ No newline at end of file diff --git a/webcards/scripts/cards/deck.js b/webcards/scripts/cards/deck.js new file mode 100644 index 0000000..544a9ef --- /dev/null +++ b/webcards/scripts/cards/deck.js @@ -0,0 +1,106 @@ +// Deck class represents multiple cards. +// Can be arranged in multiple ways. +function Deck (options = {}){ + this.cards = []; + + // View mode + // infdraw - infinite draw. always appears as if there are multiple cards + // stack - stack mode + // strip + // horizontal + // left (strip-hl) + // right (strip-hr) + // vertical + // top (strip-vt) + // bottom (strip-vb) + this.mode = options.mode; + + // Select mode + // above + // below + // around + // one + // all + this.smode = options.smode; + + // Select count (-1 = all available) + // above - controls number of cards above clicked are selected + // below - controls number of cards below clicked are selected + // around + // number - number above and below selected + // array - [first number: number above selected] [second number: number below selected] + // one - no effect + // all - no effect + this.sct = options.sct; + + // Position + // array of where the deck is centered + this.x = options.pos[0]; + this.y = options.pos[1]; + + this.e = document.createElement("deck"); +} + +Deck.prototype = { + // Add a card to the front of the deck + appendCard: function(card) { + this.cards.push(card); + this.e.appendChild(card.e); + }, + + // Add a card to the back of the deck + prependCard: function(card) { + this.cards.unshift(card); + this.e.prepend(card.e); + }, + + // Add a card at the index specified + addCardAt: function(card, index) { + if(index < 0 || index > this.cards.length) + return + + if(index == 0) { + this.prependCard(card); + } else if (index == this.cards.length) { + this.appendCard(card); + } else { + let temp = this.cards.slice(0, index); + temp[temp.length - 1].e.after(card.e); + temp.push(card); + this.cards.unshift(...temp); + } + }, + + // Swap the cards at the specified indexes + swapCard: function(index1, index2) { + if(index1 < 0 || index1 >= this.cards.length || index2 < 0 || index2 >= this.cards.length) + return + + var temp = this.cards[index1] + this.cards[index1] = this.cards[index2]; + this.cards[index2] = temp; + + this.cards[index1 - 1].e.after(this.cards[index1]); + this.cards[index2 - 1].e.after(this.cards[index2]); + }, + + // Remove the card at the front of the deck (index length - 1), returns the card removed (if any) + removeFront: function() { + return this.removeCard(this.cards.length - 1); + }, + + // Remove the card at the back of the deck (index 0), returns the card removed (if any) + removeBack: function() { + return this.removeCard(0); + }, + + // Remove a card from the deck, returning the card element + removeCard: function(index) { + + if(index < 0 || index >= this.cards.length) + return + + this.e.removeChild(this.cards[index].e); + return this.cards.splice(index, 1)[0]; + } +};
\ No newline at end of file diff --git a/webcards/scripts/cards/drag.js b/webcards/scripts/cards/drag.js new file mode 100644 index 0000000..cce1e72 --- /dev/null +++ b/webcards/scripts/cards/drag.js @@ -0,0 +1,83 @@ +function MultiDrag() { + this.del = false; + this.drag = []; + window.addEventListener("mousemove", this.update.bind(this)); + document.body.addEventListener("mouseleave", this.stopDraggingAll.bind(this)); +} + +MultiDrag.prototype = { + addDragEl: function(el, ox, oy, px, py, pt) { + if(this.del) + return; + + el.style.transitionDuration = "0.04s"; + + this.drag.push({ + e: el, + osx: ox, + osy: oy, + prx: px, + pry: py, + ptd: pt + }); + + return this.drag.length - 1; + }, + + startDragging: function(mevent) { + if(this.del) + return; + + console.log(mevent); + + if(mevent.button != 0) + return; + + let pos = mevent.target.getBoundingClientRect(); + + return this.addDragEl( + mevent.currentTarget, + mevent.clientX - pos.left, + mevent.clientY - pos.top, + mevent.currentTarget.style.left, + mevent.currentTarget.style.top, + mevent.currentTarget.style.transitionDuration + ); + }, + + stopDragging: function(i) { + this.del = true; + + if (i < 0 || i >= this.drag.length) + return; + + this.drag[i].e.style.transitionDuration = this.drag[i].ptd; + this.drag[i].e.style.left = this.drag[i].prx; + this.drag[i].e.style.top = this.drag[i].pry; + + this.drag.splice(i, 1); + + this.del = false; + }, + + stopDraggingAll: function() { + this.del = true; + + while (this.drag.length > 0) { + this.drag[0].e.style.transitionDuration = this.drag[0].ptd; + this.drag[0].e.style.left = this.drag[0].prx; + this.drag[0].e.style.top = this.drag[0].pry; + + this.drag.shift(); + } + + this.del = false; + }, + + update: function(e) { + for (let i = 0; i < this.drag.length && !this.del; i++) { + this.drag[i].e.style.left = e.clientX - this.drag[i].osx + "px"; + this.drag[i].e.style.top = e.clientY - this.drag[i].osy + "px"; + } + } +};
\ No newline at end of file diff --git a/webcards/scripts/client/client.js b/webcards/scripts/client.js index b5dd4bf..ea62e26 100644 --- a/webcards/scripts/client/client.js +++ b/webcards/scripts/client.js @@ -13,12 +13,13 @@ function Client(serveraddr, game) { } Client.prototype = { + // Initialize the connection init: function() { this.soc.init(); }, - // Entry point for a message. - // If it's a close message, the + // Entry point for a message from the server. + // If it's a close message, we close the game if it is open and change the lobby to reflect the error/close. cb: function(m) { console.log(m); @@ -42,6 +43,7 @@ Client.prototype = { } }, + // Called when negotiating with the server for the first time and we are determining versions handshake: function(m) { switch (m.type) { case "verr": @@ -57,6 +59,7 @@ Client.prototype = { } }, + // Lobby switch, called when in the lobby and a message arrives from the server lobby: function (m) { switch (m.type) { case "plist": @@ -87,6 +90,7 @@ Client.prototype = { } }, + // Game switch, called when in game and a message arrives from the server game: function (m) { switch (m.type) { diff --git a/webcards/scripts/client/lobby.js b/webcards/scripts/client/lobby.js deleted file mode 100644 index 8d46352..0000000 --- a/webcards/scripts/client/lobby.js +++ /dev/null @@ -1,102 +0,0 @@ -// Lobby manages the players and games provided by the server and allows users to join or create their own games. -function Lobby(el){ - this.root = el; - - this.elements = { - status: this.root.getElementsByClassName("status")[0], - addr: this.root.getElementsByClassName("addr")[0], - - stats: { - game: document.getElementById("game"), - packs: document.getElementById("packs"), - online: document.getElementById("online"), - ingame: document.getElementById("ingame"), - pubgame: document.getElementById("pubgame") - }, - - settings: { - name: document.getElementById("name"), - color: document.getElementById("usercolor") - } - }; - - this.top = new TopBar(document.getElementsByClassName("topbar")[0]); - - this.init = false; - this.online = []; - this.games = []; - this.packs = []; - this.players = []; -} - -Lobby.prototype = { - packList: function(data){ - - this.elements.stats.packs.innerText = this.packs.length(); - }, - - gameList: function(data, game){ - - this.elements.stats.pubgame.innerText = this.games.length(); - }, - - players: function(data) { - - this.elements.stats.online.innerText = this.players.length(); - this.init = true; - }, - - addGame: function(data){ - - }, - - removeGame: function(data){ - - }, - - addPlayer: function(data){ - - }, - - movePlayer: function(data){ - - }, - - removePlayer: function(data){ - - }, - - newGameScreen: function(){ - if(this.init) return; - }, - - setState: function(text, color, server){ - this.elements.status.style.backgroundColor = color; - this.elements.status.innerText = text; - this.elements.addr.innerText = server; - this.top.setColor(color); - }, - - reset: function(){ - this.setState("Connecting", "#DA0", this.elements.addr.innerText); - this.init = false; - } -}; - -// ############### -// # TopBar Code # -// ############### - -function TopBar(el) { - this.root = el; - - this.newGame = el.getElementsByClassName("new-game")[0]; - this.mobileSettings = el.getElementsByClassName("mobile-settings")[0]; - this.status = el.getElementsByClassName("status")[0]; -} - -TopBar.prototype = { - setColor: function(color) { - this.status.style.backgroundColor = color; - } -}; diff --git a/webcards/scripts/client/table.js b/webcards/scripts/client/table.js deleted file mode 100644 index 911763a..0000000 --- a/webcards/scripts/client/table.js +++ /dev/null @@ -1,16 +0,0 @@ -// Table represents and manages the actual game. It accepts inputs from the server and tries to queries the server when the player makes a move. -function Table(el, soc) { - this.root = el; - this.soc = soc; -} - -Table.prototype = { - - handleClose: function() { - - }, - - reset: function() { - - } -}
\ No newline at end of file diff --git a/webcards/scripts/gui/input.js b/webcards/scripts/gui/input.js new file mode 100644 index 0000000..c349a07 --- /dev/null +++ b/webcards/scripts/gui/input.js @@ -0,0 +1,159 @@ +var inputFuncs = { + createInput: function(type = "text", wrapped = false, id) { + var el = document.createElement("input"); + el.setAttribute("type", type); + + if(typeof id == "string") + el.setAttribute("id", id); + + if(wrapped) { + var wrapper = document.createElement("div"); + wrapper.className = "input-container"; + wrapper.setAttribute("type", type); + wrapper.setAttribute("onclick", "this.firstElementChild.click()"); + wrapper.appendChild(el); + wrapper.input = el; + return wrapper; + } + + el.getValue = function () { + return this.value; + } + return el; + }, + + inputLabel(text, id) { + var el = document.createElement("label"); + el.innerText = text; + if(typeof id == "string") + el.setAttribute("for", id); + return el; + }, + + colorInput: function(value, id) { + var el = this.createInput("color", true, id); + el.value = value; + return el; + }, + + textInput: function(value, placeholder, id) { + var el = this.createInput("text", false, id); + el.setAttribute("placeholder", placeholder); + el.value = value; + return el; + }, + + numberInput: function(value, id) { + var el = this.createInput("number", false, id); + el.value = value; + return el; + }, + + fileInput: function(value, id) { + var el = this.createInput("file", true, id); + + el.value = value; + + el.setAttribute("data-files", "Choose a file"); + + el.firstElementChild.onchange = function () { + let text = ""; + switch (this.files.length) { + case 0: + text = "Choose a file"; + break; + case 1: + text = "File: " + this.files[0].name; + break; + default: + text = "Files: " + this.files[0].name + "..."; + break; + } + el.setAttribute("data-files", text); + } + + return el; + }, + + checkboxInput: function(checked = false, id) { + var el = this.createInput("checkbox", false, id); + if(checked) + el.setAttribute("checked"); + return el; + }, + + radioInput: function(group, value, checked = false, id) { + var el = this.createInput("radio", false, id); + el.setAttribute("name", group); + el.setAttribute("value", value); + if(checked) + el.checked = true; + return el; + }, + + radioInputs: function(group, names, values, checked = 0) { + var wrapper = document.createElement("div"); + wrapper.className = "input-container"; + wrapper.setAttribute("type", "radio"); + + wrapper.getValue = function() { + for(let i = 0; i < this.children.length; i++){ + if(this.children[i].checked) + return this.children[i].value; + } + }; + + for(let i = 0; i < values.length; i++) { + wrapper.appendChild(this.inputLabel(names[i], group+"-"+i)); + if(i == checked) + wrapper.appendChild(this.radioInput(group, values[i], true, group+"-"+i)); + else + wrapper.appendChild(this.radioInput(group, values[i], false, group+"-"+i)); + wrapper.appendChild(document.createElement("br")); + } + + return wrapper; + }, + + wrapInput: function(el) { + + } +}; + +function Settings () { + this.settings = { + username: { + type: "text", + args: [Math.floor(Math.random() * 100000), "Username", "userName"] + } + }; + + this.genSettings(); +} + +Settings.prototype = { + getSettings: function() { + var out = {}; + for(let key in this.settings) { + + } + }, + + putSettings: function (el) { + for(let key in this.settings) { + el.appendChild(this.settings[key]); + } + }, + + genSettings: function() { + for(let key in this.settings) { + switch(this.settings[key].type) { + case "radio": + this.settings[key] = inputFuncs.radioInputs(...this.settings[key].args); + default: + if(typeof inputFuncs[this.settings[key].type+"Input"] != null) + this.settings[key] = inputFuncs[this.settings[key].type+"Input"](...this.settings[key].args); + } + } + } +};
\ No newline at end of file diff --git a/webcards/scripts/gui/lobby.js b/webcards/scripts/gui/lobby.js new file mode 100644 index 0000000..731d9ec --- /dev/null +++ b/webcards/scripts/gui/lobby.js @@ -0,0 +1,196 @@ +// Lobby manages the players and games provided by the server and allows users to join or create their own games. +function Lobby(el){ + this.root = el; + + this.e = { + status: el.getElementsByClassName("status")[0], + addr: el.getElementsByClassName("addr")[0], + games: el.getElementsByClassName("games")[0], + settings: el.getElementsByClassName("settings")[0], + + stats: { + game: document.getElementById("game"), + packs: document.getElementById("packs"), + online: document.getElementById("online"), + ingame: document.getElementById("ingame"), + pubgame: document.getElementById("pubgame") + } + }; + + this.top = new TopBar(document.getElementsByClassName("topbar")[0]); + + this.init = false; + this.online = []; + this.games = []; + this.packs = []; + this.players = []; +} + +Lobby.prototype = { + // Set initial pack list + // {data array} array of strings representing pack names + packList: function(data) { + this.packs = data; + this.top.setPacks(this.packs) + this.e.stats.packs.innerText = this.packs.length(); + }, + + // Set initial game list. + // { data object } object containing {games} and {name} + // { data.name string } name of the game the server runs + // { data.games array } array of public games the server is running + // { data.games[n].name } room name + // { data.games[n].packs } list of the pack names used by this game + // { data.games[n].id } room identifier (uuid) + // { data.games[n].max } max players in room + gameList: function(data) { + while (this.e.games.firstChild != null) { + this.e.games.remove(this.elements.games.firstChild) + } + + for (let i in data.games) { + let gel = new GameEl(i.name, i.packs, i.id); + this.games.push(gel); + this.e.games.appendChild(gel.getElement()); + } + + this.e.stats.game.innerText = data.name; + this.e.stats.pubgame.innerText = this.games.length(); + }, + + // Set the initial player list. + // { data array } represents a list of player objects from the server + // { data[n].name string } name of the player + // { data[n].game string } id of the game room (empty if not in game). + // { data[n].color string } css color chosen by player. + // { data[n].uuid string } uuid of the player + players: function(data) { + + this.e.stats.online.innerText = this.players.length(); + this.init = true; + }, + + // Called when a new public game is created on the server + // { data object } the game object + // { data.name } room name + // { data.packs } list of the pack names used by this game + // { data.id } room identifier (uuid) + addGame: function(data) { + + }, + + // Called when a new public game is removed on the server + // { data string } the uuid of the game to delete + removeGame: function(data) { + + }, + + // Called when a new player enters the lobby. + // { data object } an object representing the player + // { data.name string } name of the player + // { data.game string } id of the game room (empty if not in game). + // { data.color string } css color chosen by player. + // { data.uuid string } uuid of the player + addPlayer: function(data) { + + }, + + // Called when a player modifies their settings in the lobby. + // { data object } new player settings + // { data.name string } non null if the player has changed their name + // { data.color string } non null if the player has changed their color + // { data.uuid string } uuid of player changing their settings + modPlayer: function(data) { + + }, + + // Called when a player moves between the lobby and a game, or between two games + // { data object } new location + // { data.player } uuid of player changing location + // { data.loc } uuid of room player is moving to (empty if moving to lobby) + movePlayer: function(data) { + + }, + + // Called when a player exits the game (from lobby or game) + // {data string } uuid of player + removePlayer: function(data) { + + }, + + // Called when the client wants to toggle the new game screen + newGame: function() { + //if(this.init) return; + this.top.toggleNewGame(); + }, + + // Called when the client wants to toggle the mobile settings screen + mobileSettings: function() { + //if(this.init) return; + this.top.toggleMobileSettings(); + }, + + // Called when the WebSocket state has changed. + setState: function(text, color, server) { + this.e.status.style.backgroundColor = color; + this.e.status.innerText = text; + this.e.addr.innerText = server; + this.top.setColor(color); + }, + + // Called when we are resetting the game. + reset: function() { + while (this.e.games.firstElementChild != null) { + this.e.games.removeChild(this.e.games.firstElementChild) + } + + this.setState("Connecting", "#DA0", this.e.addr.innerText); + this.init = false; + } +}; + +// ############### +// # TopBar Code # +// ############### + +// TopBar represents the bar at the top of the screen when client is in the lobby. + +function TopBar(el) { + this.root = el; + + this.newGame = el.getElementsByClassName("new-game")[0]; + this.mobileSettings = el.getElementsByClassName("mobile-settings")[0]; + this.status = el.getElementsByClassName("status")[0]; +} + +TopBar.prototype = { + // Set color of status bar + setColor: function(color) { + this.status.style.backgroundColor = color; + }, + + // Toggle showing the new game screen + toggleNewGame: function() { + if (this.newGame.style.display !== "none") + this.newGame.style.display = "none"; + else + this.newGame.style.display = "block"; + }, + + // Toggle showing the mobile settings + toggleMobileSettings: function() { + if (this.mobileSettings.style.display !== "none") + this.mobileSettings.style.display = "none"; + else + this.mobileSettings.style.display = "block"; + } +}; + +// ############# +// # Game code # +// ############# + +// GameEl represents a single game in the lobby view. It has methods for setting up the elements and such. +function GameEl(name, packs, maxp, id) { + +}
\ No newline at end of file diff --git a/webcards/scripts/gui/table.js b/webcards/scripts/gui/table.js new file mode 100644 index 0000000..db67529 --- /dev/null +++ b/webcards/scripts/gui/table.js @@ -0,0 +1,34 @@ +// Table represents and manages the actual game. It accepts inputs from the server and tries to queries the server when the player makes a move. +function Table(el, soc) { + this.root = el; + this.soc = soc; +} + +Table.prototype = { + + openTable: function(){ + let state = this.root.getAttribute("state") + if((state == "close" || state == "closed") && state != "") { + this.root.setAttribute("state", "closed"); + setTimeout(this.root.setAttribute.bind(this.root), 50, "state", "open"); + } + }, + + closeTable: function(){ + let state = this.root.getAttribute("state") + if(state != "close" && state != "closed") { + this.root.setAttribute("state", ""); + setTimeout(this.root.setAttribute.bind(this.root), 50, "state", "close"); + } + }, + + + + handleClose: function() { + this.reset(); + }, + + reset: function() { + this.closeTable(); + } +}
\ No newline at end of file diff --git a/webcards/scripts/client/message.js b/webcards/scripts/socket/message.js index 5e821c4..5e821c4 100644 --- a/webcards/scripts/client/message.js +++ b/webcards/scripts/socket/message.js diff --git a/webcards/scripts/client/sock.js b/webcards/scripts/socket/sock.js index 78c4195..cf06a5e 100644 --- a/webcards/scripts/client/sock.js +++ b/webcards/scripts/socket/sock.js @@ -1,3 +1,4 @@ +// A wrapper around the wrapper function SockWorker(serveraddr, version, callback) { this.server = serveraddr; this.version = version; @@ -5,6 +6,7 @@ function SockWorker(serveraddr, version, callback) { } SockWorker.prototype = { + // Initialize the connection. init: function() { if(this.server == "" || this.server == null) { return; @@ -22,10 +24,13 @@ SockWorker.prototype = { } }, + // Called when the connection connects to the server o: function() { this.send("version", this.version); }, + // Called when the connection gets a message from the server + // Attempts to turn the message into a usable object and pass it to the callback msg: function(e) { if(typeof e.data == "string") { var dat = JSON.parse(e.data) @@ -33,18 +38,24 @@ SockWorker.prototype = { } }, + // Called when the connection closes. + // Passes a close object to the callback. c: function() { this.cb({type: "close", data: ""}); }, + // Called when the connection encounters an error. + // Passes an error to the callback err: function() { this.cb({type: "error", data: ""}); }, - + + // Call to close the connection to the server close: function() { this.socket.close(); }, + // Send a message to the server send: function(type, data) { var m = new Message(type, data); this.socket.send(m.stringify()) diff --git a/webcards/styles/client/base.css b/webcards/styles/client/base.css index cae61f6..fc8e310 100644 --- a/webcards/styles/client/base.css +++ b/webcards/styles/client/base.css @@ -13,7 +13,10 @@ html, body { } -.topbar { +/* Topbar rules */ + +.topbar +{ position: fixed; background-color: white; @@ -29,66 +32,75 @@ html, body { z-index: 1; } -.top-buttons { +.top-buttons +{ display: flex; } -button, input[type="button"] { - display: inline-block; - border: none; - background-color: dodgerblue; - padding: 10px; - font-size: medium; - border-radius: 5px; - box-sizing: border-box; -} - -button.top-button { +button.top-button +{ border-radius: 0; background-color: white; + color: black; flex: 1; border-left: 1px solid black; border-right: 1px solid black; + + transition-duration: 0.2s; } -.top-buttons > button:first-child { +button.top-button:hover { + background-color: #ddd; +} + +.top-buttons > button:first-child +{ border-left: none; } -.top-buttons > button:last-child { +.top-buttons > button:last-child +{ border-right: none; } -div.new-game { +div.new-game +{ flex: 1; flex-grow: 1; border-top: 2px solid black; } -div.mobile-settings { +div.mobile-settings +{ flex: 1; flex-grow: 1; border-top: 2px solid black; } -div.topbar > div.status { +div.topbar > div.status +{ height: 5px; flex-basis: auto; } -div.stats { +/* Content rules */ + +div.stats +{ padding: 5px; display: flex; flex-direction: column; } -div.stats > div { +div.stats > div +{ flex: 1; display: flex; } -div.stats > div > span { +div.stats > div > span +{ flex-basis: content; display: inline-block; } @@ -109,4 +121,52 @@ div.game { div.game:last-child { margin-bottom: none; -}
\ No newline at end of file +} + +div.settings { + display:flex; + flex-direction: column; + padding: 5px; +} + +/* Table rules */ + +.table { + position: absolute; + z-index: 2; + width: 100vw; + height: 100vh; + + animation-duration: 0.8s; + + background-color: rgba(0, 0, 0, 0.5); +} + +.table[state="open"] ,.table[state="close"]{ + animation-name: slide-in; + animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); + animation-iteration-count: 1; + animation-direction: normal; + animation-fill-mode: forwards; +} + +.table[state="close"] { + animation-direction: reverse; +} + +.table[state="closed"] { + transform: translate(0, -100vh); + opacity: 0; +} + +@keyframes slide-in { + from { + transform: translate(0, -100vh); + opacity: 0; + } + + to { + transform: translate(0, 0); + opacity: 1; + } +} diff --git a/webcards/styles/client/card.css b/webcards/styles/client/card.css new file mode 100644 index 0000000..62b4054 --- /dev/null +++ b/webcards/styles/client/card.css @@ -0,0 +1,116 @@ +card +{ + position: absolute; + display: block; + width: 150px; + height: 225px; + background-color: white; + border: 6px double #bbb; + border-radius: 10px; + transition-duration: 0.2s; + cursor: pointer; + flex-direction: column; + overflow: hidden; + user-select: none; +} + +card:hover +{ + box-shadow: 0 0 10px #0f0; +} + +carea +{ + padding-left: 2px; + padding-right: 2px; + position: absolute; + display: flex; + vertical-align: middle; + width: 100%; +} + +carea.top, carea.topl, carea.topr +{ + top: 0; + height: 8%; + text-align: center; +} + +carea.topl +{ + width: 50%; + text-align: left; +} + +carea.topr +{ + right: 0; + width: 50%; + text-align: right; +} + +carea.mid +{ + top: 8%; + height: 84%; +} + +carea.midt +{ + top: 8%; + height: 42%; +} + +carea.midb +{ + + top: 50%; + height: 42%; +} + +carea.bot, carea.botl, carea.botr +{ + bottom: 0; + height: 8%; + text-align: center; +} + +carea.botl +{ + width: 50%; + text-align: left; +} + +carea.botr +{ + right: 0; + width: 50%; + text-align: right; +} + +carea.all +{ + top: 0; + height: 100%; +} + +ctext +{ + display: inline-block; + flex: 1; + font-size: small; + height: auto; +} + +cimage +{ + background-position: center center; + background-repeat: no-repeat; + background-size: contain; + flex: 1; +} + +card[drag = "true"] +{ + z-index: 3; +} diff --git a/webcards/styles/client/desktop.css b/webcards/styles/client/desktop.css index 46dc83d..08e24fe 100644 --- a/webcards/styles/client/desktop.css +++ b/webcards/styles/client/desktop.css @@ -10,14 +10,6 @@ TopBar rules */ - button.top-button { - transition-duration: 0.2s; - } - - button.top-button:hover { - background-color: #ddd; - } - button#newgame:before{ content: "Create Game"; } @@ -101,7 +93,7 @@ padding: 10px; border-radius: 10px; background-color: #ddd; - flex: 4; + flex: 2; border: 5px solid #ddd; } diff --git a/webcards/styles/client/mobile.css b/webcards/styles/client/mobile.css index cea233d..4659ce6 100644 --- a/webcards/styles/client/mobile.css +++ b/webcards/styles/client/mobile.css @@ -1,4 +1,4 @@ -@media (max-width: 599px), (max-height: 500px) { +@media (max-width: 599px) or (max-height: 500px) { /* Hide stuff */ diff --git a/webcards/styles/client/tablet.css b/webcards/styles/client/tablet.css index 37750f8..b9b5bd1 100644 --- a/webcards/styles/client/tablet.css +++ b/webcards/styles/client/tablet.css @@ -38,4 +38,8 @@ margin-top: 0px; margin-left: 10px; } + + div.games { + flex: 4; + } } diff --git a/webcards/styles/home/base.css b/webcards/styles/home/base.css index 2ef3693..99bb148 100644 --- a/webcards/styles/home/base.css +++ b/webcards/styles/home/base.css @@ -33,21 +33,21 @@ div.content { align-items: center; } -a { +a, button { font-size: 24px; display: block; color: white; + box-sizing: border-box; border-radius: 5px; + border: none; padding: 5px; - background-color: dodgerblue; + background-color: #0084ff; transition-duration: 0.2s; text-decoration: none; margin-top: 10px; - - max-width: 100px; }
\ No newline at end of file diff --git a/webcards/styles/input.css b/webcards/styles/input.css new file mode 100644 index 0000000..16a5385 --- /dev/null +++ b/webcards/styles/input.css @@ -0,0 +1,296 @@ +/* Begin Input CSS */ + +/* All input */ + +input +{ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + margin: 5px; + + display: block; + z-index: 1; +} + +/* Button input */ + +input[type=button], input[type=submit], button +{ + padding: 10px; + box-sizing: border-box; + + border: none; + border-radius: 5px; + + color: white; + background-color: #0084ff; + + font-size: medium; + + transition-duration: 0.2s; + + cursor: pointer; +} + +input[type=button]:hover, input[type=submit]:hover, button:hover +{ + background-color: #3ea2ff; +} + +input[type=button]:active, input[type=submit]:active, button:active +{ + background-color: #0056a7; +} + +/* Text, date, number, and time input */ + +input[type=text], input[type=date], input[type=time], input[type="number"] +{ + border: 2px solid #555; + border-radius: 3px; + padding: 5px; + background-color: white; + font-size: 1em; +} + +input[type=text]:hover, input[type=date]:hover, input[type=time]:hover, input[type="number"]:hover +{ + border-color: black; +} + +input[type=text]:focus, input[type=date]:focus, input[type=time]:focus, input[type="number"]:focus +{ + border-color: #0084ff; +} + +/* Radial input */ + +input[type=radio] +{ + width: 20px; + height: 20px; + + border: 3px solid black; + border-radius: 50%; + + transition-duration: 0.2s; + + background-color: white; + + cursor: pointer; + + display: inline-block; + vertical-align: middle; +} + +input[type=radio]:checked, input[type=radio]:hover{ + border-width: 6px; +} + +input[type=radio]:checked +{ + background-color: black; + border-color: #0084ff; +} + +input[type=radio]:hover +{ + border-color: #3ea2ff; +} + +input[type=radio]:active +{ + border-color: black; +} + +/* Checkbox input */ + +input[type=checkbox] +{ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + display: inline-block; + vertical-align: middle; + + width: 20px; + height: 20px; + + border: 3px solid black; + + border-radius: 2px; + + transition-duration: 0.2s; + + background-color: white; + + cursor: pointer; +} + +input[type=checkbox]:checked +{ + border-width: 10px; + border-color: #0084ff; +} + +input[type=checkbox]:hover +{ + border-color: #3ea2ff; +} + +input[type=checkbox]:active +{ + border-color: black; +} + +input[type=checkbox]::after +{ + height: 16px; + width: 10px; + + box-sizing: border-box; + + position: relative; + + display: block; + + border-color: black; + border-style: solid; + border-width: 0px 0px 0px 0px; + + top: calc(50% - 10px); + left: calc(50% - 5px); + + transform-origin: center; + transform: rotate(45deg); + + content: ''; +} + +input[type=checkbox]:checked::after +{ + border-width: 0px 3px 3px 0px; +} + +/* Color input */ + +input[type=color] +{ + display: inline-block; + + vertical-align: middle; + + height: 20px; + width: 20px; + + margin: 0; + padding: 0; + + border: 0; + + cursor: pointer; +} + +/* File input */ + +input[type=file] +{ + display: none; +} + +/* Input container */ + +div.input-container { + margin: 5px; + padding: 5px; + + border-radius: 3px; +} + +/* Color container */ + +div.input-container[type=color] +{ + text-align: center; + + background-color: #0084ff; + color: white; + + transition-duration: 0.2s; + + cursor: pointer; + + width: max-content; +} + +div.input-container[type=color]::after +{ + display: inline; + + vertical-align: middle; + + content: ' Pick a color'; +} + +div.input-container[type=color]:hover +{ + background-color: #3ea2ff; +} + +div.input-container[type=color]:active +{ + background-color: #0056a7; +} + +/* File input container */ + +div.input-container[type=file] +{ + background-color: #0084ff; + color: white; + + transition-duration: 0.2s; + + cursor: pointer; + + text-align: center; + + width: max-content; + height: max-content; +} + +div.input-container[type=file]:hover +{ + background-color: #3ea2ff; +} + +div.input-container[type=file]:active +{ + background-color: #0056a7; +} + +div.input-container[type=file]::after +{ + display: inline; + + vertical-align: middle; + + content: attr(data-files); +} + +/* Radio input container */ + +div.input-container[type=radio]::before { + display: block; + content: 'Radio'; +} + +div.input-container[type=radio] +{ + background-color: rgba(255, 255, 255, 0.3); +} + +/* End Input CSS */
\ No newline at end of file |