From cfea7f287e53cb21c1fb9cb90efb39130bd745c8 Mon Sep 17 00:00:00 2001 From: Charles Gould Date: Sat, 31 Dec 2016 12:20:46 -0500 Subject: [PATCH] Add JavaScript client --- .../java/lingo/server/WebSocketConfig.java | 3 + server/src/main/resources/static/client.css | 204 ++++++++++++ server/src/main/resources/static/client.html | 53 +++ server/src/main/resources/static/client.js | 306 ++++++++++++++++++ server/src/main/resources/static/favicon.ico | Bin 0 -> 318 bytes server/src/main/resources/static/index.html | 24 ++ 6 files changed, 590 insertions(+) create mode 100644 server/src/main/resources/static/client.css create mode 100644 server/src/main/resources/static/client.html create mode 100644 server/src/main/resources/static/client.js create mode 100644 server/src/main/resources/static/favicon.ico create mode 100644 server/src/main/resources/static/index.html diff --git a/server/src/main/java/lingo/server/WebSocketConfig.java b/server/src/main/java/lingo/server/WebSocketConfig.java index fbd90a2..ba9e030 100644 --- a/server/src/main/java/lingo/server/WebSocketConfig.java +++ b/server/src/main/java/lingo/server/WebSocketConfig.java @@ -18,7 +18,10 @@ public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { + // For JavaFX client registry.addEndpoint("/stomp"); + // For JavaScript client + registry.addEndpoint("/sockjs").withSockJS(); } } diff --git a/server/src/main/resources/static/client.css b/server/src/main/resources/static/client.css new file mode 100644 index 0000000..1e1c8b6 --- /dev/null +++ b/server/src/main/resources/static/client.css @@ -0,0 +1,204 @@ +canvas { + display: block; + margin: 0 auto; +} + +h1 { + font: 24pt sans-serif; + font-variant: small-caps; + text-align: center +} + +.hidden { + display: none; +} + +/* Below based on http://tobiasahlin.com/spinkit */ + +.sk-cube-grid { + width: 200px; + height: 200px; + margin: 35px auto; +} + +.sk-cube-grid .sk-cube { + width: 20%; + height: 20%; + background-color: orange; + float: left; + -webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; +} + +/* +0.0s - 26 +0.1s - 27, 21 +0.2s - 28, 22, 16 +0.3s - 29, 23, 17, 11 +0.4s - 30, 24, 18, 12, 06 +0.5s - 25, 19, 13, 07, 01 +0.6s - 20, 14, 08, 02 +0.7s - 15, 09, 03 +0.8s - 10, 04 +0.9s - 05 +*/ + +/* 0 second delay */ +.sk-cube-grid .sk-cube26 { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} + +/* 0.1 second delay */ +.sk-cube-grid .sk-cube27 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} +.sk-cube-grid .sk-cube21 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} + +/* 0.2 second delay */ +.sk-cube-grid .sk-cube28 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube22 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube16 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +/* 0.3 second delay */ +.sk-cube-grid .sk-cube29 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube23 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube17 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube11 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} + +/* 0.4 second delay */ +.sk-cube-grid .sk-cube30 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube24 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube18 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube12 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube6 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +/* 0.5 second delay */ +.sk-cube-grid .sk-cube25 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube19 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube13 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube7 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube1 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} + +/* 0.6 second delay */ +.sk-cube-grid .sk-cube20 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube14 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube8 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube2 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +/* 0.7 second delay */ +.sk-cube-grid .sk-cube15 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube9 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube3 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} + +/* 0.8 second delay */ +.sk-cube-grid .sk-cube10 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} +.sk-cube-grid .sk-cube4 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +/* 0.9 second delay */ +.sk-cube-grid .sk-cube5 { + -webkit-animation-delay: 0.9s; + animation-delay: 0.9s; +} + +@-webkit-keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} + +@keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} diff --git a/server/src/main/resources/static/client.html b/server/src/main/resources/static/client.html new file mode 100644 index 0000000..4eb603b --- /dev/null +++ b/server/src/main/resources/static/client.html @@ -0,0 +1,53 @@ + + + + +Lingo + + + + +
+

Waiting for Opponent

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/server/src/main/resources/static/client.js b/server/src/main/resources/static/client.js new file mode 100644 index 0000000..58e8727 --- /dev/null +++ b/server/src/main/resources/static/client.js @@ -0,0 +1,306 @@ +var HEIGHT = 300; +var WIDTH = 250; +var SIDE = 50; +var MARGIN_TOP = 50; +var MARGIN_BOTTOM = 75; + +var myScore = 0; +var myGuess; +var myGuesses; +var myProgress; +var myResults; +var opponentScore = 0; +var opponentResults; +var lastWord; + +var canvasDiv = document.getElementById('canvasDiv'); +var waitingDiv = document.getElementById('waitingDiv'); +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +var client; + +function main() { + ctx.font = '25px Monospace'; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + + addKeydownListener(); + addKeypressListener(); + + reset(); + repaint(); + + client = Stomp.over(new SockJS('/sockjs')); + + client.connect({}, function(frame) { + console.log('Connected: ' + frame); + subscribeToOpponentJoined(); + subscribeToOpponentLeft(); + subscribeToOpponentReports(); + subscribeToPlayerReports(); + client.send('/app/lingo/join'); + }); +} + +// special keys +function addKeydownListener() { + document.addEventListener('keydown', function(e) { + // backspace + if (e.which === 8) { + myGuess = myGuess.substr(0, myGuess.length - 1); + repaint(); + e.preventDefault(); + } + // return + else if (e.which === 13) { + if (myGuess.length === 5) { + client.send("/app/lingo/guess", {}, myGuess); + myGuess = ''; + repaint(); + } + } + }); +} + +// characters +function addKeypressListener() { + document.addEventListener('keypress', function(e) { + var charCode = e.charCode; + if (isCharacter(charCode)) { + if (isCharacterLowercase(charCode)) { + charCode = charCode - 32; + } + var char = String.fromCharCode(charCode); + if (myGuess.length < 5) { + myGuess += char; + repaint(); + } + } + }); +} + +function drawMyBoard() { + var x = 25, y = MARGIN_TOP; + drawScore(x, y, myScore); + drawInput(x, y, myGuess); + var yStart = drawGuesses(x, y, myGuesses, myResults); + drawHint(x, yStart, myProgress); + drawGrid(x, y); +} + +function drawOpponentBoard() { + var x = 325, y = MARGIN_TOP; + drawScore(x, y, opponentScore); + drawResults(x, y, opponentResults); + drawGrid(x, y); +} + +function drawLastWord() { + if (lastWord) { + var x = canvas.width / 2; + var y = canvas.height - MARGIN_BOTTOM / 2; + ctx.fillStyle = 'black'; + ctx.fillText('Previous word: ' + lastWord.toUpperCase(), x, y); + } +} + +function drawScore(x, y, score) { + var scoreX = x + WIDTH / 2; + var scoreY = y / 2; + ctx.fillStyle = 'black'; + ctx.fillText(score, scoreX, scoreY); +} + +function drawGrid(xOrigin, yOrigin) { + ctx.beginPath(); + for (var x = 0; x <= WIDTH; x += SIDE) { + ctx.moveTo(xOrigin + x, yOrigin); + ctx.lineTo(xOrigin + x, yOrigin + HEIGHT); + } + for (var y = 0; y <= HEIGHT; y += SIDE) { + ctx.moveTo(xOrigin, yOrigin + y); + ctx.lineTo(xOrigin + WIDTH, yOrigin + y); + } + ctx.strokeStyle = 'black'; + ctx.stroke(); +} + +function drawInput(xOrigin, yOrigin, input) { + ctx.fillStyle = 'green'; + var x = xOrigin + SIDE * 0.5; + var y = yOrigin + SIDE * 0.5; + for (var i = 0; i < myGuess.length; i++) { + ctx.fillText(myGuess[i], x, y); + x += SIDE; + } +} + +function drawGuesses(xOrigin, yOrigin, guesses, results) { + var y = yOrigin + SIDE * 1.5; + var numGuesses = Math.min(4, guesses.length); + for (var i = 0; i < numGuesses; i++) { + var x = xOrigin + SIDE * 0.5; + var guess = guesses[guesses.length - numGuesses + i]; + var result = results[results.length - numGuesses + i]; + for (var j = 0; j < 5; j++) { + if (result[j] === 1) { + ctx.fillStyle = 'yellow'; + ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } else if (result[j] === 2) { + ctx.fillStyle = 'orange'; + ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } + ctx.fillStyle = 'green'; + ctx.fillText(guess[j], x, y); + x += SIDE; + } + y += SIDE; + } + return y; +} + +function drawResults(xOrigin, yOrigin, results) { + var y = yOrigin + SIDE * 1.5; + var numResults = Math.min(4, results.length); + for (var i = 0; i < numResults; i++) { + var x = xOrigin + SIDE * 0.5; + var result = results[results.length - numResults + i]; + for (var j = 0; j < 5; j++) { + if (result[j] === 1) { + ctx.fillStyle = 'yellow'; + ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } else if (result[j] === 2) { + ctx.fillStyle = 'orange'; + ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } + x += SIDE; + } + y += SIDE; + } + return y; +} + +function drawHint(xOrigin, yOrigin, progress) { + var x = xOrigin + SIDE * 0.5; + for (var i = 0; i < 5; i++) { + ctx.fillText(progress[i], x, yOrigin); + x += SIDE; + } +} + +function isCharacter(charCode) { + return isCharacterLowercase(charCode) || isCharacterUppercase(charCode); +} + +function isCharacterLowercase(charCode) { + return charCode >= 97 && charCode <= 122; +} + +function isCharacterUppercase(charCode) { + return charCode >= 65 && charCode <= 90; +} + +function isValidResult(result) { + for (var i = 0; i < 5; i++) { + if (result[i] !== 0 && result[i] !== 1 && result[i] !== 2) { + return false; + } + } + return true; +} + +function repaint() { + // clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // draw the components + drawMyBoard(); + drawOpponentBoard(); + drawLastWord(); +} + +function reset(firstLetter) { + if (!firstLetter) { + firstLetter = ''; + } + myGuess = ''; + myGuesses = []; + myProgress = [firstLetter, '', '', '', '']; + myResults = []; + opponentResults = []; +} + +function subscribeToOpponentJoined() { + client.subscribe('/user/topic/lingo/opponentJoined', function(message) { + var firstLetter = message.body; + reset(firstLetter); + gameDiv.classList.remove('hidden'); + waitingDiv.classList.add('hidden'); + repaint(); + }); +} + +function subscribeToOpponentLeft() { + client.subscribe('/user/topic/lingo/opponentLeft', function(message) { + lastWord = null; + gameDiv.classList.add('hidden'); + waitingDiv.classList.remove('hidden'); + repaint(); + }); +} + +function subscribeToOpponentReports() { + client.subscribe('/user/topic/lingo/opponentReports', function(message) { + var report = JSON.parse(message.body); + if (report.correct === true) { + var guess = report.guess; + var firstLetter = report.firstLetter; + console.log('Opponent guessed correctly! ' + guess); + opponentScore = opponentScore + 100; + lastWord = guess; + reset(firstLetter); + repaint(); + } else { + var result = report.result; + console.log('Opponent result: ' + result); + opponentResults.push(result); + repaint(); + } + }); +} + +function subscribeToPlayerReports() { + client.subscribe('/user/topic/lingo/playerReports', function(message) { + var report = JSON.parse(message.body); + console.log('My report: ' + report); + if (report.correct === true) { + var guess = report.guess; + var firstLetter = report.firstLetter; + console.log('I guessed correctly!'); + myScore = myScore + 100; + lastWord = guess; + reset(firstLetter); + repaint(); + } else { + var guess = report.guess; + var result = report.result; + console.log('My result: ' + result); + // TODO: use isValidResult function + if (result[0] === 9) { + myGuesses.push('-----'); + } else { + for (var i = 0; i < 5; i++) { + if (result[i] === 2) { + myProgress[i] = guess[i]; + } + } + myGuesses.push(guess); + } + myResults.push(result); + repaint(); + } + }); +} + +main(); diff --git a/server/src/main/resources/static/favicon.ico b/server/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6505bdc37c1fef7cf06595fcc4242151391497b3 GIT binary patch literal 318 zcmbVIu?>JQ408p7HCb5c$P0YO1V1rx3NNrRVh2?$42V{a( + + + +Lingo + + + +
+

Lingo

+

JavaFX Client

+

For now you need to + download + the + source code + and run a command to launch the client. +

+

Single player and multiplayer.

+

JavaScript Client (easier)

+

Play the game here.

+

Multiplayer only.

+
+ +