Práctica 4 (30%)
A continuación se encuentra el enunciado de la práctica 4. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de teoría para tratar de resolver dudas de concepto.
1 Cambios
1.1 Versión 1.0.0
- Enunciado original.
2 Archivos de partida
Aquí se encuentran los archivos de partida. Descárgalos y utilízalos como base para resolver la práctica.
Los archivos de partida contienen una estructura básica de proyecto, con el Makefile, el programa principal de prueba y los ficheros .h, .cc y .tcc que deberás completar. En esta práctica, también debes desarrollar los .h.
Los archivos se encontrarán dentro de una carpeta llamada irp2-p4, y a su vez, los archivos de cabecera y de implementación estarán en una subcarpeta llamada src.
La estructura inicial será:
irp2-p4├── Makefile├── check.cc├── mainp4.cc├── uml| ├── uml.png| └── uml_simple.png└── src ├── color.h ├── pieza.h ├── pieza.cc ├── fichanormal.h ├── fichanormal.cc ├── fichadama.h ├── fichadama.cc ├── contenedor.h ├── contenedor.tcc ├── tablero.h └── tablero.ccLos archivos de partida incluyen todos estos archivos ya creados en src, pero vacíos, con solo algunos elementos básicos como la guarda de los ficheros y comentarios para añadir el nombre y el DNI.
3 Objetivos
El objetivo principal de esta práctica es aplicar los primeros conceptos de Programación Orientada a Objetos vistos en clase.
En esta práctica trabajaremos:
- Clases y objetos.
- Herencia.
- Clases abstractas.
- Enlace dinámico.
- Relaciones entre clases (composición).
- Clases genéricas con plantillas (
template). - Sobrecarga de operadores (
operator<<yoperator[]). - Funciones amigas (
friend). - Modularización del código en archivos
.h,.ccy.tcc. - Gestión básica de memoria dinámica.
También verás que el proyecto está preparado para separar en diferentes carpetas (src, bin) el código fuente y los archivos generados tras la compilación. Para ello, puedes fijarte en el archivo Makefile. Cuando utilices la herramienta make, los archivos generados tras la compilación se guardarán en una carpeta llamada bin. Esta carpeta no es necesario que exista previamente, ya que se crea automáticamente si no existe.
4 Enunciado
Primero se va a explicar en términos generales la idea del proyecto y algunos conceptos básicos de UML. Después describiremos las funciones a implementar.
4.1 Introducción
En esta práctica vas a implementar una versión simplificada del juego de las damas utilizando C++.
El programa deberá representar:
- Un tablero de 8x8.
- Fichas para dos jugadores (blancas y negras).
- Fichas normales.
- Fichas dama.
- El turno actual.
- Un historial de movimientos.
El objetivo no es implementar todas las reglas oficiales de las damas, sino construir una pequeña aplicación orientada a objetos que permita practicar los conceptos vistos en clase.
4.2 Reglas simplificadas del juego
El tablero tendrá tamaño 8x8.
Las filas y columnas se representarán internamente con números enteros entre 0 y 7.
- La fila
0representa la parte superior del tablero. - La fila
7representa la parte inferior del tablero. - La columna
0representa la columnaaen el tablero típico. - La columna
7representa la columnahen el tablero típico.
Para mostrar movimientos en el historial se usará una notación similar a la del ajedrez. Por ejemplo: a1, h8, c3, b6.
En esta notación se escribe primero la columna y después la fila:
- La columna se representa con una letra de la
aa lah, de izquierda a derecha en el tablero. - La fila se representa con un número del
1al8, de abajo hacia arriba en el tablero.
Fíjate en los siguientes ejemplos, convirtiendo los valores internos en la notación mencionada:
fila 7, columna 0 -> a1fila 0, columna 7 -> h8fila 5, columna 2 -> c3fila 2, columna 1 -> b6Esta información la necesitarás para implementar algunas partes de la práctica.
4.3 Colocación inicial de las fichas
Al inicializar el tablero se colocarán fichas normales en las casillas oscuras (ten en cuenta que la esquina inferior izquierda a1 es oscura), y los colores se intercalan a lo largo de todo el tablero.
La colocación inicial será:
- Fichas negras en las filas
8,7y6según la notación descrita previamente, que corresponden a las filas internas0,1y2. - Fichas blancas en las filas
1,2y3según la notación descrita previamente, que corresponden a las filas internas7,6y5.
Todas las fichas colocadas inicialmente serán fichas normales.
Visualizando el tablero, se quedaría así tras colocar las piezas iniciales (o para blancas, x para negras):
a b c d e f g h8 [ ][x][ ][x][ ][x][ ][x] 87 [x][ ][x][ ][x][ ][x][ ] 76 [ ][x][ ][x][ ][x][ ][x] 65 [ ][ ][ ][ ][ ][ ][ ][ ] 54 [ ][ ][ ][ ][ ][ ][ ][ ] 43 [o][ ][o][ ][o][ ][o][ ] 32 [ ][o][ ][o][ ][o][ ][o] 21 [o][ ][o][ ][o][ ][o][ ] 1 a b c d e f g h4.4 Movimiento de las fichas
En esta práctica se separan dos responsabilidades:
- Las piezas conocen la forma en la que se pueden mover y su posición actual.
- El tablero conoce el estado de las casillas y decide si un movimiento puede realizarse en el tablero.
Por tanto, las clases FichaNormal y FichaDama solo comprobarán si la forma del movimiento es válida para su tipo de pieza.
Las comprobaciones relacionadas con el estado del tablero se harán en el método mover de la clase Tablero.
Ficha normal
Una ficha normal:
- Se mueve una casilla en diagonal hacia delante.
- Las fichas blancas avanzan hacia filas internas menores, es decir, visualmente hacia la parte superior del tablero.
- Las fichas negras avanzan hacia filas internas mayores, es decir, visualmente hacia la parte inferior del tablero.
La ficha normal solo comprobará si la forma del movimiento es correcta.
La comprobación de si la casilla de destino está vacía será responsabilidad de la clase Tablero.
Ejemplo de movimiento simple de una ficha blanca:
c3 -> d4Para simplificar el desarrollo de la práctica, no se implementarán capturas.
Ficha dama
Una ficha dama:
- Se mueve una casilla en diagonal en cualquier dirección.
La dama solo comprobará si la forma del movimiento es correcta.
La comprobación de si la casilla de destino está vacía será responsabilidad de la clase Tablero.
Para simplificar la práctica, la ficha dama no se moverá varias casillas en diagonal. Solo podrá moverse una casilla en diagonal.
Este tipo de fichas tampoco implementarán capturas.
4.5 Promoción a dama
Cuando una ficha normal llega al extremo opuesto del tablero, se convierte automáticamente en dama.
- Una ficha blanca promociona al llegar a la fila
8según la notación del tablero, que corresponde a la fila interna0. - Una ficha negra promociona al llegar a la fila
1según la notación del tablero, que corresponde a la fila interna7.
La promoción se realizará después de mover correctamente la ficha.
Para promocionar una ficha, el tablero deberá sustituir la FichaNormal por una nueva FichaDama del mismo color y en la misma posición.
Una vez se realiza la promoción, el turno pasará al otro jugador.
No hay que registrar la promoción en el historial, pero sí el movimiento que se ha realizado como cualquier otro movimiento.
4.6 Reglas no implementadas
Para mantener la práctica sencilla, no se implementarán las siguientes reglas:
- Capturas o capturas múltiples.
- Detección automática de ganador.
- Detección de empate.
- Fin automático de la partida.
- Entrada interactiva por teclado (esto lo puedes hacer en el main para simularlo, pero no forma parte del código evaluable).
Por lo tanto, cíñete a lo que se describe en el enunciado. Aunque te sepas las reglas del juego, no estamos programando el juego completo.
5 Diagramas UML
A continuación se muestran dos diagramas UML que representan las clases principales de la práctica y sus relaciones.
El primer diagrama muestra la estructura completa de las clases, incluyendo atributos, funciones y relaciones.

Figura 1: Diagrama de clases UML completo que representa la estructura del proyecto que tienes que desarrollar.
El segundo diagrama muestra una versión simplificada, centrada únicamente en las relaciones entre clases. Este diagrama puede ayudarte a entender mejor qué clases heredan de otras, qué clases contienen objetos de otras clases y qué clases simplemente usan otras clases. Sin embargo, ten en cuenta que lo habitual es trabajar con el UML completo.

Figura 2: Diagrama UML simplificado con las relaciones principales entre las clases.
6 Cómo interpretar el UML
En el diagrama UML aparecen distintos tipos de relaciones.
6.1 Herencia
En UML, la herencia se representa mediante una línea con una flecha triangular hueca.
La flecha apunta hacia la clase base.
En esta práctica, las flechas de herencia apuntan hacia Pieza, porque Pieza es la clase base.
Esto significa que:
FichaNormalhereda dePieza.FichaDamahereda dePieza.
La clase Pieza será una clase abstracta. Las clases FichaNormal y FichaDama deberán proporcionar su propia implementación de los métodos get_nombre y es_movimiento_valido.
En esta práctica trabajaremos con herencia pública.
6.2 Relación de uso
En UML, una relación de uso se suele representar mediante una línea discontinua con una flecha.
Indica que una clase utiliza otra clase de forma temporal, pero no la contiene como atributo.
Por ejemplo, en esta práctica la clase Tablero usa las clases FichaNormal y FichaDama para crear piezas.
El tablero crea fichas normales al inicializar la partida.
También crea fichas dama cuando una ficha normal promociona.
Esta relación no significa que Tablero tenga un atributo de tipo FichaNormal o FichaDama.
6.3 Composición y cardinalidad
En UML, la composición se representa mediante una línea con un rombo negro en uno de sus extremos.
El rombo negro se coloca junto a la clase que contiene a la otra.
En esta práctica, el rombo negro aparece junto a la clase Tablero, porque el tablero contiene otros objetos.
Por ejemplo, el Tablero contiene piezas.
Esto significa que:
- El
Tablerotiene piezas. - Las piezas forman parte del tablero.
- El tablero es responsable de liberar la memoria de las piezas que sigan colocadas en él cuando se destruya.
Junto a la relación también aparecen números, como 1 y 0..64.
Esos números indican la cardinalidad o multiplicidad de la relación.
En este caso:
- El
1junto aTableroindica que estamos hablando de un tablero. - El
0..64junto aPiezaindica que ese tablero puede contener desde0hasta64piezas.
En C++, esta relación se implementará mediante el atributo privado casillas:
Pieza* casillas[8][8];Aunque en una partida inicial de damas no haya 64 piezas, el tablero tiene 64 casillas y, por tanto, puede contener como máximo una pieza por casilla. Considera la posibilidad de que el tablero pueda ser utilizado para otros juegos diferentes a las damas. Con un diseño adecuado, es posible extender el proyecto a otros juegos sin demasiada complicación. Piensa cómo harías que este programa funcione para ajedrez, o para Reversi.
El Tablero también contiene un historial de movimientos.
En el diagrama, esta relación también aparece con un rombo negro junto a Tablero.
Esto significa que el historial forma parte del tablero.
En C++, esta relación se implementará mediante el atributo privado historial:
Contenedor<std::string, 50> historial;En este caso, la cardinalidad indica que cada Tablero contiene exactamente un Contenedor.
Ese contenedor almacenará cadenas de texto con los movimientos realizados durante la partida.
6.4 Visibilidad
En UML se utilizarán los siguientes símbolos de visibilidad:
+ público- privadoPor ejemplo:
-simbolo: charindica que simbolo es un atributo privado de tipo char.
+get_simbolo(): charindica que get_simbolo es un método público que devuelve un dato de tipo char.
6.5 Funciones amigas
En esta práctica se utilizarán funciones amigas mediante la palabra reservada friend.
Los operadores de salida operator<< deberán implementarse como funciones amigas de las clases correspondientes.
Esto permitirá que el operador pueda acceder directamente a los atributos privados de la clase.
Por ejemplo:
friend std::ostream& operator<<(std::ostream& os, const Pieza& pieza);La palabra friend solo se escribe en la declaración de la función amiga dentro de la clase.
No se escribe friend en la implementación de la función.
Por ejemplo, en pieza.cc, la implementación empezará así:
std::ostream& operator<<(std::ostream& os, const Pieza& pieza) { // ...}En el caso de la clase genérica Contenedor<T, CAPACIDAD>, al tratarse de una plantilla, la implementación del operador de salida estará en los archivos de cabecera, normalmente en contenedor.tcc, pero se aplica la misma idea: friend se escribe en la declaración dentro de la clase, no en la implementación.
7 Clases y otros tipos
A continuación se describen las clases que debes implementar junto al enumerado Color.
7.1 Enumerado Color
El archivo color.h contendrá un tipo enumerado llamado Color.
Este tipo representa un color del juego. Se utilizará en dos contextos:
- Como atributo de cada pieza, para indicar si la pieza es blanca o negra.
- Como atributo del tablero, para indicar qué color tiene el turno actual.
Es decir, Color no es una clase que gestione el turno. Simplemente es el tipo de dato usado por el atributo turno de la clase Tablero.
Valores
| Valor | Descripción |
|---|---|
BLANCO | Representa las fichas blancas. |
NEGRO | Representa las fichas negras. |
El enumerado tendrá la siguiente forma:
7.2 Clase Pieza
La clase Pieza representa una pieza genérica del tablero.
Será la clase base de FichaNormal y FichaDama.
La clase Pieza será una clase abstracta. Las clases derivadas deberán proporcionar su propia implementación de los métodos get_nombre y es_movimiento_valido.
Atributos privados
| Atributo | Tipo | Descripción |
|---|---|---|
simbolo | char | Carácter usado para representar la pieza en el tablero. |
color | Color | Color de la pieza. |
fila | int | Fila actual de la pieza. |
columna | int | Columna actual de la pieza. |
Funciones
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
Pieza | char simbolo, Color color, int fila, int columna | Nada | Constructor. Inicializa los atributos de la pieza. |
~Pieza | Ninguno | Nada | Destructor virtual. Permite destruir correctamente objetos derivados a través de punteros a Pieza. |
get_simbolo | Ninguno | char | Devuelve el símbolo de la pieza. |
get_color | Ninguno | Color | Devuelve el color de la pieza. |
get_fila | Ninguno | int | Devuelve la fila actual de la pieza. |
get_columna | Ninguno | int | Devuelve la columna actual de la pieza. |
set_posicion | int fila, int columna | void | Cambia la posición interna de la pieza. |
get_nombre | Ninguno | std::string | Devuelve el nombre de la pieza. |
es_movimiento_valido | int fila_destino, int columna_destino | bool | Indica si la forma del movimiento es válida para ese tipo de pieza. |
Nota: Piensa por qué la clase Pieza tiene un destructor virtual (esto no se suele indicar en el diagrama UML).
Los métodos que no modifican el estado de la pieza deberán declararse como constantes. En el diagrama UML se indica cuáles son con el modificador << const >>
El método es_movimiento_valido solo comprueba la regla de movimiento propia de cada tipo de pieza:
- Una ficha normal se mueve una casilla en diagonal hacia delante.
- Una dama se mueve una casilla en diagonal en cualquier dirección.
Las comprobaciones relacionadas con el estado del tablero se realizarán en Tablero::mover.
Operador de salida
También se deberá implementar el operador de salida para poder mostrar una pieza por pantalla.
El operador de salida será una función amiga de la clase Pieza.
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
operator<< | std::ostream& os, const Pieza& pieza | std::ostream& | Función amiga que añade al flujo de salida la representación de la pieza. |
La declaración dentro de la clase será:
friend std::ostream& operator<<(std::ostream& os, const Pieza& pieza);Una pieza se mostrará entre corchetes:
[o][x][O][X]La representación será:
o -> ficha blanca normalx -> ficha negra normalO -> ficha blanca damaX -> ficha negra dama7.3 Clase FichaNormal
La clase FichaNormal hereda de Pieza.
Representa una ficha normal del juego de damas.
Funciones
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
FichaNormal | Color color, int fila, int columna | Nada | Constructor. Crea una ficha normal del color indicado en la posición indicada. |
get_nombre | Ninguno | std::string | Devuelve el nombre de la pieza. |
es_movimiento_valido | int fila_destino, int columna_destino | bool | Indica si la forma del movimiento es válida para una ficha normal. |
Constructor
El constructor deberá asignar automáticamente el símbolo correspondiente:
o -> ficha blanca normalx -> ficha negra normalNombre de la pieza
El método get_nombre devolverá el texto: Ficha
Este texto representará el tipo de pieza y se utilizará, por ejemplo, para construir las entradas del historial de movimientos:
Ficha blanca: b7 -> a8Movimiento
Una ficha normal blanca avanza hacia filas internas menores, es decir, hacia la parte superior del tablero.
Una ficha normal negra avanza hacia filas internas mayores, es decir, hacia la parte inferior del tablero.
El método es_movimiento_valido comprobará únicamente que la ficha normal se mueve una casilla en diagonal hacia delante.
No comprobará si la casilla de destino está vacía, ya que esa responsabilidad pertenece a Tablero.
En esta práctica no se implementan capturas.
7.4 Clase FichaDama
La clase FichaDama hereda de Pieza.
Representa una dama del juego de damas.
Aunque al inicializar el tablero todas las piezas serán fichas normales, las fichas dama aparecerán durante la partida cuando una ficha normal promocione al llegar al extremo opuesto del tablero.
Funciones
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
FichaDama | Color color, int fila, int columna | Nada | Constructor. Crea una dama del color indicado en la posición indicada. |
get_nombre | Ninguno | std::string | Devuelve el nombre de la pieza. |
es_movimiento_valido | int fila_destino, int columna_destino | bool | Indica si la forma del movimiento es válida para una dama. |
Constructor
El constructor deberá asignar automáticamente el símbolo correspondiente:
O -> ficha blanca damaX -> ficha negra damaNombre de la pieza
El método get_nombre devolverá el texto: Dama
Movimiento
El método es_movimiento_valido comprobará únicamente que la dama se mueve una casilla en diagonal en cualquier dirección.
No comprobará si la casilla de destino está vacía, ya que esa responsabilidad pertenece a Tablero.
Para simplificar la práctica, la dama no se moverá varias casillas en diagonal.
En esta práctica no se implementan capturas.
7.5 Clase genérica Contenedor<T, CAPACIDAD>
La clase Contenedor será una clase genérica implementada con plantillas.
Tendrá dos parámetros de plantilla:
template <typename T, size_t CAPACIDAD>Tindica el tipo de dato que se almacena.CAPACIDADindica el número máximo de elementos que puede almacenar.
En esta práctica, el tablero usará el contenedor para guardar el historial de movimientos:
Contenedor<std::string, 50> historial;Importante
No se debe usar std::vector dentro de Contenedor.
El contenedor deberá usar un array interno de tamaño fijo.
La implementación de una plantilla debe estar disponible en el momento de la compilación. Por este motivo, el archivo contenedor.h deberá incluir al final el archivo contenedor.tcc.
El archivo contenedor.tcc no se compila por separado. Debe considerarse como un archivo de cabecera auxiliar. Si usas make, esto ya lo tiene en cuenta.
Atributos privados
| Atributo | Tipo | Descripción |
|---|---|---|
nombre | std::string | Nombre del contenedor. |
elementos | T[CAPACIDAD] | Array interno donde se almacenan los elementos. |
num_elementos | size_t | Número actual de elementos almacenados. |
Funciones
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
Contenedor | const std::string& nombre | Nada | Constructor. Inicializa el nombre y deja el contenedor vacío. |
set_nombre | const std::string& nombre | void | Cambia el nombre del contenedor. |
get_nombre | Ninguno | const std::string& | Devuelve el nombre del contenedor. |
agregar | const T& elemento | bool | Añade un elemento al final del contenedor si queda espacio. |
operator[] | size_t i | T& | Devuelve una referencia modificable al elemento situado en la posición i. |
operator[] | size_t i | const T& | Devuelve una referencia constante al elemento situado en la posición i. |
size | Ninguno | size_t | Devuelve el número de elementos almacenados. |
capacidad | Ninguno | size_t | Devuelve la capacidad máxima del contenedor. |
vacio | Ninguno | bool | Devuelve true si el contenedor no tiene elementos. |
lleno | Ninguno | bool | Devuelve true si el contenedor ha alcanzado su capacidad máxima. |
Los métodos que no modifican el estado del contenedor deberán declararse como constantes (ya indicados en el UML).
Funcionamiento de agregar
El método agregar añadirá un elemento al final del contenedor si queda espacio disponible.
Si el elemento se añade correctamente, devolverá true.
Si el contenedor está lleno, no añadirá el elemento y devolverá false.
Al ser un array automático, tendrás que hacer uso del atributo num_elementos para saber cuántos elementos válidos hay dentro del array.
Operador de acceso operator[]
La clase Contenedor<T, CAPACIDAD> deberá sobrecargar el operador [] para permitir acceder a los elementos almacenados mediante su posición.
Se implementarán dos versiones:
T& operator[](size_t i);const T& operator[](size_t i) const;Ejemplo de uso:
Contenedor<std::string, 50> historial("Historial");
historial.agregar("Ficha blanca: c3 -> d4");
std::cout << historial[0] << std::endl;No será necesario comprobar si la posición i está dentro del rango válido. Se asumirá que quien usa el contenedor accede a posiciones correctas entre 0 y size() - 1. De esta manera, tendremos un comportamiento similar a un array estándar, en el que si ocurre esto, puede finalizar automáticamente el programa.
Operador de salida
También se deberá implementar el operador de salida para Contenedor.
El operador de salida será una función amiga de la clase Contenedor.
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
operator<< | std::ostream& os, const Contenedor<T, CAPACIDAD>& contenedor | std::ostream& | Función amiga que añade al flujo de salida el contenido del contenedor. |
Al tratarse de una plantilla, el operador de salida también deberá implementarse en los archivos de cabecera, no en un .cc.
Puede declararse como función amiga dentro de la clase Contenedor.
El operador deberá mostrar:
- El nombre del contenedor, si no es una cadena vacía.
- Cada elemento almacenado, uno por línea.
Por ejemplo, un historial podría mostrarse así:
HistorialFicha blanca: c3 -> d4Ficha negra: b6 -> c5Ficha blanca: e3 -> f47.6 Clase Tablero
La clase Tablero representa el tablero de juego.
Será la clase principal de la práctica.
Atributos privados
| Atributo | Tipo | Descripción |
|---|---|---|
casillas | Pieza* [8][8] | Matriz de punteros a piezas. |
turno | Color | Color del jugador que tiene el turno. |
historial | Contenedor<std::string, 50> | Historial de movimientos realizados. |
Funciones
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
Tablero | Ninguno | Nada | Constructor. Inicializa el tablero, el turno y el historial. |
~Tablero | Ninguno | Nada | Destructor. Libera la memoria de las piezas que queden en el tablero. |
inicializar | Ninguno | void | Coloca las fichas iniciales en el tablero. |
esta_dentro | int fila, int columna | bool | Indica si una posición está dentro del tablero. |
esta_vacia | int fila, int columna | bool | Indica si una posición está dentro del tablero y no contiene ninguna pieza. |
get_pieza | int fila, int columna | Pieza* | Devuelve la pieza situada en una posición, o nullptr si no hay pieza. |
mover | int fila_origen, int columna_origen, int fila_destino, int columna_destino | bool | Intenta mover una pieza desde una posición de origen hasta una posición de destino. |
get_turno | Ninguno | Color | Devuelve el color del turno actual. |
cambiar_turno | Ninguno | void | Cambia el turno de blanco a negro o de negro a blanco. |
get_historial | Ninguno | const Contenedor<std::string, 50>& | Devuelve el historial de movimientos. |
Los métodos que no modifican el estado del tablero deberán declararse como constantes.
Constructor
El constructor de Tablero deberá:
- Inicializar todas las casillas a
nullptr. - Inicializar el turno a
Color::BLANCO. - Inicializar el historial con el nombre
"Historial". - Llamar al método
inicializar.
Destructor
El destructor de Tablero deberá liberar la memoria de todas las piezas que sigan colocadas en el tablero.
Método inicializar
El método inicializar colocará las fichas iniciales en el tablero.
Se colocarán fichas negras en las filas internas 0, 1 y 2.
Se colocarán fichas blancas en las filas internas 5, 6 y 7.
Solo se colocarán fichas en las casillas donde se cumpla:
(fila + columna) % 2 == 1Método esta_dentro
El método esta_dentro devolverá true si la fila y la columna están dentro del tablero.
Es decir, si ambas están entre 0 y 7.
Método esta_vacia
El método esta_vacia devolverá true si la posición indicada está dentro del tablero y no contiene ninguna pieza.
Método get_pieza
El método get_pieza devolverá el puntero a la pieza situada en la posición indicada.
Si la posición está fuera del tablero o no hay ninguna pieza, devolverá nullptr.
Método mover
El método mover pertenece a la clase Tablero.
Este método intentará mover una pieza desde la posición indicada por fila_origen, columna_origen hasta la posición indicada por fila_destino, columna_destino.
El método mover deberá realizar las siguientes comprobaciones:
- La posición de origen debe estar dentro del tablero.
- La posición de destino debe estar dentro del tablero.
- En la posición de origen debe haber una pieza del jugador que tiene el turno.
- La posición de destino debe estar vacía.
- El movimiento debe ser válido para el tipo concreto de pieza.
- La pieza debe moverse a la posición de destino.
- Debe actualizarse la posición interna de la pieza.
- Debe añadirse el movimiento al historial.
- Si procede, la ficha debe promocionar a dama.
- Debe cambiarse el turno.
La comprobación del punto 5 se realizará mediante enlace dinámico llamando al método es_movimiento_valido de la pieza.
Si el movimiento se realiza correctamente, devolverá true.
Si el movimiento no es válido, no modificará el tablero ni el historial y devolverá false.
Promoción dentro de mover
Después de realizar un movimiento válido, el tablero deberá comprobar si la pieza movida debe promocionar a dama.
Una ficha normal blanca promociona si llega a la fila interna 0.
Una ficha normal negra promociona si llega a la fila interna 7.
En caso de promoción:
- Se deberá crear una nueva
FichaDamadel mismo color. - Se deberá sustituir la ficha normal por la nueva dama en la casilla.
- La posición interna de la nueva dama deberá coincidir con la casilla de destino.
Tras el movimiento y la promoción, el jugador no repetirá turno, sino que el turno pasa al otro jugador.
Historial de movimientos
Cada movimiento correcto deberá añadirse al historial como una cadena de texto.
Ejemplos:
Ficha blanca: c3 -> d4Ficha negra: b6 -> c5Ficha blanca: e3 -> f4El historial tendrá un máximo de 50 movimientos.
Si el historial está lleno, el movimiento podrá realizarse igualmente, pero no será necesario guardar más movimientos.
Operador de salida
También se deberá implementar el operador de salida para Tablero.
El operador de salida será una función amiga de la clase Tablero.
| Función | Parámetros | Devuelve | Descripción |
|---|---|---|---|
operator<< | std::ostream& os, const Tablero& tablero | std::ostream& | Función amiga que añade al flujo de salida la representación del tablero. |
La declaración dentro de la clase será:
friend std::ostream& operator<<(std::ostream& os, const Tablero& tablero);El tablero se mostrará con las columnas a hasta h y las filas 8 hasta 1. También debe mostrar el color del jugador que tiene el turno actual.
Ejemplo:
Turno: blanco
a b c d e f g h8 [ ][x][ ][x][ ][x][ ][x] 87 [x][ ][x][ ][x][ ][x][ ] 76 [ ][x][ ][x][ ][x][ ][x] 65 [ ][ ][ ][ ][ ][ ][ ][ ] 54 [ ][ ][ ][ ][ ][ ][ ][ ] 43 [o][ ][o][ ][o][ ][o][ ] 32 [ ][o][ ][o][ ][o][ ][o] 21 [o][ ][o][ ][o][ ][o][ ] 1 a b c d e f g h8 Salida esperada con el mainp4.cc proporcionado
Para probarlo, primero compila con:
makeDespués ejecuta:
make runEl ejecutable se encuentra en la carpeta bin.
La salida esperada para el archivo mainp4.cc será la siguiente:
======== ESTADO INICIAL ========Turno: blanco
a b c d e f g h8 [ ][x][ ][x][ ][x][ ][x] 87 [x][ ][x][ ][x][ ][x][ ] 76 [ ][x][ ][x][ ][x][ ][x] 65 [ ][ ][ ][ ][ ][ ][ ][ ] 54 [ ][ ][ ][ ][ ][ ][ ][ ] 43 [o][ ][o][ ][o][ ][o][ ] 32 [ ][o][ ][o][ ][o][ ][o] 21 [o][ ][o][ ][o][ ][o][ ] 1 a b c d e f g h
======== MOVIMIENTOS ========
======== ESTADO FINAL ========Turno: negro
a b c d e f g h8 [ ][x][ ][x][ ][x][ ][x] 87 [x][ ][x][ ][x][ ][x][ ] 76 [ ][ ][ ][x][ ][x][ ][x] 65 [ ][ ][x][ ][ ][ ][ ][ ] 54 [ ][ ][ ][o][ ][o][ ][ ] 43 [o][ ][ ][ ][ ][ ][o][ ] 32 [ ][o][ ][o][ ][o][ ][o] 21 [o][ ][o][ ][o][ ][o][ ] 1 a b c d e f g h
======== HISTORIAL ========HistorialFicha blanca: c3 -> d4Ficha negra: b6 -> c5Ficha blanca: e3 -> f49 Uso de make
El fichero Makefile incluye la configuración de la herramienta make con varias opciones que pueden resultarte útiles.
A continuación tienes una lista de las cosas que puedes hacer:
| Comando | Descripción |
|---|---|
make | Compila y enlaza el código generando el ejecutable p4 dentro de la carpeta bin. |
make run | Compila, enlaza y ejecuta la práctica. |
make runv | Compila, enlaza y ejecuta la práctica con Valgrind. |
make vstats | Muestra un resumen de pérdidas de memoria detectadas por Valgrind. |
make debug | Compila y abre el ejecutable con gdb en modo texto. |
make tgz | Borra archivos compilados y genera el archivo comprimido irp2-p4.tgz. |
make clean | Elimina los archivos compilados, como ejecutables y objetos. |
make check | Compila y ejecuta un corrector básico de interfaz usando check.cc. |
El corrector check.cc comprueba que las clases, métodos, parámetros y tipos de retorno principales existen y enlazan correctamente. No comprueba que la lógica del programa sea correcta.
Si has compilado tu programa y vuelves a compilar sin modificar nada del código, make detectará que no necesita compilar nada. Si aun así necesitas recompilar, puedes usar primero:
make cleany después:
makePara la ejecución con Valgrind necesitas tener instalado el programa valgrind.
Puedes instalarlo en Linux así:
sudo apt-get install valgrindValgrind te será muy útil para detectar:
- accesos a memoria incorrectos,
- uso de punteros no válidos,
- fugas de memoria al no liberar correctamente las piezas creadas dinámicamente.
11 Archivos
A continuación se listan los archivos de la práctica:
mainp4.cc: contiene algunas pruebas básicas del programa. Puedes modificarlo para hacer tus propias pruebas, aunque el corrector podrá usar otro archivo principal.src/color.h: contiene el enumeradoColor.src/pieza.h: cabecera de la clasePieza.src/pieza.cc: implementación de la clasePieza.src/fichanormal.h: cabecera de la claseFichaNormal.src/fichanormal.cc: implementación de la claseFichaNormal.src/fichadama.h: cabecera de la claseFichaDama.src/fichadama.cc: implementación de la claseFichaDama.src/contenedor.h: cabecera de la plantillaContenedor.src/contenedor.tcc: implementación de la plantillaContenedor. Aunque tiene extensión.tcc, también debe incluir una guarda de inclusión.src/tablero.h: cabecera de la claseTablero.src/tablero.cc: implementación de la claseTablero.Makefile: se recomienda su uso para compilar de forma sencilla.check.cc: archivo de comprobación básica de interfaz. Sirve para probar conmake checkque las clases, funciones, parámetros y tipos de retorno principales están definidos correctamente. No debes modificarlo.
Debes implementar los siguientes archivos, que se encuentran en la carpeta src:
color.hpieza.hpieza.ccfichanormal.hfichanormal.ccfichadama.hfichadama.cccontenedor.hcontenedor.tcctablero.htablero.ccEl archivo mainp4.cc se proporciona como ejemplo de prueba.
Puedes modificarlo durante el desarrollo para hacer tus propias pruebas, pero no debes depender de él para que la práctica sea correcta. El servidor de prácticas usará sus propios archivos de prueba.
Para probar la promoción durante el desarrollo, puedes crear temporalmente un tablero con una única ficha normal cerca del extremo opuesto. Esto sería fácil de probar si modificas temporalmente la función inicializar de Tablero, añadiendo por ejemplo una única ficha de cada color. Si haces este cambio, recuerda volver a modificarlo cuando vayas a entregar la práctica con la implementación que debes realizar.
11 Entrega. Requisitos técnicos
Requisitos que tiene que cumplir este trabajo práctico para considerarse válido y ser evaluado. Si no se cumple alguno de los requisitos, la calificación será cero.
- El archivo entregado se llama
irp2-p4.tgz, todo en minúsculas. - Dentro de dicho archivo habrá una carpeta llamada
irp2-p4. - Dentro de la carpeta
irp2-p4deberá haber una carpeta llamadasrc. - Dentro de la carpeta
srcdeberán estar, como mínimo, todos los archivos que debes implementar. - No es necesario entregar
Makefile,mainp4.ccnicheck.cc. Si se hace, no pasa nada. - El servidor de prácticas usará su propio
Makefiley sus propios archivos de prueba. - Todos los archivos
.hdeben tener guardas de inclusión con#ifndef,#definey#endif. - El archivo
contenedor.tcctambién debe tener guarda de inclusión. - El archivo
contenedor.hdebe incluircontenedor.tccal final, para que la implementación de la plantilla esté disponible al compilar. - El archivo
contenedor.tccno debe compilarse por separado; será incluido desdecontenedor.h. Considéralo como un archivo.h, no como un.cc. - La clase
Contenedorno debe usarstd::vector. - Los operadores de salida
operator<<deberán implementarse como funciones amigas (friend).
Se recomienda ejecutar make check antes de entregar para comprobar que la interfaz pública de la práctica compila correctamente. Sin embargo, que make check funcione no significa que la práctica esté correctamente resuelta. Solo indica que la interfaz pública básica coincide con la esperada y que no faltan implementaciones necesarias para enlazar el programa y compilar.
El archivo comprimido lo puedes crear así en el terminal, situándote en la carpeta padre de irp2-p4:
tar cfz irp2-p4.tgz irp2-p4También puedes emplear el archivo Makefile que tienes en la carpeta de la práctica para generar el fichero de la entrega:
make tgzAl descomprimir el archivo irp2-p4.tgz se debe crear un directorio de nombre irp2-p4, todo en minúsculas.
Dentro del directorio irp2-p4 estará la carpeta src, y dentro de ella estarán al menos los archivos necesarios para corregir la práctica.
Las clases y métodos implementados se llaman como se indica en el enunciado, respetando en todo caso el uso de mayúsculas y minúsculas. También es imprescindible respetar estrictamente los textos y los formatos de salida que se indican en este enunciado.
Al principio de todos los ficheros fuente (.h, .cc y .tcc) entregados y escritos por ti se debe incluir un comentario con el nombre y el NIF, DNI o equivalente de la persona que entrega la práctica, como en el siguiente ejemplo:
// NIF: 12345678-Z// NOMBRE: GARCIA PEREZ, LAURAUn error de compilación o de enlace implicará un cero en la práctica.
Se utilizará Valgrind para comprobar que no haya fugas de memoria en tu programa. Puedes instalarlo y probarlo antes de realizar la entrega para asegurarte de corregir posibles errores relacionados con la gestión de memoria dinámica.
En este caso, la estructura mínima del fichero que debes entregar es la siguiente:
irp2-p4.tgz└── irp2-p4 └── src ├── color.h ├── pieza.h ├── pieza.cc ├── fichanormal.h ├── fichanormal.cc ├── fichadama.h ├── fichadama.cc ├── contenedor.h ├── contenedor.tcc ├── tablero.h └── tablero.ccSi incluyes también Makefile, mainp4.cc o check.cc, no pasa nada, pero no son necesarios para la entrega. El corrector del servidor utilizará sus propios archivos.
Lugar y fecha de entrega: recuerda que las entregas se realizan siempre en, y solo en, https://pracdlsi.dlsi.ua.es en las fechas y condiciones allí publicadas. Puedes entregar la práctica tantas veces como quieras; solo se corregirá la última entrega. El usuario y contraseña para entregar prácticas es el mismo que se utiliza en UACloud.
13 Recomendaciones
Se recomienda avanzar en este orden:
- Implementar
Color. - Implementar
Pieza. - Implementar
FichaNormal. - Implementar
FichaDama. - Implementar
Contenedor<T, CAPACIDAD>. - Implementar
Tablero. - Probar la inicialización del tablero.
- Probar movimientos simples de
FichaNormal. - Probar la promoción a dama.
- Probar movimientos simples de
FichaDama. - Probar el historial.
Es recomendable compilar con frecuencia. No esperes a tener toda la práctica terminada para ejecutar make.
También es recomendable utilizar nombres de parámetros descriptivos. Por ejemplo, se preferirá fila_origen frente a abreviaturas como fo, para que el código sea más legible.
14 Detección de plagios/copias
En el Grado en Ingeniería Robótica, la Programación es una materia muy importante. La manera más efectiva de aprender a programar es programando y, por tanto, haciendo las prácticas correspondientes además de otros programas.
Tratar de aprobar las asignaturas de programación sin programar, copiando, es complicado y, además, te planteará problemas para seguir otras asignaturas y también en tu futura vida laboral.
En una asignatura como Programación-II es difícil que alguien que no ha hecho las prácticas supere los exámenes de teoría o el de recuperación de prácticas.
IMPORTANTE:
- Cada práctica debe ser un trabajo original de la persona que la entrega.
- En caso de detectarse indicios de copia de una o más prácticas, se tomarán las medidas disciplinarias correspondientes, informando a las direcciones de Departamento y de la EPS por si hubiera lugar a otras medidas disciplinarias adicionales.