Matriz Dinamica en c++. Como rellenarla en coordenadas especificas?

Iniciado por xuhipoint, 8 Marzo 2014, 04:43 AM

0 Miembros y 1 Visitante están viendo este tema.

eferion

#20
En un buscaminas, si abres una mina el programa sabe que ahí hay mina y termina el juego... no te sale un número.

La versión que propones incapacita al juego para detectar las minas y las sustituye por números.

No digo que esté mal... es otro enfoque... pero yo diría que lo suyo es que el programa conociese en todo momento la ubicación de las minas.

O eso o me estoy perdiendo algo.

leosansan

Cita de: eferion en 10 Marzo 2014, 17:11 PM
1 En un buscaminas, si abres una mina el programa sabe que ahí hay mina y termina el juego... no te sale un número.

2 La versión que propones incapacita al juego para detectar las minas y las sustituye por números.

3 No digo que esté mal... es otro enfoque... pero yo diría que lo suyo es que el programa conociese en todo momento la ubicación de las minas.

O eso o me estoy perdiendo algo.


Efectivamente, de acuerdo en el primer punto. Por ejemplo en esta situación:

Citar


       *  2  1  *  0

       *  2  *  2  1

       *  1  *  2  *

       1  *  2  1  1

       *  *  *  0  *



si el jugador elige la posición (2,3) ¡¡¡BOOMMM!!!!, salta una mina. Y por qué lo sé, porrque tengo previamente la matriz de las posiciones de las minas, lo que responde a tu segundo punto:

Citar

        1  0  0  0  0

        0  0  1  0  0

        0  0  0  1  0

        0  0  0  0  0

        0  1  0  0  0





Fíjate en la imagen de la respuesta anterior del juego que salen números, como en el mío, casillas levantadas=asteriscos y casillas planas=cuadraditos. Pero lo que yo propongo es un camino para que él termine la tarea, no hacerlo yo todo.

Como ves mi enfoque no está descaminado. como alegas en tu tercer punto.

Pero lo que no iba yo es a hacerle toda la tarea. Quedaba para él guardar la primera matriz, para no perder la situación de las minas originalmente, y usar otra para el engaño, así como el tema de pedir las coordenadas para el juego propiamente.

Tan sólo es un esbozo del juego, aunque creo que me animaré a terminarlo para el nietillo.

Y no, no te estabas perdiendo nada. Era un inicio de la tarea que tenía que realizar xuhipoint , yo sólo pretendía desatascarle el arranque.




¡¡¡¡ Saluditos! ..... !!!!



Yoel Alejandro

#22
Ojo, leosansan, no había leído bien los mensajes previos tuyos y de eferion, sin embargo para que no haya confusión aclaro que en el juego que diseñé los asteriscos son MINAS.

Es más, voy a tener que usar unos #defines para especificar los distintos símbolos, así se pueden cambiar facílito de una versión a otra, jajaja

Yo no tengo en el mío casillas bloqueadas (inseleccionables), aunque esa funcionalidad se puede añadir.

En todo lo demás que has dicho sobre las características del juego buscaminas real, estoy de acuerdo!
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

Yoel Alejandro

#23
María, llega un momento en que la complejidad de un programa crece hasta que termina enredándose uno mismo en su propio trabajo, jeje. Para evitar eso debes trabajar de manera modular, es decir, divide el programa en pequeñas partes funcionales que puedan integrarse entre sí.

Ahora hago unos comentarios. Es muy válida la recomendación de eferion, si estás en C++ usa endl en lugar de '\n'. Por otra parte perdón a los que puedan discrepar pero me parece más lógico que la matriz sea considerada de enteros y no de char, pues al fin y al cabo contendrá números (1 si hay bomba, 0 si no hay). Soy partidario de que una función auxiliar sea la encargada de decidir qué caracteres se van a mostrar en pantalla, en lugar de que dichos caracteres sean almacenados directamente en la matriz. Y como verás, este enfoque te evitar tener que hacer conversiones y operaciones complicadas como:

Matriz[fil][col]=cont[fil][col]+48;   (????????)


Tomando como base el código que presentaste, y depurándolo eliminé unas cuántas líneas redundantes por aquí y por allá, también como te dije sacar del main() rutinas que pueden definirse aparte, hasta quedar un procedimiento principal breve, limpio y sencillo (como debe ser):
Código (cpp) [Seleccionar]

int main(void) {

  int dim;
  int i, j;

  cout << "Introduzca la dimension de la matriz: ";
  cin >> dim;

  int **A = new int *[dim];
  for (i = 0; i < dim; i++)
     A[i] = new int [dim];
  for (i = 0; i < dim; i++)
     for (int j = 0; j < dim; j++)
        A[i][j] = 0;
 
  /* indicar donde van las minas, e imprimir */      
  colocar_minas( A, dim);
  imprimir( A, dim, 1);
 
  /* descubrir las casillas */
  descubrir_casillas( A, dim);
}

donde me tomé el atrevimiento de cambiar el nombre "Matriz" por simplemente "A".

Ahora, la función auxuliar de colocar las minas se encarga de rellenar la matriz (en lugar de hacerlo el main()). En lo particular me pareció un poco pesado e innecesario que el usuario tenga que decir de antemano cuántas bombas hay. En lugar de ello lo modifiqué para que él te vaya preguntando si quieres meter otra mina, y sigue hasta que respondas 'n'. El código:
Código (cpp) [Seleccionar]

void colocar_minas (int **A, int dim) {

  int i, fil, col;
  char op;
 
  cout << endl << "*** Colocando las minas ***" << endl << endl;
 
  op = 's';
  do {
     do {
        cout << "Introduzca fila: ";
        cin >> fil;
     } while (fil < 1 || fil > dim);
     fil = fil - 1;
   
     do {
        cout << "Introduzca columna: ";
        cin >> col;
     } while (col < 1 || col > dim);
     col = col - 1;
     
     A[fil][col] = 1;
   
     /* imprimimos, para que el usuario vea como va quedando */
     imprimir( A, dim, 0);
     
     do {
        cout << "Otro? s/n: ";
        cin >> op;
     } while ( op != 's' && op != 'S' && op != 'n' && op != 'N' );
     
  } while ( op == 's' || op == 'S' );
}


También me pareció más adecuado separar la utilidad de contar las minas que rodean una casilla en una función aparte int contar_minas( int **A, int dim, int i, int j ).

Ahora vamos a la parte delicada, y lo que creó el meollo: si en la matriz reemplazas el valor de cada casilla (1 si hay bomba y 0 si no hay) por la suma de las casillas adyacentes, entonces pierdes el valor original. ¿Cómo hacer? Una solución sería crear dos matrices, pero desperdicia espacio de memoria. Otra alternativa es modificar la función:

imprimir(int **A, int dim, int estilo)

que recibe una matriz A de 1's y 0's, pero donde el tercer argumento estilo permite definir tres modos posibles de funcionamiento:

  • estilo = 0, hace que se imprima '*' donde hay bomba y '0' donde no hay
  • estilo = 1, hace que se impriman las sumas de las casillas circundantes, en lugar de bomba/no-moba
  • estilo = 2, es para cuando el usuario va descubriendo las casillas. Imprime la suma circundante si la casilla no ha sido destapada, '0' si ha sido destapada y no hay bomba, y '*' si se destapó y hay una bomba (el juego termina).
La idea es usar el estilo 0 cuando estás rellenando la matriz, el 1 cuando terminaste de llenar, y el 2 cuando vas destapando las casillas. Pero resumes los tres en una sola función, de un modo sintético y elegante.

Sin embargo surge un problema delicado. Si una casilla no tiene bomba y no ha sido destapada se representa por la suma circundante. Pero si no tiene bomba y ha sido destapada, se representa por un '0'. ¿Cómo distinguir un caso de otro? Como dije antes una solución (ineficiente) es crear una segunda matriz que se vaya actualizando según las casillas que el usuario vaya descubriendo.
Mejor es apelar a un "truco" que usamos los programadores. Si miras bien, hasta ahora hemos usado 2 valores posibles para cada celda de la matriz A, esto es, 0 y 1. Quedan otros valores disponibles, con los que podemos representar otros significados. Podríamos asignar el valor -1 para la casilla que tenga una bomba que ha sido descubierta (boom!!, termina el juego), y el 2 para la casilla que ha sido destapada y no tiene bomba. Así quedaría:

  • A[ i][j] = -1, si hay una bomba y fue destapada (termina el juego)
  • A[ i][j] = 0, si no hay bomba, y no ha sido destapada
  • A[ i][j]) = 1, si hay bomba, pero no ha sido destapada
  • A[ i][j] = 2, si no hay bomba, y fue destapada
La función de imprimir, con las tres posibilidades quedaría:
Código (cpp) [Seleccionar]

void imprimir( int **A, int dim, int estilo) {
 
  int i, j;
 
  for (i = 0; i < dim; i++) {
  cout << "\t\t" ;
     for (j = 0; j < dim; j++) {
        /* aqui decidimos como queremos imprimir */
        switch (estilo) {
           /* solo mina / no-mina */
           case 0:
              if ( A[i][j] == 1 )
                 cout << " * ";
              else
                 cout << " 0 ";
              break;
           /* contar las minas que rodean */
           case 1:
              cout << ' ' << contar_minas( A, dim, i, j ) << ' ';
              break;
           /* a medida se van descubriendo las minas */
           case 2:
              if ( A[i][j] == -1 )
                 cout << " * ";      /* boom, jeje */
              else if ( A[i][j] == 0 || A[i][j] == 1 )  
                 cout << ' ' << contar_minas( A, dim, i, j ) << ' ';
              else if ( A[i][j] == 2 )
                 cout << " 0 ";
           break;
        }
     }
     cout << endl << endl;
  }
  cout << endl << endl;
}


Ahora, hay que tener cuidado cuando codificamos la función de sumar los adyacentes. Recuerda que se debe sumar cualquier bomba (destapada o no) o sea, el valor 1 ó -1 en la matriz. Por lo tanto:
Código (cpp) [Seleccionar]

/* Cuenta la cantidad de minas que rodean a una posicion dada de la matriz,
* incluyendo al elemento propio */
int contar_minas( int **A, int dim, int i, int j ) {
 
  int k, l, contador;

  contador = 0;
  for ( k = -1; k < 2; k++) {
     for ( l = -1; l < 2; l++) {
        if (i+k >= 0 && j+l >= 0 && i+k < dim && j+l < dim)
           if ( A[i+k][j+l] == 1 || A[i+k][j+l] == -1)
              contador ++;
     }
  }
 
  return contador;
}


Ya lo que quedaría sería definir la función que permite al usuario ir destapando las casillas a su gusto. Si destapa una bomba, pierde. De lo contrario, el juego continúa hasta que destapa todas las que no tienen bomba en cuyo caso gana. Este código te lo muestro al final, junto con el programa completo.

El código a continuación ya es funcional, está listo para copiar, compilar y ejecutar.

NOTA. Téngase en cuenta que en el juego buscaminas real hay características que nuestro programa aún no posee.

Código (cpp) [Seleccionar]

#include <iostream>

using namespace std;

void imprimir (int **A, int dim, int estilo);
void colocar_minas (int **A, int dim);
int contar_minas (int **A, int dim, int i, int j);
void descubrir_casillas (int **A, int dim);

int main(void) {

  int dim;
  int i, j;

  cout << "Introduzca la dimension de la matriz: ";
  cin >> dim;

  int **A = new int *[dim];
  for (i = 0; i < dim; i++)
     A[i] = new int [dim];
  for (i = 0; i < dim; i++)
     for (int j = 0; j < dim; j++)
        A[i][j] = 0;
 
  /* indicar donde van las minas, e imprimir */      
  colocar_minas( A, dim);
  imprimir( A, dim, 1);
 
  /* descubrir las casillas */
  descubrir_casillas( A, dim);
}

/* Permite al usuario ir colocando las minas. Para seguir colocando,
* responder 's' al final, de lo contrario termina. */
void colocar_minas (int **A, int dim){

  int i, fil, col;
  char op;
 
  cout << endl << "*** Colocando las minas ***" << endl << endl;
 
  op = 's';
  do {
     do {
        cout << "Introduzca fila: ";
        cin >> fil;
     } while (fil < 1 || fil > dim);
     fil = fil - 1;
   
     do {
        cout << "Introduzca columna: ";
        cin >> col;
     } while (col < 1 || col > dim);
     col = col - 1;
     
     A[fil][col] = 1;
   
     /* imprimimos, para que el usuario vea como va quedando */
     imprimir( A, dim, 0);
     
     do {
        cout << "Otro? s/n: ";
        cin >> op;
     } while ( op != 's' && op != 'S' && op != 'n' && op != 'N' );
     
  } while ( op == 's' || op == 'S' );
}

/* Imprime la matriz en varios estilos:
*
*  0: Indican simplemente donde hay minas y donde no.
*  1: Indica cuantas minas rodean a cada casilla (incluyendo a si misma)
*  2: Como se muestra a medida que se descubren las casillas.
*     El valor -1 imprime una bomba, para los valores entre 0 y 9 imprimen
*     la suma circundante, y el 10 se imprime como 0.
*/
void imprimir( int **A, int dim, int estilo) {
 
  int i, j;
 
  for (i = 0; i < dim; i++) {
  cout << "\t\t" ;
     for (j = 0; j < dim; j++) {
        /* aqui decidimos como queremos imprimir */
        switch (estilo) {
           /* solo mina / no-mina */
           case 0:
              if ( A[i][j] == 1 )
                 cout << " * ";
              else
                 cout << " 0 ";
              break;
           /* contar las minas que rodean */
           case 1:
              cout << ' ' << contar_minas( A, dim, i, j ) << ' ';
              break;
           /* a medida se van descubriendo las minas */
           case 2:
              if ( A[i][j] == -1 )
                 cout << " * ";      /* boom, jeje */
              else if ( A[i][j] == 0 || A[i][j] == 1 )  
                 cout << ' ' << contar_minas( A, dim, i, j ) << ' ';
              else if ( A[i][j] == 2 )
                 cout << " 0 ";
           break;
        }
     }
     cout << endl << endl;
  }
  cout << endl << endl;
}

/* Cuenta la cantidad de minas que rodean a una posicion dada de la matriz,
* incluyendo al elemento propio */
int contar_minas( int **A, int dim, int i, int j ) {
 
  int k, l, contador;

  contador = 0;
  for ( k = -1; k < 2; k++) {
     for ( l = -1; l < 2; l++) {
        if (i+k >= 0 && j+l >= 0 && i+k < dim && j+l < dim)
           if ( A[i+k][j+l] == 1 || A[i+k][j+l] == -1)
              contador ++;
     }
  }
 
  return contador;
}

/* Permite al usuario ir descubriendo casillas del tablero, y si se
* encuentra una mina, pierde !!! */
void descubrir_casillas( int **A, int dim ) {

  int i, j;
  char terminar;

  cout << endl << "*** Ahora descubriendo las casillas ***" << endl << endl;
     
  imprimir( A, dim, 2);
 
  terminar = 'n';
  do {
     do {
        cout << "Introduzca fila: ";
        cin >> i;
     } while (i < 1 || i > dim);
     i = i - 1;
   
     do {
        cout << "Introduzca columna: ";
        cin >> j;
     } while (j < 1 || j > dim);
     j = j - 1;
     
     if ( A[i][j] == 1) {
        /* encuentra mina: pierde !!! */
        A[i][j] = -1;
        imprimir( A, dim, 2 );
        cout << endl << "Boom!!!, ha perdido" << endl;
        terminar = 's';
     }
     else {
        /* sigue jugando */
        A[i][j] = 2;
        imprimir( A, dim, 2 );
     }
     
     /* Nota: 0 significa casilla sin bomba y sin descubrir.
      *       2 significa casilla sin bomba y ya descubierta.
     /* El programa termina si no quedan por descubrir casillas
      * sin bombas (casillas con el valor 0) */
     if ( terminar == 'n' ) {
        terminar = 's';
        for (i = 0; terminar == 's' && i < dim; i++)
           for (j = 0; terminar == 's' && j < dim; j++)
              if ( A[i][j] == 0 ) terminar = 'n';
       
        if ( terminar == 's' ) {
           cout << endl << "Felicitaciones, ha ganado !!!" << endl;
           imprimir( A, dim, 0);
        }
     }
             
  } while ( terminar == 'n' );
}
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

leosansan

#24
Sólo un detalle, el autor del tema quería que el usuario introdujera las minas, de ahí mi propuesta.

Y te recuerdo lo que le comenté a eferion, no se trata de hacerle la tarea al que inició el tema sino de resolverle dudas y orientarlo. Ya luego él debe elegir el camino correcto, de ahí que mis códigos sean casi iguales al de los autores del tema pero funcionales, es decir con los errores corregidos y aclaradas las dudas. Esa es una cosa que me diferencia de otros usuarios, yo suelo contestar con código en lugar de con orientaciones, que la mayor parte de las veces les deja en el limbo, o con enlaces. Como ya comenté una vez, lo que nos parece trivial para algunos, para otros les puede resultar un muro insalvable.

En ningún momento me plantearía resolverlo como propone el autor, ni siquiera tú que indirectamente has seguido mi estela, que era la del autor original, sería "otra cosa".

Otra cosa son las llamadas "aportaciones" que algunos usuario decidan hacer, como es tu caso. Ahí si hay un nivel de exigencia mayor ya que se trata de código propio y se espera de él que esté a la altura de nuestras expectativas.

eferion

Jejejeje.

A mí es que me da muchísima pereza ponerme a escribir código cuando veo que el que pregunta no ha puesto ni un mísero main :).

Cuando veo que el autor del hilo se lo ha currado un mínimo y no ha puesto código copiado ( a veces canta mucho ), la cosa cambia y me vuelco más.

Eso sí, hay veces que me levanto con mejor humor y no tengo problemas en escribir el quijote si hace falta. Pero por norma general...

xuhipoint

Haber primero quiero agradecer a todas las personas que me ayudaron aun sin conocerme o pedirme nada a cambio, ese fue uno de los motivos por los cuales elegí esta carrera me gusta como se ayudan entre si. Segundo yo jamas he sido de las personas que se copia un código, soy e las personas que les gusta hacer las cosas por ella misma, ser independiente. Tercero vine aquí por una asesoría, JAMAS DIJE HÁGANME LA TAREA,  y si eso fue lo que di a entender me disculpo. Cuarto, aunque parezca que no entiendo nada y casi piso lo ignorante, no es porque yo quiera ser así, solo esta tarea me fui enviada por un correo no he podido ni ver a mis profesores con la situación que vive mi país, no es porque yo me quede en mi casa sentada viendo televisión, he salido aun con bombas lagrimogenas a la universidad, para tener un mejor futuro y poderte ir porque la patria que te vio nacer hoy no puedes vivir en ella, y con el no saber si regresas a tu casa, porque por el solo hecho de ser estudiante te maltratan, los que se suponen deben defenderte. Y para finalizar quiero dejar claro que no tengo pensado utilizar el código envidado por yoel, me engañaría a mi misma, yo he ido creando mi propio código con las recomendaciones que me han escrito. Bueno con esto me despido de ustedes y nuevamente gracias a todas las personas que aportaron ideas.

Yoel Alejandro

Ok xuhipoint, pero por favor mantennos al tanto de lo que has hecho y cómo va funcionando. Es la manera de corresponder a los que nos hemos involucrado en este tema, y quisiéramos saber que lo has terminado y si te ha ido bien  :D.

Yo me uní a este foro porque precisamente rescata el espíritu anteriormente prevaleciente entre la comunidad de programadores, que era la solidaridad (bueno, hasta que llegó Microsoft y echó a perder todo, jaja). Es la misma filosofía GNU. Los programadores debemos ser personas sumamente humanitarias.

Ahora, pienso que un programa de esta complejidad, aunque para nosotros no parezca mucho, sí lo es para alguien que esté comenzando. Lleva asignación dinámica de memoria, manejo de matrices, elementos delicados donde si te pones a trabajar en "solitario" te vas a enredar y equivocar. No está mal que recibir ayuda.

Así que por favor ten en cuenta las recomendaciones generales que te hemos hecho. Todos hemos pasado por lo mismo y ya ganamos experiencia con ésto. En particular, yo señalaría con mucho énfasis:


  • Modulariza el código. De otro modo terminas teniendo lo que se llama "espagueti". Por ejemplo, en la función de asignar las minas tenías que ahí mismo calculabas cuántas minas rodeaban una casilla para mostrar las sumas. Así llegabas a tener cuatro (4) ciclos anidados, un for dentro de for, dentro de for, dentro de for, y empieza a volverse complicado. Debes tener una función exclusiva para colocar las minas, otra par sumar las minas adyacentes, otra para imprimir la matriz, etc. "Divide y vencerás".
  • El formato de declaración for (int i = 0 ....) donde el tipo de la variable contador está "auto-declarado" en lo particular me parece algo desaconsejable aquí. Tengo entendido (no se si me equivoco) que primero ésto es sólo válido en C99, y segundo al hacer ésto la "vida" o alcance de la variable es sólo dentro del bloque o ciclo en cuestión donde se declara, por lo que no puedes reutilizar el contador en otra parte del código. Declara las variables contador al inicio de la definición de la función.
  • Decide para los elementos de la matriz un tipo "consistente" de datos, que luego no tengas que estarlo cambiando o transformando. Si es char, que sea char. Si en entero, que sea entero. Pero no que sea char, el cual luego lo estás transformando a entero, y de nuevo a char, etc. Esto es fuente probable de errores, es inseguro, y por un pequeño descuido te puede echar a perder todo el programa.

Esto es en general lo que puedo comentar. Trabaja como quieras, pero sigue dichos consejos y no se si leosansan y eferion quieren añadir otros basados en su experiencia.
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

Yoel Alejandro

#28
Cita de: leosansan en 11 Marzo 2014, 11:25 AM
... de ahí que mis códigos sean casi iguales al de los autores del tema pero funcionales, es decir con los errores corregidos y aclaradas las dudas. Esa es una cosa que me diferencia de otros usuarios, yo suelo contestar con código en lugar de con orientaciones, que la mayor parte de las veces les deja en el limbo, o con enlaces. Como ya comenté una vez, lo que nos parece trivial para algunos, para otros les puede resultar un muro insalvable.

Totalmente de acuerdo leosansan, pienso que hay que ser respetuoso con la línea original trazada por el usuario, y no procurar cambiársela sino mejorársela. Esto implica un compromiso delicado, pues a veces si por nosotros fuera le añadiríamos las mil y una cosas pero el autor no va a entender nada, jaja. Hay que agregar las mejoras pero siempre al alcance de la comprensión del usuario, y no de una forma que desnaturalice por completo la idea original del autor.

Y sí, a veces como "una imagen vale más que mil palabras", se tiene que "una línea de código vale más que mil palabras". Uno se da a entender mejor de esta manera en lugar de que te digan "anda a tal página web" y cuando vas no tiene la menor relación con lo que buscas  >:( >:(, o al menos a mí me ha pasado eso.

Yo creo que se trata de hacer a los demás como harías contigo mismo. Muchas veces me he beneficiado de los aportes de otras personas en la web, y lo que estoy haciendo aquí es básicamente agradecer esa ayuda que he recibido. De eso se trata la solidaridad humana, y es la ÚNICA vía que mejorará al planeta, sólo que la gente no tiene fe y a veces son muy pocos los que están dispuestos a dar su grano de arena por otra persona, .... pero no importa, ánimo y a seguir trabajando  :D

===============================

POR CIERTO, se han dado cuenta que el tema ha sido veces 693 veces hasta el momento ?. Se está haciendo popular !!!
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)