elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Trabajando con las ramas de git (tercera parte)


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación C/C++ (Moderadores: Eternal Idol, Littlehorse, K-YreX)
| | |-+  [C++] [Aporte] Buscaminas por consola
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] Ir Abajo Respuesta Imprimir
Autor Tema: [C++] [Aporte] Buscaminas por consola  (Leído 7,538 veces)
Wofo

Desconectado Desconectado

Mensajes: 168


Ver Perfil
[C++] [Aporte] Buscaminas por consola
« en: 11 Marzo 2013, 16:20 pm »

Buenas gente, luego de algunos días trabajando en un buscaminas para aprender, les dejo el código para que lo puedan aprovechar.

Lo programé poniendo especial empeño en que fuera orientado a objetos y exclusivamente C++ (intenté no usar nada de C). También escribí muchos comentarios para que quede todo más claro y sea muy fácil de entender.

Espero que a alguien le sirva para aprender. Por lo menos a mí me sirvió bastante.

Una particularidad del programa es que, gracias a que está orientado a objetos, es posible crear una interfaz gráfica sin tener que modificar ni una sola línea de código dentro de las clases.

Conceptos implementados:
-Herencia
-Manejo de errores (excepciones)
-Uso de la clase vector
-Generación de números aleatorios



*Nota: este es un segundo upload con correcciones para mejorar el manejo de excepciones y una mejor orientación a objetos
Programa: Buscaminas
IDE: Ninguna (Notepad++) *Se puede compilar escribiendo g++ main.cpp -o archivo_compilado
Link: http://depositfiles.com/files/z79itfr7f

________

Por si alguien prefiere copiar el código en vez de descargarlo de depositfiles, lo dejo aquí también:
*Es importante guardar los archivos con el mismo nombre, para que funcionen los includes correctamente.

main.cpp
Código
  1. #include <iostream> //Necesario para mostrar el contenido por la consola
  2. #include <stdexcept> //Necesario para manejo de errores
  3. #include <cstdlib> //Necesario para rand() en la clase Random_Number
  4. #include <time.h> //Necesario para la clase Random_Number
  5. #include <vector> //Necesario para usar vectores
  6.  
  7. #include "console_board.cpp"
  8.  
  9. using namespace std;
  10.  
  11. int main() {
  12. try {
  13. Console_Board board;
  14. int level = -1;
  15.  
  16. //Menú principal
  17. cout << "Bienvenido al Buscaminas. Elige el nivel que deseas jugar" << endl
  18. << "1- Principiante" << endl
  19. << "2- Intermedio" << endl << endl
  20. << "Escribe el numero del nivel: ";
  21. cin >> level;
  22.  
  23. //Creo el nivel
  24. if(level == 2)
  25. board.create(16, 16, 40); //16x16 y 40 minas
  26. else
  27. board.create(9, 9, 10); //9x9 y 10 minas
  28.  
  29. //Muestro las instrucciones
  30. cout << endl << "--INSTRUCCIONES--";
  31. cout << endl << "Elegir un cuadrado: e x y (ejemplo: e 2 3).";
  32. cout << endl << "Marcar un cuadrado como mina: m x y (ejemplo: m 2 3). " << endl << endl;
  33.  
  34. system("pause");
  35.  
  36. //Preparo el game_loop y lo comienzo
  37. bool game_loop = true, victory = false;
  38. int x = 0, y = 0;
  39. char option = '-';
  40. while(game_loop == true && victory == false) {
  41. system("CLS");
  42. board.print();
  43. cout << "Minas restantes: " << board.get_left_mines() << endl;
  44. cout << "Ultima eleccion: " << option << ' ' << x << ", " << y << endl << endl;
  45. cout << "Comando: ";
  46. cin >> option >> x >> y;
  47. /*Por si no introdujo un número limpio el cin. De lo contrario, al introducir un string empieza a correr el while de modo infinito sin volver a preguntar por las coordenadas*/
  48. cin.clear(); cin.ignore(99,'\n');
  49. if(option == 'm')
  50. board.mark_square(x-1, y-1);
  51. else {
  52. board.choose_square(x-1, y-1);
  53. if( board.is_valid(x-1, y-1) && board.square(x-1, y-1).is_mine() ) game_loop = false;
  54. }
  55. if( board.no_squares_left() ) {
  56. board.mark_all_mines();
  57. victory = true;
  58. }
  59. }
  60.  
  61. //Muestro el tablero por última vez
  62. system("CLS");
  63. board.print();
  64. cout << endl << endl;
  65.  
  66. if(victory == false)
  67. cout << "Has perdido!";
  68. else
  69. cout << "Felicitaciones! Has ganado!";
  70.  
  71. cout << endl;
  72.  
  73. return 0;
  74. }
  75. catch(exception& ex) {
  76. cerr << "Se ha procucido un error: " << ex.what();
  77. return 1;
  78. }
  79. }

board.cpp
Código
  1. /* Clase Board.
  2.  * Maneja los elementos del juego Buscaminas. Consiste en un tablero (Board en inglés)
  3.  * que tiene cuadrados (Squares).
  4.  *
  5.  *
  6.  * Includes necesarios para el funcionamiento de esta clase
  7.  *
  8.  * #include <iostream>  | Para usar cout
  9.  * #include <vector>    | Para usar vector
  10.  * #include <stdexcept> | Para manejo de errores */
  11.  
  12. #include "square.cpp"
  13. #include "random_number.cpp"
  14.  
  15. class Board {
  16. std::vector< std::vector<Square> > squares;
  17. int width, height;
  18. int left_mines; //Minas que quedan (va descontando cuando el usuario marca una)
  19. int total_mines; //Cantidad de cuadrados que contienen una mina
  20. int total_number_squares; //Cantidad de cuadrados que contienen un número
  21. bool first_choosing;
  22.  
  23. //Métodos privados
  24. void set_size(int width, int height);
  25. void set_mines(int x, int y);
  26. void set_numbers();
  27. void show_empty_squares(int x, int y);
  28. int count_surrounding_mines(int x, int y);
  29. int shown_squares();
  30.  
  31. //Métodos públicos
  32. public:
  33. void create(int width, int height, int param_mines);
  34. void choose_square(int x, int y);
  35. void mark_square(int x, int y);
  36. void mark_all_mines();
  37.  
  38. bool is_valid(int x, int y);
  39. bool no_squares_left();
  40.  
  41. Square square(int x, int y);
  42. inline int get_width() { return width; }
  43. inline int get_height() { return height; }
  44. inline int get_left_mines() { return left_mines; }
  45. };
  46.  
  47. void Board::set_size(int param_width, int param_height) {
  48. //Asigna el tamaño al tablero
  49. //Condición: los argumentos deben ser positivos
  50. if(param_width <= 0 || param_height <= 0)
  51. throw std::runtime_error("No puedes crear un tablero con ancho o alto negativos.");
  52.  
  53. //Método de la clase vector que sirve para inicializarlo. En este caso creo un vector bidimensional con el ancho "param_width" y alto "param_height"
  54. squares.assign(param_width, std::vector<Square>(param_height));
  55.  
  56. //Guardo el tamaño del tablero en atributos para usarlo más tarde
  57. width = param_width;
  58. height = param_height;
  59. }
  60.  
  61. void Board::set_mines(int x, int y) {
  62. //Asigna las minas de manera aleatoria a partir del atributo total_mines
  63. //No se pone ninguna mina en el punto x, y
  64. Random_Number random_number = Random_Number();
  65. int x_aux;
  66. int y_aux;
  67.  
  68. for(int i = 0; i < total_mines;) {
  69. x_aux = random_number.get(0, width-1);
  70. y_aux = random_number.get(0, height-1);
  71.  
  72. if( !squares[x_aux][y_aux].is_mine() && !squares[x][y].is_mine() ) {
  73. //Si el punto aleatorio no es mina NI el punto entregado como argumento.
  74. squares[x_aux][y_aux].set_mine();
  75. ++i;
  76. }
  77. }
  78. }
  79.  
  80. void Board::set_numbers() {
  81. //Calcula y guarda los números que van en el tablero (y que indican el número de minas que hay alrededor de un cuadrado)
  82. for(int x = 0; x < width; ++x)
  83. for(int y = 0; y < height; ++y) {
  84. //Con estos dos for recorro el tablero completo (se toman todas las combinaciones posibles de x e y)
  85.  
  86. if( !squares[x][y].is_mine() )
  87. //Si el cuadrado actual es una mina no es necesario asignarle un número.
  88. //Si no es una mina, cuento las minas alrededor y le asigno un número.
  89. //Recordar que x, y son las coordenadas del punto
  90. squares[x][y].set_value( count_surrounding_mines(x, y) );
  91. }
  92. }
  93.  
  94. void Board::show_empty_squares(int x, int y) {
  95. //Función recursiva que revisa si un cuadrado está vacío y lo muestra. En caso afirmativo revisa los que lo rodean y así sucesivamente.
  96. if(is_valid(x, y) && squares[x][y].get_value() == 0 && squares[x][y].is_hidden()) {
  97. squares[x][y].show();
  98. for(int x_axis = x-1; x_axis <= x+1; ++x_axis)
  99. for(int y_axis = y-1; y_axis <= y+1; ++y_axis) {
  100. //Con estos dos for puedo moverme por los 8 cuadrados circundantes
  101. if(is_valid(x_axis, y_axis) && squares[x_axis][y_axis].get_value() > 0 && !squares[x][y].is_mine())
  102. //Muestro los números que hayan
  103. squares[x_axis][y_axis].show();
  104. show_empty_squares(x_axis, y_axis);
  105. }
  106. }
  107. }
  108.  
  109. int Board::count_surrounding_mines(int x, int y) {
  110. //Cuenta el número de minas alrededor de un punto
  111. //Condición: el punto debe estar dentro del tabelero
  112. if( !is_valid(x, y) )
  113. throw std::runtime_error("No puedes contar las minas alrededor de un punto que se encuentra fuera del tablero.");
  114.  
  115. int count = 0;
  116. for(int x_axis = x-1; x_axis <= x+1; ++x_axis)
  117. for(int y_axis = y-1; y_axis <= y+1; ++y_axis)
  118. /*Recorro los 9 puntos, incluyendo el del centro. Pierdo un mínimo de eficiencia, pero el código queda más ordenado. Además, sé que el número del centro no es una mina, por lo que el resultado de la función no cambia*/
  119. if( is_valid(x_axis, y_axis) && squares[x_axis][y_axis].is_mine() )
  120. ++count;
  121. return count;
  122. }
  123.  
  124. int Board::shown_squares() {
  125. int total = 0;
  126.  
  127. for(int x = 0; x < width; ++x)
  128. for(int y = 0; y < height; ++y)
  129. if( !squares[x][y].is_hidden() && !squares[x][y].is_mine() )
  130. ++total;
  131.  
  132. return total;
  133. }
  134.  
  135. void Board::create(int param_width, int param_height, int param_mines) {
  136. //Crea el tablero de buscaminas con el tamaño adecuado, asigna las minas y asigna los números.
  137. //Condición: los argumentos deben ser positivos (se comprueba en las otras funciones)
  138. //Condición: la cantidad de minas debe ser positiva (se comprueba aquí)
  139. if(param_mines < 0) throw std::runtime_error("No puedes crear un tablero con un número negativo de minas");
  140. set_size(param_width, param_height); //Asigna al tablero el tamaño adecuado
  141. left_mines = param_mines;
  142. total_mines = param_mines;
  143. total_number_squares = param_width*param_height - param_mines;
  144. first_choosing = true;
  145. }
  146.  
  147. void Board::choose_square(int x, int y) {
  148. //Hace que el cuadrado elegido muestre su contenido.
  149. //Si el punto x, y está fuera del tablero, la función no hace nada.
  150. if( is_valid(x, y) ) {
  151. if( first_choosing ) {
  152. //Si es la primera vez que se elige un número, pongo las minas excepto en ese lugar y luego proceso la elección.
  153. //De esta manera se hace imposible perder en la primera jugada (es imposible elegir una mina el primer turno)
  154. set_mines(x, y);
  155. set_numbers();
  156. first_choosing = false;
  157. }
  158.  
  159. if(squares[x][y].get_value() == 0)
  160. //Si el cuadrado elegido está vacío (sin minas ni números)
  161. show_empty_squares(x, y);
  162. else
  163. //Si es un número o una mina
  164. squares[x][y].show();
  165. //En ambos casos, si el cuadrado estaba marcado como mina sumo uno al número de minas restantes
  166. if( squares[x][y].is_marked() ) ++left_mines;
  167. }
  168. }
  169.  
  170. void Board::mark_square(int x, int y) {
  171. //Marca un cuadrado con un signo para ayudar al usuario a identificar minas.
  172. //La función no hace nada si el punto x, y: está fuera del tablero / no está oculto / ya está marcado
  173. if( is_valid(x, y) && squares[x][y].is_hidden() && !squares[x][y].is_marked() ) {
  174. squares[x][y].mark();
  175. --left_mines;
  176. }
  177. }
  178.  
  179. void Board::mark_all_mines() {
  180. //Marca todas las minas que no estén marcadas. Así, cuando el usuario gana las puede ver en el tablero.
  181. for(int x = 0; x < width; ++x)
  182. for(int y = 0; y < height; ++y)
  183. if( squares[x][y].is_mine() )
  184. squares[x][y].mark();
  185. left_mines = 0;
  186. }
  187.  
  188. bool Board::no_squares_left() {
  189. //Calcula el número de cuadrados que están ocultos y no son minas. Devuelve true si no queda ninguno.
  190. return shown_squares() == total_number_squares;
  191. }
  192.  
  193. bool Board::is_valid(int x, int y) {
  194. //Devuelve true si el punto está dentro del tablero y false si no lo está
  195. return (-1 < x && x < width) && (-1 < y && y < height);
  196. }
  197.  
  198. Square Board::square(int x, int y) {
  199. //Función que retorna el objeto "Square" dadas las coordenadas x e y.
  200. //Condición: las coordenadas deben apuntar a un "Square" existente.
  201. if( !is_valid(x, y) )
  202. throw std::runtime_error("Board::square no acepta coordenadas de puntos inexistentes.");
  203. return squares[x][y];
  204. }

console_board.cpp
Código
  1. /* Clase Console_Board derivada de la clase Console
  2.  * Añade el método "print", que permite mostrar el tablero por consola */
  3.  
  4. #include "board.cpp"
  5.  
  6. class Console_Board : public Board {
  7. public: void print();
  8. };
  9.  
  10. void Console_Board::print() {
  11. std::cout << "     ";
  12. for(int i = 0; i < get_width(); ++i) {
  13. if(i < 9)
  14. std::cout << "  " << i+1;
  15. else
  16. std::cout << " " << i+1;
  17. }
  18. std::cout << std::endl << "     ";
  19. for(int i = 0; i < get_width(); ++i)
  20. std::cout << "___";
  21. std::cout << std::endl;
  22.  
  23. //Imprime los números de la izquierda (coordenadas del eje y)
  24. for(int y = 0; y < get_height(); ++y) {
  25.  
  26. if(y < 9)
  27. std::cout << "  " << y+1 << " |";
  28. else
  29. std::cout << ' ' << y+1 << " |";
  30.  
  31. //Imprimo el contenido del tablero. Es necesario que este for esté dentro del anterior.
  32. for(int x = 0; x < get_width(); ++x)
  33. if( square(x, y).is_hidden() ) {
  34. if( square(x, y).is_marked() ) std::cout << "  #";
  35. else std::cout << "  *";
  36. }
  37. else if(square(x, y).get_value() == 0) std::cout << "   ";
  38. else if(square(x, y).get_value() == 9) std::cout << "  X";
  39. else std::cout << "  " << square(x, y).get_value();
  40. std::cout << "\n\n";
  41. }
  42. }

square.cpp
Código
  1. /* Clase Square
  2.  * El tablero (clase Board) se forma a partir de cuadrados (clase Square).
  3.  * Un cuadrado puede contener una mina o un número. */
  4.  
  5. class Square {
  6. //Atributos
  7. int value;
  8. bool hidden;
  9. bool marked;
  10.  
  11. //Métodos públicos
  12. public:
  13. Square();
  14. inline int get_value() { return value; }
  15. inline bool is_mine() { return value == 9; }
  16. inline bool is_hidden() { return hidden; }
  17. inline bool is_marked() { return marked; }
  18. inline void set_mine() { value = 9; } //Asigna al cuadrado el valor correspondiente a una mina
  19. inline void set_value(int number) { value = number; }
  20. inline void show() { hidden = false; }
  21. inline void mark() { marked = true; }
  22. };
  23.  
  24. Square::Square() {
  25. //Esconde el valor del cuadrado (así, todo cuadrado que se crea viene escondido)
  26. hidden = true;
  27. marked = false;
  28. }

random_number.cpp
Código
  1. /* Clase Random_number
  2.  * Simplifica el cálculo de números "aleatorios"
  3.  *
  4.  *
  5.  * Includes necesarios para que esta clase funcione
  6.  * #include <cstdlib> -> Para usar rand()
  7.  * #include <time.h> -> Para usar time_t y time() */
  8.  
  9. class Random_Number {
  10. public:
  11. Random_Number();
  12. int get(int min, int max);
  13. };
  14.  
  15. Random_Number::Random_Number() {
  16. //Proceso necesario para después generar números (no entiendo exactamente cómo funciona)
  17. time_t seconds; time(&seconds);
  18. srand((unsigned int)seconds);
  19. }
  20.  
  21. int Random_Number::get(int min, int max) {
  22. //Devuelver un número "aleatorio" entre el rango dado
  23. return rand() % (max - min + 1) + min;
  24. }


« Última modificación: 15 Marzo 2013, 16:17 pm por Wofo » En línea

daryo


Desconectado Desconectado

Mensajes: 1.070



Ver Perfil WWW
Re: [C++] [Aporte] Buscaminas por consola
« Respuesta #1 en: 11 Marzo 2013, 21:47 pm »

excelente aporte seguro aprendere mucho de este code  ;-)


En línea

buenas
amchacon


Desconectado Desconectado

Mensajes: 1.211



Ver Perfil
Re: [C++] [Aporte] Buscaminas por consola
« Respuesta #2 en: 12 Marzo 2013, 16:16 pm »

Código:
[code=cpp]class Random_Number {
unsigned int seed;
 
public:
Random_Number();
int get(int min, int max);
};

Para que usas seed? *_*

Por otro lado:

Código
  1. throw "No puedes contar las minas alrededor de un punto que se encuentra fuera del tablero.";
Lanzar cadenas sueltas puede resultar complejo de capturar además pueden colisionar con las excepciones de otras clases.. Lo mejor es hacerte una clase para las excepciones:

Código
  1. struct Excepcion
  2.    {
  3.  
  4.        unsigned int N_Error;
  5.        string Mensaje;
  6.  
  7.        Excepcion(string mensaje,unsigned int Error) : Mensaje(mensaje),N_Error(Error) {};
  8.    };
  9.  

Siguiendo ese ejemplo:

Código
  1. throw Excepcion("No puedes contar las minas alrededor de un punto que se encuentra fuera del tablero.",FUERA_DE_RANGO);
  2. // Fuera de rango será una constante o una macro que definas

Se pueden hacer clases que hereden de esta si quieres ser más especifico (por ejemplo, Excepcion_Minas, Excepcion_Aleatorios...).

Otra cuestión es:

Código
  1. void render(Board board);

Entiendo que no la has metido dentro de la clase para que sea más general. No obstante creo, que al ser una función relacionada con el tablero debería ir dentro. Para asegurarnos la independencia de cout, lo que haremos es devolver un string:

Código
  1. std::string Board::ToString() // Devuelve un string con la representacion del campo
  2. {
  3.  std::stringstream Devolver;
  4.  
  5. //Muestra el tablero
  6. //Imprime los números de arriba (coordenadas del eje x)
  7. Devolver<<  "     ";
  8. for(int i = 0; i < this.get_width(); ++i) {
  9. if(i < 9)
  10. Devolver<<"  " << i+1;
  11. else
  12. Devolver<< " " << i+1;
  13. }
  14. Devolver<< std::endl << "     ";
  15. for(int i = 0; i < this.get_width(); ++i)
  16. Devolver<< "___";
  17. Devolver<< std::endl;
  18.  
  19. //Imprime los números de la izquierda (coordenadas del eje y)
  20. for(int y = 0; y < this.get_height(); ++y) {
  21.  
  22. if(y < 9)
  23. Devolver << "  " << y+1 << " |";
  24. else
  25. Devolver<< ' ' << y+1 << " |";
  26.  
  27. //Imprimo el contenido del tablero. Es necesario que este for esté dentro del anterior.
  28. for(int x = 0; x < this.get_width(); ++x)
  29. if(this.square(x, y).is_hidden() ) {
  30. if(this.square(x, y).is_marked() ) Devolver<< "  #";
  31. else Devolver << "  *";
  32. }
  33. else if(board.square(x, y).get_value() == 0) Devolver << "   ";
  34. else if(board.square(x, y).get_value() == 9)Devolver<< "  X";
  35. else Devolver << "  " << board.square(x, y).get_value();
  36. Devolver<< "\n\n";
  37. }
  38.  
  39.        return Devolver.str(); // Devolvemos el string
  40. }

El programador usuario podrá optar por mostrarlo con cout, interpetrarlo para una interfaz gráfica o bien usarlo en un archivo de depuración.

Por lo demás muy bueno y muy buen ordenado. Me gusta :)[/code]
« Última modificación: 12 Marzo 2013, 16:55 pm por amchacon » En línea

Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar
Wofo

Desconectado Desconectado

Mensajes: 168


Ver Perfil
Re: [C++] [Aporte] Buscaminas por consola
« Respuesta #3 en: 12 Marzo 2013, 17:37 pm »

Gracias amchacon por tus comentarios.

La verdad es que no me había dado cuenta de que ese "seed" estaba ahí. Habrá que eliminarlo.

Lo de las excepciones me parece una excelente idea, pero no entiendo por qué usas "struct" en vez de "class".

Por otro lado, meter el render dentro de la clase no me termina de convencer por la dificultad que significa procesar ese string para transformarlo en una interfaz visual. Creo que es excesivamente complicado (mucho más que usar los métodos que provee la clase Board). Quizá una buena alternativa sería crear una tercera clase que se llame Screen o algo por el estilo, donde esté el método render.

Gracias denuevo!
Wofo.
En línea

amchacon


Desconectado Desconectado

Mensajes: 1.211



Ver Perfil
Re: [C++] [Aporte] Buscaminas por consola
« Respuesta #4 en: 12 Marzo 2013, 18:05 pm »

Lo de las excepciones me parece una excelente idea, pero no entiendo por qué usas "struct" en vez de "class".
Cuando todos los miembros de un objeto son públicos, uso struct en vez de class... Simple manía, no hay ninguna otra razón.

Por otro lado, meter el render dentro de la clase no me termina de convencer por la dificultad que significa procesar ese string para transformarlo en una interfaz visual. Creo que es excesivamente complicado (mucho más que usar los métodos que provee la clase Board). Quizá una buena alternativa sería crear una tercera clase que se llame Screen o algo por el estilo, donde esté el método render.
Cierto para lo visual puede ser complicado pero aún así no lo descataría... Para el modo consola sería bastante sencillo la representacion (cout<<board.ToString(); ) y tener un "mapa" en modo texto puede ser útil para una depuración en una interfaz visual.

Puedes crear un método virtual "Dibujar", que el programador herede la clase y lo implemente...
« Última modificación: 12 Marzo 2013, 20:30 pm por amchacon » En línea

Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar
Wofo

Desconectado Desconectado

Mensajes: 168


Ver Perfil
Re: [C++] [Aporte] Buscaminas por consola
« Respuesta #5 en: 15 Marzo 2013, 11:18 am »

Me parece excelente la idea de crear una subclase. Acabo de hacerlo y de subir el code.

Las excepciones las hice usando la clase estándar runtime_exception (derivada de exception).

¡Muchas gracias!
« Última modificación: 15 Marzo 2013, 11:55 am por Wofo » En línea

Páginas: [1] Ir Arriba Respuesta Imprimir 

Ir a:  

WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines