[Encerrar] Minesweeper

Keith

Power Member
Boa noite.

Estou a tentar fazer o jogo Minesweeper em C. De momento estou na fase crítica do projecto, destapar as casas vazias.

Já tenho o tabuleiro (L x C), gero N minas, conto quantas minas há em torno de cada posição do tabuleiro. Realmente o que me falta, e o que me está a dar mais dores de cabeça, é mostrar as casas conforme elas são escolhidas. Ou seja, há uma série de casas juntas umas das outras, que não têm minas em torno delas. Elas deverão ser mostradas todas ao mesmo tempo.

Quem já jogou o clássico sabe o que estou a falar. O que peço são dicas de como revelar essas casas. Já experimentei vários métodos, com e sem recursividade, mas não consigo que ele funcione.

Já agora, estou a usar um tabuleiro de char's [linhas][colunas][2], em que 1ª dimensão ([l][c][0]) têm o tabuleiro tal como o jogador o deverá ver, e a 2ª têm tudo o que já fiz em relação a gerar minas e conta-las.

Aqui vai um screen:

MSweeper.jpg


Alguém pode sugerir alguma coisa?

Muito obrigado desde já,
Keith
 
Última edição:
Pelo que entendi, ao escolheres uma posição que tenha um 0 (ex: [10][10]) vais ter de verificar a casa de cima[9][10], baixo[11][10], esquerda[10][9] e direita[10][11]. Se, por exemplo, a casa de cima for 0, chamas a própria função (ou seja, recursiva) que vai verificar se essa casa ([9][10]) tem um 0 na posição acima ([8][10]), etc. Como toda a função recursiva tem que ter uma condição de paragem que é achar um valor diferente de 0. Parâmetros são a posição da casa e o vector.
No clássico dá a ideia que os grupos de 0's não costumam ser tão grandes. Neste tabuleiro se escolhesses a [10][10] ficavas com quase 1/4 do tabuleiro descoberto. Mas isso tem a ver com o sorteio. Desde que o algoritmo esteja bem é o que interessa.
Espero ter ajudado!
 
O algoritmo está correcto. O número de minas é que é demasiado baixo para o tamanho do tabuleiro. Experimenta no clássico e já vês.

Não me basta verificar apenas nas casas em torno da qual escolhi. Se todas elas estiverem ligadas por zeros, todas têm de ser abertas. O problema é esse mesmo. Como conseguir abrir todas as casas ligadas por zeros.

Se puderes ajudar aí, agradeço.
 
Se jogar na 10x10 como disseste, isto é o que abre:

MSweeper2.jpg


e não tanto como disseste.

Tal como disse, depende do número de minas no tabuleiro.

Keith
 
Antes de escrever o texto eu fui experimentar o clássico para ter a certeza que não te enganava. Mas no tabuleiro de 9x9 realmente a abertura das casas vazias é mínima.
Cada casa que a função processa, chama a própria função 4 vezes (uma para cada direcção). Só pára quando encontra uma casa diferente de 0, a qual também tem de mostrar.
 
Algo deste género?

Código:
int Revelar (int L, int C)
{
int i, j;

   if (CM[L][C][1] != '0') { CM[L][C][0] = CM[L][C][1]; return 0; }

   else { CM[L][C][0] = CM[L][C][1];
            Revelar (L-1,C);
            Revelar (L,C-1);
            Revelar (L+1,C);
            Revelar (L,C+1); }

   return 0;
}

Obtenho um segmentation fault.
Podes sugerir algo?

Obrigado,
Keith
 
Algo deste género?

Código:
int Revelar (int L, int C)
{
int i, j;

   if (CM[L][C][1] != '0') { CM[L][C][0] = CM[L][C][1]; return 0; }

   else { CM[L][C][0] = CM[L][C][1];
            Revelar (L-1,C);
            Revelar (L,C-1);
            Revelar (L+1,C);
            Revelar (L,C+1); }

   return 0;
}
Obtenho um segmentation fault.
Podes sugerir algo?

Obrigado,
Keith

Algumas coisas:

1) o "int i,j" já podes tirar...

2) a funcao pode ser void porque nunca precisas de retornar nada para a funcao que chama esta... desde que aqui chames uma funcao para mostrar a casa vazia quando é caso disso (dentro do if)... assim escusas de encher a heap de int's inuteis e melhoras um pouco a eficiencia...

3) para que serve a terceira dimensao do array? => CM[L][C][esta-posicao]

4) faltam-te ai os casos especiais em que o L-1, L+1, C-1 e C+1 ultrapassam as dimensoes do tabuleiro... neste caso L-1 < 0, C-1< 0, L+1 > LMAX, C+1 > CMAX (LMAX e CMAX tens que definir como variavel global ou passa-los como parametro para a funcao recursiva...). Deve ser por isso que tens seg fault.

Sugestão:

Código:
void Revelar (int L, int C) {
   if (L < 0 || C < 0 || L > LMAX || C > CMAX) 
            return;

   else if (CM[L][C][1] != '0') 
            CM[L][C][0] = CM[L][C][1];

   else { 
            CM[L][C][0] = CM[L][C][1];
            Revelar (L-1,C);
            Revelar (L,C-1);
            Revelar (L+1,C);
            Revelar (L,C+1);
   }
   return;
}
 
Última edição:
Algumas coisas:

1) o "int i,j" já podes tirar...

2) a funcao pode ser void porque nunca precisas de retornar nada para a funcao que chama esta... desde que aqui chames uma funcao para mostrar a casa vazia quando é caso disso (dentro do if)... assim escusas de encher a heap de int's inuteis e melhoras um pouco a eficiencia...

3) para que serve a terceira dimensao do array? => CM[L][C][esta-posicao]

4) faltam-te ai os casos especiais em que o L-1, L+1, C-1 e C+1 ultrapassam as dimensoes do tabuleiro... neste caso L-1 < 0, C-1< 0, L+1 > LMAX, C+1 > CMAX (LMAX e CMAX tens que definir como variavel global ou passa-los como parametro para a funcao recursiva...)

1) eu sei. esqueci-me de o fazer antes de colocar aqui o código.
2) usei o tipo int, pois em c uso pouca recursividade (uso apenas em Haskell e o estilo de programação é totalmente diferente) e pensei que poderia forçar a saída da função dessa forma.
3) a terceira dimensão contém tudo o que fiz, desde minar, contar posicões, etc. Se ela for 1 mostra o tabuleiro de baixo, se for 0 mostra o tabuleiro de cima. Tal como está na imagem.
4) Esqueci-me disso. Estou a tentar agora.

Obrigado
 
Algumas coisas:
Sugestão:

Código:
void Revelar (int L, int C) {
   if (L < 0 || C < 0 || L > LMAX || C > CMAX) 
            return;

   else if (CM[L][C][1] != '0') 
            CM[L][C][0] = CM[L][C][1];

   else { 
            CM[L][C][0] = CM[L][C][1];
            Revelar (L-1,C);
            Revelar (L,C-1);
            Revelar (L+1,C);
            Revelar (L,C+1);
   }
   return;
}


Continuo com segmentation fault.
Obrigado à mesma.

Keith
 
2) usei o tipo int, pois em c uso pouca recursividade (uso apenas em Haskell e o estilo de programação é totalmente diferente) e pensei que poderia forçar a saída da função dessa forma.
3) a terceira dimensão contém tudo o que fiz, desde minar, contar posicões, etc. Se ela for 1 mostra o tabuleiro de baixo, se for 0 mostra o tabuleiro de cima. Tal como está na imagem.

2) podes sair da funcao quando quiseres usando o "return;", sendo ela void.
3) Continuo sem perceber o que é o terceiro indice... nao estarás a fazer aqui confusão? Não deveria ser CM[L][C] != '0' naquele if? Qual é a representação interna que estás a usar para os elementos do tabuleiro, não são chars tipo '0', '1' e '*' ?

EU faria desta forma:

Usando essa representação para os elementos, e partindo do principio que tens uma funcao do genero "print-tabuleiro" que se limita a printar os 0s, 1s, e *s que encontra no array, usando dois ciclos for (para linhas e colunas).

Neste caso o mais simples seria ter dois arrays de duas dimensoes, um completamente preenchido (que é gerado de forma aleatoria para cada novo jogo) e outro com as mesmas dimensões, que é inicializado a NULL... neste caso seriam respectivamente "tabuleiro" e "tabuleiro_visivel", ambos com dimensao LMAX x CMAX.

Dessa forma, a função para revelar os zeros primos de um zero seria chamada sempre que o jogador escolhesse uma posicao do "tabuleiro" que contém um zero... se contém outra coisa basta revelar o que essa coisa é (numero ou *) e caso seja um * => game over.

A função para mostrar os zeros "ligados" ficaria assim:

Código:
void mostrar_zeros (int L, int C) {
   /* se tiver fora dos limites ou nao for um zero... */
   if (L < 0 || C < 0 || L > LMAX || C > CMAX || tabuleiro[L][C] != '0')
            return;

   else { 
            tabuleiro_visivel[L][C] = '0';
            mostrar_zeros(L-1,C);
            mostrar_zeros(L,C-1);
            mostrar_zeros(L+1,C);
            mostrar_zeros(L,C+1);
   }
   return;
}
Enfim, apeteceu-me pensar um bocado no problema mas cada um o resolve à sua maneira... boa sorte nisso que eu vou dormir!
 
Tal como tu tens um "tabuleiro" e um "tabuleiro_visivel", eu juntei tudo num.
Sei lá, imagina um paralepipedo. Tens a largura, o comprimento e tens a altura. Neste caso a altura é 2, e encontra-se divido: ou seja na metade de cima tens o "tabuleiro_visivel", na metade de baixo tens o "tabuleiro".

Continuo com segmentation fault. *Dass...

Keith
 
Penso que o erro está na comparação do maior. Se o vector tiver dimensão 10 a posição 10 não existe. Logo em vez de:
Código:
   if (L < 0 || C < 0 || L > LMAX || C > CMAX || tabuleiro[L][C] != '0')
deveria estar:
Código:
   if (L < 0 || C < 0 || L > LMAX-1 || C > CMAX-1 || tabuleiro[L][C] != '0')
Já agora se quiseres poupar chamadas à função de posições inválidas, fazes os if antes de as chamar, ou seja:
Código:
void revelar (int L, int C) {
/* esta posição revela sempre, seja mina, 0, 1, 2, etc  porque é sempre válida*/
   CM[L][C][0] = CM[L][C][1];

   if(CM[L][C][1] == '0') { 
        if(L > 0)
            revelar (L-1,C);
        if(C > 0)
            revelar (L,C-1);
        if(L < LMAX-1)
            revelar (L+1,C);
        if(C < CMAX-1)
            revelar (L,C+1);
   }
}

edit: corrigir um erro de português e um sinal no código
 
Última edição:
Estive a experimentar algumas hipóteses fornecidas por vocês, e aquela que revela mais casas de uma vez, resultado de várias combinações de código é a seguinte:

Código:
void Revelar (int L, int C)
{
   CM[L][C][0] = CM[L][C][1];

   if(CM[L][C][1] == '0')
   { CM[L-1][C-1][0] = CM[L-1][C-1][1];    CM[L-1][C][0] = CM[L-1][C][1];
     CM[L-1][C+1][0] = CM[L-1][C+1][1];   CM[L][C-1][0] = CM[L][C-1][1];
     CM[L][C+1][0] = CM[L][C+1][1];      CM[L+1][C-1][0] = CM[L+1][C-1][1];
     CM[L+1][C][0] = CM[L+1][C][1];      CM[L+1][C+1][0] = CM[L+1][C+1][1];
   }

   if ((CM[L][C][1] == '0') && (L - 1 > -1))       Revelar (L-1,C);
   if ((CM[L][C][1] == '0') && (L + 1 > LMAX-1))  Revelar (L+1,C);
   if ((CM[L][C][1] == '0') && (C - 1 > -1))       Revelar (L,C-1);
   if ((CM[L][C][1] == '0') && (C + 1 > CMAX-1))  Revelar (L,C+1);

   if ((CM[L][C][1] == '0') && (L - 1 > -1) && (C - 1 > 0))               Revelar (L-1,C-1);
   if ((CM[L][C][1] == '0') && (L - 1 > -1) && (C + 1 > CMAX-1))      Revelar (L-1,C+1);
   if ((CM[L][C][1] == '0') && (L + 1 > LMAX-1) && (C - 1 > 0))        Revelar (L+1,C-1);
   if ((CM[L][C][1] == '0') && (L + 1 > LMAX-1) && (C + 1 > CMAX-1))  Revelar (L+1,C+1);
}
Mas mesmo assim, não consigo todas as posições:

MineS3.jpg


Além disso, acontece uma cena estúpida, que é neste caso, joguei na posicão [10][8] e ele mostra-me casas do outro extremo do campo.

Keith
 
Corrigi um erro que tinha no código que te dei, num sinal. Não sei se reparaste e corrigiste ou copiaste logo!
Mas acho que tens o mesmo erro aqui:
Código:
&& (L + 1 > LMAX-1) &&
devia estar
Código:
&& (L + 1 < LMAX) &&
O objectivo é ser verdadeiro se for menor que o máximo e não maior que o máximo, senão vais parar fora do vector.
Nunca reparei que a versão clássica abre as casas na diagonal até ver o teu código. Claro que fui experimentar para confirmar.
Não era mais fácil meteres os IF's todos dentro do
Código:
if(CM[L][C][1] == '0')
do que andares a meter
Código:
if ((CM[L][C][1] == '0') &&
em todos?
 
Se mudar o sinal para <, fico com segmentation fault. Já se ficar com o sinal > ele abre casas.
No mínimo estranho, mas pronto...
Eu tinha reparado que tinhas um sinal trocado.

Eu acho que tens razão nos if's, estou só numa fase de experimentação, pois cada vez mais consigo abrir mais casas, mas não a totalidade, e já estou a começar a ficar irritado... lol

Obrigado pela atenção que tens dado ao meu problema, ngr.

Keith
 
Back
Topo