|
|
| 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()
|
|
|