Índex
Miguel A. Almarza
Departament d'Informàtica
IES Mare de Deu de la Merce

       

 
Capítol 9

Alguns exercicis amb programació dirigida a objectes.

  1. El joc del ximple.
    1. Descripció del joc.
    2. Estudi dels objectes adients.
    3. Implementació del programa.
  2. La classe String.

9.1. El joc del ximple.

Aquest exercici ja es va fer amb programació C i crec que és bo fer-ho ara amb programació dirigida a objectes per tal de veure les avantatges d'aquest tipus de programació.

9.1.1 Descripció del joc.


Aquest solitari és un joc que farem amb el joc de cartes de 40 cartes.

Analitzem els objectes que es necessiten per a aquest joc:

Estat inicial: Disposem de sis llistes on posar cartes: Maç, CartesSortides, Oros, Copes, Espases i Bastons.
El joc consisteix en treure les cartes de la llista Maç de dues en dues i apilalar-les a la llista CartesSortides. D'aquesta llista anirem passant cartes a les llistes d'oros, copes espases o bastos de manera ordenada, és a dir que les llistes dels pals estan ordenades de menor a major i totes les cartes d'una llista de pal són del mateix pal.

Per fer el programa de manera que pugui jugar un usuari farem un menú amb les següents opcions:
 

0 Acabar

1 Iniciar el joc
2 Treure dues cartes
3 Passar carta de cartes sortides a Oros
4 Passar carta de cartes sortides a Copes
5 Passar carta de cartes sortides a Espases
6 Passar carta de cartes sortides a Bastons
7 Torna les cartes sortides al Maç

9.1.2 Estudi dels objectes adients.

Com veiem en la figura tenim quatre tipus d'objectes:
Arxiu de capçalera carta.h
La classe Carta.
#include <iostream.h>
#include <string.h>
#include "boolean.h"

class Carta {
    int Codigo;
public:
    Carta() {Codigo=0;}
    Carta(int C){Codigo=C;}

    friend ostream &operator<< (ostream &stream, Carta &o);
    friend ostream &operator<< (ostream &stream, Carta *o);
    Boolean operator==(Carta &o);
    {
        if (Codigo==o.Codigo) return CIERTO;
        else return FALSO;
    }

    void CalculaPalo(char * Pal);
    void CalculaNombreCarta(char * Nombre);
};

ostream &operator<< (ostream &stream, Carta &o)
{
    char Carta[10], Palo[10];
    o.CalculaNombreCarta(Carta);
    o.CalculaPalo(Palo);
    stream << Carta << " de " << Palo;
    return stream;
}

ostream &operator<< (ostream &stream, Carta *o)
{
    char Carta[10], Palo[10];
    o->CalculaNombreCarta(Carta);
    o->CalculaPalo(Palo);
    stream << Carta << " de " << Palo;
    return stream;
}

void Carta::CalculaPalo(char * Pal)
{
    int p;
    char NombrePalo[4][8]={"Oros","Copas","Espadas","Bastos"};
    p=(int)(((float) Codigo)/10);
    strcpy(Pal,NombrePalo[p]);
}

void Carta::CalculaNombreCarta(char * Nombre)
{
    int n;
    char NombreCarta[10][8] =
    { "As","Dos","Tres","Cuatro","Cinco","Seis","Siete",
      "Sota","Caballo","Rey"};
    n = Codigo % 10;

    strcpy(Nombre, NombreCarta[n]);
}

Les cartes que treballarem seran les espanyoles, així tindrem quatre pals, oros, copes espases i bastons. Tindrem quaranta cartes i 10 per cadascuns dels pals.

Així la dada principal d'una carta serà un codi numèric del 0 al 39 de manera que la desena d'aquest codi indicarà el pal i la unitat d'aquest codi indica el nombre de la carta dins el pal.

Relació de funcions membre:
  • Dos constructors, un per defecte i altre que inicialitza el codi.
  • Dues sobrecàrregues de l'operador d'inserció <<
  • Sobrecàrrega de l'operador de comparació ==
  • Funcions per calcular el pal i el nombre d'una carta.
Les funcions membre que hem fet són les necessàries per fer funcionar després el solitari. Per fer un disseny complet hauríem d'afegir-hi moltes més funcions.




Fitxer de capçalera PILA.H
Fitxer de capçalera PILA.H: Classe genèrica NodoPila
#include <iostream.h>
#include "boolean.h"

template <class TipoDato> class NodoPila
{
    TipoDato Informacion;
    NodoPila<TipoDato> * Siguiente;
public:
    NodoPila() {Siguiente = NULL;}
    NodoPila(TipoDato V){Informacion=V;Siguiente = NULL;}

    NodoPila<TipoDato> * DameSiguiente() {return Siguiente;}
    void PonSiguiente(NodoPila * p){Siguiente = p;}
    void DameInformacion(TipoDato &c) {c=Informacion;}
    void PonInformacion(TipoDato c){Informacion=c;}

    friend ostream &operator<<(ostream &stream,NodoPila<TipoDato> o)
    {
        stream << o.Informacion;
        return stream;
    }
    friend ostream &operator<<(ostream &stream,NodoPila<TipoDato> *o)
    {
        stream << o->Informacion;
        return stream;
    }
};
Recordem que les classes per fer piles són les classes NodoPila i Pila, de manera que la classes Pila anava afegint o eliminant objectes NodoPila.

Segons veiem la classe NodoPila disposa dels constructors per defecte i amb pas de dades, dels mètodes per extreure dades i posar les dades en un node i l'operador d'extracció sobrecarregat.
Fitxer de capçalera PILA.H: Classe genèrica Pila
template <class TipoDato> class Pila
{
    NodoPila<TipoDato> *Principio;
public:
    Pila(){Principio=NULL;}
    NodoPila<TipoDato> * DamePrincipio() {return Principio;}

    Boolean Push(TipoDato c);
    Boolean Pop(TipoDato &c);

    Boolean EstaVacia()
    {
        if (Principio==NULL) return CIERTO;
        else return FALSO;
    }
};

template <class TipoDato> 
Boolean Pila<TipoDato>::Push(TipoDato c)
{
    NodoPila<TipoDato> *p;

    p=new NodoPila<TipoDato>;
    if (p)
    {
        p->PonInformacion(c);
        p->PonSiguiente(Principio);
        Principio=p;
        return CIERTO;
    }
    else return FALSO;
}

template <class TipoDato> 
Boolean Pila<TipoDato>::Pop(TipoDato &c)
{
    if (Principio!=NULL)
    {
        NodoPila<TipoDato> * Auxiliar;
        Auxiliar=Principio;
        Principio->DameInformacion(c);
        Principio = Principio->DameSiguiente();
        delete Auxiliar;

        return CIERTO;
    }
    else return FALSO;
}
La classe genèrica Pila té el constructor i les funcions Push i Pop que insereixen i extreuen respectivament dades de la pila.
Fitxer de capçalera PILA.H: Classe genèrica PilaV
template <class TipoDato> class PilaV : public Pila<TipoDato>
{
public:
    void PilaV<TipoDato>::VaciaPila();
};

template <class TipoDato> 
void PilaV<TipoDato>::VaciaPila()
{
    TipoDato c;
    while (!EstaVacia())
        Pop(c);
}
La classe pila no disposa de mètodes per buidar els objectes de tipus pila. Hem fet aquesta classe que hereta de forma pública de la classe Pila per tal d'afegir aquesta funcionalitat.

Podem dir que hem fet aquesta nova classe per mantenir un purisme, quasi bé exagerat. Altre solució més simple hagués estat afegir directament el mètode VaciaPila directament a la classe Pila.
El fitxer de capçalera auxiliar boolen.h
#ifndef _BOOLEAN_

#define _BOOLEAN_ 1
typedef int Boolean;
#define CIERTO 1
#define FALSO 0

#endif
Al llarg de tot el programa farem ús d'aquestes constants booleanes




La classe JuegoDelTonto i el programa principal.
#include "pila.h"
#include "cartas.h"
#include "boolean.h"

#include <STDLIB.H>
#include <conio.h>

class JuegoDelTonto
{
    PilaV<Carta> Mazo;
    PilaV<Carta> Palo[4];
    PilaV<Carta> Deposito;
    int NumeroCartasASacar;

public:
    JuegoDelTonto() { IniciaJuego(); NumeroCartasASacar =2;}

    void IniciaJuego();
    void PresentaDatos();
    void PasaCartaDePila(PilaV<Carta> &Origen,PilaV<Carta> &Destino)
    {
        Carta Trans;
        if (!Origen.EstaVacia())
        {
            Origen.Pop(Trans);
            Destino.Push(Trans);
        }
    }
    void PasaDepositoAMazo();
    void SacaCartas();
    void PideCartasASacar();
    void Juega();
};

void JuegoDelTonto::IniciaJuego()
{
    int Numero[40];
    int i,j;
    Boolean Sortear;
    NumeroCartasASacar =2;

    Mazo.VaciaPila();
    for(i=0;i<4;i++) Palo[i].VaciaPila();
    Deposito.VaciaPila();

    randomize();
    for (i=0;i<40;i++)
    {
        Sortear=CIERTO;
        while (Sortear)
        {
            Numero[i]=random (40);
            Sortear=FALSO;
            for(j=0;j<i;j++) if (Numero[i]==Numero[j]) Sortear=CIERTO;
        }
    Mazo.Push(Numero[i]);
    }
}

void JuegoDelTonto::PideCartasASacar()
{
    gotoxy(40,1);
    cout << "Dame el numero de cartas a sacar: ";
    cin >> NumeroCartasASacar;
}

void JuegoDelTonto::PasaDepositoAMazo()
{
    Carta Trans;
    while (!Deposito.EstaVacia())
    {
        Deposito.Pop(Trans);
        Mazo.Push(Trans);
    }
}

void JuegoDelTonto::SacaCartas()
{
    int i;
    Carta Trans;
    for (i=0;i<NumeroCartasASacar;i++) PasaCartaDePila(Mazo,Deposito);
}

void JuegoDelTonto::PresentaDatos()
{
    gotoxy(3,14); cout << "Mazo:                      ";
    gotoxy(3,15); cout << "Oros:                      ";
    gotoxy(3,16); cout << "Copas:                     ";
    gotoxy(3,17); cout << "Espadas:                   ";
    gotoxy(3,18); cout << "Bastos:                    ";
    gotoxy(33,14);cout << "Deposito:                        ";

    gotoxy(40,1);
    cout << "        Número de cartas a sacar: ";
	cout << NumeroCartasASacar;

    gotoxy(12,14);
	if (!Mazo.EstaVacia()) cout << Mazo.DamePrincipio();
    for(int i=0; i <4; i++)
    {
        gotoxy(12,15+i);
        if (! Palo[i].EstaVacia()) cout << Palo[i].DamePrincipio();
    }
    gotoxy(43,14);
    if (!Deposito.EstaVacia()) cout << Deposito.DamePrincipio();
}


void JuegoDelTonto::Juega()
{
    int Opcion=0;
    Boolean Acabar = FALSO;

    clrscr();
    gotoxy(1,1);
    cout << "1 Iniciar juego" << endl;
    cout << "2 Saca cartas" << endl;
    cout << "3 Mover del mazo a oros" << endl;
    cout << "4 Mover del mazo a copas" << endl;
    cout << "5 Mover del mazo a espadas" << endl;
    cout << "6 Mover del mazo a bastos" << endl;
    cout << "7 Pasar deposito a mazo" << endl;
    cout << "8 Numero de cartas a sacar" << endl;
    cout << "9 Salir" << endl;
    while (!Acabar)
    {
        gotoxy(1,10);
        cin >> Opcion;
        switch (Opcion)
        {
            case 1 : IniciaJuego(); break;
            case 2 : SacaCartas();break;
            case 3: case 4: case 5: case 6:
                     PasaCartaDePila(Deposito,Palo[Opcion-3]); break;
            case 7 : PasaDepositoAMazo(); break;
            case 8 : PideCartasASacar(); break;
            case 9 : Acabar=CIERTO; break;
        }
        PresentaDatos();
    }
}
La classe JuegoDelTonto és la classe que ens permet jugar a aquest joc.

Dades:
  • Les piles Mazo i Deposito: La pila Mazo serà la pìla on posarem les cartes desordenades per treure'n cartes d'una en una, o de dues en dues ...., i posar-les en la pila Deposito.
  • El vector de quatre piles Palo: Cadascuna d'aquestes piles correspon a un pal i rebrà una carta de la pila Deposito quan calgui.
  • NumeroCartasASacar: És el nombre de cartes que sortiran de la pila Mazo vers la pila Deposito en una jugada del jugador.
Mètodes:
  • JuegoDelTonto(): És un constructor que inicialitza el joc, cridant a la funció membre IniciaJuego() i posa a 2 les cartes a sacar.
  • IniciaJuego(): Posa en el Mazo les quaranta cartes desordenades i buida la resta de les piles del joc. No canvia el nombre de cartes a sacar.
  • PasaDeDepositoAMazo(): Passa totes les cartes de la pila Deposito a la Pila Mazo.
  • SacaCartas(): Passa NumeroCartasASacar de la pila Mazo la pila Deposito si hi ha cartes en la pila Mazo.
  • PideCartasASacar(): Demana al jugador el nombre de cartes que han de treure's en cada jugada (per part de la funció SacaCartas()).
  • PasaCartasDePila(): És una funció auxiliar que rep dues piles i canvia la primera carta d'una pila a l'altra.
  • PresentaDatos(): Posa a la pantalla la primera carta de cada pila.
  • Juega(): Presenta un menú que permet triar entre una de les possibles accions que té aquest joc, incloent les accions d'iniciar un joc i de finalitzar el joc.


9.1.3 Implementació del programa.

Si ens fixem el programa principal de la funció main només declara un objecte de la classe JuegoDelTonto i lo posa a jugar. És a dir, que l'objecte de tipus JuegoDelTonto és totalment autònom.

També veiem que des de la funció principal podem declara més d'un joc i establir un mecanisme que ens permeti passar d'un dels jocs a l'altre.

Si tenim unes classes de finestres emergents, tipus Windows, podrem tenir més d'un joc, un a cada finestra.

Programa del juego del tonto
main()
{
    JuegoDelTonto Juego1;
    Juego1.Juega();

    return 0;
}


2. La classe String.

Implementació d'una classe per treballar amb cadenes de caràcters.

La classe String.
#include <iostream.h>
#include <string.h>
#include <stdlib.h>

class ClaseString
{
	char * Punter;
	int Longitud;
public:
	ClaseString();
	ClaseString(char *Cadena);
	ClaseString(const ClaseString &obClaseString);

	~ClaseString() {delete [] Punter;}

	friend ostream &operator<<(ostream &Flux, ClaseString &obClaseString);
	friend istream &operator>>(istream &Flux, ClaseString &obClaseString);

	ClaseString operator=(ClaseString &obClaseString);
	ClaseString operator=(char *Cadena);

	ClaseString operator+(ClaseString &obClaseString);
	ClaseString operator+(char *Cadena);
	friend ClaseString operator+(char *Cadena, ClaseString &obClaseString);

	int operator==(ClaseString &obClaseString) {return !strcmp(Punter,obClaseString.Punter);}
	int operator!=(ClaseString &obClaseString) {return strcmp(Punter,obClaseString.Punter);}
	int operator<(ClaseString &obClaseString) {return strcmp(Punter,obClaseString.Punter)<0;}
	int operator>(ClaseString &obClaseString) {return strcmp(Punter,obClaseString.Punter)>0;}
	int operator<=(ClaseString &obClaseString) {return strcmp(Punter,obClaseString.Punter)<=0;}
	int operator>=(ClaseString &obClaseString) {return strcmp(Punter,obClaseString.Punter)>=0;}


	int operator==(char *Cadena) {return !strcmp(Punter,Cadena);}
	int operator!=(char *Cadena) {return strcmp(Punter,Cadena);}
	int operator<(char *Cadena) {return strcmp(Punter,Cadena)<0;}
	int operator>(char *Cadena) {return strcmp(Punter,Cadena)>0;}
	int operator<=(char *Cadena) {return strcmp(Punter,Cadena)<=0;}
	int operator>=(char *Cadena) {return strcmp(Punter,Cadena)>=0;}

	int Long(){return strlen(Punter);}
	void HazCadena(char * Cadena) { strcpy(Cadena,Punter);}

	operator char * () {return Punter;} //Cast hacia char *
};

ClaseString::ClaseString()
{
	Longitud=1;
	Punter = new char[Longitud];
	if (!Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	Punter[0]='\0';
}

ClaseString::ClaseString(char *Cadena)
{
	Longitud=strlen(Cadena)+1;
	Punter = new char[Longitud];
	if (!Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	strcpy(Punter,Cadena);
}

ClaseString::ClaseString(const ClaseString &obClaseString)
{
	Longitud=strlen(obClaseString.Punter) + 1;
	Punter = new char[Longitud];
	if (!Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	strcpy(Punter,obClaseString.Punter);
}

ostream &operator<<(ostream &Flux, ClaseString &obClaseString)
{
	Flux << obClaseString.Punter;
	return Flux;
}

istream &operator>>(istream &Flux, ClaseString &obClaseString)
{
	char CadenaIn[1024];
	int Longitud;

	for(Longitud=0; Longitud < 1024; Longitud++)
	{
		Flux.get(CadenaIn[Longitud]);
		if (CadenaIn[Longitud]=='\n') break;
		if ((CadenaIn[Longitud]=='\b') && (Longitud)) Longitud--;
	}
	CadenaIn[Longitud] = '\0';

	if (Longitud > obClaseString.Longitud)
	{
		delete obClaseString.Punter;
		obClaseString.Punter = new char[Longitud + 1];
		if (!obClaseString.Punter)
		{
			cout << "Error. No puedo alojar ClaseString";
			exit(1);
		}
		obClaseString.Longitud=Longitud;
	}
	strcpy(obClaseString.Punter, CadenaIn);
	return Flux;
}

ClaseString ClaseString::operator=(ClaseString &obClaseString)
{
	ClaseString obTemporal(obClaseString);

	if (Longitud < obClaseString.Longitud)
	{
		delete Punter;
		Punter = new char[obClaseString.Longitud+1];
		if (!Punter)
		{
			cout << "Error. No puedo alojar ClaseString";
			exit(1);
		}
		Longitud = obClaseString.Longitud;
	}
	strcpy(Punter,obClaseString.Punter);

	return obTemporal;
}

ClaseString ClaseString::operator=(char *Cadena)
{
	int L = strlen(Cadena);
	if (Longitud < L)
	{
		delete Punter;
		Punter = new char[L+1];
		if (!Punter)
		{
			cout << "Error. No puedo alojar ClaseString";
			exit(1);
		}
		Longitud = L;
	}
	strcpy(Punter,Cadena);

	return * this;
}

ClaseString ClaseString::operator+(ClaseString &obClaseString)
{
	ClaseString obTemporal;
	int L = Longitud + obClaseString.Longitud +1;

	delete obTemporal.Punter;
	obTemporal.Punter = new char[L+1];
	if (!obTemporal.Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	obTemporal.Longitud = L;
	strcpy(obTemporal.Punter,Punter);
	strcat(obTemporal.Punter,obClaseString.Punter);

	return obTemporal;
}

ClaseString ClaseString::operator+(char *Cadena)
{	ClaseString obTemporal;
	int L = Longitud + strlen(Cadena) + 1;

	delete obTemporal.Punter;
	obTemporal.Punter = new char[L+1];
	if (!obTemporal.Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	obTemporal.Longitud = L;
	strcpy(obTemporal.Punter,Punter);
	strcat(obTemporal.Punter,Cadena);

	return obTemporal;
}

ClaseString operator+(char *Cadena, ClaseString &obClaseString)
{
	ClaseString obTemporal;
	int L = strlen(Cadena) + obClaseString.Longitud +1;

	delete obTemporal.Punter;
	obTemporal.Punter = new char[L+1];
	if (!obTemporal.Punter)
	{
		cout << "Error. No puedo alojar ClaseString";
		exit(1);
	}
	obTemporal.Longitud = L;
	strcpy(obTemporal.Punter,Cadena);
	strcat(obTemporal.Punter,obClaseString.Punter);

	return obTemporal;
}