9 Commits

6 changed files with 120 additions and 65 deletions

View File

@@ -50,11 +50,11 @@ mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
packaging==24.2 packaging==24.2
# via gunicorn # via gunicorn
pydantic==2.9.2 pydantic==2.10.1
# via # via
# fastapi # fastapi
# pydantic-settings # pydantic-settings
pydantic-core==2.23.4 pydantic-core==2.27.1
# via pydantic # via pydantic
pydantic-settings==2.6.1 pydantic-settings==2.6.1
# via -r requirements.in # via -r requirements.in
@@ -86,7 +86,7 @@ typing-extensions==4.12.2
# pydantic # pydantic
# pydantic-core # pydantic-core
# typer # typer
uvicorn==0.32.0 uvicorn==0.32.1
# via # via
# fastapi # fastapi
# fastapi-cli # fastapi-cli

View File

@@ -46,11 +46,11 @@ markupsafe==3.0.2
# via jinja2 # via jinja2
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
pydantic==2.9.2 pydantic==2.10.1
# via # via
# fastapi # fastapi
# pydantic-settings # pydantic-settings
pydantic-core==2.23.4 pydantic-core==2.27.1
# via pydantic # via pydantic
pydantic-settings==2.6.1 pydantic-settings==2.6.1
# via -r requirements.in # via -r requirements.in
@@ -82,7 +82,7 @@ typing-extensions==4.12.2
# pydantic # pydantic
# pydantic-core # pydantic-core
# typer # typer
uvicorn==0.32.0 uvicorn==0.32.1
# via # via
# fastapi # fastapi
# fastapi-cli # fastapi-cli

View File

@@ -1,6 +1,6 @@
"""Main alfagok API application.""" """Main alfagok API application."""
import logging import logging
from datetime import date from datetime import date, datetime, timezone
from typing import Union from typing import Union
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
@@ -51,11 +51,19 @@ if settings.debug:
def get_game_id(): def get_game_id():
"""Calculate the index for the game/word we are handling today.""" """Calculate the index for the game/word we are handling today."""
today = date.today() today = datetime.now(timezone.utc).date()
# Calculate the amount of days since the start of the games so we know which word is used today # Calculate the amount of days since the start of the games so we know which word is used today
return (today - settings.start_date).days return (today - settings.start_date).days
def get_game_deadline():
"""Calculate the amount of time left for the current game."""
this_moment = datetime.now(timezone.utc)
midnight = datetime.now(timezone.utc).replace(hour=23, minute=59, second=59, microsecond=0)
# Calculate the amount of time left till midnight (and the start of the next game)
return midnight - this_moment
def is_valid_dictionary_word(word: str) -> bool: def is_valid_dictionary_word(word: str) -> bool:
"""Verify if `word` is in the dictionary provided.""" """Verify if `word` is in the dictionary provided."""
# Either we: [ ] strip all the endlines during file load, or [x] use the endline to search here # Either we: [ ] strip all the endlines during file load, or [x] use the endline to search here
@@ -71,8 +79,8 @@ async def index(request: Request):
@app.get('/api/game') @app.get('/api/game')
def what_game(): def what_game():
"""Handle incoming guess.""" """Which game is currently on?"""
return {'game': get_game_id()} return {'game': get_game_id(), 'deadline': get_game_deadline()}
@app.get('/api/guess/{word}') @app.get('/api/guess/{word}')

View File

@@ -37,6 +37,10 @@ a.title {
color: #CCC;; color: #CCC;;
} }
.guessesheading, .guessesbefore, .guessesafter {
text-align: center;
}
input[type="text"] { input[type="text"] {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;

View File

@@ -2,9 +2,11 @@ document.addEventListener('alpine:init', () => {
Alpine.store('alfagok', { Alpine.store('alfagok', {
// isLocalStorageAvailable: this.testLocalStorage(), // isLocalStorageAvailable: this.testLocalStorage(),
isLocalStorageAvailable: false, isLocalStorageAvailable: false,
savedGameKey: 'saveGame',
/* Main alfagok application, state etc */ /* Main alfagok application, state etc */
gameID: 0, gameID: 0,
countingDown: '',
loading: false, loading: false,
@@ -113,15 +115,21 @@ document.addEventListener('alpine:init', () => {
}, },
// # Local Storage Persistence // # Local Storage Persistence
storeGameState() { storeGameState() {
localStorage.setItem(this.savedGameKey, JSON.stringify({
startTime,
winTime,
gaveUpTime,
guessesBefore,
}));
}, },
getStoredGameState() { getStoredGameState() {
if (!this.isLocalStorageAvailable) return undefined; if (!this.isLocalStorageAvailable) return undefined;
const savedGameJson = localStorage.getItem('saveGame'); const savedGameJson = localStorage.getItem(this.savedGameKey);
try { try {
return savedGameJSON && JSON.parse(savedGameJSON); return savedGameJson && JSON.parse(savedGameJson);
} catch (e) { } catch (e) {
localStorage.removeItem('saveGame'); localStorage.removeItem(this.savedGameKey);
} }
return undefined; return undefined;
}, },
@@ -163,7 +171,7 @@ document.addEventListener('alpine:init', () => {
} }
}, },
resetSavedGames() { resetSavedGames() {
localStorage.removeItem('saveGame'); localStorage.removeItem(this.savedGameKey);
}, },
testLocalStorage() { testLocalStorage() {
// stolen from https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available // stolen from https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available
@@ -176,8 +184,46 @@ document.addEventListener('alpine:init', () => {
this.isLocalStorageAvailable = false; this.isLocalStorageAvailable = false;
} }
console.log('Local storage is available? ' + this.isLocalStorageAvailable); console.log('Local storage is available? ' + this.isLocalStorageAvailable);
},
// # Countdown timer
getFormattedTime(milliseconds) {
if (!Number.isInteger(milliseconds)) {
return '';
} }
}), let seconds = Math.round((milliseconds) / 1000);
const hours = Math.floor(seconds / 3600);
seconds %= 3600;
const minutes = Math.floor(seconds / 60);
seconds %= 60;
const formattedTime = [];
if (hours) {
formattedTime.push(`${hours}u`);
}
if (minutes) {
formattedTime.push(`${minutes}m`);
}
if (seconds) {
formattedTime.push(`${seconds}s`);
}
return formattedTime.join(' ') || '0s';
},
addZero(num){
if(num <=9) return '0'+num;
else return num;
},
countDownTimer(){
let nextgame = document.getElementById('nextgame');
let now = new Date();
let midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1, 0, 0, 0);
let diff = Math.floor((midnight - now)/1000);
let hoursRemain = Math.floor(diff/(60*60));
let minutesRemain = Math.floor((diff-hoursRemain*60*60)/60);
let secondsRemain = Math.floor(diff%60);
nextgame.innerHTML = '<span class="nextgame">'+addZero(hoursRemain)+':'+addZero(minutesRemain)+':'+addZero(secondsRemain)+' over</span>';
}
})
Alpine.store('darkMode', { Alpine.store('darkMode', {
/* Different Alpine app, dark mode settings for the game */ /* Different Alpine app, dark mode settings for the game */
@@ -223,7 +269,7 @@ function getFormattedTime(milliseconds) {
/* Clipboard stuff **/ /* Clipboard stuff **/
var clip = new Clipboard('.copy'); let clip = new ClipboardJS('.copy');
clip.on("success", function(e) { clip.on("success", function(e) {
document.getElementById('copyresults').innerHTML = '<p style="font-size:var(--small);opacity:50%">Gekopieerd! Deel je resultaat.</p>'; document.getElementById('copyresults').innerHTML = '<p style="font-size:var(--small);opacity:50%">Gekopieerd! Deel je resultaat.</p>';
@@ -242,13 +288,13 @@ function go() {
} }
function timer(){ function timer(){
var nextgame = document.getElementById('nextgame'); let nextgame = document.getElementById('nextgame');
var now = new Date(); let now = new Date();
var midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1, 0, 0, 0); let midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1, 0, 0, 0);
var diff = Math.floor((midnight - now)/1000); let diff = Math.floor((midnight - now)/1000);
var hoursRemain = Math.floor(diff/(60*60)); let hoursRemain = Math.floor(diff/(60*60));
var minutesRemain = Math.floor((diff-hoursRemain*60*60)/60); let minutesRemain = Math.floor((diff-hoursRemain*60*60)/60);
var secondsRemain = Math.floor(diff%60); let secondsRemain = Math.floor(diff%60);
nextgame.innerHTML = '<span class="nextgame">'+addZero(hoursRemain)+':'+addZero(minutesRemain)+':'+addZero(secondsRemain)+' over</span>'; nextgame.innerHTML = '<span class="nextgame">'+addZero(hoursRemain)+':'+addZero(minutesRemain)+':'+addZero(secondsRemain)+' over</span>';
} }

View File

@@ -11,21 +11,20 @@
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="static/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="static/images/favicon-16x16.png">
<link rel="manifest" href="static/images/site.webmanifest"> <link rel="manifest" href="static/images/site.webmanifest">
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head> </head>
<body> <body>
<div id="container" x-data=""> <div id="container" x-data="">
<a href="/" x-cloak class="title">alfagok</a> <span class="puzzleno">puzzel #<span x-text="$store.alfagok.gameID"></span><span id="nextgame"></span><span x-text="$store.alfagok.nrGuesses"></span> gokken</span> <a href="/" x-cloak class="title">alfagok</a> <span class="puzzleno">puzzel #<span x-text="$store.alfagok.gameID"></span><span id="nextgame"></span> | <span x-text="$store.alfagok.countingDown"></span><span x-text="$store.alfagok.nrGuesses"></span> gokken</span>
<div x-cloak class="instructions" x-show="$store.alfagok.guessesBefore.length === 0 && $store.alfagok.guessesAfter.length === 0"> <div x-cloak class="instructions" x-show="$store.alfagok.guessesBefore.length === 0 && $store.alfagok.guessesAfter.length === 0">
<p>Raad het woord van de dag. Elke gok geeft een hint over waar het woord zich in het alfabet bevindt.</p> <p>Raad het woord van de dag. Elke gok geeft een hint over waar het woord zich in het alfabet bevindt.</p>
<p x-show="$store.alfagok.isLocalStorageAvailable"></p> <p x-show="$store.alfagok.isLocalStorageAvailable"></p>
</div> </div>
<center>
<p class="guessesheading" x-cloak x-show="$store.alfagok.guessesBefore.length">Het woord van de dag komt <em>na</em>:</p> <p class="guessesheading" x-cloak x-show="$store.alfagok.guessesBefore.length">Het woord van de dag komt <em>na</em>:</p>
<ul class="guessesbefore"> <ul class="guessesbefore">
<template x-for="item in $store.alfagok.guessesBefore" :key="item"> <template x-for="item in $store.alfagok.guessesBefore" :key="item">
@@ -64,11 +63,9 @@
</template> </template>
</ul> </ul>
</center>
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.12/clipboard.min.js" rel=preload></script> <script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script src="/static/game.js"></script> <script src="/static/game.js"></script>
{# {#
<button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button> <button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button>