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 0000000..6505bdc
Binary files /dev/null and b/server/src/main/resources/static/favicon.ico differ
diff --git a/server/src/main/resources/static/index.html b/server/src/main/resources/static/index.html
new file mode 100644
index 0000000..bf31344
--- /dev/null
+++ b/server/src/main/resources/static/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+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.
+
+
+