En aquest post construirem una versió del joc del penjat en Python amb un objectiu clar: entendre com es gestiona l’estat del joc (progrés, intents, lletres usades) i com dissenyem una experiència d’usuari mínimament robusta quan treballem amb entrades reals.
Si el joc d’“Endevina el nombre” ens servia per practicar bucles i condicionals, aquí fem un pas endavant: ara el repte és mantenir coherència entre diverses variables que evolucionen al llarg de la partida.
Objectius d’aprenentatge
- Modelar l’estat d’una partida (paraula, progrés, intents).
- Evitar errors típics: entrades buides, repeticions, caràcters invàlids.
- Fer servir un set per gestionar lletres ja utilitzades.
- Millorar la “UX” amb feedback clar i missatges coherents.
1) Què vol dir “gestionar estat” en un joc
En un joc iteratiu, l’estat és el conjunt de valors que descriuen la partida en cada moment. En el penjat, com a mínim, necessitem:
- paraula secreta: la solució.
- progrés: quines lletres hem encertat (p. ex.
_ _ a _ _). - intents restants: quants errors podem cometre.
- lletres usades: per evitar repeticions i donar feedback.
El punt crític és que aquests elements han d’estar sincronitzats: cada jugada modifica (o no) l’estat i el programa ha de reflectir-ho correctament.
2) Decisió de disseny: com representem el progrés
Una estratègia habitual és representar el progrés amb una llista de caràcters (o guions baixos) perquè sigui fàcil actualitzar posicions concretes. Per exemple, si la paraula és "python", podem començar així:
paraula = "python"
progres = ["_"] * len(paraula) # ['_', '_', '_', '_', '_', '_']Aquesta representació ens permet substituir guions baixos per lletres quan encertem. Quan vulguem mostrar el progrés, simplement unim la llista amb espais:
print(" ".join(progres))3) Evitar repeticions: set per a lletres usades
En termes de qualitat d’interacció, és important detectar si l’usuari repeteix una lletra. Un set és ideal perquè:
- emmagatzema valors únics,
- la consulta
lletra in lletres_usadesés molt eficient, - ens facilita donar feedback immediat.
lletres_usades = set()4) Validació d’entrada: errors típics i criteris mínims
Quan treballem amb input(), hem d’assumir que l’entrada pot ser incorrecta. En un disseny mínimament robust, validarem que:
- no sigui buida,
- tingui longitud 1 (una sola lletra),
- sigui una lletra (no números ni símbols),
- no s’hagi provat abans.
Aquest tipus de validacions no són “decoració”: formen part del que diferencia un exercici de consola d’un programa que es pot utilitzar de manera consistent.
5) Implementació completa (versió consola)
A continuació presentem una versió completa i comentada. El focus està en la lògica i en la gestió de l’estat (no en la selecció de paraules ni en la interfície web, que abordarem quan portem el joc a Streamlit).
import random
PARAULES = [
"python",
"funcio",
"llista",
"diccionari",
"streamlit",
"algorisme",
]
def triar_paraula(paraules: list[str]) -> str:
"""Tria aleatòriament una paraula d'una llista."""
return random.choice(paraules).lower()
def mostrar_estat(progres: list[str], intents: int, lletres_usades: set[str]) -> None:
"""Mostra l'estat actual de la partida de manera llegible."""
print("\nProgrés:", " ".join(progres))
print("Intents restants:", intents)
if lletres_usades:
print("Lletres usades:", ", ".join(sorted(lletres_usades)))
def demanar_lletra(lletres_usades: set[str]) -> str:
"""Demana una lletra i aplica validacions mínimes."""
while True:
entrada = input("Introdueix una lletra: ").strip().lower()
if not entrada:
print("Entrada buida. Escriu una lletra.")
continue
if len(entrada) != 1:
print("Introdueix només una lletra.")
continue
if not entrada.isalpha():
print("Introdueix una lletra (a-z).")
continue
if entrada in lletres_usades:
print("Aquesta lletra ja l'hem provat. Tria'n una altra.")
continue
return entrada
def actualitzar_progres(paraula: str, progres: list[str], lletra: str) -> bool:
"""
Actualitza el progrés si la lletra és dins la paraula.
Retorna True si hi ha hagut encert, False si no.
"""
encert = False
for i, ch in enumerate(paraula):
if ch == lletra:
progres[i] = lletra
encert = True
return encert
def jugar_penjat() -> None:
"""Executa una partida del penjat (mode consola)."""
paraula = triar_paraula(PARAULES)
progres = ["_"] * len(paraula)
lletres_usades: set[str] = set()
intents = 6
print("=== Joc del Penjat (Python) ===")
print("Objectiu: endevinar la paraula abans de quedar-nos sense intents.")
while intents > 0 and "_" in progres:
mostrar_estat(progres, intents, lletres_usades)
lletra = demanar_lletra(lletres_usades)
lletres_usades.add(lletra)
if actualitzar_progres(paraula, progres, lletra):
print("✅ Encert!")
else:
intents -= 1
print("❌ No hi és. Perdem un intent.")
# Final de partida
mostrar_estat(progres, intents, lletres_usades)
if "_" not in progres:
print(f"\n🎉 Hem guanyat! La paraula era: {paraula}")
else:
print(f"\n💀 Hem perdut. La paraula era: {paraula}")
if __name__ == "__main__":
jugar_penjat()
6) Què hem practicat
- Control de flux: bucle
whileamb condicions de finalització clares. - Estat del programa: coherència entre
progres,intentsilletres_usades. - Estructures de dades: llista per al progrés i
setper a evitar repeticions. - Validació d’entrada: gestió d’errors habituals en programes interactius.
- UX mínima: feedback explícit per a guiar la partida.
Aquest joc forma part de DeGalaLab Arcade, una col·lecció de mini jocs fets amb Python i portats a web amb Streamlit.
⏭️ Següent post de la sèrie: Construir un quiz en Python (preguntes, puntuació i feedback).