Nesneye Yönelik Programlamayla İlgili Bir Çalışma

ODTÜ(KKK) ‘de geçtiğimiz sene verilmiş güzel hazırlanmış programlama ödevi önüme gelmişti. Hoşuma gittiği için istenilenleri uyguladım. Benzer ödevlerle karşılaşanların kullanımı için paylaşıyorum.

Hocayı da tebrik etmek istiyorum. Gerçekten ödevi kaliteli bir şekilde hazırlamış ve öğrenciye sunmuş.

Ödevde istenilenleri atlamak ve çözüme geçmek için tıklayabilirsiniz.

Ödevde verilenler şu şekilde:

” In this assignment you are asked to implement a very simple game simulation. In this game there is 1 player and several obstacles. In the game loop, a player is either wandering or interacting with an obstacle. When the obstacle the player faces is inactive, the player wanders. The game ends when all the obstacles are inactive. We are providing a main method that implements the game loop.  (This scenario is already implemented, see section Game Simulation Code)

The game has two modes: nice and ugly. In nice mode, the player is a kitten interacting with puzzles. Interacting with a puzzle means solving the puzzle. When a kitten is wandering around she sings “meow”.

In ugly mode, the player is a warrior and interacting with monsters. Interaction with a monster means fighting with the monster. In a fight, a warrior hits the monster several times and then kills it. When a warrior is wandering around he sings “of man and steel”.

 

Goal: Use of abstract classes and virtual functions

 

SPECIFICATION: (Özellikler)

 

You are asked to implement subclasses of 3 (abstract) classes given below.

 

1) Abstract class Player

class Player{

public:

void wander(){       sing();          }

virtual void sing()=0;

virtual void interact(Obstacle * o)=0;

};

 

Player class will have 2 subclasses: Kitten and Warrior.

class Kitten:

  1. sing() method of Kitten will print “meow”  on screen.
  2. Have a public method void solve(Puzzle *p) which calls p->solve();
  3. The interact(Obstacle *o) method will call the  solve method explained above in (2).

class Warrior:

  1. sing() method of Warrior will print “of man and steel”  on screen.
  2. The interact(Obstacle *o) method will call the fight method explained below.
  3. Warrior has an extra member variable of type Feature as private(see below). There is a composition relationship between a warrior and a Feature instance. I.e. you need to consider creating a Feature during construction of the warrior.

struct Feature { int stamina; int strength;}

  1. Have a public method void fight(Monster *m) in which the warrior hits the monster several times until the strength of the warrior is greater than the stamina of the monster m. Then kill the monster. Hitting a monster means calling the Monster::hit(int n) where n is the strength of the warrior. Similarly, killing a monster is calling Monster::kill(int n) where n is the strength of the warrior

 

2) class Obstacle

Obstacle class will have 3 member variables: 1) to hold the status (active or not) 2) a static member variable that will count the number of instances. 3) id that uniquely identifies the instance.

Obstacle class will have the following 2 public methods (besides constructor)

Obstacle::Obstacle(){status=1;}

bool Obstacle:: isActive() {return status;}

Obstacle class will have 2 subclasses: Puzzle and Monster

class Puzzle:

  1. Constructor will print “New Puzzle! id:” on screen
  2. solve method will print “puzzle X solved! Congratulations” where X is the id of the puzzle and set the status to inactive.

class Monster:

  1. constructor will print “”New Monster! Id:” on screen
  2. void kill(int n) method will print “monster X is dead! Congratulations.” Where X is the id, and set the status to inactive. This method is public. This method throws an exception if n<= stamina.
  3. Monster has an extra member variable of type Feature as private (see Warrior specification). There is a composition relationship between a monster and a Feature instance. I.e. you need to consider creating a Feature during construction of the monster.
  4. void hit(int n) method will print “monster X says ouch!” where X is the id of the monster. This method will decrease the stamina of the monster by n/3 points.
  5. Have  a public int getStrength() method that returns the current strength of the monster.

3) Abstract class Factory

class Factory{

public:

virtual Player * createPlayer()=0;

virtual Obstacle * createObstacle()=0;

};

Factory class is used in the game to create Player and Obstacle instances.

Implement 2 subclasses: 1) NiceFactory class will create only Kittens and Puzzles.

2) UglyFactory will create only Warriors and Monster.

Notice that, because of these factory classes, no game can create kittens facing monsters.

USAGE:

 

Game Simulation Code

 

#include <cstdlib>#include <iostream>#include <vector>#include “Factory.h”using namespace std;void playGame(Factory *factory){

/**set up the game*/

int cnt=5;

vector<Obstacle*> obstacle;

for(int i=0;i<cnt;i++)

obstacle.push_back(factory->createObstacle());

Player  *player=factory->createPlayer();

/*game loop*/

while(cnt>0){

player->wander();

int index=rand()%5;

if(obstacle[index]->isActive()){

player->interact(obstacle[index]);

cnt–;

}else{

player->wander();

}

}

cout<<“game over”<<endl;

}

int main(int argc, char *argv[])

{

Factory *factory=new NiceFactory();

playGame(factory);

cout<<“Level UP”<<endl;

factory=new UglyFactory();

playGame(factory);

system(“PAUSE”);

return 0;

}

 

 

Sample Output

solution

Çözüm:

Hoca ödevi zorlaştırmak için değil yardımcı olmak için uzun tutmuş. Ben burada tüm sınıfları yazmayacağım hepsinin kodunu vereceğim ama önemli gördüğüm yerleri uygulayacağım.

Öncelikle verilenlere göre kaba taslak bir UML diyagramı çiziyoruz:

uml
Burada Factory Abstract sınıfının ismi factory verilerek öğrencilere yardımcı olunmuş. Hemen factory design patternin nasıl uygulandığına bakarak burdaki boşlukları doldurabilirler. NiceFatory ve UglyFactory aynı şekilde çalışmaktadır. Biri oyuncu olarak kitten üretirken diğeri warrior üretmektedir. Üretilen engel de buna göre olmaktadır. Birinde puzzle üretilirken diğerinde monster üretilmektedir. Kitten, puzzle çözecek, warrior da monsterla savaşacaktır.

Factory sanal sınıfını ve NiceFactory sınıfını uygulayalım.

Factory.h

[code]

#include
#include
#ifndef FACTORY_H
#define FACTORY_H

class Factory
{
public:
Factory();
virtual ~Factory();
virtual Player* createPlayer()=0;
virtual Obstacle* createObstacle()=0;
protected:
private:
};

#endif // FACTORY_H

[/code]

NiceFactory.h

[code]

#include
#include
#include
#ifndef NICEFACTORY_H
#define NICEFACTORY_H

class NiceFactory:public Factory
{
public:
NiceFactory();
virtual ~NiceFactory();
Player* createPlayer();
Obstacle* createObstacle();
protected:
private:
};

#endif // NICEFACTORY_H

[/code]

NiceFactory.cpp

[code]

#include “NiceFactory.h”
#include

NiceFactory::NiceFactory()
{
//ctor
}

NiceFactory::~NiceFactory()
{
//dtor
}

Player* NiceFactory::createPlayer()
{
return new Kitten;

}

Obstacle* NiceFactory::createObstacle()
{
return new Puzzle;
}

[/code]

Factory design patterni bize burada çok büyük bir esneklik kazandırmış oldu. Factory sınıfından implemente ettiğimiz NiceFactory ve UglyFactory kendi oyuncularını ve engellerini oluşturacaktır.

Obstacle sınıfı, puzzle veya monster için temel bir sınıftır. Ödevde puzzle ve monster için unique id tanımlanması istenmiş. Üretilen oyunculara ait ve başka fonksiyonlar tarafından değiştirilemez olmalıdırlar. Uygulaması aşağıdaki gibidir:

Obstacle.h

[code]

#ifndef OBSTACLE_H
#define OBSTACLE_H

class Obstacle
{
public:
Obstacle();
virtual ~Obstacle();
bool isActive();
bool status;
int getUid();

protected:
//static unsigned int id;
private:
const int uid;
static int newUid;

};

#endif // OBSTACLE_H

[/code]

Obstacle.cpp

[code]

#include “Obstacle.h”
#include
using namespace std;
//unsigned int Obstacle::id = 0;
int Obstacle::newUid = 0;//static için atama
Obstacle::Obstacle():uid(newUid++) //const için atama
{
status = true;
}

Obstacle::~Obstacle()
{
//dtor
}

bool Obstacle::isActive()
{
return status;
}

int Obstacle::getUid()
{
return uid;
}

[/code]

Player Abstract sınıfında, saf sanal fonksiyon olan interact’ın parametresi Obstacle sınıfındandır. Interact metodunun içinde çağrılacak solve veya fight metodunun obstacle’dan türetilmiş bir parametresi vardır. Burada bir tür dönüşümü yapmak gerekmektedir. Obstacle’ın ne olacağı yani puzzle mı monster mı olacağı run-time olarak belirlendiği için buna göre cast yapmak gerekmektedir. C++ programlama dilinde run-time casting yapılabiliyor. Warrior sınıfında böyle bir örnek bulunmaktadır. dynamic_cast<Monster*> (Obstacle* o)  şeklinde kullanılmıştır.

Warrior.h

[code]

#include
#include
#include
#ifndef WARRIOR_H
#define WARRIOR_H

class Warrior:public Player
{
public:
Warrior();
virtual ~Warrior();
void sing();
void interact(Obstacle *o);
void fight(Monster *m);
void setWarriorFeature();
int getWarriorStamina();
int getWarriorStrength();
protected:
private:
struct Feature warrior;
};

#endif // WARRIOR_H

[/code]

Warrior.cpp

[code]

#include “Warrior.h”
#include
#include

using namespace std;
Warrior::Warrior()
{
setWarriorFeature();
}

Warrior::~Warrior()
{
//dtor
}

void Warrior::setWarriorFeature()
{
/*
cout<<“Warrior stamina: “; cin>>warrior.stamina;
cout<<“Warrior strength: “; cin>>warrior.strength;
*/
warrior.stamina = 4;
warrior.strength = 4;
}

int Warrior::getWarriorStamina()
{
return warrior.stamina;
}

int Warrior::getWarriorStrength()
{
return warrior.strength;
}

void Warrior::sing()
{
cout<<“of man and steel”<<endl; } void Warrior::fight(Monster *m) { //Monster’in dayanma gucu sýfýr olana kadar vur. while(m->getMonsterStamina() != 0)
{
m->hit(warrior.strength);
}

if(m->getMonsterStamina() == 0)
m->kill(warrior.strength);
}

void Warrior::interact(Obstacle *o)
{
fight(dynamic_cast<Monster*> (o));
}

[/code]

Kodların tamamına buradan bakabilirsiniz.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.