596 lines
15 KiB
JavaScript
596 lines
15 KiB
JavaScript
var KEYCODE_BACKSPACE = 8;
|
|
var KEYCODE_RETURN = 13;
|
|
|
|
var HEIGHT = 300;
|
|
var WIDTH = 250;
|
|
var SIDE = 50;
|
|
var MARGIN_TOP = 100;
|
|
var MARGIN_BOTTOM = 75;
|
|
|
|
var client;
|
|
var sessionId = null;
|
|
|
|
var vm = new Vue({
|
|
el: '#vue-app',
|
|
data: {
|
|
games: [],
|
|
gameId: null,
|
|
messages: [],
|
|
username: null,
|
|
usernameError: '',
|
|
myScore: 0,
|
|
myGuess: '',
|
|
myGuesses: [],
|
|
myProgress: [],
|
|
myResults: [],
|
|
opponentScore: 0,
|
|
opponentResults: [],
|
|
opponentUsername: null,
|
|
lastWord: null
|
|
},
|
|
computed: {
|
|
inGame: function() {
|
|
return this.gameId !== null;
|
|
},
|
|
inStartedGame: function() {
|
|
var game = this.getGame(this.gameId);
|
|
return game !== null && game.started === true;
|
|
}
|
|
},
|
|
directives: {
|
|
autoscroll: {
|
|
bind: function(element, binding) {
|
|
var observer = new MutationObserver(scrollToBottom);
|
|
var config = { childList: true };
|
|
observer.observe(element, config);
|
|
|
|
function scrollToBottom() {
|
|
element.scrollTop = element.scrollHeight;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
drawMyBoard: function(ctx) {
|
|
var x = 25, y = MARGIN_TOP;
|
|
this.drawUsername(ctx, x, y, this.username);
|
|
this.drawScore(ctx, x, y, this.myScore);
|
|
this.drawInput(ctx, x, y, this.myGuess);
|
|
var yStart = this.drawGuesses(ctx, x, y, this.myGuesses, this.myResults);
|
|
this.drawHint(ctx, x, yStart, this.myProgress);
|
|
this.drawGrid(ctx, x, y);
|
|
},
|
|
drawOpponentBoard: function(ctx) {
|
|
var x = 325, y = MARGIN_TOP;
|
|
this.drawUsername(ctx, x, y, this.opponentUsername);
|
|
this.drawScore(ctx, x, y, this.opponentScore);
|
|
this.drawResults(ctx, x, y, this.opponentResults);
|
|
this.drawGrid(ctx, x, y);
|
|
},
|
|
drawLastWord: function(canvas, ctx) {
|
|
if (this.lastWord) {
|
|
var x = canvas.width / 2;
|
|
var y = canvas.height - MARGIN_BOTTOM / 2;
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillText('Previous word: ' + this.lastWord.toUpperCase(), x, y);
|
|
}
|
|
},
|
|
drawUsername: function(ctx, x, y, username) {
|
|
var usernameX = x + WIDTH / 2;
|
|
var usernameY = y - 60;
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillText(username, usernameX, usernameY);
|
|
},
|
|
drawScore: function(ctx, x, y, score) {
|
|
var scoreX = x + WIDTH / 2;
|
|
var scoreY = y - 25;
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillText(score, scoreX, scoreY);
|
|
},
|
|
drawGrid: function(ctx, 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();
|
|
},
|
|
drawInput: function(ctx, xOrigin, yOrigin, input) {
|
|
ctx.fillStyle = 'green';
|
|
var x = xOrigin + SIDE * 0.5;
|
|
var y = yOrigin + SIDE * 0.5;
|
|
for (var i = 0; i < input.length; i++) {
|
|
ctx.fillText(input[i], x, y);
|
|
x += SIDE;
|
|
}
|
|
},
|
|
drawGuesses: function(ctx, 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;
|
|
},
|
|
drawResults: function(ctx, 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;
|
|
},
|
|
drawHint: function(ctx, xOrigin, yOrigin, progress) {
|
|
var x = xOrigin + SIDE * 0.5;
|
|
for (var i = 0; i < 5; i++) {
|
|
ctx.fillText(progress[i], x, yOrigin);
|
|
x += SIDE;
|
|
}
|
|
},
|
|
getGame: function(gameId) {
|
|
for (var i = 0; i < this.games.length; i++) {
|
|
if (this.games[i].id === gameId) {
|
|
return this.games[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
hostGame: function(event) {
|
|
client.send('/app/hostGame');
|
|
},
|
|
joinGame: function(event) {
|
|
// Discard 'game-' prefix
|
|
var buttonId = event.target.id;
|
|
var gameId = buttonId.substr(5);
|
|
client.send('/app/joinGame', {}, gameId);
|
|
},
|
|
leaveGame: function(event) {
|
|
client.send('/app/leaveGame');
|
|
},
|
|
removeGame: function(gameId) {
|
|
var indexToRemove = null;
|
|
for (var i = 0; i < this.games.length; i++) {
|
|
if (this.games[i].id === gameId) {
|
|
indexToRemove = i;
|
|
break;
|
|
}
|
|
}
|
|
this.games.splice(indexToRemove, 1);
|
|
},
|
|
onCanvasKeydown: function(event) {
|
|
if (event.which === KEYCODE_BACKSPACE) {
|
|
event.preventDefault();
|
|
this.myGuess = this.myGuess.substr(0, this.myGuess.length - 1);
|
|
this.repaint();
|
|
}
|
|
else if (event.which === KEYCODE_RETURN) {
|
|
if (this.myGuess.length === 5) {
|
|
client.send("/app/guess", {}, this.myGuess);
|
|
this.myGuess = '';
|
|
this.repaint();
|
|
}
|
|
}
|
|
},
|
|
onCanvasKeypress: function(event) {
|
|
var charCode = event.charCode;
|
|
if (isCharacter(charCode)) {
|
|
if (isCharacterLowercase(charCode)) {
|
|
charCode = charCode - 32;
|
|
}
|
|
var char = String.fromCharCode(charCode);
|
|
if (this.myGuess.length < 5) {
|
|
this.myGuess += char;
|
|
this.repaint();
|
|
}
|
|
}
|
|
},
|
|
onChatKeypress: function(event) {
|
|
var messageInput = event.target;
|
|
if (event.which === KEYCODE_RETURN) {
|
|
// Shift+Enter -> new line
|
|
if (!event.shiftKey) {
|
|
event.preventDefault();
|
|
var text = messageInput.value.trim();
|
|
if (text.length === 0) {
|
|
return;
|
|
}
|
|
messageInput.value = '';
|
|
client.send('/app/chat', {}, text);
|
|
addChatMessage(this.username, text);
|
|
}
|
|
}
|
|
},
|
|
repaint: function() {
|
|
var canvas = document.getElementById('canvas');
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.font = '25px Monospace';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.textAlign = 'center';
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
this.drawMyBoard(ctx);
|
|
this.drawOpponentBoard(ctx);
|
|
this.drawLastWord(canvas, ctx);
|
|
},
|
|
reset: function(firstLetter, clearScore) {
|
|
if (!firstLetter) {
|
|
firstLetter = '';
|
|
}
|
|
this.myGuess = '';
|
|
this.myGuesses = [];
|
|
this.myProgress = [firstLetter, '', '', '', ''];
|
|
this.myResults = [];
|
|
this.opponentResults = [];
|
|
if (clearScore) {
|
|
this.myScore = 0;
|
|
this.opponentScore = 0;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function afterConnected(stompConnectedFrame) {
|
|
console.log('Connected to STOMP endpoint')
|
|
var sessionIdTopic = '/user/topic/sessionId';
|
|
var sessionIdSubscription = null;
|
|
var sessionIdHandler = function(message) {
|
|
console.log('Session ID: ' + message.body);
|
|
sessionId = message.body;
|
|
sessionIdSubscription.unsubscribe();
|
|
}
|
|
sessionIdSubscription = client.subscribe(sessionIdTopic, sessionIdHandler);
|
|
}
|
|
|
|
function main() {
|
|
|
|
client = Stomp.over(new SockJS('/stomp'));
|
|
client.connect({}, afterConnected);
|
|
|
|
var usernameInput = document.getElementById('nicknameInput');
|
|
|
|
var usernameTopic = '/user/topic/sessionUsername';
|
|
var usernameSubscription = null;
|
|
var usernameHandler = function(message) {
|
|
var response = JSON.parse(message.body);
|
|
if (response.success === true) {
|
|
console.log('Username: ' + response.username);
|
|
vm.username = response.username;
|
|
start();
|
|
} else {
|
|
vm.usernameError = response.errorMessage;
|
|
}
|
|
};
|
|
|
|
usernameInput.addEventListener('keydown', function(e) {
|
|
if (event.keyCode === KEYCODE_RETURN) {
|
|
event.preventDefault();
|
|
if (sessionId === null) {
|
|
vm.usernameError = 'Not connected to server';
|
|
return;
|
|
}
|
|
var usernameValue = usernameInput.value.trim();
|
|
if (usernameValue.length === 0) {
|
|
vm.usernameError = 'Name cannot be empty';
|
|
return;
|
|
}
|
|
if (usernameSubscription === null) {
|
|
usernameSubscription = client.subscribe(usernameTopic, usernameHandler);
|
|
client.subscribe('/topic/userJoined', onUserJoined);
|
|
}
|
|
client.send('/app/setUsername', {}, usernameValue);
|
|
}
|
|
});
|
|
usernameInput.addEventListener('keyup', function(e) {
|
|
if (e.keyCode === KEYCODE_RETURN) {
|
|
return;
|
|
}
|
|
var usernameValue = usernameInput.value.trim();
|
|
if (usernameValue.length !== 0) {
|
|
vm.usernameError = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function start() {
|
|
|
|
// Request permission to show notifications
|
|
Notification.requestPermission().then(function(result) {
|
|
console.log('Notification permission: ' + result);
|
|
});
|
|
|
|
// Load initial data
|
|
doHttpGet('/games', function(games) {
|
|
for (var i = 0; i < games.length; i++) {
|
|
var game = games[i];
|
|
vm.games.push({
|
|
id: game.id,
|
|
playerOne: game.playerOne.username,
|
|
playerTwo: game.playerTwo ? game.playerTwo.username : null,
|
|
started: game.playerTwo !== null
|
|
});
|
|
}
|
|
});
|
|
|
|
// Subscribe to updates
|
|
client.subscribe('/topic/chat', onChat);
|
|
client.subscribe('/topic/gameClosed', onGameClosed);
|
|
client.subscribe('/topic/gameHosted', onGameHosted);
|
|
client.subscribe('/topic/gameJoined', onGameJoined);
|
|
client.subscribe('/topic/gameLeft', onGameLeft);
|
|
client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
|
|
client.subscribe('/user/topic/opponentLeft', onOpponentLeft);
|
|
client.subscribe('/user/topic/opponentReports', onOpponentReport);
|
|
client.subscribe('/user/topic/playerReports', onPlayerReport);
|
|
}
|
|
|
|
function addChatAnnouncement(body) {
|
|
vm.messages.push({
|
|
body: body
|
|
});
|
|
showNotification('Announcement', body);
|
|
}
|
|
|
|
function addChatMessage(sender, body) {
|
|
vm.messages.push({
|
|
sender: sender,
|
|
body: body
|
|
});
|
|
showNotification(sender, body);
|
|
}
|
|
|
|
function doHttpGet(url, callback) {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
|
var response = JSON.parse(xhr.responseText);
|
|
callback(response);
|
|
}
|
|
};
|
|
xhr.open('GET', url, true);
|
|
xhr.send();
|
|
}
|
|
|
|
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 onChat(message) {
|
|
var chatMessage = JSON.parse(message.body);
|
|
var messageSender = chatMessage.username;
|
|
var messageBody = chatMessage.message;
|
|
if (messageSender === null) {
|
|
addChatAnnouncement(messageBody);
|
|
} else if (messageSender === vm.username) {
|
|
// Ignore messages sent by yourself
|
|
} else {
|
|
console.log('Message from ' + messageSender + ": " + messageBody);
|
|
addChatMessage(messageSender, messageBody);
|
|
}
|
|
}
|
|
|
|
function onGameClosed(message) {
|
|
var game = JSON.parse(message.body);
|
|
var gameId = game.id;
|
|
var playerOne = game.playerOne.username;
|
|
console.log(playerOne + ' closed Game ' + gameId);
|
|
if (playerOne === vm.username) {
|
|
vm.gameId = null;
|
|
}
|
|
vm.removeGame(gameId);
|
|
}
|
|
|
|
function onGameHosted(message) {
|
|
var game = JSON.parse(message.body);
|
|
var gameId = game.id;
|
|
var playerOne = game.playerOne.username;
|
|
console.log(playerOne + ' hosted Game ' + gameId);
|
|
vm.games.push({
|
|
id: gameId,
|
|
playerOne: playerOne,
|
|
started: false
|
|
});
|
|
if (playerOne === vm.username) {
|
|
vm.gameId = gameId;
|
|
}
|
|
}
|
|
|
|
function onGameJoined(message) {
|
|
var game = JSON.parse(message.body);
|
|
var gameId = game.id;
|
|
var playerOne = game.playerOne.username;
|
|
var playerTwo = game.playerTwo.username;
|
|
|
|
var message = playerTwo + ' joined ' + playerOne + "'s game"
|
|
console.log(message);
|
|
addChatAnnouncement(message);
|
|
|
|
for (var i = 0; i < vm.games.length; i++) {
|
|
if (vm.games[i].id === gameId) {
|
|
vm.games[i].playerTwo = playerTwo;
|
|
vm.games[i].started = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (playerTwo === vm.username) {
|
|
vm.gameId = gameId;
|
|
}
|
|
}
|
|
|
|
function onGameLeft(message) {
|
|
var report = JSON.parse(message.body);
|
|
var game = report.game;
|
|
var gameId = game.id;
|
|
var playerOne = game.playerOne.username;
|
|
var gameLeaver = report.gameLeaver.username;
|
|
console.log(gameLeaver + ' left ' + playerOne + "'s game");
|
|
var previousPlayers = [];
|
|
for (var i = 0; i < vm.games.length; i++) {
|
|
if (vm.games[i].id === gameId) {
|
|
previousPlayers.push(vm.games[i].playerOne);
|
|
previousPlayers.push(vm.games[i].playerTwo);
|
|
vm.games[i].playerOne = playerOne;
|
|
vm.games[i].playerTwo = game.playerTwo ? game.playerTwo.username : null;
|
|
vm.games[i].started = false;
|
|
break;
|
|
}
|
|
}
|
|
if (gameLeaver === vm.username) {
|
|
vm.gameId = null;
|
|
}
|
|
if (previousPlayers.indexOf(vm.username) != -1) {
|
|
onOpponentLeft();
|
|
}
|
|
}
|
|
|
|
function onOpponentJoined(message) {
|
|
var report = JSON.parse(message.body);
|
|
var firstLetter = report[0];
|
|
vm.opponentUsername = report[1];
|
|
console.log('Opponent username: ' + vm.opponentUsername);
|
|
vm.reset(firstLetter, true);
|
|
vm.repaint();
|
|
}
|
|
|
|
function onOpponentLeft(message) {
|
|
vm.opponentUsername = null;
|
|
vm.lastWord = null;
|
|
vm.repaint();
|
|
}
|
|
|
|
function onOpponentReport(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);
|
|
vm.opponentScore = vm.opponentScore + 100;
|
|
vm.lastWord = guess;
|
|
vm.reset(firstLetter, false);
|
|
vm.repaint();
|
|
} else {
|
|
var result = report.result;
|
|
console.log('Opponent result: ' + result);
|
|
vm.opponentResults.push(result);
|
|
vm.repaint();
|
|
}
|
|
}
|
|
|
|
function onPlayerReport(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!');
|
|
vm.myScore = vm.myScore + 100;
|
|
vm.lastWord = guess;
|
|
vm.reset(firstLetter, false);
|
|
vm.repaint();
|
|
} else {
|
|
var guess = report.guess;
|
|
var result = report.result;
|
|
console.log('My result: ' + result);
|
|
// TODO: use isValidResult function
|
|
if (result[0] === 9) {
|
|
vm.myGuesses.push('-----');
|
|
} else {
|
|
for (var i = 0; i < 5; i++) {
|
|
if (result[i] === 2) {
|
|
vm.myProgress[i] = guess[i];
|
|
}
|
|
}
|
|
vm.myGuesses.push(guess);
|
|
}
|
|
vm.myResults.push(result);
|
|
vm.repaint();
|
|
}
|
|
}
|
|
|
|
function onUserJoined(message) {
|
|
var report = JSON.parse(message.body);
|
|
var username = report[0];
|
|
var numUsers = report[1];
|
|
if (username === vm.username) {
|
|
addChatAnnouncement('Welcome to Lingo!');
|
|
if (numUsers === 1) {
|
|
addChatAnnouncement('You are the only player online');
|
|
} else {
|
|
addChatAnnouncement('There are ' + numUsers + ' players online');
|
|
}
|
|
} else {
|
|
addChatAnnouncement(username + ' joined');
|
|
}
|
|
}
|
|
|
|
function canShowNotification() {
|
|
if (document.hidden === 'undefined' || document.hidden === false) {
|
|
return false;
|
|
}
|
|
return Notification.permission === 'granted';
|
|
}
|
|
|
|
function showNotification(messageSender, messageBody) {
|
|
if (canShowNotification()) {
|
|
var title = messageSender;
|
|
var options = {
|
|
body : messageBody,
|
|
icon : '/chat-bubble.png'
|
|
};
|
|
var notification = new Notification(title, options);
|
|
setTimeout(function() {
|
|
notification.close();
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
main();
|