|
|
|
|
|
import streamlit as st
|
|
|
import random
|
|
|
import unicodedata
|
|
|
from datetime import date, timedelta
|
|
|
|
|
|
|
|
|
def reset_keys(keys):
|
|
|
for k in keys:
|
|
|
st.session_state.pop(k, None)
|
|
|
|
|
|
|
|
|
def strip_accents(s: str) -> str:
|
|
|
if not isinstance(s, str):
|
|
|
return s
|
|
|
return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn")
|
|
|
|
|
|
|
|
|
|
|
|
def _init_estado_jogos():
|
|
|
st.session_state.setdefault("pontuacao", 0)
|
|
|
st.session_state.setdefault("rodadas", 0)
|
|
|
st.session_state.setdefault("ultimo_resultado", None)
|
|
|
|
|
|
st.session_state.setdefault("dado_resultados_vistos", [])
|
|
|
st.session_state.setdefault("dado_lados_atual", None)
|
|
|
|
|
|
|
|
|
st.session_state.setdefault("forca_palavras_vistas", {})
|
|
|
|
|
|
st.session_state.setdefault("forca_letras_usadas", set())
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
st.session_state.setdefault("tesouro_perguntas_vistas", {})
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
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)
|
|
|
|
|
|
|
|
|
if st.session_state.dado_lados_atual != lados:
|
|
|
st.session_state.dado_lados_atual = lados
|
|
|
st.session_state.dado_resultados_vistos = []
|
|
|
|
|
|
if st.button("Girar dado"):
|
|
|
resultado = random.randint(1, lados)
|
|
|
st.success(f"🎲 Você rolou: {resultado}")
|
|
|
st.info(curiosidades[resultado % len(curiosidades)])
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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()
|
|
|
st.subheader("🔤 Jogo da Forca (Treinamento)")
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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"]
|
|
|
|
|
|
|
|
|
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()
|
|
|
st.session_state.forca_round_contabilizado = False
|
|
|
|
|
|
st.write(f"Pergunta: {pergunta}")
|
|
|
st.write("Palavra:", " ".join(st.session_state.letras_descobertas))
|
|
|
st.write(f"Tentativas restantes: {st.session_state.tentativas}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown(
|
|
|
"""
|
|
|
<style>
|
|
|
div.stButton > button {
|
|
|
padding: 6px 8px !important;
|
|
|
margin: 2px !important;
|
|
|
font-size: 14px !important;
|
|
|
}
|
|
|
</style>
|
|
|
""",
|
|
|
unsafe_allow_html=True
|
|
|
)
|
|
|
|
|
|
|
|
|
alfabeto = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
|
st.session_state.forca_letras_usadas.add(letra)
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
if letra not in st.session_state.letras_erradas:
|
|
|
st.session_state.letras_erradas.append(letra)
|
|
|
st.session_state.tentativas -= 1
|
|
|
|
|
|
|
|
|
st.rerun()
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
normalizada = strip_accents(resposta)
|
|
|
|
|
|
if venceu:
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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:
|
|
|
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:
|
|
|
|
|
|
if not st.session_state.forca_round_contabilizado:
|
|
|
st.session_state.forca_falhas_hoje += 1
|
|
|
st.session_state.forca_round_contabilizado = True
|
|
|
|
|
|
|
|
|
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()
|
|
|
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))
|
|
|
|
|
|
|
|
|
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"])
|
|
|
)
|
|
|
|
|
|
|
|
|
pergunta_norm = strip_accents(pergunta_atual).upper()
|
|
|
|
|
|
if tudo_correto:
|
|
|
st.session_state.tes_concluido = True
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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:
|
|
|
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()
|
|
|
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()
|
|
|
|