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
#include <iostream> //Necesario para mostrar el contenido por la consola #include <stdexcept> //Necesario para manejo de errores #include <cstdlib> //Necesario para rand() en la clase Random_Number #include <time.h> //Necesario para la clase Random_Number #include <vector> //Necesario para usar vectores #include "console_board.cpp" using namespace std; int main() { try { Console_Board board; int level = -1; //Menú principal cout << "Bienvenido al Buscaminas. Elige el nivel que deseas jugar" << endl << "1- Principiante" << endl << "2- Intermedio" << endl << endl << "Escribe el numero del nivel: "; cin >> level; //Creo el nivel if(level == 2) board.create(16, 16, 40); //16x16 y 40 minas else board.create(9, 9, 10); //9x9 y 10 minas //Muestro las instrucciones cout << endl << "--INSTRUCCIONES--"; cout << endl << "Elegir un cuadrado: e x y (ejemplo: e 2 3)."; cout << endl << "Marcar un cuadrado como mina: m x y (ejemplo: m 2 3). " << endl << endl; system("pause"); //Preparo el game_loop y lo comienzo bool game_loop = true, victory = false; int x = 0, y = 0; char option = '-'; while(game_loop == true && victory == false) { system("CLS"); board.print(); cout << "Minas restantes: " << board.get_left_mines() << endl; cout << "Ultima eleccion: " << option << ' ' << x << ", " << y << endl << endl; cout << "Comando: "; cin >> option >> x >> y; /*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*/ cin.clear(); cin.ignore(99,'\n'); if(option == 'm') board.mark_square(x-1, y-1); else { board.choose_square(x-1, y-1); if( board.is_valid(x-1, y-1) && board.square(x-1, y-1).is_mine() ) game_loop = false; } if( board.no_squares_left() ) { board.mark_all_mines(); victory = true; } } //Muestro el tablero por última vez system("CLS"); board.print(); cout << endl << endl; if(victory == false) cout << "Has perdido!"; else cout << "Felicitaciones! Has ganado!"; cout << endl; return 0; } catch(exception& ex) { cerr << "Se ha procucido un error: " << ex.what(); return 1; } }
board.cpp
Código
/* Clase Board. * Maneja los elementos del juego Buscaminas. Consiste en un tablero (Board en inglés) * que tiene cuadrados (Squares). * * * Includes necesarios para el funcionamiento de esta clase * * #include <iostream> | Para usar cout * #include <vector> | Para usar vector * #include <stdexcept> | Para manejo de errores */ #include "square.cpp" #include "random_number.cpp" class Board { std::vector< std::vector<Square> > squares; int width, height; int left_mines; //Minas que quedan (va descontando cuando el usuario marca una) int total_mines; //Cantidad de cuadrados que contienen una mina int total_number_squares; //Cantidad de cuadrados que contienen un número bool first_choosing; //Métodos privados void set_size(int width, int height); void set_mines(int x, int y); void set_numbers(); void show_empty_squares(int x, int y); int count_surrounding_mines(int x, int y); int shown_squares(); //Métodos públicos public: void create(int width, int height, int param_mines); void choose_square(int x, int y); void mark_square(int x, int y); void mark_all_mines(); bool is_valid(int x, int y); bool no_squares_left(); Square square(int x, int y); inline int get_width() { return width; } inline int get_height() { return height; } inline int get_left_mines() { return left_mines; } }; void Board::set_size(int param_width, int param_height) { //Asigna el tamaño al tablero //Condición: los argumentos deben ser positivos if(param_width <= 0 || param_height <= 0) throw std::runtime_error("No puedes crear un tablero con ancho o alto negativos."); //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" squares.assign(param_width, std::vector<Square>(param_height)); //Guardo el tamaño del tablero en atributos para usarlo más tarde width = param_width; height = param_height; } void Board::set_mines(int x, int y) { //Asigna las minas de manera aleatoria a partir del atributo total_mines //No se pone ninguna mina en el punto x, y Random_Number random_number = Random_Number(); int x_aux; int y_aux; for(int i = 0; i < total_mines;) { x_aux = random_number.get(0, width-1); y_aux = random_number.get(0, height-1); if( !squares[x_aux][y_aux].is_mine() && !squares[x][y].is_mine() ) { //Si el punto aleatorio no es mina NI el punto entregado como argumento. squares[x_aux][y_aux].set_mine(); ++i; } } } void Board::set_numbers() { //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) for(int x = 0; x < width; ++x) for(int y = 0; y < height; ++y) { //Con estos dos for recorro el tablero completo (se toman todas las combinaciones posibles de x e y) if( !squares[x][y].is_mine() ) //Si el cuadrado actual es una mina no es necesario asignarle un número. //Si no es una mina, cuento las minas alrededor y le asigno un número. //Recordar que x, y son las coordenadas del punto squares[x][y].set_value( count_surrounding_mines(x, y) ); } } void Board::show_empty_squares(int x, int y) { //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. if(is_valid(x, y) && squares[x][y].get_value() == 0 && squares[x][y].is_hidden()) { squares[x][y].show(); for(int x_axis = x-1; x_axis <= x+1; ++x_axis) for(int y_axis = y-1; y_axis <= y+1; ++y_axis) { //Con estos dos for puedo moverme por los 8 cuadrados circundantes if(is_valid(x_axis, y_axis) && squares[x_axis][y_axis].get_value() > 0 && !squares[x][y].is_mine()) //Muestro los números que hayan squares[x_axis][y_axis].show(); show_empty_squares(x_axis, y_axis); } } } int Board::count_surrounding_mines(int x, int y) { //Cuenta el número de minas alrededor de un punto //Condición: el punto debe estar dentro del tabelero if( !is_valid(x, y) ) throw std::runtime_error("No puedes contar las minas alrededor de un punto que se encuentra fuera del tablero."); int count = 0; for(int x_axis = x-1; x_axis <= x+1; ++x_axis) for(int y_axis = y-1; y_axis <= y+1; ++y_axis) /*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*/ if( is_valid(x_axis, y_axis) && squares[x_axis][y_axis].is_mine() ) ++count; return count; } int Board::shown_squares() { int total = 0; for(int x = 0; x < width; ++x) for(int y = 0; y < height; ++y) if( !squares[x][y].is_hidden() && !squares[x][y].is_mine() ) ++total; return total; } void Board::create(int param_width, int param_height, int param_mines) { //Crea el tablero de buscaminas con el tamaño adecuado, asigna las minas y asigna los números. //Condición: los argumentos deben ser positivos (se comprueba en las otras funciones) //Condición: la cantidad de minas debe ser positiva (se comprueba aquí) if(param_mines < 0) throw std::runtime_error("No puedes crear un tablero con un número negativo de minas"); set_size(param_width, param_height); //Asigna al tablero el tamaño adecuado left_mines = param_mines; total_mines = param_mines; total_number_squares = param_width*param_height - param_mines; first_choosing = true; } void Board::choose_square(int x, int y) { //Hace que el cuadrado elegido muestre su contenido. //Si el punto x, y está fuera del tablero, la función no hace nada. if( is_valid(x, y) ) { if( first_choosing ) { //Si es la primera vez que se elige un número, pongo las minas excepto en ese lugar y luego proceso la elección. //De esta manera se hace imposible perder en la primera jugada (es imposible elegir una mina el primer turno) set_mines(x, y); set_numbers(); first_choosing = false; } if(squares[x][y].get_value() == 0) //Si el cuadrado elegido está vacío (sin minas ni números) show_empty_squares(x, y); else //Si es un número o una mina squares[x][y].show(); //En ambos casos, si el cuadrado estaba marcado como mina sumo uno al número de minas restantes if( squares[x][y].is_marked() ) ++left_mines; } } void Board::mark_square(int x, int y) { //Marca un cuadrado con un signo para ayudar al usuario a identificar minas. //La función no hace nada si el punto x, y: está fuera del tablero / no está oculto / ya está marcado if( is_valid(x, y) && squares[x][y].is_hidden() && !squares[x][y].is_marked() ) { squares[x][y].mark(); --left_mines; } } void Board::mark_all_mines() { //Marca todas las minas que no estén marcadas. Así, cuando el usuario gana las puede ver en el tablero. for(int x = 0; x < width; ++x) for(int y = 0; y < height; ++y) if( squares[x][y].is_mine() ) squares[x][y].mark(); left_mines = 0; } bool Board::no_squares_left() { //Calcula el número de cuadrados que están ocultos y no son minas. Devuelve true si no queda ninguno. return shown_squares() == total_number_squares; } bool Board::is_valid(int x, int y) { //Devuelve true si el punto está dentro del tablero y false si no lo está return (-1 < x && x < width) && (-1 < y && y < height); } Square Board::square(int x, int y) { //Función que retorna el objeto "Square" dadas las coordenadas x e y. //Condición: las coordenadas deben apuntar a un "Square" existente. if( !is_valid(x, y) ) throw std::runtime_error("Board::square no acepta coordenadas de puntos inexistentes."); return squares[x][y]; }
console_board.cpp
Código
/* Clase Console_Board derivada de la clase Console * Añade el método "print", que permite mostrar el tablero por consola */ #include "board.cpp" class Console_Board : public Board { public: void print(); }; void Console_Board::print() { std::cout << " "; for(int i = 0; i < get_width(); ++i) { if(i < 9) std::cout << " " << i+1; else std::cout << " " << i+1; } std::cout << std::endl << " "; for(int i = 0; i < get_width(); ++i) std::cout << "___"; std::cout << std::endl; //Imprime los números de la izquierda (coordenadas del eje y) for(int y = 0; y < get_height(); ++y) { if(y < 9) std::cout << " " << y+1 << " |"; else std::cout << ' ' << y+1 << " |"; //Imprimo el contenido del tablero. Es necesario que este for esté dentro del anterior. for(int x = 0; x < get_width(); ++x) if( square(x, y).is_hidden() ) { if( square(x, y).is_marked() ) std::cout << " #"; else std::cout << " *"; } else if(square(x, y).get_value() == 0) std::cout << " "; else if(square(x, y).get_value() == 9) std::cout << " X"; else std::cout << " " << square(x, y).get_value(); std::cout << "\n\n"; } }
square.cpp
Código
/* Clase Square * El tablero (clase Board) se forma a partir de cuadrados (clase Square). * Un cuadrado puede contener una mina o un número. */ class Square { //Atributos int value; bool hidden; bool marked; //Métodos públicos public: Square(); inline int get_value() { return value; } inline bool is_mine() { return value == 9; } inline bool is_hidden() { return hidden; } inline bool is_marked() { return marked; } inline void set_mine() { value = 9; } //Asigna al cuadrado el valor correspondiente a una mina inline void set_value(int number) { value = number; } inline void show() { hidden = false; } inline void mark() { marked = true; } }; Square::Square() { //Esconde el valor del cuadrado (así, todo cuadrado que se crea viene escondido) hidden = true; marked = false; }
random_number.cpp
Código
/* Clase Random_number * Simplifica el cálculo de números "aleatorios" * * * Includes necesarios para que esta clase funcione * #include <cstdlib> -> Para usar rand() * #include <time.h> -> Para usar time_t y time() */ class Random_Number { public: Random_Number(); int get(int min, int max); }; Random_Number::Random_Number() { //Proceso necesario para después generar números (no entiendo exactamente cómo funciona) time_t seconds; time(&seconds); srand((unsigned int)seconds); } int Random_Number::get(int min, int max) { //Devuelver un número "aleatorio" entre el rango dado return rand() % (max - min + 1) + min; }