first commit

This commit is contained in:
rain 2023-04-16 00:40:43 -05:00
commit 36040d9744
9 changed files with 580 additions and 0 deletions

29
cookie.js Normal file
View File

@ -0,0 +1,29 @@
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 *1000));
var expires = "; expires=" + date.toGMTString();
} else {
var expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') {
c = c.substring(1,c.length);
}
if (c.indexOf(nameEQ) == 0) {
return c.substring(nameEQ.length,c.length);
}
}
return null;
}
function eraseCookie(name) {
createCookie(name,"",-1);
}

53
example.txt Normal file
View File

@ -0,0 +1,53 @@
/*
* Jeopardy File Format
* Questions and answers go below categories as seen below
* # is the character to start a category
* ? is the character to start a question
* & is the character to provide an answer
* Lines starting with any other character are ignored
*
* If 5 questions aren't provided for a category
* The rest of the 5 buttons will be autofilled as blank/used questions
*
* If more than 5 questions/answers are provided, the site uses the first five
*
* Keybinds are as follows
* c : Reset the page / delete any stored games
* esc/q : Return back to the main board
* space : Reveal the answer for the selected question
* p : Play / Stop the jeopardy theme song
* - : Add another team score box
* + : Remove the last team score box
* h : Reveal this help file
*
* Note: if you refresh the page, it will reset the board (What has been answered)
* Team scores will not be saved on page refresh!
*/
#This is Example Category 1
?Question
&Answer
?Question
&Answer
?Question
&Answer
?Question
&Answer
?Question
&Answer
#This is Example Category 2
?Question that is worth 100
&Answer that is worth 100
?Question that is worth 200
&Answer that is worth 200
?Question that is worth 300
&Answer that is worth 300
?Question that is worth 400
&Answer that is worth 400
?Question that is worth 500
&Answer that is worth 500

169
game.js Normal file
View File

@ -0,0 +1,169 @@
function packButtonEvents(){
document.querySelectorAll("td").forEach(td => {
td.addEventListener('mouseup', function () {
this.classList.add("selected");
});
});
}
function displayGame(game){
// Ensure file upload is hidden
document.getElementById("file").style.display = "none";
document.getElementById("game").style.display = "table";
// This will build the board
game = JSON.parse(game);
let table = document.querySelector('#game');
let tableHeaders = document.querySelector('#game tr');
let tableRows = document.querySelectorAll('#game tr:not(:first-child)');
game.forEach(category => {
//console.log(category.title);
//console.log(category.questions);
// Add category as a header
let title = document.createTextNode(category.title);
let th = document.createElement('th');
th.appendChild(title);
tableHeaders.appendChild(th);
//Now Pack Each Question
for(let i = 0;i < category.questions.length || i < 5;i++){
let td = document.createElement('td');
let valueText = document.createTextNode((i+1)*100);
let valueNode = document.createElement('div');
valueNode.classList.add('value');
valueNode.appendChild(valueText);
//Ensure we actually have a question / placeholder if not
let quesText = category.questions[i];
if(quesText === undefined || quesText.question === undefined) {
quesText = "";
td.classList.add("revealed");
} else {
quesText = category.questions[i].question;
}
let quesTextNode = document.createTextNode(quesText);
let quesNode = document.createElement('div');
quesNode.classList.add('question');
quesNode.appendChild(quesTextNode);
//Ensure we actually have an answer / placeholder if not
let ansText = category.questions[i];
if(ansText === undefined || ansText.answer === undefined) {
ansText = "";
td.classList.add("revealed");
} else {
ansText = category.questions[i].answer;
}
let ansTextNode = document.createTextNode(ansText);
let ansNode = document.createElement('div');
ansNode.classList.add('answer');
ansNode.appendChild(ansTextNode);
td.appendChild(valueNode);
td.appendChild(quesNode);
td.appendChild(ansNode);
tableRows[i].appendChild(td);
}
});
packButtonEvents();
}
function fileParse(){
//This runs to build the data to be saved as a cookie
var tis = document.querySelector("#file input[type=file]");
console.log("Recieved file to parse!");
var file = tis.files[0];
var reader = new FileReader();
reader.onload = function(progressEvent){
let tempLines = this.result.split(/\r\n|\n/);
var lines = (function(){
let finalLines = [];
// Remove Blank Pages
for(var line = 0; line < tempLines.length; line++){
if(tempLines[line] != "") {
//normal line to keep
finalLines.push(tempLines[line]);
}
}
return finalLines;
})();
let game = [];
let qOverflow = false;
for(var line = 0; line < lines.length; line++){
// Make the sub-div for the line of the current slide
switch(lines[line][0]){
case '#':
// Category Title
//Reset question overflow detection
qOverflow = false;
let title = lines[line].substring(1);
game.push({title: title, questions: []});
break;
case '?':
// Question
let que = lines[line].substring(1);
let qlen = game[game.length-1].questions.length;
//Check if last question has been set already
if(game[game.length-1].questions[qlen-1] != undefined && game[game.length-1].questions[qlen-1].answer == undefined){
game[game.length-1].questions[qlen-1].question += " " + que;
} else if(game[game.length-1].questions.length >= 5){
console.log("Error: too many questions provided for category");
qOverflow = true;
} else {
game[game.length-1].questions.push({question: que});
}
break;
case '&':
// Answer
let ans = lines[line].substring(1);
let qlen2 = game[game.length-1].questions.length;
//Check if no questions have been made, last answer has been set already
if(game[game.length-1].questions[qlen2-1] === undefined){
console.log("Error: Answer provided but no question");
} else if(qOverflow == true){
console.log("Error: Answer Overflow")
} else if(game[game.length-1].questions[qlen2-1].answer != undefined){
game[game.length-1].questions[qlen2-1].answer += " " + ans;
} else {
game[game.length-1].questions[qlen2-1].answer = ans;
}
break;
}
}
// Save Cookie here(game)
let strGame = JSON.stringify(game);
// document.cookie = "game="+strGame;
// Save the game for 2 days
createCookie("game",strGame,2);
displayGame(strGame);
}
reader.readAsText(file);
}
function rememberGame() {
//let game = (document.cookie.match(/^(?:.*;)?\s*game\s*=\s*([^;]+)(?:.*)?$/)||[,null])[1]
let game = readCookie("game")
if(game){
console.log("Resuming Old Game!");
displayGame(game);
} else {
console.log("Waiting for Game Data!");
var tis = document.querySelector("#file input[type=file]");
document.getElementById("file").style.display = "flex";
tis.addEventListener('change', fileParse);
}
}
window.onload = rememberGame();

31
index.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<link rel="icon" href="./media/favicon.png" media="(prefers-color-scheme: no-preference)" type="image/png">
<link rel="icon" href="./media/favicon-dark.png" media="(prefers-color-scheme: dark)" type="image/png">
<link rel="icon" href="./media/favicon.png" media="(prefers-color-scheme: light)" type="image/png">
<script src="keybinds.js"></script>
<script src="cookie.js"></script>
</head>
<audio id="audio">
<source src="media/Jeopardy-theme-song.mp3" type="audio/mpeg">
</audio>
<body>
<div id="file">
<input type="file">
<h1>Upload Jeopardy Game File<br>For Formatting Help, press 'H'</h1>
</div>
<table id="game">
<tr id="categories"></tr>
<tr></tr>
<tr></tr>
<tr></tr>
<tr></tr>
<tr></tr>
</table>
<div id="scores">
</div>
</body>
<script src="game.js"></script>
</html>

134
keybinds.js Normal file
View File

@ -0,0 +1,134 @@
var playstate = 0;
function decTeam(){
let input = this.parentNode.querySelector("input[type=text]");
// Check if there is an active page
let selected = document.querySelector(".selected");
if(selected != undefined && selected.querySelector(".value") != undefined){
// Use the correct innerText method for whatever browser
let textMethod = ('innerText' in selected.querySelector(".value"))? 'innerText' : 'textContent';
let selectedValue = selected.querySelector(".value")[textMethod];
input.value = parseInt(input.value) - parseInt(selectedValue);
} else {
input.value = parseInt(input.value) - 100;
}
}
function incTeam(){
let input = this.parentNode.querySelector("input[type=text]");
// Check if there is an active page
let selected = document.querySelector(".selected");
if(selected != undefined && selected.querySelector(".value") != undefined){
// Use the correct innerText method for whatever browser
let textMethod = ('innerText' in selected.querySelector(".value"))? 'innerText' : 'textContent';
let selectedValue = selected.querySelector(".value")[textMethod];
input.value = parseInt(input.value) + parseInt(selectedValue);
} else {
input.value = parseInt(input.value) + 100;
}
}
document.onkeydown = function(e) {
switch (e.keyCode) {
case 27:
case 81:
// ESC/Q Key pressed
// Set active question to inactive (Return to board)
//console.log("Returning to board view");
document.querySelector(".selected").classList.remove("selected");
break;
case 32:
// Space Key pressed
// Reveal answer
// Mark active question as used
//console.log("Toggling the answer");
// Prevent button incrementing/decrementing team score
e.preventDefault();
document.querySelector(".selected").classList.toggle("revealed");
break;
case 67:
// C Key pressed
// Reset/clear the game board
createCookie("game","",-1);
window.location.reload();
break;
case 72:
// H Key pressed
// Show the example file
let url = 'https://pingforagoodtime.com/jeopardy/example.txt';
window.open(url, '_blank');
break;
case 80:
// P Key pressed
// Toggle playing audio
var audio = document.getElementById('audio');
if(playstate == 0){
playstate = 1;
audio.play();
}else if(playstate == 1 && audio.duration == audio.currentTime){
audio.pause();
audio.currentTime = 0;
audio.play();
}else{
playstate = 0;
audio.pause();
audio.currentTime = 0;
}
break;
case 61: //Firefox
case 187:
// + Key pressed
// Add a team
let scores = document.querySelector('#scores');
let teamCount = document.querySelectorAll('#scores div').length+1;
// Limit the teams to 12
if(teamCount > 12){
break;
}
let teamNode = document.createElement('div');
let nameNode = document.createElement('h3');
let teamName = document.createTextNode("Team " + teamCount);
let minusNode = document.createElement('input');
minusNode.type = "button";
minusNode.value = "-";
minusNode.addEventListener('click', decTeam);
let textNode = document.createElement('input');
textNode.type = "text";
//textNode.disabled = "true";
textNode.value = "0";
let plusNode = document.createElement('input');
plusNode.type = "button";
plusNode.value = "+";
plusNode.addEventListener('click', incTeam);
nameNode.appendChild(teamName);
teamNode.appendChild(nameNode);
teamNode.appendChild(minusNode);
teamNode.appendChild(textNode);
teamNode.appendChild(plusNode);
scores.appendChild(teamNode);
break;
case 173: //Firefox
case 189:
// - Key pressed
// Remove last team
let lastTeam = document.querySelector('#scores div:last-child');
if(lastTeam != undefined){
lastTeam.parentNode.removeChild(lastTeam);
}
break;
}
};

Binary file not shown.

BIN
media/favicon-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
media/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

164
style.css Normal file
View File

@ -0,0 +1,164 @@
:root {
--fg-default:#C5C6C7;
--bg-default:#0B0C10;
--header-font: ;
--fg-header:#ffffff;
--bg-header:linear-gradient(to top, #1d2675 0%,#2a3698 100%);
--cell-font: "impact, sans-serif";
--fg-cell:#d49f4b;
--bg-cell:linear-gradient(to top, #1d2675 0%,#2a3698 100%);
}
* {
padding:0;
margin:0;
word-wrap: break-word;
}
html {
color-scheme: light dark;
background: var(--bg-default);
height:95vh;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
line-height: 1.22;
height:100vh;
white-space: nowrap;
background: var(--bg-default);
}
h1 {
color:var(--fg-default);
text-align:center;
}
#file {
width:100%;
height:100%;
display:none;
align-items:center;
justify-content:center;
background:
linear-gradient(to right, var(--fg-default) 8px, transparent 8px) 0 0,
linear-gradient(to right, var(--fg-default) 8px, transparent 8px) 0 100%,
linear-gradient(to left, var(--fg-default) 8px, transparent 8px) 100% 0,
linear-gradient(to left, var(--fg-default) 8px, transparent 8px) 100% 100%,
linear-gradient(to bottom, var(--fg-default) 8px, transparent 8px) 0 0,
linear-gradient(to bottom, var(--fg-default) 8px, transparent 8px) 100% 0,
linear-gradient(to top, var(--fg-default) 8px, transparent 8px) 0 100%,
linear-gradient(to top, var(--fg-default) 8px, transparent 8px) 100% 100%;
background-repeat: no-repeat;
background-size: 20px 20px;
}
#file input[type=file]{
height:100%;
width:100%;
position:absolute;
top:0;
left:0;
opacity:0;
cursor:pointer;
}
#game {
display: none;
width: 100%;
height: 100%;
text-align:center;
table-layout: fixed;
}
#game th {
font-family: var(--header-font);
font-size: 2vw;
color: var(--fg-header);
background: var(--bg-header);
white-space: pre-line;
height:0;
}
#game td {
font-family: var(--cell-font);
font-size: 3.5vw;
color: var(--fg-cell);
background: var(--bg-cell);
white-space: pre-line;
}
.value {
text-shadow: 5px 5px black;
font-weight:bold;
}
td:not(.selected):hover {
cursor:pointer;
}
.question,
.answer {
display: none;
color: var(--fg-header);
}
td.selected {
position: absolute;
top: 0;
left: 0;
width: 99.9vw;
height: 100%;
z-index:100;
}
td.selected .value {
width: 100%;
}
td.selected .question {
margin: 10% 0 0 0;
display: block;
}
td.revealed:not(.selected) .value{
font-style:italic;
opacity: 5%;
}
td.selected.revealed .answer {
display: block;
margin: 5% 0 0 0;
padding: 5% 0 0 0;
border-top:1px solid var(--fg-header);
}
#scores {
z-index: 200;
position: absolute;
bottom: 0;
text-align: center;
width: 100%;
display: flex;
justify-content: center;
}
#scores div {
margin: 5px;
padding: 5px;
color: #000000;
background: #fff;
display: inline-block;
}
#scores div input {
padding: 5px;
text-align: center;
font-size: 20px;
}
#scores div input[type=text] {
width: 80px;
}
#scores div input[type=button] {
width: 30px;
}