File size: 16,953 Bytes
0f0ef8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385

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