Projeto Jogo da Forca

O jogo da forca de caderno, agora no terminal: o computador escolhe uma palavra e mostra só os tracinhos, um para cada letra. Você chuta letras; cada acerto revela a letra em todos os lugares onde ela aparece, e cada erro desenha mais uma parte do boneco. Seis erros e o boneco está completo: você perdeu. É o projeto ideal para praticar textos e listas juntos.

Você vai praticar

O que vamos construir

A forca pronta, no terminal da Ana. A letra depois de cada Letra: foi digitada por ela:

$ python3 forca.py
=== Jogo da Forca: frutas ===
  +---+
      |
      |
      |
      |
=======
_ _ _ _ _ _
Letra: e
Não tem e.
  +---+
  O   |                                « primeiro erro: a cabeça
      |
      |
      |
=======
_ _ _ _ _ _
Erradas: e
Letra: i
Tem 1 letra(s) i!
  +---+
  O   |
      |
      |
      |
=======
_ _ i _ _ _
Erradas: e
Letra: a
Tem 2 letra(s) a!
  +---+
  O   |
      |
      |
      |
=======
_ _ i a _ a
Erradas: e
Letra: b
Tem 1 letra(s) b!
  +---+
  O   |
      |
      |
      |
=======
_ _ i a b a
Erradas: e
Letra: g
Tem 1 letra(s) g!
  +---+
  O   |
      |
      |
      |
=======
g _ i a b a
Erradas: e
Letra: o
Tem 1 letra(s) o!
  +---+
  O   |
      |
      |
      |
=======
g o i a b a
Erradas: e
Você venceu! A palavra era goiaba.

Cinco versões, uma novidade em cada:

  1. A palavra escondida (percorrer o texto, a lista certas).
  2. Erros contados: dá para perder (a lista erradas, len).
  3. Chute validado (strip, lower, isalpha, continue).
  4. Funções, desenho e palavra sorteada (def, random.choice).
  5. As palavras num arquivo (open e with).

Antes de começar

Você vai usar strings, listas, while True com break (como na Calculadora) e random (como no Jogo de Adivinhação). Crie a pasta; o arquivo vai se chamar forca.py:

$ mkdir -p projetos/forca
$ cd projetos/forca

Versão 1 · A palavra escondida

Primeiro, só o coração do jogo: a palavra fixa "banana", os tracinhos e as letras aparecendo. Sem erros, sem forca: o jogo termina quando a palavra inteira estiver descoberta.

A ideia: o programa guarda numa lista as letras que a pessoa já acertou. A cada volta, ele monta a "máscara" da palavra do zero: passa letra por letra de "banana" e, para cada uma, pergunta "esta letra já foi acertada?". Se sim, escreve a letra; se não, escreve _.

palavra = "banana"
certas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)

    if "_" not in mascara:
        print("Você descobriu a palavra!")
        break

    chute = input("Letra: ")
    if chute in palavra:
        certas.append(chute)
certas = []
Uma lista vazia: as letras acertadas vão entrar aqui.
mascara = ""
Um texto vazio, que vai crescer letra por letra. Recomeça vazio a cada volta do while.
for letra in palavra:
Um for sobre um texto passa por cada caractere: "b", "a", "n", "a", "n", "a".
if letra in certas:
"Esta letra está na lista das acertadas?"
mascara += letra + " "
Acrescenta a letra e um espaço no fim da máscara. O += é "o que já tinha, mais isto".
if "_" not in mascara:
Se não sobrou nenhum tracinho, todas as letras foram descobertas.
if chute in palavra: certas.append(chute)
Se a letra existe na palavra, entra na lista. Uma vez na lista, ela aparece em todos os lugares, porque a máscara confere cada posição.
$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Letra: a
_ a _ a _ a                            « um chute, três letras
Letra: x
_ a _ a _ a                            « x não existe: nada acontece
Letra: b
b a _ a _ a
Letra: n
b a n a n a
Você descobriu a palavra!

Funciona, mas repare no x: errar não custa nada, e a pessoa nem fica sabendo que errou. A versão 2 conta os erros.

Por que montar a máscara do zero a cada volta?

Parece mais fácil guardar o texto "_ _ _ _ _ _" e ir trocando os tracinhos pelas letras. Mas em Python um texto não pode ser alterado por dentro: ele é imutável. Tentar trocar uma letra dá erro:

palavra = "banana"
palavra[0] = "B"
$ python3 editar.py
Traceback (most recent call last):
  File "/home/ana/projetos/forca/editar.py", line 2, in <module>
    palavra[0] = "B"
    ~~~~~~~^^^
TypeError: 'str' object does not support item assignment

TypeError: 'str' object does not support item assignment = "um objeto str não aceita atribuição a um item": dá para ler palavra[0], mas não para trocá-lo. Para "mudar" um texto, cria-se um texto novo. É o que a versão 1 faz: um mascara novo a cada volta. A aula Mutável × Imutável explica a diferença entre textos e listas.

Missão (versão 1): o programa mostra os tracinhos e pede letras, mas nunca guarda os acertos: a palavra nunca aparece. No lugar do comentário, escreva o if que coloca o chute na lista certas quando a letra está na palavra. A entrada da missão é b, a, n.

palavra = "banana"
certas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)

    if "_" not in mascara:
        print("Você descobriu a palavra!")
        break

    chute = input("Letra: ")
    # guarde o chute em certas, se a letra estiver na palavra
palavra = "banana"
certas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)

    if "_" not in mascara:
        print("Você descobriu a palavra!")
        break

    chute = input("Letra: ")
    if chute in palavra:
        certas.append(chute)

No código inicial, quando a entrada da missão acaba, o input() não tem mais o que ler e o programa termina com um erro EOFError ("fim do arquivo": a entrada acabou). É o sinal de que o jogo continuava esperando letras.


Versão 2 · Erros contados: dá para perder

Agora cada letra errada vai para uma segunda lista, erradas. Quantos erros a pessoa já fez? É o tamanho dessa lista: len(erradas). Com 6 erros, ela perde.

palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = input("Letra: ")
    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")
O que entrouPara quê
MAX_ERROS = 6O limite (cabeça, tronco, dois braços, duas pernas), em MAIÚSCULAS por ser um valor fixo.
erradas = []A segunda lista: as letras que não existem na palavra.
len(erradas)Quantos itens a lista tem = quantos erros (veja Funções Embutidas).
if len(erradas) == MAX_ERROS:O fim do jogo por derrota. Vem depois do teste da vitória e antes do input: assim, o jogo para assim que o sexto erro acontece.
else: erradas.append(chute)A letra não está na palavra: vai para as erradas, e o jogo avisa.

Uma vitória e uma derrota:

$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Erros: 0 de 6
Letra: a
Tem!
_ a _ a _ a
Erros: 0 de 6
Letra: e
Não tem.
_ a _ a _ a
Erros: 1 de 6
Letra: n
Tem!
_ a n a n a
Erros: 1 de 6
Letra: o
Não tem.
_ a n a n a
Erros: 2 de 6
Letra: b
Tem!
b a n a n a
Erros: 2 de 6
Você venceu! A palavra era banana.
$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Erros: 0 de 6
Letra: e
Não tem.
_ _ _ _ _ _
Erros: 1 de 6
Letra: i
Não tem.
_ _ _ _ _ _
Erros: 2 de 6
Letra: o
Não tem.
_ _ _ _ _ _
Erros: 3 de 6
Letra: u
Não tem.
_ _ _ _ _ _
Erros: 4 de 6
Letra: s
Não tem.
_ _ _ _ _ _
Erros: 5 de 6
Letra: t
Não tem.
_ _ _ _ _ _
Erros: 6 de 6
Você perdeu! A palavra era banana.

Quando dá errado na versão 2

Letra maiúscula conta como erro
$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Erros: 0 de 6
Letra: A
Não tem.                               « mas banana tem a!
_ _ _ _ _ _
Erros: 1 de 6
Letra: B
Não tem.
_ _ _ _ _ _
Erros: 2 de 6
Letra: N
Não tem.
_ _ _ _ _ _
Erros: 3 de 6
Letra: a
Tem!
_ a _ a _ a
Erros: 3 de 6
Letra: b
Tem!
b a _ a _ a
Erros: 3 de 6
Letra: n
Tem!
b a n a n a
Erros: 3 de 6
Você venceu! A palavra era banana.

Para o Python, "A" e "a" são caracteres diferentes, e "A" in "banana" é False. Com o Caps Lock ligado, a Ana gastou três erros com letras que existiam. Não houve traceback: o programa fez exatamente o que foi mandado. A versão 3 passa o chute para minúscula.

Só Enter: "Tem!"
$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Erros: 0 de 6
Letra:
Tem!                                   « a Ana só apertou Enter
_ _ _ _ _ _
Erros: 0 de 6
Letra: x
Não tem.
_ _ _ _ _ _
Erros: 1 de 6
Letra: ^CTraceback (most recent call last):
  File "/home/ana/projetos/forca/forca.py", line 24, in <module>
    chute = input("Letra: ")
KeyboardInterrupt

Apertar só Enter faz o input() devolver o texto vazio, "". E o texto vazio está "dentro" de qualquer texto:

$ python3
Python 3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> "" in "banana"
True
>>> exit()

Então o jogo disse "Tem!" e guardou "" na lista certas, sem revelar nada. Também "an" daria "Tem!", porque o in entre textos procura um pedaço. A versão 3 só aceita exatamente uma letra. (No fim, a Ana saiu com Ctrl+C, que interrompe o programa com KeyboardInterrupt.)

Missão (versão 2): o jogo conta os erros, mas nunca termina por derrota: depois do sexto erro, continua pedindo letras. Logo depois do if da vitória, acrescente o if que confere len(erradas) == MAX_ERROS, mostra Você perdeu! A palavra era banana. (use {palavra}) e sai com break. A entrada da missão são seis letras que não existem em banana.

palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break

    chute = input("Letra: ")
    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")
palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = input("Letra: ")
    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")

Versão 3 · Chute validado

Três defesas novas, todas antes de o chute ser contado: o chute é limpo (sem espaços, em minúscula), tem de ser uma letra só, e não pode ser uma letra já tentada. Se não passar, o programa avisa e volta para o começo do laço com continue, sem contar nada.

palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = input("Letra: ").strip().lower()
    if len(chute) != 1 or not chute.isalpha():
        print("Digite uma letra só.")
        continue
    if chute in certas or chute in erradas:
        print(f"Você já tentou a letra {chute}.")
        continue

    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")
input("Letra: ").strip().lower()
Tira os espaços das pontas e passa para minúscula: " B " vira "b".
len(chute) != 1
Recusa o texto vazio (só Enter, tamanho 0) e os chutes de duas letras ou mais ("ab").
not chute.isalpha()
isalpha() ("é alfabético?") dá True só para letras. O not inverte: recusa "1", "?", "-".
or
Basta um dos dois problemas para recusar (veja and, or e not).
chute in certas or chute in erradas
A letra já está numa das listas: a pessoa já tentou. Sem este teste, repetir uma letra errada contaria como mais um erro.
continue
Volta para o começo do while: a máscara aparece de novo e o programa pede outra letra, sem mexer nas listas.
$ python3
Python 3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> "b".isalpha()
True
>>> "1".isalpha()
False
>>> "ç".isalpha()
True
>>> exit()

O "ç" também é letra para o Python: o isalpha() conhece os acentos e as letras de outros alfabetos.

$ python3 forca.py
=== Jogo da Forca ===
_ _ _ _ _ _
Erros: 0 de 6
Letra: B
Tem!                                   « o B virou b
b _ _ _ _ _
Erros: 0 de 6
Letra: b
Você já tentou a letra b.
b _ _ _ _ _
Erros: 0 de 6
Letra:
Digite uma letra só.
b _ _ _ _ _
Erros: 0 de 6
Letra: ab
Digite uma letra só.
b _ _ _ _ _
Erros: 0 de 6
Letra: 1
Digite uma letra só.
b _ _ _ _ _
Erros: 0 de 6
Letra: a
Tem!
b a _ a _ a
Erros: 0 de 6
Letra: n
Tem!
b a n a n a
Erros: 0 de 6
Você venceu! A palavra era banana.

Nenhuma das quatro tentativas inválidas virou erro: o placar ficou em 0 de 6 o tempo todo.

Missão (versão 3): este é o código da versão 2. Faça o chute passar por .strip().lower() e acrescente, logo depois do input, os dois testes com continue: "uma letra só" e "já tentou". A entrada da missão é B, b, só Enter, ab, 1, a, n: devem aparecer os dois avisos e a vitória sem nenhum erro (Erros: 0 de 6 no fim).

palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = input("Letra: ")

    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")
palavra = "banana"
MAX_ERROS = 6
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    print(mascara)
    print(f"Erros: {len(erradas)} de {MAX_ERROS}")

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = input("Letra: ").strip().lower()
    if len(chute) != 1 or not chute.isalpha():
        print("Digite uma letra só.")
        continue
    if chute in certas or chute in erradas:
        print(f"Você já tentou a letra {chute}.")
        continue

    if chute in palavra:
        certas.append(chute)
        print("Tem!")
    else:
        erradas.append(chute)
        print("Não tem.")

Versão 4 · Funções, desenho e palavra sorteada

Hora de virar jogo de verdade: a palavra é sorteada de uma lista de frutas com random.choice(), e o boneco aparece na forca, uma parte por erro. Com tanta coisa, o programa ganha três funções: uma desenha, uma monta a máscara e uma lê a letra.

import random

PALAVRAS = ["abacaxi", "acerola", "banana", "caju", "goiaba",
            "laranja", "melancia", "morango", "pitanga"]
MAX_ERROS = 6


def desenhar(erros):
    """Desenha a forca com as partes do boneco que já apareceram."""
    cabeca = "O" if erros >= 1 else " "
    tronco = "|" if erros >= 2 else " "
    braco_esq = "/" if erros >= 3 else " "
    braco_dir = "\\" if erros >= 4 else " "
    perna_esq = "/" if erros >= 5 else " "
    perna_dir = "\\" if erros >= 6 else " "
    print("  +---+")
    print(f"  {cabeca}   |")
    print(f" {braco_esq}{tronco}{braco_dir}  |")
    print(f" {perna_esq} {perna_dir}  |")
    print("      |")
    print("=======")


def mascarar(palavra, certas):
    """Devolve a palavra com _ no lugar das letras ainda não descobertas."""
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    return mascara.strip()


def ler_letra(tentadas):
    """Pergunta até a pessoa digitar uma letra que ainda não tentou."""
    while True:
        chute = input("Letra: ").strip().lower()
        if len(chute) != 1 or not chute.isalpha():
            print("Digite uma letra só.")
        elif chute in tentadas:
            print(f"Você já tentou a letra {chute}.")
        else:
            return chute


palavra = random.choice(PALAVRAS)
certas = []
erradas = []

print("=== Jogo da Forca: frutas ===")
while True:
    desenhar(len(erradas))
    mascara = mascarar(palavra, certas)
    print(mascara)
    if erradas:
        print("Erradas:", ", ".join(erradas))

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = ler_letra(certas + erradas)
    if chute in palavra:
        certas.append(chute)
        print(f"Tem {palavra.count(chute)} letra(s) {chute}!")
    else:
        erradas.append(chute)
        print(f"Não tem {chute}.")
PeçaO que faz
PALAVRAS = [ … ]A lista de frutas, quebrada em duas linhas: dentro de colchetes, o Python deixa continuar na linha de baixo. Palavras sem acento, de propósito (veja Ideias para continuar).
random.choice(PALAVRAS)"Escolha": sorteia um item da lista. Cada fruta tem a mesma chance.
cabeca = "O" if erros >= 1 else " "Um if em uma linha: vale "O" se já houve 1 erro, senão um espaço. Cada parte do boneco tem a sua linha, e aparece a partir do seu número de erros.
"\\"A barra invertida sozinha. Dentro de um texto, \ começa um escape (como \n), então, para escrever uma barra, escrevem-se duas (veja Caracteres de Escape).
return mascara.strip()A função devolve a máscara, sem o espaço que sobrava no fim.
ler_letra(certas + erradas)O + entre duas listas cria uma lista nova com os itens das duas: todas as letras já tentadas.
if erradas:Uma lista vazia conta como False num if; com algum item, como True. Então: "se já há letras erradas, mostre-as".
", ".join(erradas)Junta os itens da lista num texto, com ", " entre eles: "e, o, r".
palavra.count(chute)Conta quantas vezes a letra aparece: "banana".count("a") é 3.
$ python3
Python 3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> "banana".count("a")
3
>>> ", ".join(["x", "z", "e"])
'x, z, e'
>>> bool([])
False
>>> bool(["x"])
True
>>> ["a", "b"] + ["x"]
['a', 'b', 'x']
>>> exit()

Uma partida perdida, para ver o boneco inteiro:

$ python3 forca.py
=== Jogo da Forca: frutas ===
  +---+
      |
      |
      |
      |
=======
_ _ _ _ _ _ _
Letra: a
Tem 3 letra(s) a!
  +---+
      |
      |
      |
      |
=======
a _ a _ a _ _
Letra: e
Não tem e.
  +---+
  O   |
      |
      |
      |
=======
a _ a _ a _ _
Erradas: e
Letra: o
Não tem o.
  +---+
  O   |
  |   |
      |
      |
=======
a _ a _ a _ _
Erradas: e, o
Letra: r
Não tem r.
  +---+
  O   |
 /|   |
      |
      |
=======
a _ a _ a _ _
Erradas: e, o, r
Letra: s
Não tem s.
  +---+
  O   |
 /|\  |
      |
      |
=======
a _ a _ a _ _
Erradas: e, o, r, s
Letra: i
Tem 1 letra(s) i!
  +---+
  O   |
 /|\  |
      |
      |
=======
a _ a _ a _ i
Erradas: e, o, r, s
Letra: n
Não tem n.
  +---+
  O   |
 /|\  |
 /    |
      |
=======
a _ a _ a _ i
Erradas: e, o, r, s, n
Letra: t
Não tem t.
  +---+
  O   |
 /|\  |
 / \  |
      |
=======
a _ a _ a _ i
Erradas: e, o, r, s, n, t
Você perdeu! A palavra era abacaxi.
Testando com a semente

Como no Jogo de Adivinhação, a missão abaixo usa random.seed(5) antes do sorteio: com essa semente, o random.choice(PALAVRAS) escolhe sempre goiaba (no terminal e no editor), e a missão pode conferir o resultado. No jogo de verdade, sem semente.

Missão (versão 4): a lista PALAVRAS existe, mas a palavra ainda é sempre "banana". Troque essa linha por um sorteio com random.choice(PALAVRAS). Com a semente 5, a palavra sorteada é goiaba, e a entrada da missão (g, o, i, a, b) a descobre.

import random

random.seed(5)  # só para a missão: o sorteio dá sempre goiaba
PALAVRAS = ["abacaxi", "acerola", "banana", "caju", "goiaba",
            "laranja", "melancia", "morango", "pitanga"]
MAX_ERROS = 6


def desenhar(erros):
    """Desenha a forca com as partes do boneco que já apareceram."""
    cabeca = "O" if erros >= 1 else " "
    tronco = "|" if erros >= 2 else " "
    braco_esq = "/" if erros >= 3 else " "
    braco_dir = "\\" if erros >= 4 else " "
    perna_esq = "/" if erros >= 5 else " "
    perna_dir = "\\" if erros >= 6 else " "
    print("  +---+")
    print(f"  {cabeca}   |")
    print(f" {braco_esq}{tronco}{braco_dir}  |")
    print(f" {perna_esq} {perna_dir}  |")
    print("      |")
    print("=======")


def mascarar(palavra, certas):
    """Devolve a palavra com _ no lugar das letras ainda não descobertas."""
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    return mascara.strip()


def ler_letra(tentadas):
    """Pergunta até a pessoa digitar uma letra que ainda não tentou."""
    while True:
        chute = input("Letra: ").strip().lower()
        if len(chute) != 1 or not chute.isalpha():
            print("Digite uma letra só.")
        elif chute in tentadas:
            print(f"Você já tentou a letra {chute}.")
        else:
            return chute


palavra = "banana"
certas = []
erradas = []

print("=== Jogo da Forca: frutas ===")
while True:
    desenhar(len(erradas))
    mascara = mascarar(palavra, certas)
    print(mascara)
    if erradas:
        print("Erradas:", ", ".join(erradas))

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = ler_letra(certas + erradas)
    if chute in palavra:
        certas.append(chute)
        print(f"Tem {palavra.count(chute)} letra(s) {chute}!")
    else:
        erradas.append(chute)
        print(f"Não tem {chute}.")
import random

random.seed(5)  # só para a missão: o sorteio dá sempre goiaba
PALAVRAS = ["abacaxi", "acerola", "banana", "caju", "goiaba",
            "laranja", "melancia", "morango", "pitanga"]
MAX_ERROS = 6


def desenhar(erros):
    """Desenha a forca com as partes do boneco que já apareceram."""
    cabeca = "O" if erros >= 1 else " "
    tronco = "|" if erros >= 2 else " "
    braco_esq = "/" if erros >= 3 else " "
    braco_dir = "\\" if erros >= 4 else " "
    perna_esq = "/" if erros >= 5 else " "
    perna_dir = "\\" if erros >= 6 else " "
    print("  +---+")
    print(f"  {cabeca}   |")
    print(f" {braco_esq}{tronco}{braco_dir}  |")
    print(f" {perna_esq} {perna_dir}  |")
    print("      |")
    print("=======")


def mascarar(palavra, certas):
    """Devolve a palavra com _ no lugar das letras ainda não descobertas."""
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    return mascara.strip()


def ler_letra(tentadas):
    """Pergunta até a pessoa digitar uma letra que ainda não tentou."""
    while True:
        chute = input("Letra: ").strip().lower()
        if len(chute) != 1 or not chute.isalpha():
            print("Digite uma letra só.")
        elif chute in tentadas:
            print(f"Você já tentou a letra {chute}.")
        else:
            return chute


palavra = random.choice(PALAVRAS)
certas = []
erradas = []

print("=== Jogo da Forca: frutas ===")
while True:
    desenhar(len(erradas))
    mascara = mascarar(palavra, certas)
    print(mascara)
    if erradas:
        print("Erradas:", ", ".join(erradas))

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = ler_letra(certas + erradas)
    if chute in palavra:
        certas.append(chute)
        print(f"Tem {palavra.count(chute)} letra(s) {chute}!")
    else:
        erradas.append(chute)
        print(f"Não tem {chute}.")

Versão 5 · As palavras num arquivo

Para trocar as palavras, hoje é preciso mexer no código. Muito melhor guardá-las num arquivo de texto, palavras.txt, uma por linha, na mesma pasta do jogo. Qualquer pessoa edita o arquivo (no editor de textos) e o jogo muda, sem tocar no programa. A Ana fez um com animais:

$ cat palavras.txt
capivara
jabuti
tucano
tatu
papagaio
macaco

No programa, a lista PALAVRAS some, entra o nome do arquivo e uma função nova, carregar_palavras. As outras três funções ficam iguais:

import random

ARQUIVO = "palavras.txt"
MAX_ERROS = 6


def carregar_palavras(nome_arquivo):
    """Lê o arquivo (uma palavra por linha) e devolve a lista de palavras."""
    palavras = []
    with open(nome_arquivo, encoding="utf-8") as arquivo:
        for linha in arquivo:
            linha = linha.strip().lower()
            if linha:
                palavras.append(linha)
    return palavras


# desenhar, mascarar e ler_letra: iguais à versão 4


palavras = carregar_palavras(ARQUIVO)
palavra = random.choice(palavras)
certas = []
erradas = []

print("=== Jogo da Forca ===")
# … o laço principal, igual à versão 4 …
with open(nome_arquivo, encoding="utf-8") as arquivo:
Abre o arquivo para leitura e o chama de arquivo dentro do bloco. O with fecha o arquivo sozinho no fim do bloco, mesmo se der erro; o encoding="utf-8" garante que acentos sejam lidos certo. Veja Abrindo Arquivos.
for linha in arquivo:
Um for sobre um arquivo aberto passa por cada linha. Cada linha vem com o \n do fim (veja Lendo Arquivos).
linha = linha.strip().lower()
Tira o \n e espaços, e passa para minúscula: "Tatu \n" vira "tatu".
if linha:
Um texto vazio conta como False: pula as linhas em branco do arquivo (uma linha vazia no fim é comum).
return palavras
Devolve a lista pronta, no mesmo formato da antiga PALAVRAS. Por isso o resto do programa nem percebe a mudança.
$ python3 forca.py
=== Jogo da Forca ===
  +---+
      |
      |
      |
      |
=======
_ _ _ _ _ _
Letra: e
Não tem e.
  +---+
  O   |
      |
      |
      |
=======
_ _ _ _ _ _
Erradas: e
Letra: a
Tem 2 letra(s) a!
  +---+
  O   |
      |
      |
      |
=======
_ a _ a _ _
Erradas: e
Letra: c
Tem 2 letra(s) c!
  +---+
  O   |
      |
      |
      |
=======
_ a c a c _
Erradas: e
Letra: m
Tem 1 letra(s) m!
  +---+
  O   |
      |
      |
      |
=======
m a c a c _
Erradas: e
Letra: o
Tem 1 letra(s) o!
  +---+
  O   |
      |
      |
      |
=======
m a c a c o
Erradas: e
Você venceu! A palavra era macaco.

Quando dá errado na versão 5

O arquivo não está na pasta: FileNotFoundError
$ mv palavras.txt guardado.txt
$ python3 forca.py
Traceback (most recent call last):
  File "/home/ana/projetos/forca/forca.py", line 57, in <module>
    palavras = carregar_palavras(ARQUIVO)
  File "/home/ana/projetos/forca/forca.py", line 10, in carregar_palavras
    with open(nome_arquivo, encoding="utf-8") as arquivo:
         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'palavras.txt'

(As linhas 57 e 10 são as do arquivo completo, com as quatro funções.) Este traceback tem dois trechos File …, e é preciso lê-los de cima para baixo como um caminho: a linha 57 (no programa principal, <module>) chamou carregar_palavras; dentro dela, na linha 10, o open falhou. A última linha diz o motivo: FileNotFoundError: [Errno 2] No such file or directory: 'palavras.txt' = "arquivo não encontrado: não existe esse arquivo ou pasta". O Python procura o arquivo na pasta de onde o programa foi rodado. Como consertar: rode o jogo de dentro da pasta onde está o palavras.txt (ou devolva o arquivo para lá).

O arquivo existe, mas está vazio: IndexError
$ touch palavras.txt
$ python3 forca.py
Traceback (most recent call last):
  File "/home/ana/projetos/forca/forca.py", line 58, in <module>
    palavra = random.choice(palavras)
  File "/usr/lib/python3.14/random.py", line 353, in choice
    raise IndexError('Cannot choose from an empty sequence')
IndexError: Cannot choose from an empty sequence

O touch criou um palavras.txt vazio. A leitura deu certo (uma lista vazia), mas sortear de uma lista vazia é impossível: IndexError: Cannot choose from an empty sequence = "não dá para escolher de uma sequência vazia". Repare no segundo File: o erro aconteceu dentro do módulo random (no arquivo random.py do próprio Python). Isso não quer dizer que o Python está com defeito: siga o traceback até a última linha que é sua (a 58), que é onde está a causa. Como consertar: confira a lista antes de sortear: if not palavras: print("O arquivo palavras.txt está vazio."); sys.exit(1) (o sys.exit está na aula Os Módulos os e sys).

Na caixa abaixo, o palavras.txt aparece numa aba ao lado do programa: ele fica na mesma pasta, e você pode editá-lo.

Missão (versão 5): o jogo ainda usa a lista de frutas. Escreva a função carregar_palavras(nome_arquivo) como na versão 5 (com with open(…)) e troque o sorteio por random.choice(carregar_palavras(ARQUIVO)). Com a semente 7 e o arquivo da aba palavras.txt, a palavra é tucano, e a entrada da missão a descobre.

import random

random.seed(7)  # só para a missão: com o palavras.txt da aba, dá tucano
ARQUIVO = "palavras.txt"
PALAVRAS = ["abacaxi", "acerola", "banana", "caju", "goiaba",
            "laranja", "melancia", "morango", "pitanga"]
MAX_ERROS = 6


def desenhar(erros):
    """Desenha a forca com as partes do boneco que já apareceram."""
    cabeca = "O" if erros >= 1 else " "
    tronco = "|" if erros >= 2 else " "
    braco_esq = "/" if erros >= 3 else " "
    braco_dir = "\\" if erros >= 4 else " "
    perna_esq = "/" if erros >= 5 else " "
    perna_dir = "\\" if erros >= 6 else " "
    print("  +---+")
    print(f"  {cabeca}   |")
    print(f" {braco_esq}{tronco}{braco_dir}  |")
    print(f" {perna_esq} {perna_dir}  |")
    print("      |")
    print("=======")


def mascarar(palavra, certas):
    """Devolve a palavra com _ no lugar das letras ainda não descobertas."""
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    return mascara.strip()


def ler_letra(tentadas):
    """Pergunta até a pessoa digitar uma letra que ainda não tentou."""
    while True:
        chute = input("Letra: ").strip().lower()
        if len(chute) != 1 or not chute.isalpha():
            print("Digite uma letra só.")
        elif chute in tentadas:
            print(f"Você já tentou a letra {chute}.")
        else:
            return chute


palavra = random.choice(PALAVRAS)
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    desenhar(len(erradas))
    mascara = mascarar(palavra, certas)
    print(mascara)
    if erradas:
        print("Erradas:", ", ".join(erradas))

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = ler_letra(certas + erradas)
    if chute in palavra:
        certas.append(chute)
        print(f"Tem {palavra.count(chute)} letra(s) {chute}!")
    else:
        erradas.append(chute)
        print(f"Não tem {chute}.")
capivara
jabuti
tucano
tatu
papagaio
macaco
import random

random.seed(7)  # só para a missão: com o palavras.txt da aba, dá tucano
ARQUIVO = "palavras.txt"
MAX_ERROS = 6


def carregar_palavras(nome_arquivo):
    """Lê o arquivo (uma palavra por linha) e devolve a lista de palavras."""
    palavras = []
    with open(nome_arquivo, encoding="utf-8") as arquivo:
        for linha in arquivo:
            linha = linha.strip().lower()
            if linha:
                palavras.append(linha)
    return palavras


def desenhar(erros):
    """Desenha a forca com as partes do boneco que já apareceram."""
    cabeca = "O" if erros >= 1 else " "
    tronco = "|" if erros >= 2 else " "
    braco_esq = "/" if erros >= 3 else " "
    braco_dir = "\\" if erros >= 4 else " "
    perna_esq = "/" if erros >= 5 else " "
    perna_dir = "\\" if erros >= 6 else " "
    print("  +---+")
    print(f"  {cabeca}   |")
    print(f" {braco_esq}{tronco}{braco_dir}  |")
    print(f" {perna_esq} {perna_dir}  |")
    print("      |")
    print("=======")


def mascarar(palavra, certas):
    """Devolve a palavra com _ no lugar das letras ainda não descobertas."""
    mascara = ""
    for letra in palavra:
        if letra in certas:
            mascara += letra + " "
        else:
            mascara += "_ "
    return mascara.strip()


def ler_letra(tentadas):
    """Pergunta até a pessoa digitar uma letra que ainda não tentou."""
    while True:
        chute = input("Letra: ").strip().lower()
        if len(chute) != 1 or not chute.isalpha():
            print("Digite uma letra só.")
        elif chute in tentadas:
            print(f"Você já tentou a letra {chute}.")
        else:
            return chute


palavra = random.choice(carregar_palavras(ARQUIVO))
certas = []
erradas = []

print("=== Jogo da Forca ===")
while True:
    desenhar(len(erradas))
    mascara = mascarar(palavra, certas)
    print(mascara)
    if erradas:
        print("Erradas:", ", ".join(erradas))

    if "_" not in mascara:
        print(f"Você venceu! A palavra era {palavra}.")
        break
    if len(erradas) == MAX_ERROS:
        print(f"Você perdeu! A palavra era {palavra}.")
        break

    chute = ler_letra(certas + erradas)
    if chute in palavra:
        certas.append(chute)
        print(f"Tem {palavra.count(chute)} letra(s) {chute}!")
    else:
        erradas.append(chute)
        print(f"Não tem {chute}.")

Formas diferentes de montar a máscara

O for com += das versões 1 a 4 é o jeito mais fácil de entender. Programadores experientes costumam escrever a mesma coisa numa linha só, com join e o if em uma linha dentro de uma list comprehension:

$ python3
Python 3.14.4 (main, Aug 20 2026, 10:41:58) [GCC 15.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> palavra = "banana"
>>> certas = ["a", "n"]
>>> " ".join([letra if letra in certas else "_" for letra in palavra])
'_ a n a n a'
>>> " ".join(letra if letra in certas else "_" for letra in palavra)
'_ a n a n a'
>>> exit()

Lendo de dentro para fora: "para cada letra da palavra, use a letra se ela estiver em certas, senão "_""; depois, o " ".join junta tudo com espaços (e já sem o espaço sobrando no fim). A segunda forma, sem os colchetes, dá o mesmo resultado: quando a "comprehension" é o único argumento da função, os colchetes podem sair (ela vira uma expressão geradora, assunto da aula Geradores).

FormaQuando usar
for com mascara += …Quando você está aprendendo, ou quando cada passo precisa de mais de uma linha. É a forma mais clara.
" ".join([… for letra in palavra])Quando a regra cabe numa linha e você já lê comprehensions com facilidade.
Listas (certas, erradas)Guardam a ordem das tentativas, que o jogo mostra em "Erradas: e, o, r".
Um conjunto (set())Se a ordem não importasse: o in num conjunto é mais rápido e ele nunca repete itens. Numa forca, com poucas letras, tanto faz.

Ideias para continuar

  • Palavras com acento (maçã, pêssego): do jeito que está, a pessoa teria de digitar ã e ê. Uma saída é comparar sem acentos, com o módulo unicodedata da biblioteca do Python.
  • Chutar a palavra inteira: se o chute tiver mais de uma letra, compare com a palavra; acertou, venceu; errou, perde duas vidas.
  • Categorias: um arquivo por tema (frutas.txt, animais.txt), e o jogo pergunta qual tema a pessoa quer.
  • Jogar de novo, com placar de vitórias e derrotas, como no Jogo de Adivinhação.

Referência rápida do projeto

EscritaPara que serviu
for letra in palavra:Passar por cada letra do texto
mascara += letra + " "Montar a máscara aos poucos (textos não mudam por dentro: cria-se um novo)
letra in certas / "_" not in mascaraPerguntar se está (ou não está) dentro
certas.append(chute)Guardar a letra na lista
len(erradas)Contar os erros
.strip().lower(), len(chute) != 1, .isalpha()Aceitar só uma letra, em minúscula
continueRecusar o chute sem contar nada
"O" if erros >= 1 else " "Escolher entre dois valores numa linha
"\\"Uma barra invertida dentro de um texto
random.choice(lista)Sortear um item da lista
", ".join(erradas), palavra.count(letra)Juntar a lista num texto; contar uma letra
with open(nome, encoding="utf-8") as arquivo:Ler as palavras do arquivo, linha por linha
SintomaCausa provável
TypeError: 'str' object does not support item assignmentTentou trocar uma letra de um texto (palavra[0] = "B"): monte um texto novo
Letra maiúscula conta como erroFaltou o .lower()
Só Enter (ou duas letras) dá "Tem!""" in "banana" é True: exija len(chute) == 1
Repetir uma letra errada gasta outra vidaFaltou o teste "já tentou"
O jogo nunca termina com 6 errosFaltou o if len(erradas) == MAX_ERROS: … break
EOFError numa missãoA entrada da missão acabou e o programa ainda pedia letras
FileNotFoundError: … 'palavras.txt'O arquivo não está na pasta de onde o programa foi rodado
IndexError: Cannot choose from an empty sequencerandom.choice com uma lista vazia (arquivo vazio)

Exercícios

1. Guardar a letra

Complete o método que acrescenta o chute no fim da lista:

certas.(chute)

2. Ainda tem tracinho?

Complete para testar se não sobrou nenhum _ na máscara:

if "_" mascara:

3. Sortear da lista

Complete a função do módulo random que sorteia um item da lista:

palavra = random.(PALAVRAS)

4. Na versão 2, a Ana apertou só Enter e o jogo respondeu "Tem!". Por quê?

5. Por que a versão 1 monta a máscara de novo a cada volta, em vez de trocar o _ pela letra?

6. Para escrever uma barra invertida \ num texto do Python, como se escreve?


Resumo

  • Um for sobre um texto passa letra por letra; com +=, dá para montar outro texto aos poucos.
  • Textos são imutáveis: para "mudar" um, crie um novo. Listas mudam: append acrescenta.
  • O in pergunta "está dentro?"; entre textos, procura um pedaço (e o texto vazio está em todos).
  • len(erradas) conta os erros; o teste de derrota vem antes de pedir a próxima letra.
  • .strip().lower(), len(chute) == 1 e .isalpha() garantem uma letra só; continue recusa sem contar.
  • random.choice sorteia um item de uma lista; random.seed deixa o sorteio previsível para testar.
  • Guardar os dados num arquivo (palavras.txt) separa o jogo do conteúdo. Leia com with open(…, encoding="utf-8").