6 Commits

6 changed files with 108 additions and 59 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,6 +2,7 @@ document.addEventListener('alpine:init', () => {
Alpine.store('alfagok', { Alpine.store('alfagok', {
/* Main alfagok application, state etc */ /* Main alfagok application, state etc */
gameID: 0, gameID: 0,
countingDown: '',
loading: false, loading: false,
@@ -88,7 +89,47 @@ document.addEventListener('alpine:init', () => {
this.resultGuesses = '🤔 '+ this.nrGuesses + ' gokken'; this.resultGuesses = '🤔 '+ this.nrGuesses + ' gokken';
this.resultTimeTaken = '⏱️ ' + getFormattedTime(this.winTime - this.startTime); this.resultTimeTaken = '⏱️ ' + getFormattedTime(this.winTime - this.startTime);
} }
},
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', {
@@ -135,7 +176,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>';
@@ -150,23 +191,23 @@ clip.on("error", function() {
/* Game timer, original from alphaguess.com **/ /* Game timer, original from alphaguess.com **/
function go() { function go() {
window.timerID = window.setInterval(timer, 0); window.timerID = window.setInterval(timer, 0);
} }
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>';
} }
function addZero(num){ function addZero(num){
if(num <=9) return '0'+num; if(num <=9) return '0'+num;
else return num; else return num;
} }
go(); go();

View File

@@ -17,58 +17,54 @@
<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 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>
<ul class="guessesbefore">
<template x-for="item in $store.alfagok.guessesBefore" :key="item">
<li x-text="item"></li>
</template>
</ul>
<p class="guessesheading" x-cloak x-show="$store.alfagok.guessesBefore.length">Het woord van de dag komt <em>na</em>:</p> <div x-show="!$store.alfagok.winTime">
<ul class="guessesbefore"> <input type="text" autocomplete="new-password" autocorrect="off" x-model="$store.alfagok.guessValue" @keyup.enter="$store.alfagok.doGuess()">
<template x-for="item in $store.alfagok.guessesBefore" :key="item">
<li x-text="item"></li>
</template>
</ul>
<div x-show="!$store.alfagok.winTime">
<input type="text" autocomplete="new-password" autocorrect="off" x-model="$store.alfagok.guessValue" @keyup.enter="$store.alfagok.doGuess()">
{# <p x-cloak>Je huidige gok is: <span x-text="$store.alfagok.guessValue"></span></p>#} {# <p x-cloak>Je huidige gok is: <span x-text="$store.alfagok.guessValue"></span></p>#}
<button @click="$store.alfagok.doGuess()">Doe een gok</button> <button @click="$store.alfagok.doGuess()">Doe een gok</button>
<p class="error" x-cloak x-show="$store.alfagok.guessError" x-text="$store.alfagok.guessError"></p> <p class="error" x-cloak x-show="$store.alfagok.guessError" x-text="$store.alfagok.guessError"></p>
</div> </div>
<div x-cloak x-show="$store.alfagok.winTime" class="win"> <div x-cloak x-show="$store.alfagok.winTime" class="win">
<h3><b>Je hebt hem! 🎉</b></h3> <h3><b>Je hebt hem! 🎉</b></h3>
<p>Het woord van vandaag was <b x-text="$store.alfagok.guessValue"></b>.</p> <p>Het woord van vandaag was <b x-text="$store.alfagok.guessValue"></b>.</p>
<div id="stats"> <div id="stats">
<div id="results"> <div id="results">
<p><b x-text="$store.alfagok.resultGameID"></b></p> <p><b x-text="$store.alfagok.resultGameID"></b></p>
<p x-text="$store.alfagok.resultGuesses"></p> <p x-text="$store.alfagok.resultGuesses"></p>
<p x-text="$store.alfagok.resultTimeTaken"></p> <p x-text="$store.alfagok.resultTimeTaken"></p>
<p>🔗 <span style="color:var(--blue)">alfagok.diginaut.net</span></p> <p>🔗 <span style="color:var(--blue)">alfagok.diginaut.net</span></p>
</div>
</div> </div>
<div id="copyresults"></div>
<button class="copy" data-clipboard-target="#results">
Tik om te kopi&euml;ren en te delen ❤️
</button>
</div> </div>
<div id="copyresults"></div>
<button class="copy" data-clipboard-target="#results">
Tik om te kopi&euml;ren en te delen ❤️
</button>
</div>
<p class="guessesheading" x-cloak x-show="$store.alfagok.guessesAfter.length">Het woord van de dag komt <em>voor</em>:</p> <p class="guessesheading" x-cloak x-show="$store.alfagok.guessesAfter.length">Het woord van de dag komt <em>voor</em>:</p>
<ul class="guessesafter"> <ul class="guessesafter">
<template x-for="item in $store.alfagok.guessesAfter" :key="item"> <template x-for="item in $store.alfagok.guessesAfter" :key="item">
<li x-text="item"></li> <li x-text="item"></li>
</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>