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