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.
 
 
 
 
 

454 lines
17 KiB

{% include 'header.html' %}
<div id="app">
<div class="singleCol">
<form method="{{ formMethod }}">
<input type="hidden" name="username" value="{{ username }}" />
<input type="hidden" name="page" value="lobby" />
<input style="position: relative; top: 2px; float: left;" class="greenButton" type="submit" value="Lobby" />
</form>
<h1 class="scale" style="margin-bottom: 1em; text-align: center;">BoggleCV</h1>
</div>
<div class="leftCol">
<form id="upload_form" method="POST" enctype="multipart/form-data" action="">
<input type="hidden" name="request" value="upload">
<input id="file_upload" type="file" name="upload" accept="image/*;capture=camera" capture="camera"
style="display:none" onclick="this.value = null" onchange="pick_file()">
</form>
<div id="pick_file" class="grayBox" style="background-color: lightblue; float: left; width: 47%"
onclick="document.getElementById('file_upload').click();">
<p>pick file</p>
<svg xmlns="http://www.w3.org/2000/svg" style="width: 20%; height: 20%" fill="currentColor" class="bi bi-upload"
viewBox="0 0 16 16">
<path
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
<path
d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
</svg>
</div>
<div id="use_camera" class="grayBox" style="background-color: lightblue; float: right; width: 47%"
onclick="use_camera()">
<p id="use_camera_text">use camera</p>
<svg xmlns="http://www.w3.org/2000/svg" style="width: 20%; height: 20%" fill="currentColor" class="bi bi-camera"
viewBox="0 0 16 16">
<path
d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z" />
<path
d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z" />
</svg>
</div>
<p id="video_instruction" style="text-align: center; display:none">tap/click the video to take a snapshot</p>
<!-- Stream video via webcam -->
<video id="video" style="width: 80%; display: none; margin: 0 auto" playsinline autoplay
onclick="take_photo()"></video>
<p style="color: #E02A2A;"><span id="errorMsg"></span></p>
<!-- Webcam video snapshot -->
<canvas style="display: none" id="canvas"></canvas>
<div style="text-align: center;">
<img id="feedbackImg" style="width: 50%; display: block; margin: 0 auto">
</div>
<br />
<div id="confirm" style="display: none">
<div id="good" class="grayBox" style="background-color: lightgreen; float: left; width: 47%" onclick="good()">
<svg xmlns="http://www.w3.org/2000/svg" style="width: 20%; height: 20%" fill="currentColor" class="bi bi-check2"
viewBox="0 0 16 16">
<path
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
</svg>
</div>
<div id="bad" class="grayBox" style="background-color: red; float: right; width: 47%" onclick="bad()">
<svg xmlns="http://www.w3.org/2000/svg" style="width: 20%; height: 20%" fill="currentColor"
class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
<path
d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
</svg>
</div>
find words with
<select id="letters">
<option>2</option>
<option>3</option>
<option selected="selected">4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</select> letters or more in
<select id="minutes">
<option>0.5</option>
<option>1</option>
<option>2</option>
<option selected="selected">3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
</select> minutes
</div>
</div>
<div class="rightCol">
<p style="text-align: center" v-if="board != undefined">tap/click on the letters that are wrong to change them</p>
<table id="board" v-if="board != undefined" class="boggleBoard noselect">
<tbody>
<tr v-for="(row, i) in board">
<td v-for="(item, j) in row">
<div>
<select style="display: none">
<option>A</option>
<option>B</option>
<option>C</option>
<option>D</option>
<option>E</option>
<option>F</option>
<option>G</option>
<option>H</option>
<option>I</option>
<option>J</option>
<option>K</option>
<option>L</option>
<option>M</option>
<option>N</option>
<option>O</option>
<option>P</option>
<option>Qu</option>
<option>R</option>
<option>S</option>
<option>T</option>
<option>U</option>
<option>V</option>
<option>W</option>
<option>X</option>
<option>Y</option>
<option>Z</option>
</select>
<span v-if="item == 'Qu'" v-bind:style="styleObjectQu" v-on:click="select_alphabet(i, j)">[[ item
]]</span>
<span v-else v-bind:style="styleObject" v-on:click="select_alphabet(i, j)">[[ item ]]</span>
</div>
</td>
</tr>
</tbody>
</table>
<p id="response"></p>
</div>
{% include 'boggle/common.html' %}
<script src="/static/jquery-3.6.0.min.js" type="text/javascript"></script>
<script>
//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";
}
const app = new Vue({
delimiters: ['[[', ']]'],
el: "#app",
data: {
board: undefined, //[["Qu","W","E","R","T"], ["A","S","D","F","G"], ["Qu","W","E","R","T"], ["Qu","W","E","R","T"], ["Qu","W","E","R","T"]],
az: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Qu", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
styleObject: undefined,
styleObjectQu: undefined,
},
methods: {
updateLetterSize: function () {
if (window.innerWidth > 720) {
scaleFactor = 27 * 4 / 5;
} else {
scaleFactor = 50;
}
fontSize = scaleFactor / 5;
this.styleObject = { fontSize: fontSize + 'vw' };
this.styleObjectQu = { fontSize: fontSize * .7 + 'vw' };
},
select_alphabet: function (i, j) {
cell = document.getElementById("board").children[0].children[i].children[j].children[0];
alphabet = cell.children[0];
letter = cell.children[1];
alphabet.selectedIndex = app.az.indexOf(letter.innerText);
alphabet.style.display = "initial";
letter.style.display = "none";
doneFunc = function () {
app.board[i][j] = alphabet.options[alphabet.selectedIndex].text;
alphabet.style.display = "none";
letter.style.display = "table-cell";
app.$forceUpdate();
};
alphabet.addEventListener('change', doneFunc, false);
alphabet.addEventListener('focusout', doneFunc, false);
}
},
mounted() {
let self = this;
self.updateLetterSize();
window.onresize = function () {
self.updateLetterSize();
}
}
});
/**
* Convert a base64 string in a Blob according to the data and contentType.
*
* @param b64Data {String} Pure base64 string without contentType
* @param contentType {String} the content type of the file i.e (image/jpeg - image/png - text/plain)
* @param sliceSize {Int} SliceSize to process the byteCharacters
* @see http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
* @return Blob
*/
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
var isUsingVideo = false;
const video = document.getElementById('video');
let currentStream;
function stopMediaTracks(stream) {
stream.getTracks().forEach(track => {
track.stop();
});
}
current_device_idx = 0;
devices = [];
function gotDevices(mediaDevices) {
// select.innerHTML = '';
// select.appendChild(document.createElement('option'));
let count = 1;
mediaDevices.forEach(mediaDevice => {
if (mediaDevice.kind === 'videoinput' && mediaDevice.deviceId != "") {
devices.push(mediaDevice.deviceId);
// const option = document.createElement('option');
// option.value = mediaDevice.deviceId;
// const label = mediaDevice.label || `Camera ${count++}`;
// const textNode = document.createTextNode(label);
// option.appendChild(textNode);
// select.appendChild(option);
}
});
}
function switch_camera() {
current_device_idx = (current_device_idx + 1) % devices.length;
use_camera();
}
function use_camera() {
if (typeof currentStream !== 'undefined') {
stopMediaTracks(currentStream);
}
const videoConstraints = {};
if (devices.length == 0) {
videoConstraints.facingMode = 'environment';
} else {
videoConstraints.deviceId = { exact: devices[current_device_idx] };
}
console.log(videoConstraints)
const constraints = {
video: videoConstraints,
audio: false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
currentStream = stream;
video.srcObject = stream;
return navigator.mediaDevices.enumerateDevices();
})
.then(gotDevices)
.catch(error => {
console.error(error);
document.getElementById("errorMsg").innerText = error;
});
document.getElementById("use_camera_text").innerText = "switch camera";
document.getElementById("use_camera").onclick = switch_camera;
document.getElementById('video').style.display = "block";
document.getElementById("video_instruction").style.display = "block";
isUsingVideo = true;
}
navigator.mediaDevices.enumerateDevices().then(gotDevices);
// Draw image
var context = canvas.getContext('2d');
function take_photo() {
//context.drawImage(video, 0, 0, 640, 480);
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// canvas.width = 20;
// canvas.height = 20;
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
// context.drawImage(video, 0, 0, 20, 20);
image_base64 = context.canvas.toDataURL('image/jpeg');
// Split the base64 string in data and contentType
var block = image_base64.split(";");
// Get the content type
var contentType = block[0].split(":")[1];// In this case "image/gif"
// get the real base64 content of the file
var realData = block[1].split(",")[1];// In this case "iVBORw0KGg...."
// Convert to blob
var blob = b64toBlob(realData, contentType);
// var fd = new FormData(hidden_form);
var fd = new FormData();
fd.append("request", "upload");
fd.append("upload", blob);
// fd.submit();
// var xhr = new XMLHttpRequest();
// xhr.open("POST", "/boggle", true);
// xhr.setRequestHeader('Content-Type', 'multipart/form-data');
// // xhr.setRequestHeader('Content-Type', 'application/json');
// // xhr.send(JSON.stringify({
// // image_base64: image_base64,
// // request: "upload"
// // }));
// xhr.send(fd);
my_ajax(fd);
}
function pick_file() {
isUsingVideo = false;
document.getElementById("use_camera_text").innerText = "use camera";
document.getElementById('video').style.display = "none";
document.getElementById("video_instruction").style.display = "none";
fd = new FormData(document.getElementById("upload_form"));
my_ajax(fd);
}
function my_ajax(fd) {
document.getElementById('response').innerText = "uploading...";
$.ajax({
url: "/boggle",
type: "POST",
data: fd,
enctype: 'multipart/form-data',
processData: false, // tell jQuery not to process the data
contentType: false // tell jQuery not to set contentType
}).done(function (data) {
json = JSON.parse(data);
if (json.board != undefined) {
app.board = json.board;
document.getElementById('response').innerText = "";
document.getElementById('pick_file').style.display = "none";
document.getElementById('use_camera').style.display = "none";
document.getElementById('video').style.display = "none";
document.getElementById("video_instruction").style.display = "none";
document.getElementById('confirm').style.display = "initial";
} else {
document.getElementById('response').innerText = json.message;
document.getElementById('pick_file').style.display = "initial";
document.getElementById('use_camera').style.display = "initial";
}
document.getElementById('feedbackImg').src = json.warpedimage;
document.getElementById('feedbackImg').style.display = "initial";
// document.getElementById('feedbackImg').src = 'data:image/jpeg;base64,' + data.warpedimage;
});
}
function good() {
form = document.createElement("form");
element = document.createElement("input");
element.name = "username";
element.value = "{{ username}}";
form.appendChild(element);
element = document.createElement("input");
element.name = "action";
element.value = "create";
form.appendChild(element);
element = document.createElement("input");
element.name = "page";
element.value = "pregame";
form.appendChild(element);
element = document.createElement("input");
element.name = "preset";
element.value = "5x5bogglecv";
form.appendChild(element);
element = document.createElement("input");
element.name = "letters";
element.value = document.getElementById("letters").value;
form.appendChild(element);
element = document.createElement("input");
element.name = "minutes";
element.value = document.getElementById("minutes").value;
form.appendChild(element);
element = document.createElement("input");
element.name = "key";
key = document.getElementById('feedbackImg').src.split("=").pop()
element.value = key;
form.appendChild(element);
element = document.createElement("input");
element.name = "board";
element.value = app.board;
form.appendChild(element);
form.style.display = "none";
document.body.appendChild(form);
form.submit();
}
function bad() {
document.getElementById('response').innerText = "";
if (isUsingVideo) {
document.getElementById('video').style.display = "block";
document.getElementById("video_instruction").style.display = "block";
} else {
document.getElementById('video').style.display = "none";
document.getElementById("video_instruction").style.display = "none";
}
document.getElementById('pick_file').style.display = "initial";
document.getElementById('use_camera').style.display = "initial";
document.getElementById('feedbackImg').style.display = "none";
document.getElementById('confirm').style.display = "none";
app.board = undefined;
}
</script>
{% include 'footer.html' %}