WeBlog · Games
A frog, a fly and some black tea: a Python game written in Portuguese
A hungry frog, flies falling from the sky, five levels that get faster and faster, and arrows you can tap on a phone. It's a complete arcade game from the gallery, written in Python and in Portuguese. Here's how it works, what its words mean, and a few ways to make it your own.
Meet Chá Preto
O chá preto come as moscas means "Black Tea eats the flies". Black Tea (chá preto) is the frog's name. The game was made by Nilsan L. and shared to the PyWebLib gallery. Around here we call it Chapreto for short.
Here's how you play:
- Move the frog with the arrow keys, or tap the arrows in the corner on a phone. Each tap is one hop.
- Catch the falling fly to score a point (pontos). A new fly drops somewhere else straight away.
- Miss a fly and it hits the ground: you lose one of your three lives (vidas). Hopping into the edge of the pond costs a life too, so stay away from the walls.
- Every 10 points is a new level (nível). The flies fall faster, the frog hops further, and the whole pond changes colour: sky blue, green, orange, red, then purple.
- From level 2, a pink heart 💗 starts falling as well. Touch it and you lose a life and get sent back to the middle.
Play it Opens Chá Preto in the gallery. Works on a phone too.
Why it's a great game
Lots of first games are a sprite that moves. This one has everything a real arcade game needs, in 206 lines:
- A goal and a score. Catch flies, watch the number go up.
- A way to lose. Three lives, three different ways to lose them, and a Fim do jogo! ("Game over!") screen.
- A difficulty ramp. Five levels, each one faster than the last, and a new danger from level 2. The game is easy to start and gets hard slowly, which is exactly what keeps people playing.
- It works on phones. Most people who open a game from a link are on a phone, with no arrow keys. Nilsan thought of that and drew arrow buttons right into the game.
- A world to play in. Sixteen lotus flower sprites 🪷 make the pond, before a single line of game logic.
And it's written in Portuguese. Python's own words stay in English (if, while, import), but everything Nilsan named, the frog's speed, the scoreboard, the buttons, the comments, is in Portuguese. Python doesn't mind at all. You can name things in whatever language you think in, and your code is easier to read and fix when you do.
Portuguese to English: the words in the code
If you don't speak Portuguese, here's what the names in the code mean. Reading the code with this table is a great way to learn a few words, too.
| Portuguese | English |
|---|---|
sapo | frog |
mosca | fly |
chá preto | black tea (the frog's name) |
jogador | player |
pontos | points |
vidas, vida | lives, life |
nivel, nível | level |
velocidade_mosca | fly speed |
velocidade_sapo | frog speed |
placar | scoreboard |
nivel_texto | level text |
obstaculo | obstacle |
esquerda, direita | left, right |
cima, baixo | up, down |
cenário | scenery |
botões para celular | buttons for the phone |
teclado | keyboard |
paredes | walls |
cor | colour |
mosca caindo | falling fly |
sapo comeu a mosca | the frog ate the fly |
nova posição | new position |
chegou ao chão | reached the ground |
fim do jogo | end of the game (game over) |
How the frog eats the fly
The heart of the game is a few lines inside the game loop. Every frame (30 times a second), the fly falls a little, and the game checks whether the frog has caught it:
# 🪰 Mosca caindo
mosca.y += velocidade_mosca
# 🐸 Sapo comeu a mosca
if player.touches(mosca):
pontos += 1
placar.content = "Pontos: " + str(pontos)
# (the level code goes here, see below)
# 🪰 Nova posição da mosca
mosca.x = random.randint(30, 450)
mosca.y = 50
# 🪰 Mosca chegou ao chão
elif mosca.y > 350:
vidas -= 1
vida.content = "Vidas: " + str(vidas)
mosca.x = random.randint(30, 450)
mosca.y = 50
Line by line:
mosca.y += velocidade_moscamoves the fly down by the fly speed. In a PyWebLib game, y grows downwards, so adding to y means falling.player.touches(mosca)is True when the frog and the fly overlap. That's the whole collision check, in one line.- When the frog eats the fly, the score goes up and the fly jumps back to the top (
mosca.y = 50) at a random spot along the top, withrandom.randint(30, 450). It's the same fly every time. It just looks like a new one. elif mosca.y > 350catches the fly that got away. It's anelif, so it only runs when the frog didn't eat it.
The scoreboard is three labels, made once at the top of the program with game.label(). Changing a label's .content changes the words on screen, which is what placar.content = "Pontos: " + str(pontos) does:
# Placar
placar = game.label("Pontos: 0", 70, 30, size=20)
vida = game.label("Vidas: 3", 400, 30, size=20)
nivel_texto = game.label("Nível: 1", 240, 30, size=20)
Arrow buttons for phones, made from sprites
This is the clever part. A phone has no arrow keys, so Nilsan made four arrow emoji into sprites and put them in the bottom-left corner:
# 🎮 Botões para celular
esquerda = game.sprite("⬅️", 50, 315, size=40)
baixo = game.sprite("⬇️", 100, 315, size=40)
cima = game.sprite("⬆️", 100, 265, size=40)
direita = game.sprite("➡️", 150, 315, size=40)
Look at the numbers: the up arrow sits at (100, 265), right above the down arrow at (100, 315), with left and right on either side. That's the same shape as the arrow keys on a keyboard. Then, in the loop, the keyboard and the touch buttons each get their turn (here are left and right; up and down work the same way):
# ⌨️ Teclado
if game.pressed("left"):
player.x -= velocidade_sapo
if game.pressed("right"):
player.x += velocidade_sapo
# 📱 Celular
if esquerda.at_mouse() and game.clicked():
player.x -= velocidade_sapo
if direita.at_mouse() and game.clicked():
player.x += velocidade_sapo
game.pressed("left")is True while the left arrow key is held down.esquerda.at_mouse()is True when the pointer, or a finger on a phone, is over the left arrow sprite.game.clicked()is True once for each new click or tap.
Together, "the finger is on the left arrow and it just tapped" means "move left". The order is a nice touch, too. Python checks at_mouse() first, and only asks game.clicked() about the arrow that's actually under your finger. That matters, because clicked() answers True only once per tap: ask it about the wrong button first and the tap gets used up. The game.clicked() page warns about exactly that, and this game gets it right.
Try it yourself: any sprite can be a button. A 🔄 to restart, a ⏸️ to pause, a 🛒 to open a shop: put the sprite where you want it, then check sprite.at_mouse() and game.clicked() in your loop.
Levels that speed up
Each time the frog eats a fly, the game works out which level you're on:
# 🎯 Nível + velocidade da mosca + velocidade do sapo
if pontos >= 40:
nivel = 5
velocidade_mosca = 10
velocidade_sapo = 36
elif pontos >= 30:
nivel = 4
velocidade_mosca = 8
velocidade_sapo = 35
elif pontos >= 20:
nivel = 3
velocidade_mosca = 6
velocidade_sapo = 33
elif pontos >= 10:
nivel = 2
velocidade_mosca = 4
velocidade_sapo = 32
else:
nivel = 1
velocidade_mosca = 2
velocidade_sapo = 20
nivel_texto.content = "Nível: " + str(nivel)
Two things make this work well:
- The biggest number is checked first. With 25 points,
pontos >= 40is False,pontos >= 30is False, andpontos >= 20is True, so it's level 3. If the checks started at 10, a score of 25 would stop at "10 or more" and be stuck on level 2 forever. Going from the top down means the first True one is the right one. - The frog speeds up too. The fly speed goes 2, 4, 6, 8, 10: on level 1 a fly takes about 5 seconds to fall, and on level 5 about one second. If only the fly got faster, the game would quickly become impossible. Giving the frog bigger hops as well keeps it hard but fair.
Just after this, a second if chain picks the pond's colour for each level, and a third part of the loop starts the 💗 falling once nivel >= 2. Same idea each time: one number, nivel, decides how the whole game looks and feels.
Lives, walls and the end
The frog has to stay in the pond. If it hops past an edge, it's put back on the edge, and it costs a life:
# 🧱 Paredes
if player.x < 25:
player.x = 25
vidas -= 1
vida.content = "Vidas: " + str(vidas)
The same four lines are there for the right edge, the top and the bottom. Whenever lives run out, game.game_over() shows a banner across the game and ends the loop:
# ❤️ Fim do jogo
if vidas <= 0:
game.game_over(
"Fim do jogo! Pontos: " + str(pontos)
)
Make it yours
Here are a few small things you could add, to Chá Preto or to your own version. Each is a line or two, and each has a page in the docs.
A leaderboard, in one line
Every published game can have its own leaderboard, and you don't have to build it. Add game.submit_score(pontos) right before game.game_over, and the game's page in the gallery gets a top 10. Add retry=True and one tap starts a new game, which is very handy on a phone:
# ❤️ Fim do jogo
if vidas <= 0:
game.submit_score(pontos) # onto the leaderboard
game.game_over(
"Fim do jogo! Pontos: " + str(pontos),
retry=True # one tap starts a new game
)
Chá Preto has a game.game_over in two places, so give each one the new line. Each player keeps only their best score on the board, so it doesn't matter if a score is sent twice.
Sounds
game.sound() has ten built-in arcade sounds, no files needed. A "coin" for every fly and a "hit" for every lost life make the game feel alive:
# 🐸 Sapo comeu a mosca
if player.touches(mosca):
game.sound("coin") # a happy "ding" for every fly
pontos += 1
vidas -= 1
game.sound("hit") # ouch
Change the colour with game.background()
Chá Preto changes the pond's colour by calling game.window() again with a new background. PyWebLib also has a function made just for this, game.background(). It changes only the colour, and leaves the window, the keys being held and the score counter as they are. The game.window() page suggests keeping one game.window() at the top and using game.background() for every colour change after that:
# 🎨 Cor do cenário
if nivel == 5:
game.background("#800080")
elif nivel == 4:
game.background("#FF0000")
Remember the best score
game.save() and game.load() remember a value between games, in that browser. Load the recorde (record, or best score) at the top and show it, then save it when it's beaten:
recorde = game.load("recorde", 0) # best score so far, 0 the first time
recorde_texto = game.label("Recorde: " + str(recorde), 240, 60, size=16)
# ❤️ Fim do jogo
if vidas <= 0:
if pontos > recorde:
game.save("recorde", pontos) # remembered next time
Start your own frog game
Want to build one from scratch? Here's a small starter that uses the same ideas as Chá Preto, with the same Portuguese names: a frog, a falling fly, a scoreboard, two arrows you can tap, flies that speed up every 5 points, and some of the upgrades from above (sounds, the leaderboard line and tap to play again) already in. It's just over 40 lines, so there's lots of room to add lives, levels and lotus flowers of your own.
import game
import random
game.window(480, 360, background="#87CEEB")
sapo = game.sprite("🐸", 240, 250, size=50)
mosca = game.sprite("🪰", random.randint(30, 450), 0, size=35)
placar = game.label("Pontos: 0", 70, 30)
# Botões para celular: two arrows you can tap
esquerda = game.sprite("⬅️", 50, 320, size=40)
direita = game.sprite("➡️", 430, 320, size=40)
pontos = 0
velocidade_mosca = 2 # how far the fly falls each frame
velocidade_sapo = 8 # how far the frog moves each frame
while game.playing():
if game.pressed("left"):
sapo.x -= velocidade_sapo
if game.pressed("right"):
sapo.x += velocidade_sapo
if esquerda.at_mouse() and game.clicked():
sapo.x -= 40
if direita.at_mouse() and game.clicked():
sapo.x += 40
mosca.y += velocidade_mosca
if sapo.touches(mosca):
pontos += 1
placar.content = "Pontos: " + str(pontos)
game.sound("coin")
velocidade_mosca = 2 + pontos // 5 # a little faster every 5 points
mosca.x = random.randint(30, 450)
mosca.y = 0
elif mosca.y > 370:
game.sound("lose")
game.submit_score(pontos)
game.game_over("Fim do jogo! Pontos: " + str(pontos), retry=True)
game.frame()
Open in the Playground Opens with this code already in the editor. Click the game first so it hears the arrow keys.
pontos // 5 is whole-number division: 0 to 4 points give 0, 5 to 9 give 1, and so on, so the fly speed goes up by one every 5 points. It's a shorter way to make levels than an if chain, and the if chain is easier to tune by hand. Both are good choices.
Obrigado, Nilsan!
Thank you for the frog. Go and play Chá Preto, see how far you get, then make a game in your own language and share it to the gallery.
Play Chá Preto