13
13
domain = "https://moodle-game.com"
14
14
15
15
# Wordlist
16
- words = ['lips' , 'caterpillar' , 'ants' , 'jellyfish' , 'cupcake' , 'seashell' , 'grass' , 'island' , 'coat' , 'bee' ,
17
- 'eye' , 'lion' , 'car' , 'bus' , 'boy' , 'knee' , 'bathroom' , 'ball' , 'jacket' , 'flag' , 'snowflake' , 'football' ,
18
- 'grapes' , 'bumblebee' , 'music' , 'book' , 'lemon' , 'dragon' , 'dream' , 'eyes' , 'balloon' , 'triangle' , 'sunglasses' , 'zebra' ,
19
- 'feet' , 'ant' , 'bed' , 'rocket' , 'river' , 'candle' , 'float' , 'smile' , 'alligator' , 'bunny' , 'plant' , 'snake' , 'bird' , 'duck' ,
20
- 'kitten' , 'earth' , 'starfish' , 'ear' , 'monkey' , 'lollipop' , 'sun' , 'branch' , 'blanket' , 'orange' , 'carrot' , 'cube' , 'dinosaur' ,
21
- 'hippo' , 'candy' , 'jail' , 'cow' , 'drum' , 'hamburger' , 'hat' , 'light' , 'inchworm' , 'snail' , 'cat' , 'shirt' , 'nose' , 'alive' ,
22
- 'person' , 'jar' , 'tail' , 'motorcycle' , 'whale' , 'zigzag' , 'suitcase' , 'backpack' , 'feather' , 'line' , 'mitten' , 'woman' , 'robot' ,
23
- 'cheese' , 'chimney' , 'comb' , 'egg' , 'worm' , 'zoo' , 'pizza' , 'fly' , 'pen' , 'coin' , 'apple' , 'baseball' , 'oval' , 'skateboard' , 'frog' ,
24
- 'spoon' , 'horse' , 'beach' , 'slide' , 'ladybug' , 'window' , 'rabbit' , 'helicopter' , 'desk' , 'head' , 'leg' , 'crayon' , 'clock' , 'boat' ,
25
- 'diamond' , 'bug' , 'ears' , 'box' , 'face' , 'night' , 'square' , 'pie' , 'bear' , 'finger' , 'banana' , 'mouth' , 'nail' , 'cherry' , 'bike' ,
26
- 'broom' , 'fire' , 'sea' , 'beak' , 'baby' , 'bowl' , 'popsicle' , 'lamp' , 'blocks' , 'bark' , 'elephant' , 'spider' , 'rock' , 'purse' , 'leaf' ,
27
- 'ship' , 'shoe' , 'kite' , 'mountains' , 'moon' , 'table' , 'rain' , 'sheep' , 'curl' , 'daisy' , 'snowman' , 'train' , 'legs' , 'swing' , 'mountain' ,
28
- 'cup' , 'truck' , 'flower' , 'glasses' , 'crab' , 'owl' , 'ring' , 'love' , 'lizard' , 'door' , 'heart' , 'button' , 'giraffe' , 'chicken' ,
29
- 'chair' , 'bridge' , 'key' , 'neck' , 'ghost' , 'computer' , 'bow' , 'bread' , 'corn' , 'water' , 'angel' , 'fork' , 'bone' , 'candy' , 'roof' ,
30
- 'underwear' , 'drum' , 'spider' , 'shoe' , 'smile' , 'cup' , 'hat' , 'bird' , 'kite' , 'snowman' , 'doll' , 'skateboard' , 'sleep' , 'sad' ,
31
- 'butterfly' , 'elephant' , 'ocean' , 'book' , 'egg' , 'house' , 'dog' , 'ball' , 'star' , 'shirt' , 'cookie' , 'fish' , 'bed' , 'phone' , 'airplane' , 'nose' ,
32
- 'apple' , 'sun' , 'sandwich' , 'cherry' , 'bubble' , 'moon' , 'snow' , 'rocket' , 'cliff' , 'stingray' , 'horse' , 'sack' , 'paper' , 'drumstick' , 'teapot' ,
33
- 'plug' , 'button' , 'cave' , 'crumb' , 'children' , 'bib' , 'panda' , 'unite' , 'eel' , 'cocoon' , 'cook' , 'city' , 'stove' , 'apologize' , 'maze' ,
34
- 'sunset' , 'step' , 'organ' , 'jump' , 'ribbon' , 'pizza' , 'pop' , 'tape' , 'pot' , 'table' , 'calendar' ,
35
- 'squirrel' , 'letter' , 'coconut' , 'napkin' , 'hero' , 'newborn' , 'doghouse' , 'baby' , 'turkey' , 'cheetah' , 'sidekick' ,
16
+ words = ['lips' , 'caterpillar' , 'ants' , 'jellyfish' , 'cupcake' , 'seashell' , 'grass' , 'island' , 'coat' , 'bee' ,
17
+ 'eye' , 'lion' , 'car' , 'bus' , 'boy' , 'knee' , 'bathroom' , 'ball' , 'jacket' , 'flag' , 'snowflake' , 'football' ,
18
+ 'grapes' , 'bumblebee' , 'music' , 'book' , 'lemon' , 'dragon' , 'dream' , 'eyes' , 'balloon' , 'triangle' , 'sunglasses' , 'zebra' ,
19
+ 'feet' , 'ant' , 'bed' , 'rocket' , 'river' , 'candle' , 'float' , 'smile' , 'alligator' , 'bunny' , 'plant' , 'snake' , 'bird' , 'duck' ,
20
+ 'kitten' , 'earth' , 'starfish' , 'ear' , 'monkey' , 'lollipop' , 'sun' , 'branch' , 'blanket' , 'orange' , 'carrot' , 'cube' , 'dinosaur' ,
21
+ 'hippo' , 'candy' , 'jail' , 'cow' , 'drum' , 'hamburger' , 'hat' , 'light' , 'inchworm' , 'snail' , 'cat' , 'shirt' , 'nose' , 'alive' ,
22
+ 'person' , 'jar' , 'tail' , 'motorcycle' , 'whale' , 'zigzag' , 'suitcase' , 'backpack' , 'feather' , 'line' , 'mitten' , 'woman' , 'robot' ,
23
+ 'cheese' , 'chimney' , 'comb' , 'egg' , 'worm' , 'zoo' , 'pizza' , 'fly' , 'pen' , 'coin' , 'apple' , 'baseball' , 'oval' , 'skateboard' , 'frog' ,
24
+ 'spoon' , 'horse' , 'beach' , 'slide' , 'ladybug' , 'window' , 'rabbit' , 'helicopter' , 'desk' , 'head' , 'leg' , 'crayon' , 'clock' , 'boat' ,
25
+ 'diamond' , 'bug' , 'ears' , 'box' , 'face' , 'night' , 'square' , 'pie' , 'bear' , 'finger' , 'banana' , 'mouth' , 'nail' , 'cherry' , 'bike' ,
26
+ 'broom' , 'fire' , 'sea' , 'beak' , 'baby' , 'bowl' , 'popsicle' , 'lamp' , 'blocks' , 'bark' , 'elephant' , 'spider' , 'rock' , 'purse' , 'leaf' ,
27
+ 'ship' , 'shoe' , 'kite' , 'mountains' , 'moon' , 'table' , 'rain' , 'sheep' , 'curl' , 'daisy' , 'snowman' , 'train' , 'legs' , 'swing' , 'mountain' ,
28
+ 'cup' , 'truck' , 'flower' , 'glasses' , 'crab' , 'owl' , 'ring' , 'love' , 'lizard' , 'door' , 'heart' , 'button' , 'giraffe' , 'chicken' ,
29
+ 'chair' , 'bridge' , 'key' , 'neck' , 'ghost' , 'computer' , 'bow' , 'bread' , 'corn' , 'water' , 'angel' , 'fork' , 'bone' , 'candy' , 'roof' ,
30
+ 'underwear' , 'drum' , 'spider' , 'shoe' , 'smile' , 'cup' , 'hat' , 'bird' , 'kite' , 'snowman' , 'doll' , 'skateboard' , 'sleep' , 'sad' ,
31
+ 'butterfly' , 'elephant' , 'ocean' , 'book' , 'egg' , 'house' , 'dog' , 'ball' , 'star' , 'shirt' , 'cookie' , 'fish' , 'bed' , 'phone' , 'airplane' , 'nose' ,
32
+ 'apple' , 'sun' , 'sandwich' , 'cherry' , 'bubble' , 'moon' , 'snow' , 'rocket' , 'cliff' , 'stingray' , 'horse' , 'sack' , 'paper' , 'drumstick' , 'teapot' ,
33
+ 'plug' , 'button' , 'cave' , 'crumb' , 'children' , 'bib' , 'panda' , 'unite' , 'eel' , 'cocoon' , 'cook' , 'city' , 'stove' , 'apologize' , 'maze' ,
34
+ 'sunset' , 'step' , 'organ' , 'jump' , 'ribbon' , 'pizza' , 'pop' , 'tape' , 'pot' , 'table' , 'calendar' ,
35
+ 'squirrel' , 'letter' , 'coconut' , 'napkin' , 'hero' , 'newborn' , 'doghouse' , 'baby' , 'turkey' , 'cheetah' , 'sidekick' ,
36
36
'cucumber' , 'crust' , 'sunglasses' , 'computer' , 'scar' , 'stick' , 'grill' , 'rat' , 'teacher' , 'farm' , 'tusk' ,
37
- 'lung' , 'lock' , 'refrigerator' , 'ambulance' , 'ship' , 'harmonica' , 'soda' , 'eagle' , 'rainstorm' , 'hoof' , 'fern' ,
37
+ 'lung' , 'lock' , 'refrigerator' , 'ambulance' , 'ship' , 'harmonica' , 'soda' , 'eagle' , 'rainstorm' , 'hoof' , 'fern' ,
38
38
'platypus' , 'pitchfork' , 'pinecone' , 'pencil' , 'parent' ,'trombone' , 'midnight' , 'sap' , 'pharaoh' ,'panda' ]
39
39
40
40
# App
@@ -77,7 +77,7 @@ def Navbar(page="home"):
77
77
document.querySelector('.navbar-toggle').addEventListener('click', function() {
78
78
const navbarLinks = document.querySelector('.navbar-links');
79
79
navbarLinks.classList.toggle('active');
80
-
80
+
81
81
// Toggle aria-expanded attribute
82
82
const expanded = this.getAttribute('aria-expanded') === 'true' || false;
83
83
this.setAttribute('aria-expanded', !expanded);
@@ -139,20 +139,20 @@ def active_area(session, last_game_id:int=None):
139
139
Canvas (id = "drawingCanvas" , width = "512" , height = "512" , cls = 'actitem canvas' ), # TODO style this
140
140
Div (Div (B ("Recent guesses:" ), cls = "guess" , style = "margin-bottom: 0.5rem;" ),
141
141
Div (id = "guess-area" ,
142
- hx_trigger = "every 0.3s" , hx_get = "/guesses" ,
142
+ hx_trigger = "every 0.3s" , hx_get = "/guesses" ,
143
143
hx_target = "#guess-area" , hx_swap = "afterbegin" ),
144
144
style = "border: 10px solid #FF4136; height: 512px; width: 350px; overflow-y: auto; text-align: left;" ,
145
145
cls = "actitem guessarea" ,
146
146
),
147
- cls = "actcontainer" ,
147
+ cls = "actcontainer" ,
148
148
),
149
149
id = "active-area" )
150
150
151
151
# If they're in the queue, show the queue status (will poll every second for updates)
152
152
if session ['sid' ] in player_queue :
153
153
154
154
# Update last request time so they don't get pruned
155
- player_queue [session ['sid' ]]['last_request' ] = time .time ()
155
+ player_queue [session ['sid' ]]['last_request' ] = time .time ()
156
156
157
157
# Estimate time remaining until the player will play:
158
158
remaining_times = [game_max_duration - (time .time () - game .start_time ) for game in active_games ]
@@ -161,7 +161,7 @@ def active_area(session, last_game_id:int=None):
161
161
estimate = int (sorted (remaining_times )[position_in_queue - 1 ])
162
162
else :
163
163
estimate = game_max_duration * len (player_queue ) / max_concurrent_games
164
-
164
+
165
165
# Show the queue status # <<< TODO restyle
166
166
status = Div (
167
167
P ("Game(s) full. You have been added to the queue." ),
@@ -188,7 +188,7 @@ def active_area(session, last_game_id:int=None):
188
188
style = "display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%;"
189
189
),
190
190
id = "active-area" )
191
-
191
+
192
192
# If they're not in an active game or the queue, show the start game button
193
193
intro = P ("Welcome to Moodle!" ,
194
194
"Put your drawing skills to the test while a team of AI models try to guess your word." ,
@@ -222,7 +222,7 @@ def get_recent_guesses(session):
222
222
game_ended = True
223
223
if game_ended :
224
224
# Could make this a script that uses htmx.ajax in a Timeout instead of using a hidden div
225
- gs .append (Hidden ("endgame" , hx_get = f"/active_area?last_game_id={ game .id } " , hx_target = "#active-area" ,
225
+ gs .append (Hidden ("endgame" , hx_get = f"/active_area?last_game_id={ game .id } " , hx_target = "#active-area" ,
226
226
hx_trigger = "load delay:3s" ,hx_swap = "outerHTML" , id = "endgame" , hx_swap_oob = "outerHTML" ))
227
227
end_game (game )
228
228
return * gs , ""
@@ -256,7 +256,7 @@ def end(session):
256
256
# End the game
257
257
game = [game for game in active_games if game .player == session ['sid' ]][0 ]
258
258
end_game (game )
259
-
259
+
260
260
# Replace active area
261
261
return active_area (session , last_game_id = game .id )
262
262
@@ -309,7 +309,7 @@ def end_game(game):
309
309
with db_lock :
310
310
game .game_gif = gif_path
311
311
games .update (game )
312
-
312
+
313
313
if True :
314
314
final_draw = drawings [game .last_drawing ]
315
315
# Reject if no drawings
@@ -338,7 +338,7 @@ def process_canvas(image: str, session):
338
338
game .last_drawing = drawing .id
339
339
games .update (game )
340
340
return {'active_game' : 'yes' , 'latest_image' : fn }
341
-
341
+
342
342
# They clicked "Play A Game!" to start a game - updates active area
343
343
@app .post ("/join" )
344
344
def join (session ):
@@ -429,7 +429,7 @@ def game_summary_page(game_id: int, session):
429
429
href = twitter_url ,
430
430
target = "_blank" ,
431
431
cls = "btn btn-primary twitter-s`hare-button" ),
432
- P ("" ),)
432
+ P ("" ),)
433
433
content = [
434
434
H3 (f"Game { game_id } Summary" ),
435
435
P (f"Word: { game .word } " ),
@@ -441,7 +441,7 @@ def game_summary_page(game_id: int, session):
441
441
twitter_share ,
442
442
gif
443
443
]
444
-
444
+
445
445
# Twitter meta tags
446
446
metas = [
447
447
Meta (name = "twitter:card" , content = "summary_large_image" ),
@@ -503,19 +503,19 @@ def stats():
503
503
)
504
504
505
505
about_md = """## About
506
-
506
+
507
507
Moodle was born from a demo that got a little out of control. I wanted to see if any of these multi-modal
508
508
LLMs could play pictionary. It turns out they can! And it's rather fun...
509
509
510
510
### Technical Details
511
-
512
- This app is built with a new framework we're working on - [FastHTML](https://fastht.ml). It's a Python framework
511
+
512
+ This app is built with a new framework we're working on - [FastHTML](https://fastht.ml). It's a Python framework
513
513
that makes it easy to build web apps with Python and HTML. It's still in development, but it's already pretty powerful!
514
-
514
+
515
515
The canvas (HTML/JS) sends images to the backend, which ships them off to a few different models that try to guess the word.
516
-
516
+
517
517
### Future Plans
518
-
518
+
519
519
I think this will be a fun, quirky eval for models. I'm saving all the drawings (I hope you don't draw anything bad) and the progressions
520
520
will make a good classification problem. Can a model guess from the final image? from the sequence? etc..."""
521
521
@@ -546,7 +546,7 @@ def static(fname: str, ext: str):
546
546
547
547
548
548
# Threaded guess loop #
549
- # For each game, for each model, we have a thread going that
549
+ # For each game, for each model, we have a thread going that
550
550
# sends the image to the model and gets the guess back
551
551
552
552
## API clients ##
@@ -660,11 +660,11 @@ def __init__(self, task_name, stop_event, func, game_idx=None, interval=3):
660
660
self .interval = interval
661
661
def run (self ):
662
662
# Debug info
663
- if self .game_idx is None :
663
+ if self .game_idx is None :
664
664
if thread_debug : print (f"Task { self .task_name } is starting with func { self .func } " )
665
- else :
665
+ else :
666
666
if thread_debug : print (f"Task { self .task_name } is starting for game { self .game_idx } with guesser { self .func } " )
667
-
667
+
668
668
# Run in loop
669
669
while not self .stop_event .is_set ():
670
670
start_time = time .time ()
@@ -701,7 +701,7 @@ def run(self):
701
701
if thread_debug : print (f"Game idx { self .game_idx } guess by { guesser_name } : { guess_text } (correct: { is_correct } )" )
702
702
except Exception as e :
703
703
print (f"Error: { e } " )
704
-
704
+
705
705
# Sleep for the remaining time with a bit of randomness added
706
706
time .sleep (max (0 , self .interval - (time .time () - start_time )) + random .random () * 0.5 )
707
707
@@ -713,38 +713,31 @@ def run(self):
713
713
714
714
def start_background_tasks ():
715
715
global tasks
716
- if len (tasks ) > 0 :
717
- print ("Tasks already running" )
718
- return
716
+ if len (tasks ) > 0 : return print ("Tasks already running" )
719
717
for i in range (max_concurrent_games ):
720
718
for guesser in guessers :
721
719
task = BackgroundTask (f'game_{ i } _guesser_{ guesser } ' , stop_event , guessers [guesser ], i )
722
720
tasks .append (task )
723
721
tasks .append (BackgroundTask ('queue_pruner' , stop_event , queue_pruner , interval = 1 ))
724
722
tasks .append (BackgroundTask ('game_ender' , stop_event , game_ender , interval = 1 ))
725
- for task in tasks :
726
- task .start ()
723
+ for task in tasks : task .start ()
727
724
728
725
def stop_background_tasks ():
729
726
print ("Stopping all tasks..." )
730
727
stop_event .set ()
731
- for task in tasks :
732
- task .join ()
728
+ for task in tasks : task .join ()
733
729
print ("All tasks stopped" )
734
730
735
731
@app .on_event ("startup" )
736
- async def startup_event ():
737
- start_background_tasks ()
732
+ async def startup_event (): start_background_tasks ()
738
733
739
734
@app .on_event ("shutdown" )
740
- async def shutdown_event ():
741
- stop_background_tasks ()
735
+ async def shutdown_event (): stop_background_tasks ()
742
736
743
737
if __name__ == "__main__" :
744
- try :
745
- serve ()
746
- except KeyboardInterrupt :
747
- pass
738
+ try : serve ()
739
+ except KeyboardInterrupt : pass
748
740
finally :
749
741
stop_background_tasks ()
750
- sys .exit (0 )
742
+ sys .exit (0 )
743
+
0 commit comments