Building a Game App with Just Python
"Guess the Sketch" is a multiplayer game where you draw sketches and challenge friends to guess what they are. Players earn points for correct guesses, with fewer attempts leading to higher scores. Each round, one player is chosen to sketch, while the others try to guess as quickly as possible. I built this game using Python and Anvil, and it was surprisingly easy! I didn’t need to write HTML, CSS, or JavaScript. In this post, I will walk you through the key parts of building Guess the Sketch: Building the UI, Saving and sending sketches, Sending invites, Managing players and scores, and Publishing and sharing. Let's get started! Building the UI With Anvil’s drag-and-drop editor, building the UI was very intuitive. Instead of writing HTML and CSS, I simply dragged components from the Toolbox onto the Form and arranged them as needed. Here is how easy it was to build the SketchScreen, where players create and name their sketches. Anvil’s Canvas component provided a seamless way to add a drawing area to the game. It is a Python wrapper around the HTML element, with built-in methods and event handlers that handle drawing effortlessly. I used the mouse_down, mouse_move, and mouse_up events to track real-time mouse movement and draw lines on the canvas. Adding an eraser was as simple as setting the brush colour to match the background of the drawing area. Want to learn more about the Canvas component? Check out this Canvas Tutorial: Saving and Sending Sketches Once a player finishes drawing and clicks the Send Sketch button, the image is retrieved from the Canvas using the .get_image() method: sketch = self.canvas_1.get_image() This converts the sketch into a Media object, which is then stored in a Data Table for later retrieval. When a player submits a sketch, one of two things happens: A new game is created, along with its first round (if the player is starting a new game). A new round is added to an existing game, which happens when a player joins an ongoing game and takes their turn. Here’s how I handled both scenarios: sketch = self.canvas_1.get_image() # Add a round to an existing game if self.game_id: anvil.server.call('add_game_round', self.game_id, sketch, sketch_name) navigate(path = "/invite/:id", params = {"id":self.game_id}) # Create a new game else: game_id = anvil.server.call('start_game', sketch, sketch_name) navigate(path = "/invite/:id", params = {"id":game_id}) In both cases, a new game round is created and linked to the current game, whether the game is newly created or ongoing. With Data Tables, creating a round is as simple as inserting a new row into the Rounds Table and adding the round ID to the corresponding game's rounds column (which stores a list of round IDs for that game). Here’s how I create and link a round to an existing game: @anvil.server.callable(require_user=True) def add_game_round(game_id, image, sketch_name): # Other game logic... game = app_tables.game_table.get_by_id(game_id) round = app_tables.rounds_table.add_row(game_id=game_id, image=image, owner=player, sketch_name=sketch_name, start_time=datetime.now()) game['rounds'] += [round] # Other game logic... Sending Invites Anvil has a built-in Email Service, which makes sending invites as simple as a single function call. I used anvil.email.send() to email friends a game invite with a preview of the sketch as an inline attachment: anvil.email.send( bcc = emails, from_address = player['email'], subject = f"{player['name']} is inviting you to Guess the Sketch!", html = f""" Hey, I just created this sketch: Think you can figure out what it is? Click this link to play: {link} """, inline_attachments = {'sketch': image}) Players receive this email with a preview of the sketch right in their inbox. Here’s how it looks: This makes it really easy for players to see the sketch before they click the link to play. Managing Players and Scores To manage player authentication, I used Anvil’s Users Service, which automatically created a Users Table that stored player details. With just one line, I could add a login Form: anvil.users.login_with_form() This allows players to sign up and log in with their Google accounts, making it easy to track their progress across games. Each new user is automatically added as a row in the Users Table. At any point in the app, I can access the currently logged-in player by calling: player = anvil.users.get_user() Tracking Player Scores To track scores, I added a points column to the automatically generated Users Table, where I store the total points each player accumulates. For every point a player earns in a game, I update the player’s total score: player = anvil.users.get_user() # Other game logic... if player['points']: player['points'] += points_gaine

"Guess the Sketch" is a multiplayer game where you draw sketches and challenge friends to guess what they are. Players earn points for correct guesses, with fewer attempts leading to higher scores. Each round, one player is chosen to sketch, while the others try to guess as quickly as possible.
I built this game using Python and Anvil, and it was surprisingly easy! I didn’t need to write HTML, CSS, or JavaScript. In this post, I will walk you through the key parts of building Guess the Sketch:
- Building the UI,
- Saving and sending sketches,
- Sending invites,
- Managing players and scores, and
- Publishing and sharing.
Let's get started!
Building the UI
With Anvil’s drag-and-drop editor, building the UI was very intuitive. Instead of writing HTML and CSS, I simply dragged components from the Toolbox onto the Form and arranged them as needed. Here is how easy it was to build the SketchScreen, where players create and name their sketches.
Anvil’s Canvas component provided a seamless way to add a drawing area to the game. It is a Python wrapper around the HTML element, with built-in methods and event handlers that handle drawing effortlessly. I used the
mouse_down
, mouse_move
, and mouse_up
events to track real-time mouse movement and draw lines on the canvas. Adding an eraser was as simple as setting the brush colour to match the background of the drawing area.
Want to learn more about the Canvas component? Check out this Canvas Tutorial:
Saving and Sending Sketches
Once a player finishes drawing and clicks the Send Sketch button, the image is retrieved from the Canvas using the .get_image()
method:
sketch = self.canvas_1.get_image()
This converts the sketch into a Media object, which is then stored in a Data Table for later retrieval.
When a player submits a sketch, one of two things happens:
- A new game is created, along with its first round (if the player is starting a new game).
- A new round is added to an existing game, which happens when a player joins an ongoing game and takes their turn.
Here’s how I handled both scenarios:
sketch = self.canvas_1.get_image()
# Add a round to an existing game
if self.game_id:
anvil.server.call('add_game_round', self.game_id, sketch, sketch_name)
navigate(path = "/invite/:id", params = {"id":self.game_id})
# Create a new game
else:
game_id = anvil.server.call('start_game', sketch, sketch_name)
navigate(path = "/invite/:id", params = {"id":game_id})
In both cases, a new game round is created and linked to the current game, whether the game is newly created or ongoing.
With Data Tables, creating a round is as simple as inserting a new row into the Rounds Table and adding the round ID to the corresponding game's rounds
column (which stores a list of round IDs for that game).
Here’s how I create and link a round to an existing game:
@anvil.server.callable(require_user=True)
def add_game_round(game_id, image, sketch_name):
# Other game logic...
game = app_tables.game_table.get_by_id(game_id)
round = app_tables.rounds_table.add_row(game_id=game_id, image=image, owner=player, sketch_name=sketch_name, start_time=datetime.now())
game['rounds'] += [round]
# Other game logic...
Sending Invites
Anvil has a built-in Email Service, which makes sending invites as simple as a single function call. I used anvil.email.send()
to email friends a game invite with a preview of the sketch as an inline attachment:
anvil.email.send(
bcc = emails,
from_address = player['email'],
subject = f"{player['name']} is inviting you to Guess the Sketch!",
html = f"""
Hey,
I just created this sketch:
"cid:sketch">
Think you can figure out what it is? Click this link to play: {link}
""",
inline_attachments = {'sketch': image})
Players receive this email with a preview of the sketch right in their inbox. Here’s how it looks:
This makes it really easy for players to see the sketch before they click the link to play.
Managing Players and Scores
To manage player authentication, I used Anvil’s Users Service, which automatically created a Users Table that stored player details.
With just one line, I could add a login Form:
anvil.users.login_with_form()
This allows players to sign up and log in with their Google accounts, making it easy to track their progress across games. Each new user is automatically added as a row in the Users Table.
At any point in the app, I can access the currently logged-in player by calling:
player = anvil.users.get_user()
Tracking Player Scores
To track scores, I added a points
column to the automatically generated Users Table, where I store the total points each player accumulates.
For every point a player earns in a game, I update the player’s total score:
player = anvil.users.get_user()
# Other game logic...
if player['points']:
player['points'] += points_gained
else:
player['points'] = points_gained
This ensures that scores remain updated and persistent across game sessions.
Publishing and Sharing
Once everything was ready, publishing the game was just one click on the Publish button in the Anvil editor. I instantly got a public URL, that I could share with friends to start playing right away!
Want to Build Your Own?
These are just some of the highlights, but you can clone the full project and explore how it works yourself.
More about Anvil
If you're new here, welcome! Anvil is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just build it all in Python.