Spaces:
Sleeping
Sleeping
from fasthtml.common import * | |
from fasthtml.svg import * | |
from svgs import fasthtml_logo | |
from fasthtml_hf import setup_hf_backup | |
app,rt = fast_app( | |
live=True, | |
id=int, | |
title=str, | |
done=bool, | |
pk='id', | |
hdrs=(Script(src="https://unpkg.com/alpinejs", defer=True), Link(rel="stylesheet", href="/public/app.css", type="text/css"),), | |
pico=False, | |
debug=True, | |
) | |
js = """ | |
function game() { | |
const cds = []; | |
let id = 1; | |
for (let i = 1; i <= 12; i++) { | |
cds.push({ id: id++, flipped: false, cleared: false, card: i }); | |
cds.push({ id: id++, flipped: false, cleared: false, card: i }); | |
} | |
cds.sort((a, b) => 0.5 - Math.random()); | |
return { | |
card_clicks: 0, | |
cheats: 3, | |
cards: cds, | |
get flippedCards() { | |
return this.cards.filter(card => card.flipped); | |
}, | |
flipCard(card) { | |
this.card_clicks += 1; | |
if( card.cleared ) { return; } | |
if( this.flippedCards.length <= 1 ) { card.flipped = ! card.flipped; } | |
if( this.flippedCards.length === 2 ) { | |
if( this.flippedCards[0].card === this.flippedCards[1].card ) { | |
this.flippedCards.forEach(card => { | |
card.cleared = true; | |
card.flipped = false; | |
}); | |
} else { | |
if( card.id !== this.flippedCards[0].id && card.id !== this.flippedCards[1].id ) { | |
this.flippedCards.forEach(card => { | |
card.flipped = false; | |
}); | |
card.flipped = ! card.flipped; | |
} else { | |
setTimeout((cd, fl) => { | |
fl.forEach(card => { | |
card.flipped = false; | |
}); | |
}, 1000, card, this.flippedCards); | |
} | |
} | |
} | |
}, | |
restart() { | |
this.cards.forEach(c => { | |
c.flipped = false; | |
c.cleared = false; | |
}); | |
this.cheats = 3; | |
this.card_clicks = 0; | |
setTimeout(() => { | |
this.cards.sort((a, b) => 0.5 - Math.random()); | |
}, 500); | |
}, | |
cheat() { | |
if( this.cheats >= 1 ) { | |
this.cheats -= 1; | |
this.cards.forEach(c => { | |
c.flipped = true; | |
}); | |
setTimeout(() => { | |
this.cards.forEach(c => { | |
c.flipped = false; | |
}); | |
}, 750); | |
} | |
}, | |
}; | |
} | |
""" | |
def cards(): | |
return Template( | |
Div( | |
Div( | |
Div( | |
NotStr(fasthtml_logo), | |
cls='flip-card-front' | |
), | |
Div( | |
Img( | |
#src='/assets/card_imgs/' + card_num + '.png', | |
**{":src": "'/assets/card_imgs/' + card.card + '.png'"}, | |
), | |
cls='flip-card-back' | |
), | |
cls='flip-card-inner', | |
**{":class": "{'flip-rotate-y-180': card.flipped || card.cleared}"}, | |
), | |
tabindex='0', | |
cls='flip-card', | |
**{"@click": "flipCard(card)"}, | |
**{":class": "{'flip-cleared': card.cleared}"}, | |
), | |
x_for='card in cards', | |
id="fr", | |
data_something="123" | |
) | |
def get(): return Title('Card Memory Game in FastHTML'), Div( | |
Section( | |
Div( | |
H1('Card Memory Game', cls='text-4xl md:text-6xl font-bold mb-4'), | |
P('Built with FastHTML, HTMX, TailwindCSS, and AlpineJS.', cls='text-lg md:text-2xl'), | |
cls='text-center text-white px-6 md:px-12 drop-shadow-lg' | |
), | |
cls='min-h-[300px] flex items-center justify-center bg-gradient-to-r from-[#3cdd8c] to-[#ffdb6d]' | |
), | |
Section( | |
Div( | |
Div( | |
Div( | |
cards(), | |
cls='grid grid-cols-6 gap-4 w-[944px]' | |
), | |
), | |
cls='flex items-center justify-center mt-10' | |
), | |
Div( | |
Div( | |
Div(), | |
cls='flex items-center justify-center my-5 font-bold', | |
**{"x-text": "'Card clicks: ' + card_clicks"}, | |
), | |
Div( | |
A( | |
'Restart Game', | |
cls='bg-white text-[#3cdd8c] font-semibold px-6 py-3 rounded-lg shadow-lg hover:bg-gray-100 transition', | |
**{"x-on:click.prevent": "restart()"}, | |
), | |
A( | |
cls='bg-white text-[#3cdd8c] font-semibold px-6 py-3 rounded-lg shadow-lg hover:bg-gray-100 transition', | |
**{"x-on:click.prevent": "cheat()"}, | |
**{"x-text": "'Cheat Mode! (' + cheats + ')'"}, | |
), | |
cls='flex items-center justify-center gap-4' | |
), | |
Div( | |
'Reveal all the matching pairs of cards in the fewest clicks possible.', | |
cls='mt-6 flex items-center justify-center gap-4' | |
), | |
Div( | |
'Click the \'Cheat Mode\' button to reveal all tiles, but you only have three per game so use wisely!', | |
cls='flex items-center justify-center gap-4' | |
), | |
), | |
cls='mb-20', | |
x_data='game()', | |
), | |
Footer( | |
A('Created by David Gwyer - Follow me on X', href="https://x.com/dgwyer", _target="_blank", cls="drop-shadow-lg text-lg"), | |
cls='bg-gray-800 text-white text-center py-8' | |
), | |
cls='bg-gray-100 text-gray-800' | |
), Script(js) | |
setup_hf_backup(app) | |
serve(reload_includes=["*.css"]) | |