My personal website's games section https://games.johanv.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

410 lines
19 KiB

{% include 'header.html' %}
<div id="app" class="gameArea extraSpace" v-cloak><form method="{{ formMethod }}">
<!-- <div class="leftCol" style="position: fixed; left: 0"> -->
<div class="leftCol leftCol2">
<form method="{{ formMethod }}">
<input type="hidden" name="username" value="{{ username }}"/>
<input type="hidden" name="page" value="{{ prev }}"/>
<input style="position: relative; top: 2px; float: left;" class="{% if prev == 'stats' %}purpleButton{% else %}greenButton{% endif %}" type="submit" value="{{ prev[0]|upper }}{{ prev[1:] }}"/>
</form>
<h1 class="scale">Boggle 2.0</h1>
<table v-if="game != undefined" class="boggleBoard noselect"><tbody>
<tr v-for="(row, i) in game.board">
<td v-for="(item, j) in row">
<div>
<span v-if="item == 'Qu'" v-bind:style="styleObjectQu">[[ item ]]</span>
<span v-else v-bind:style="styleObject">[[ item ]]</span>
</div>
</td>
</tr>
</tbody></table>
<div id="definition">
</div>
</div>
<div class="rightCol scoreTable">
<h1 v-if="game == undefined || !game.isArchived" class="scale hideSmall">Time's Up!</h1>
<h1 v-else class="scale hideSmall">Past Game</h1>
<p v-if="game != undefined" class="scale"><span style="background-color: #4f4; padding:3px; border-radius: 5px;">
<span v-for="(winner, i) in game.winners"><span v-if="i>0">, </span>[[ winner ]]</span>
</span>
<span v-if="game.isArchived">
<span v-if="game.winners.length > 1">tied</span><span v-else>won</span>
</span>
<span v-else>
<span v-if="game.winners.length > 1">tie</span><span v-else>wins</span>
</span>
with [[ game.winScore ]] points!</p>
<div v-if="game != undefined" class="compactText">
Game #[[ game._id ]]: [[ game.size ]]x[[ game.size ]] board, words with [[ game.letters ]] at least letters, [[ game.minutes ]] minute timer.<br/><br/>
<span v-if="'words' in game">
It took [[ (Math.round(game.secondsToSolve * 100) / 100).toFixed(2) ]] seconds for the computer to solve this board.<br/>
[[ game.maxWords ]] words were possible for a max score of [[ game.maxScore ]] possible points.<br/>
[[ game.numWordsPlayersFound ]] words ([[ (Math.round(game.percentFound * 100) / 100).toFixed(2) ]]% of words) were found by players. [[ game.duplicates ]] were duplicates.<br/>
<span v-if="game.wasImported != undefined && game.wasImported">This game was imported from BoggleCV.</span><br/>
</span>
<span v-else>
The computer is still solving the board. This will update when it's done.<br/>
</span>
<form v-if="isHost" method="{{ formMethod }}">
<br/>
Create a board of the same type and invite everyone:
<input type="hidden" name="username" value="{{ username }}"/>
<input type="hidden" name="action" value="create"/>
<input type="hidden" name="page" value="pregame"/>
<input type="hidden" name="preset" value="custom"/>
<input type="hidden" name="size" :value="game.size+'x'+game.size"/>
<input type="hidden" name="letters" :value="game.letters"/>
<input type="hidden" name="minutes" :value="game.minutes"/>
<input type="hidden" name="inviteFrom" :value="game._id"/>
<input class="greenButton" type="submit" value="Play Again"/>
</form>
<form v-else-if="invitation != undefined" method="{{ formMethod }}">
<br/>
The host wants to play another board of the same type.
<input type="hidden" name="username" value="{{ username }}"/>
<input type="hidden" name="id" v-bind:value="invitation"/>
<input type="hidden" name="action" value="join"/>
<input type="hidden" name="page" value="pregame"/>
<input class="greenButton" type="submit" value="Join Again"/>
</form>
<br/>
Display words:
Chronological
<label class="switch">
<input type="checkbox" onclick="app.parallelView = this.checked;">
<span class="slider round"></span>
</label>
Parallel
<table class="grayTable" style="white-space: nowrap;" v-if="game != undefined">
<thead>
<tr>
<th v-for="player in game.players" :style="'max-width: 100px; overflow: auto;' + (game.winners.indexOf(player) == -1 ? '' : 'background-color: #4f4')">
[[ player ]]<br/>
<span v-if="player in game.playerData">
[[ game.playerData[player].score ]] point<span v-if="game.playerData[player].score != 1">s</span><br/>
[[ game.playerData[player].numWords ]] word<span v-if="game.playerData[player].numWords != 1">s</span>
</span>
<span v-else-if="!game.isArchived">
Waiting for<br/>
results...
</span>
<span v-else>
Left the<br/>
game :(
</span>
</th>
</tr>
</thead>
<tbody v-if="parallelView">
<tr v-for="word in allWordsPlayersFound">
<td v-for="player in game.players" :class="game.winners.indexOf(player) == -1 ? '' : 'winner'">
<span v-if="player in game.playerData && word in game.playerData[player].words">
<span v-if="game.playerData[player].words[word]" style="color: #e22" @mouseenter="wordHoverStart(word)" @mouseleave="wordHoverEnd(word)">
0 [[ word ]]
</span>
<span v-else @mouseenter="wordHoverStart(word)" @mouseleave="wordHoverEnd(word)">
[[ calculatePoints(word) ]] [[ word ]]
</span>
</span>
</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td v-for="player in game.players" :style="'vertical-align:top;' + (game.winners.indexOf(player) == -1 ? '' : 'background-color: #4f4')">
<span v-if="player in game.playerData">
<span v-for="[word, isDup] in Object.entries(game.playerData[player].words)">
<span v-if="isDup" style="color: #e22" @mouseenter="wordHoverStart(word)" @mouseleave="wordHoverEnd(word)">
0 [[ word ]]
</span>
<span v-else @mouseenter="wordHoverStart(word)" @mouseleave="wordHoverEnd(word)">
[[ calculatePoints(word) ]] [[ word ]]
</span>
<br/>
</span>
</span>
<!-- <span v-else>NO DATA</span> -->
</td>
</tr>
</tbody>
<!-- text-decoration: line-through -->
</table>
</div>
<div class="compactText" style="padding-left: 20px" v-if="game != undefined">
<br/>
<span v-if="'words' in game">
All [[ game.maxWords ]] possible words for this board.<br/>
Hover/Tap a word to see the definition.<br/>
Sort by:
Points
<label class="switch">
<input type="checkbox" onclick="app.sortByPoints = !this.checked;">
<span class="slider round"></span>
</label>
Alphabetical
<!-- <button onclick="app.sortByPoints = true;">Points</button>
<button onclick="app.sortByPoints = false;">Alphabetical</button> -->
<br/><br/>
<span v-if="sortByPoints"><span v-for="word in wordsPointsByPoints">
<span @mouseenter="wordHoverStart(word[0])" @mouseleave="wordHoverEnd(word[0])" :style="found.includes(word[0]) ? 'color:green; font-weight:bold' : ''">
[[ word[1] ]] [[ word[0] ]]
</span>
<br/>
</span></span>
<span v-else><span v-for="word in wordsPointsAlphabetical">
<span @mouseenter="wordHoverStart(word[0])" @mouseleave="wordHoverEnd(word[0])" :style="found.includes(word[0]) ? 'color:green; font-weight:bold' : ''">
[[ word[1] ]] [[ word[0] ]]
</span>
<br/>
</span></span>
</span>
</div>
</div>
<div class="singleCol"></div>
</form></div>
{% include 'boggle/common.html' %}
<script>
const app = new Vue ({
delimiters: ['[[',']]'],
el: "#app",
data: {
game: undefined,
styleObject: undefined,
styleObjectQu: undefined,
sortByPoints: true,
username: "{{ username }}",
sizeStr: undefined,
isHost: undefined,
invitation: undefined,
allWordsPlayersFound: undefined,
parallelView: false,
definitions: undefined,
wordPaths: {},
board: undefined,
hoverTimer: undefined
},
computed: {
found: function () {
if (this.game == undefined) {
return [];
}
found = [];
for (player in this.game.playerData) {
for (word in this.game.playerData[player].words) {
found.push(word);
}
}
return found;
},
wordsPointsAlphabetical: function () {
wpAlpha = [];
for (word of this.game.words.sort()) {
wpAlpha.push([word, this.calculatePoints(word)]);
}
return wpAlpha;
},
wordsPointsByPoints: function () {
return this.wordsPointsAlphabetical.slice().sort(function(a,b){return b[1]-a[1]});
}
},
created () {
this.getGame();
},
methods: {
getGame: function() {
fetch('?request=game&id={{ id }}')
.then(response => response.json())
.then(json => {
this.game = json.game
console.log(this.game)
this.isHost = (this.game.players[0] == this.username)
this.sizeStr = this.game.size + "x" + this.game.size
this.updateLetterSize()
this.allWordsPlayersFound = []
Object.entries(this.game.players).forEach(x => {
player = x[1]
Object.entries(this.game.playerData[player].words).forEach(y => {
word = y[0]
if (!this.allWordsPlayersFound.includes(word)) {
this.allWordsPlayersFound.push(word)
}
})
})
this.allWordsPlayersFound.sort()
this.solve();
})
},
getInvitation: function() {
fetch('?request=invitation&id={{ id }}')
.then(response => response.json())
.then(json => {
this.invitation = json.invitation
console.log("this.invitation: " + this.invitation)
})
},
updateLetterSize: function() {
if (window.innerWidth > 720) {
scaleFactor = 27*4/5;
} else {
scaleFactor = 50;
}
fontSize = scaleFactor / this.game.size;
this.styleObject = {fontSize: fontSize + 'vw'};
this.styleObjectQu = {fontSize: fontSize*.7 + 'vw'};
},
calculatePoints: function(word) {
l = word.length;
if (l > 8) {
l = 8;
}
return [0,1,1,1,1,2,3,5,11][l]
},
wordHoverStart: function(word) {
hoverTimer = setTimeout(function() {
app.wordSelected(word);
}, 175);
},
wordHoverEnd: function(word) {
clearTimeout(hoverTimer);
},
wordSelected: function(word) {
this.clearHighlight();
this.highlightPath(this.wordPaths[word]);
this.showDefinition(word);
},
highlightPath: function(path) {
// console.log("path:", path)
if (path == undefined) {
return
}
this.board = document.getElementsByClassName("boggleBoard")[0];
path.forEach(p => {
x = p[1]
y = p[0]
// console.log("x, y", x, y)
this.board.rows[y].cells[x].childNodes[0].style["color"] = "#ccc";
this.board.rows[y].cells[x].childNodes[0].style["backgroundColor"] = "#238";
})
},
clearHighlight: function() {
this.board = document.getElementsByClassName("boggleBoard")[0];
for (var x=0; x<this.game.size; x++) {
for (var y=0; y<this.game.size; y++) {
this.board.rows[y].cells[x].childNodes[0].style["color"] = "#238";
this.board.rows[y].cells[x].childNodes[0].style["backgroundColor"] = "#ccc";
}
}
},
showDefinition: function(word) {
if (this.definitions === undefined) {
fetch('?request=definitions&id=' + this.game._id)
.then(response => response.json())
.then(json => {
this.definitions = json.definitions;
console.log("defs gotten!!!");
this.showDefinition(word);
})
} else {
document.getElementById("definition").innerHTML = '<p class="compactText">' + word + ': ' + this.definitions[word] + '</p>';
}
},
solve: function() {
this.game.words.forEach(word => {
this.wordPaths[word] = this.solveWord(word);
});
console.log(this.wordPaths);
},
solveWord: function(word) {
for (var x=0; x<this.game.size; x++) {
for (var y=0; y<this.game.size; y++) {
path = this.solveWordAux(word, x, y, []);
if (path != undefined) {
return path
}
}
}
},
solveWordAux: function(word, x, y, path) {
// console.log(x, y, word, path)
if (word.size == 0) {
return path
}
var mypath = path.slice() //create a copy
mypath.push([x,y]) //add the current spot so it cannot use this letter again
var l = this.game.board[x][y].toLowerCase();
var newword;
if (l == "qu") {
if (word.size < 2 || word[0] != "q" || word[1] != "u") {
return undefined
}
if (word == "qu") {
return mypath
}
newword = word.substr(2) //remove 1st 2 chars
} else {
if (word[0] != l) {
return undefined
}
if (word == l) {
return mypath
}
newword = word.substr(1) //remove 1st char
}
// console.log("----------")
for (var newx=x-1; newx <= x+1; newx++) {
for (var newy=y-1; newy <= y+1; newy++) {
// console.log(l, newx, newy, mypath)
if (newx>=0 && newy>=0
&& newx<this.game.size && newy<this.game.size
&& !this.containsTuple(mypath, [newx, newy])
) {
path2 = this.solveWordAux(newword, newx, newy, mypath);
if (path2 != undefined) {
return path2
}
}
}
}
return undefined
},
containsTuple: function(array, tuple) {
for (var i=0; i<array.length; i++) {
if (array[i][0] === tuple[0] && array[i][1] === tuple[1]) {
return true
}
}
return false;
}
},
mounted () {
this.interval = setInterval(() => {
if (!this.game.isArchived) {
this.getGame();
}
}, 5000);
setInterval(() => {
this.getInvitation();
}, 1000);
let self = this;
window.onresize = function() {
self.updateLetterSize();
}
}
});
//have to wait for the page to load to access the footer
window.onload = function() {
// hide the footer since it gets in the way
document.getElementsByTagName('footer')[0].style.display = "none";
}
</script>
{% include 'footer.html' %}