match_cards / main.py
dgwyer's picture
deploy at 2024-09-16 13:21:26.356100
ef7d187 verified
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"
)
@rt('/')
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"])