IOI-RUN / jogos.py
Roudrigus's picture
Upload 82 files
0f0ef8d verified
import streamlit as st
import random
import unicodedata
from datetime import date, timedelta # ✅ para controle diário de tentativas falhas
def reset_keys(keys):
for k in keys:
st.session_state.pop(k, None)
def strip_accents(s: str) -> str: # ✅ corrigido '->' (sem entidades HTML)
if not isinstance(s, str):
return s
return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn")
# ✅ Helper de estado: garantia defensiva (executa sem impactar sua lógica)
def _init_estado_jogos():
st.session_state.setdefault("pontuacao", 0)
st.session_state.setdefault("rodadas", 0)
st.session_state.setdefault("ultimo_resultado", None)
# ⬇️ NOVO (Dado): memória de resultados já vistos por configuração de lados
st.session_state.setdefault("dado_resultados_vistos", [])
st.session_state.setdefault("dado_lados_atual", None)
# ⬇️ NOVO (Forca): palavras já concluídas por categoria (evita pontuar repetidas)
# Estrutura: { "FPSO": ["INSPECAO VISUAL", ...], "Estoque e Armazenagens": [...], ... }
st.session_state.setdefault("forca_palavras_vistas", {})
# ⬇️ NOVO (Forca): letras já clicadas (apagadas/desativadas no teclado)
st.session_state.setdefault("forca_letras_usadas", set())
# ⬇️ NOVO (Forca): controle diário por rodada perdida (tentativa de jogo falha)
st.session_state.setdefault("forca_falhas_hoje", 0)
st.session_state.setdefault("forca_block_expires_on", None)
st.session_state.setdefault("forca_data_ref", str(date.today()))
st.session_state.setdefault("forca_round_contabilizado", False) # marca se a perda já foi contabilizada nesta rodada
# ⬇️ NOVO (Tesouro): perguntas já concluídas por categoria (evita pontuar repetidas)
# Estrutura: { "FPSO": ["Identifique o item correto...", ...], ... }
st.session_state.setdefault("tesouro_perguntas_vistas", {})
# Mantém inicialização que você tinha (compatível com o helper)
if "pontuacao" not in st.session_state:
st.session_state.pontuacao = 0
FORCA_BANK = [
{
"categoria": "FPSO",
"pergunta": "Qual procedimento prioriza a integridade de carga no deck?",
"resposta": "INSPEÇÃO VISUAL",
},
{
"categoria": "Estoque e Armazenagens",
"pergunta": "Qual política prioriza itens com menor validade remanescente?",
"resposta": "FEFO",
},
{
"categoria": "Óleo e Gás",
"pergunta": "Qual documento acompanha movimentação de resíduos perigosos?",
"resposta": "MANIFESTO DE RESÍDUOS",
},
]
TESOURO_BANK = [
{
"categoria": "FPSO",
"pergunta": "Identifique o item correto para amarração segura no deck.",
"pistas": [
{"texto": "Requer inspeção visual antes do uso.", "correto": True},
{"texto": "É descartável após uma operação.", "correto": False},
{"texto": "Possui etiqueta de carga segura com WLL.", "correto": True},
{"texto": "Não pode ser usado em ambiente offshore.", "correto": False},
],
},
{
"categoria": "Estoque e Armazenagens",
"pergunta": "Determine a política correta para expedição de produtos com validade.",
"pistas": [
{"texto": "Prioriza vencimento mais próximo.", "correto": True},
{"texto": "Ignora datas de validade.", "correto": False},
],
},
]
def jogo_dado():
_init_estado_jogos() # ✅ garante chaves antes do uso
st.subheader("🎲 Jogo do Dado (Curiosidades)")
curiosidades = [
"FIFO e FEFO impactam diretamente a acuracidade de estoque.",
"Cross-docking reduz tempos e evita armazenagem desnecessária.",
"WMS integra endereçamento, picking e inventários cíclicos.",
]
lados = st.slider("Escolha o número de lados do dado:", 6, 20, 8)
# ⬇️ NOVO: ao alterar o número de lados, resetamos os resultados vistos para esta configuração
if st.session_state.dado_lados_atual != lados:
st.session_state.dado_lados_atual = lados
st.session_state.dado_resultados_vistos = [] # limpa histórico para a nova configuração de lados
if st.button("Girar dado"):
resultado = random.randint(1, lados)
st.success(f"🎲 Você rolou: {resultado}")
st.info(curiosidades[resultado % len(curiosidades)])
# ⬇️ NOVO: só pontua se o resultado ainda NÃO tiver saído nesta configuração de lados
if resultado in st.session_state.dado_resultados_vistos:
st.warning("🔁 Resultado repetido — nenhum ponto adicionado ao ranking.")
else:
st.session_state.dado_resultados_vistos.append(resultado)
st.session_state.pontuacao += 5
st.balloons()
# Informações úteis
if st.session_state.dado_resultados_vistos:
vistos_fmt = ", ".join(str(v) for v in sorted(st.session_state.dado_resultados_vistos))
st.caption(f"🔎 Valores únicos já obtidos com {lados} lados: {vistos_fmt}")
st.write(f"Pontuação atual: {st.session_state.pontuacao}")
def jogo_forca_treinamento():
_init_estado_jogos() # ✅ garante chaves antes do uso
st.subheader("🔤 Jogo da Forca (Treinamento)")
# 🔧 Reset diário dos contadores/bloqueio por rodada perdida
if st.session_state.get("forca_data_ref") != str(date.today()):
st.session_state.forca_data_ref = str(date.today())
st.session_state.forca_falhas_hoje = 0
st.session_state.forca_block_expires_on = None
# 🔒 Se bloqueado até amanhã, impedir jogar
block_until = st.session_state.get("forca_block_expires_on")
if block_until and date.today() < block_until:
st.error(f"⏳ Você atingiu 3 tentativas falhas hoje. Tente novamente em {block_until.strftime('%d/%m/%Y')}.")
st.write(f"Pontuação atual: {st.session_state.pontuacao}")
return
categorias = sorted(set(q["categoria"] for q in FORCA_BANK))
cat_sel = st.selectbox("Categoria:", ["Todas"] + categorias, index=0)
banco_filtrado = [q for q in FORCA_BANK if cat_sel == "Todas" or q["categoria"] == cat_sel]
total = len(banco_filtrado)
# Reset ao trocar categoria ou na primeira carga
if "forca_idx" not in st.session_state or st.session_state.get("forca_cat") != cat_sel:
st.session_state.forca_idx = 0
st.session_state.forca_cat = cat_sel
reset_keys(["forca_palavra", "letras_descobertas", "tentativas", "letras_erradas", "forca_input", "forca_letras_usadas", "forca_round_contabilizado"])
if not banco_filtrado:
st.warning("Não há perguntas para esta categoria.")
return
nivel = st.session_state.forca_idx % total
st.write(f"Progresso: Pergunta {nivel + 1}/{total}")
atual = banco_filtrado[nivel]
pergunta, resposta = atual["pergunta"], atual["resposta"].upper()
categoria_atual = atual["categoria"]
# Inicializa rodada da forca
if "forca_palavra" not in st.session_state or st.session_state.get("forca_palavra") != resposta:
st.session_state.forca_palavra = resposta
st.session_state.letras_descobertas = ["_" if c.isalpha() else c for c in resposta]
st.session_state.tentativas = 6
st.session_state.letras_erradas = []
st.session_state.forca_letras_usadas = set() # ⬅️ inicia teclado limpo
st.session_state.forca_round_contabilizado = False # ⬅️ nova rodada ainda não contabilizada como falha
st.write(f"Pergunta: {pergunta}")
st.write("Palavra:", " ".join(st.session_state.letras_descobertas))
st.write(f"Tentativas restantes: {st.session_state.tentativas}")
# ==============================
# 🔤 NOVO: TECLADO DE ALFABETO (compacto)
# ==============================
# CSS para deixar botões mais juntos (afeta globalmente — usar com parcimônia)
st.markdown(
"""
<style>
div.stButton > button {
padding: 6px 8px !important;
margin: 2px !important;
font-size: 14px !important;
}
</style>
""",
unsafe_allow_html=True
)
# A–Z; comparação sem acentos (ex.: 'O' casa 'Ó', 'C' casa 'Ç')
alfabeto = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
# Duas linhas com 13 letras cada (mais compacto)
linhas = [
alfabeto[0:13],
alfabeto[13:26],
]
for idx_linha, linha in enumerate(linhas):
cols = st.columns(len(linha))
for idx_letra, letra in enumerate(linha):
usada = letra in st.session_state.forca_letras_usadas
if cols[idx_letra].button(letra, key=f"forca_btn_{nivel}_{letra}", disabled=usada):
# Marca como usada (apagada)
st.session_state.forca_letras_usadas.add(letra)
# Verifica se a letra existe na palavra (comparando sem acentos)
hit = False
for i, ch in enumerate(st.session_state.forca_palavra):
if strip_accents(ch) == strip_accents(letra):
st.session_state.letras_descobertas[i] = ch
hit = True
if not hit:
# Letra errada: registra e decrementa tentativa
if letra not in st.session_state.letras_erradas:
st.session_state.letras_erradas.append(letra)
st.session_state.tentativas -= 1
# Rerender após o clique para atualizar visualmente a palavra e tentativas
st.rerun()
# Feedback de letras erradas (visível)
if st.session_state.letras_erradas:
st.caption(f"❌ Letras erradas: {', '.join(st.session_state.letras_erradas)}")
venceu = "_" not in st.session_state.letras_descobertas
perdeu = st.session_state.tentativas <= 0 # ✅ corrigido '<=' (sem entidades HTML)
# ⬇️ NOVO: prepara lista de palavras já vistas por categoria
# normaliza removendo acentos para evitar duplicidades por grafia
normalizada = strip_accents(resposta)
if venceu:
# Checa repetição por categoria e evita somar pontos
vistas = st.session_state.forca_palavras_vistas.get(categoria_atual, [])
if normalizada in vistas:
st.warning("🔁 Palavra repetida nesta categoria — nenhum ponto adicionado ao ranking.")
else:
vistas.append(normalizada)
st.session_state.forca_palavras_vistas[categoria_atual] = vistas
st.success("🎉 Você venceu! Palavra completa.")
st.session_state.pontuacao += 10
st.balloons()
# Mostrar resumo de palavras únicas concluídas nesta categoria
if st.session_state.forca_palavras_vistas.get(categoria_atual):
lista_fmt = ", ".join(st.session_state.forca_palavras_vistas[categoria_atual])
st.caption(f"🔎 Palavras únicas concluídas ({categoria_atual}): {lista_fmt}")
if nivel + 1 < total: # ✅ corrigido '<' (sem entidades HTML)
if st.button("➡️ Próxima pergunta"):
st.session_state.forca_idx += 1
reset_keys(["forca_palavra", "letras_descobertas", "tentativas", "letras_erradas", "forca_input", "forca_letras_usadas", "forca_round_contabilizado"])
st.rerun()
else:
st.success("🏆 Você finalizou todas as perguntas do jogo da Forca!")
elif perdeu:
# ✅ NOVO: contabiliza tentativa falha (rodada perdida) uma única vez
if not st.session_state.forca_round_contabilizado:
st.session_state.forca_falhas_hoje += 1
st.session_state.forca_round_contabilizado = True
# 🔒 Bloqueia após 3 tentativas falhas no dia
if st.session_state.forca_falhas_hoje >= 3:
st.session_state.forca_block_expires_on = date.today() + timedelta(days=1)
st.error(
f"⏳ Limite diário atingido (3 tentativas falhas). "
f"Tente novamente em {st.session_state.forca_block_expires_on.strftime('%d/%m/%Y')}."
)
st.error(f"💀 Você perdeu! A palavra era: {st.session_state.forca_palavra}")
st.write(f"Pontuação atual: {st.session_state.pontuacao}")
def jogo_tesouro_niveis():
_init_estado_jogos() # ✅ garante chaves antes do uso
st.subheader("🗺️ Caça ao Tesouro (Níveis)")
categorias = sorted(set(q["categoria"] for q in TESOURO_BANK))
cat_sel = st.selectbox("Categoria:", ["Todas"] + categorias, index=0)
banco_filtrado = [q for q in TESOURO_BANK if cat_sel == "Todas" or q["categoria"] == cat_sel]
total = min(100, len(banco_filtrado))
# Reset ao trocar categoria ou primeira carga
if "tes_idx" not in st.session_state or st.session_state.get("tes_cat") != cat_sel:
st.session_state.tes_idx = 0
st.session_state.tes_cat = cat_sel
reset_keys(["tes_respostas", "tes_concluido"])
if total == 0:
st.warning("Não há perguntas para esta categoria.")
return
nivel = st.session_state.tes_idx % total
st.write(f"Progresso: Nível {nivel + 1}/{total}")
atual = banco_filtrado[nivel]
pergunta_atual = atual["pergunta"]
categoria_atual = atual["categoria"]
st.write(f"Pergunta: {pergunta_atual}")
if "tes_respostas" not in st.session_state:
st.session_state.tes_respostas = {i: None for i in range(len(atual["pistas"]))}
st.session_state.tes_concluido = False
for i, pista in enumerate(atual["pistas"]):
cols = st.columns([6, 1, 1])
cols[0].write(f"🧩 {pista['texto']}")
if cols[1].button("Sim", key=f"tes_sim_{nivel}_{i}"):
st.session_state.tes_respostas[i] = True
if cols[2].button("Não", key=f"tes_nao_{nivel}_{i}"):
st.session_state.tes_respostas[i] = False
r = st.session_state.tes_respostas[i]
if r is not None:
if r == pista["correto"]:
cols[0].success("✅ Correto")
else:
cols[0].error("❌ Incorreto")
tudo_respondido = all(v is not None for v in st.session_state.tes_respostas.values())
tudo_correto = tudo_respondido and all(
st.session_state.tes_respostas[i] == p["correto"] for i, p in enumerate(atual["pistas"])
)
# ⬇️ NOVO: prepara lista de perguntas já concluídas por categoria
pergunta_norm = strip_accents(pergunta_atual).upper()
if tudo_correto:
st.session_state.tes_concluido = True
# Checa repetição por categoria e evita somar pontos
vistas = st.session_state.tesouro_perguntas_vistas.get(categoria_atual, [])
if pergunta_norm in vistas:
st.warning("🔁 Nível/pergunta já concluído anteriormente — nenhum ponto adicionado ao ranking.")
else:
vistas.append(pergunta_norm)
st.session_state.tesouro_perguntas_vistas[categoria_atual] = vistas
st.success("🎉 Parabéns! Nível concluído.")
st.session_state.pontuacao += 10
st.balloons()
# Mostrar resumo de perguntas únicas concluídas nesta categoria
if st.session_state.tesouro_perguntas_vistas.get(categoria_atual):
lista_fmt = ", ".join(st.session_state.tesouro_perguntas_vistas[categoria_atual])
st.caption(f"🔎 Perguntas únicas concluídas ({categoria_atual}): {lista_fmt}")
if nivel + 1 < total: # ✅ corrigido '<' (sem entidades HTML)
if st.button("➡️ Avançar para o próximo nível"):
st.session_state.tes_idx += 1
reset_keys(["tes_respostas", "tes_concluido"])
st.experimental_rerun()
else:
st.success("🏆 Você finalizou todos os níveis do Caça ao Tesouro!")
st.write(f"Pontuação atual: {st.session_state.pontuacao}")
def main():
_init_estado_jogos() # ✅ garante estado ao entrar na página Jogos
st.title("🎮 Jogos Interativos para Treinamento")
jogo = st.selectbox(
"Escolha um jogo:",
["Jogo do Dado (Curiosidades)", "Jogo da Forca (Treinamento)", "Caça ao Tesouro (Níveis)"],
)
if jogo == "Jogo do Dado (Curiosidades)":
jogo_dado()
elif jogo == "Jogo da Forca (Treinamento)":
jogo_forca_treinamento()
elif jogo == "Caça ao Tesouro (Níveis)":
jogo_tesouro_niveis()
if __name__ == "__main__":
main()