Ir ao conteúdo
  • Cadastre-se

Exemplo C++ - Hanoi não recursivo


Edmorte

Posts recomendados

Essa é uma nova versão que eu refinei da antiga, discutindo com os colegas do fórum me levaram a ter ideias.

Melhorias:

- Uso do map agora não faz mais cast (nem sei porque fazia antes!);

- Não existem mais HARDCODES das torres no código, ou seja, se você alterar no ENUM nada quebra;

- Melhorias no design, agora sim a engine não conhece como funcionam as torres e apenas consome. (melhoria no encapsulamento);

- Coloquei mais recursos do C++ para as pessoas verem coisas novas, algumas situações são forçadas por causa disso!!!

Espero que gostem, esse está bem melhor que antes.

28/12/13 Edit: Correção do post increment, estava fazendo como o pré.


#include <iostream>
#include <vector>
#include <string>
#include <map>

namespace HanoiGame
{
using namespace std;

enum class Tower : char
{
TowerA = 'A',
TowerB = 'B',
TowerC = 'C'
};

class GameEngine
{
class Hanoi
{
vector<int> tower;

public:
// Construtores para demonstrar, não há necessidade de defini-los nesse caso.
Hanoi() = default;
Hanoi(const Hanoi& t) : tower{ t.tower } {}
// Fim construtores

bool isValidMove(int disc) const;
int size() const;
int peek() const;
int pop();
void push(int disc);
};

int discs;

public:
map<Tower, Hanoi> tower;

explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD) const;
bool isGameFinished() const;
string HanoiGame::GameEngine::print() const;
};

ostream& operator<<(ostream& os, const HanoiGame::GameEngine& obj);
Tower& operator++(Tower&);
Tower operator++(Tower& t, int);
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(char tower)
{
return ((Tower)tower >= Tower::TowerA && (Tower)tower <= Tower::TowerC);
}

void playGame()
{
HanoiGame::GameEngine game{};
char origem, destino;

do
{
cout << game;

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) origem, (Tower) destino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

cout << game;
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;

for (size_t i = discs; i > 0; --i)
{
tower[Tower::TowerA].push(i);
}

tower[Tower::TowerB] = Hanoi{};
tower[Tower::TowerC] = Hanoi{};
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD) const
{
return tower.find(towerO)->second.size() > 0 &&
tower.find(towerD)->second.isValidMove(tower.find(towerO)->second.peek());
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
tower[towerD].push(tower[towerO].pop());

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished() const
{
return tower.find(Tower::TowerA)->second.size() == 0 &&
(
tower.find(Tower::TowerB)->second.size() == discs ||
tower.find(Tower::TowerC)->second.size() == discs
);
}

string HanoiGame::GameEngine::print() const
{
string str;
std::map<Tower, Hanoi> m{ tower };

for (char i = discs; i > 0; --i)
{
Tower t = Tower::TowerA;

do
{
str += (m.find(t)->second.size() >= i ?
std::to_string(m.find(t)->second.pop()) :
"|");

str += '\t';
} while (t++ < Tower::TowerC);

str += '\n';
}

return str;
}

std::ostream& HanoiGame::operator << (std::ostream& os, const HanoiGame::GameEngine& obj)
{
os << obj.print();

return os;
}

Tower& HanoiGame::operator++(Tower& t)
{
using namespace HanoiGame;

return t = (t == Tower::TowerC ? Tower::TowerA : static_cast<Tower>((char) t + 1));
}

Tower HanoiGame::operator++(Tower& t, int)
{
using namespace HanoiGame;

Tower old{ t };

t = (t == Tower::TowerC ? Tower::TowerA : static_cast<Tower>((char) t + 1));

return old;
}

int HanoiGame::GameEngine::Hanoi::size() const
{
return tower.size();
}

int HanoiGame::GameEngine::Hanoi::peek() const
{
return tower.back();
}

void HanoiGame::GameEngine::Hanoi::push(int disc)
{
tower.push_back(disc);
}

int HanoiGame::GameEngine::Hanoi::pop()
{
int disc = peek();
tower.pop_back();
return disc;
}

bool HanoiGame::GameEngine::Hanoi::isValidMove(int disc) const
{
return size() == 0 || disc < peek();
}

Link para o comentário
Compartilhar em outros sites

Seu codigo ficou muito bom. Fiz algumas alterações nele. Veja como ficou

	enum class Tower : int
{
TowerA,
TowerB,
TowerC,
};

primeiro troquei para int.

class GameEngine
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;
vector<int> *tower[3];
int discs;

...

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;
this->tower[0] = &towerA;
this->tower[1] = &towerB;
this->tower[2] = &towerC;

for (size_t i = discs; i > 0; --i)
{
towerA.push_back(i);
}
}

nesta classe adicionei um vector<int> *tower[3]; para ter enumerado as torres.

vector<int>& HanoiGame::GameEngine::getTower(Tower tower)
{
return *this->tower[(int)tower];
}

neste momento posso tirar o switch.

E algumas alterações para aceitar A B C no codigo...

[hide]

#include <iostream>
#include <vector>
#include <string>

namespace HanoiGame
{
using namespace std;

enum class Tower : int
{
TowerA,
TowerB,
TowerC,
};

class GameEngine
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;
vector<int> *tower[3];
int discs;

vector<int>& getTower(Tower tower);
public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os) const;
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(int tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game(3);
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

int iorigem = origem - 'A';
int idestino = destino - 'A';

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) iorigem, (Tower) idestino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;
this->tower[0] = &towerA;
this->tower[1] = &towerB;
this->tower[2] = &towerC;

for (size_t i = discs; i > 0; --i)
{
towerA.push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (getTower(towerO).size() == 0)
{
return false;
}

if (getTower(towerD).size() == 0)
{
return true;
}

return getTower(towerO).back() < getTower(towerD).back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = getTower(towerO).back();
getTower(towerO).pop_back();
getTower(towerD).push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return towerA.size() == 0 && (towerB.size() == discs || towerC.size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os) const
{
for (size_t i = discs; i > 0; --i)
{
os << (towerA.size() >= i && towerA[i - 1] ? std::to_string(towerA[i - 1]) : "|");

os << '\t';

os << (towerB.size() >= i && towerB[i - 1] ? std::to_string(towerB[i - 1]) : "|");

os << '\t';

os << (towerC.size() >= i && towerC[i - 1] ? std::to_string(towerC[i - 1]) : "|");

os << endl;
}
}

vector<int>& HanoiGame::GameEngine::getTower(Tower tower)
{
return *this->tower[(int)tower];
}

[/hide]

Link para o comentário
Compartilhar em outros sites

Seu codigo ficou muito bom. Fiz algumas alterações nele. Veja como ficou

	enum class Tower : int
{
TowerA,
TowerB,
TowerC,
};

primeiro troquei para int.

class GameEngine
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;
vector<int> *tower[3];
int discs;

...

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;
this->tower[0] = &towerA;
this->tower[1] = &towerB;
this->tower[2] = &towerC;

for (size_t i = discs; i > 0; --i)
{
towerA.push_back(i);
}
}

nesta classe adicionei um vector<int> *tower[3]; para ter enumerado as torres.

vector<int>& HanoiGame::GameEngine::getTower(Tower tower)
{
return *this->tower[(int)tower];
}

neste momento posso tirar o switch.

E algumas alterações para aceitar A B C no codigo...

[hide]

#include <iostream>
#include <vector>
#include <string>

namespace HanoiGame
{
using namespace std;

enum class Tower : int
{
TowerA,
TowerB,
TowerC,
};

class GameEngine
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;
vector<int> *tower[3];
int discs;

vector<int>& getTower(Tower tower);
public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os) const;
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(int tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game(3);
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

int iorigem = origem - 'A';
int idestino = destino - 'A';

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) iorigem, (Tower) idestino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;
this->tower[0] = &towerA;
this->tower[1] = &towerB;
this->tower[2] = &towerC;

for (size_t i = discs; i > 0; --i)
{
towerA.push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (getTower(towerO).size() == 0)
{
return false;
}

if (getTower(towerD).size() == 0)
{
return true;
}

return getTower(towerO).back() < getTower(towerD).back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = getTower(towerO).back();
getTower(towerO).pop_back();
getTower(towerD).push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return towerA.size() == 0 && (towerB.size() == discs || towerC.size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os) const
{
for (size_t i = discs; i > 0; --i)
{
os << (towerA.size() >= i && towerA[i - 1] ? std::to_string(towerA[i - 1]) : "|");

os << '\t';

os << (towerB.size() >= i && towerB[i - 1] ? std::to_string(towerB[i - 1]) : "|");

os << '\t';

os << (towerC.size() >= i && towerC[i - 1] ? std::to_string(towerC[i - 1]) : "|");

os << endl;
}
}

vector<int>& HanoiGame::GameEngine::getTower(Tower tower)
{
return *this->tower[(int)tower];
}

[/hide]

Link para o comentário
Compartilhar em outros sites

Não precisa mudar pra int, você tentou com char? Não testei, estou falando conceitualmente não deve haver problemas.

O char usei pra economizar espaço mesmo.

Economizou várias chamadas, já dá pra matar o GET e em termos de design até criar uma interface com o ponteiro cont.

Ficou bom cara.

PS: Entendi o que você fez, fiz algumas modificações, não gostei ficou feio o que eu fiz.

Agora que entendi o que você quis atingir, acho que MAP seria melhor do que o ponteiro. Não acho interessante ficar vazando conhecimento sobre a classe para fora, do tipo 'A' - tower. Se queremos ser práticos devemos usar um dicionário e não incluir esses detalhes na parte de fora da classe. Eu já fui além e coloquei do lado de dentro e não traz vantagem em economizar o GET, pois fica uma sintaxe horrorosa e usando o GET não vejo vantagem em retirar o switch por um ponteiro, estamos trocando milésimos de performance por menos compreensão imediata do código.

Acho que o MAP seria a solução ideal para indexar os valores do enum. Independente de usar 0 ou 'A' podemos continuar com o tipo char.

Fica aí minha opinião final após mexer usando esse ponteiro.

Perceba como fica mais limpo sem expor nada, nem depender de artimanhas.


#include <iostream>
#include <vector>
#include <string>
#include <map>

namespace HanoiGame
{
using namespace std;

enum class Tower : char
{
TowerA = 'A',
TowerB = 'B',
TowerC = 'C'
};

class Hanoi
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;

public:
map<char, vector<int>> tower;

Hanoi()
{
tower['A'] = towerA;
tower['B'] = towerB;
tower['C'] = towerC;
}
};

class GameEngine
{
Hanoi hanoi_tower;
int discs;

public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os);
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(char tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game{};
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) origem, (Tower) destino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;

for (size_t i = discs; i > 0; --i)
{
hanoi_tower.tower['A'].push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (hanoi_tower.tower[(char)towerO].size() == 0)
{
return false;
}

if (hanoi_tower.tower[(char)towerD].size() == 0)
{
return true;
}

return hanoi_tower.tower[(char) towerO].back() < hanoi_tower.tower[(char) towerD].back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = hanoi_tower.tower[(char) towerO].back();
hanoi_tower.tower[(char) towerO].pop_back();
hanoi_tower.tower[(char) towerD].push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return hanoi_tower.tower['A'].size() == 0 && (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os)
{
for (size_t i = discs; i > 0; --i)
{
os << (hanoi_tower.tower['A'].size() >= i && hanoi_tower.tower['A'][i - 1] ? std::to_string(hanoi_tower.tower['A'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['B'].size() >= i && hanoi_tower.tower['B'][i - 1] ? std::to_string(hanoi_tower.tower['B'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['C'].size() >= i && hanoi_tower.tower['C'][i - 1] ? std::to_string(hanoi_tower.tower['C'][i - 1]) : "|");

os << endl;
}
}

Link para o comentário
Compartilhar em outros sites

Não precisa mudar pra int, você tentou com char? Não testei, estou falando conceitualmente não deve haver problemas.

O char usei pra economizar espaço mesmo.

Economizou várias chamadas, já dá pra matar o GET e em termos de design até criar uma interface com o ponteiro cont.

Ficou bom cara.

PS: Entendi o que você fez, fiz algumas modificações, não gostei ficou feio o que eu fiz.

Agora que entendi o que você quis atingir, acho que MAP seria melhor do que o ponteiro. Não acho interessante ficar vazando conhecimento sobre a classe para fora, do tipo 'A' - tower. Se queremos ser práticos devemos usar um dicionário e não incluir esses detalhes na parte de fora da classe. Eu já fui além e coloquei do lado de dentro e não traz vantagem em economizar o GET, pois fica uma sintaxe horrorosa e usando o GET não vejo vantagem em retirar o switch por um ponteiro, estamos trocando milésimos de performance por menos compreensão imediata do código.

Acho que o MAP seria a solução ideal para indexar os valores do enum. Independente de usar 0 ou 'A' podemos continuar com o tipo char.

Fica aí minha opinião final após mexer usando esse ponteiro.

Perceba como fica mais limpo sem expor nada, nem depender de artimanhas.


#include <iostream>
#include <vector>
#include <string>
#include <map>

namespace HanoiGame
{
using namespace std;

enum class Tower : char
{
TowerA = 'A',
TowerB = 'B',
TowerC = 'C'
};

class Hanoi
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;

public:
map<char, vector<int>> tower;

Hanoi()
{
tower['A'] = towerA;
tower['B'] = towerB;
tower['C'] = towerC;
}
};

class GameEngine
{
Hanoi hanoi_tower;
int discs;

public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os);
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(char tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game{};
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) origem, (Tower) destino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;

for (size_t i = discs; i > 0; --i)
{
hanoi_tower.tower['A'].push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (hanoi_tower.tower[(char)towerO].size() == 0)
{
return false;
}

if (hanoi_tower.tower[(char)towerD].size() == 0)
{
return true;
}

return hanoi_tower.tower[(char) towerO].back() < hanoi_tower.tower[(char) towerD].back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = hanoi_tower.tower[(char) towerO].back();
hanoi_tower.tower[(char) towerO].pop_back();
hanoi_tower.tower[(char) towerD].push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return hanoi_tower.tower['A'].size() == 0 && (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os)
{
for (size_t i = discs; i > 0; --i)
{
os << (hanoi_tower.tower['A'].size() >= i && hanoi_tower.tower['A'][i - 1] ? std::to_string(hanoi_tower.tower['A'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['B'].size() >= i && hanoi_tower.tower['B'][i - 1] ? std::to_string(hanoi_tower.tower['B'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['C'].size() >= i && hanoi_tower.tower['C'][i - 1] ? std::to_string(hanoi_tower.tower['C'][i - 1]) : "|");

os << endl;
}
}

Link para o comentário
Compartilhar em outros sites

Não precisa mudar pra int, você tentou com char? Não testei, estou falando conceitualmente não deve haver problemas.

O char usei pra economizar espaço mesmo.

Economizou várias chamadas, já dá pra matar o GET e em termos de design até criar uma interface com o ponteiro cont.

Ficou bom cara.

PS: Entendi o que você fez, fiz algumas modificações, não gostei ficou feio o que eu fiz.

Agora que entendi o que você quis atingir, acho que MAP seria melhor do que o ponteiro. Não acho interessante ficar vazando conhecimento sobre a classe para fora, do tipo 'A' - tower. Se queremos ser práticos devemos usar um dicionário e não incluir esses detalhes na parte de fora da classe. Eu já fui além e coloquei do lado de dentro e não traz vantagem em economizar o GET, pois fica uma sintaxe horrorosa e usando o GET não vejo vantagem em retirar o switch por um ponteiro, estamos trocando milésimos de performance por menos compreensão imediata do código.

Acho que o MAP seria a solução ideal para indexar os valores do enum. Independente de usar 0 ou 'A' podemos continuar com o tipo char.

Fica aí minha opinião final após mexer usando esse ponteiro.

Perceba como fica mais limpo sem expor nada, nem depender de artimanhas.


#include <iostream>
#include <vector>
#include <string>
#include <map>

namespace HanoiGame
{
using namespace std;

enum class Tower : char
{
TowerA = 'A',
TowerB = 'B',
TowerC = 'C'
};

class Hanoi
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;

public:
map<char, vector<int>> tower;

Hanoi()
{
tower['A'] = towerA;
tower['B'] = towerB;
tower['C'] = towerC;
}
};

class GameEngine
{
Hanoi hanoi_tower;
int discs;

public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os);
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(char tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game{};
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) origem, (Tower) destino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;

for (size_t i = discs; i > 0; --i)
{
hanoi_tower.tower['A'].push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (hanoi_tower.tower[(char)towerO].size() == 0)
{
return false;
}

if (hanoi_tower.tower[(char)towerD].size() == 0)
{
return true;
}

return hanoi_tower.tower[(char) towerO].back() < hanoi_tower.tower[(char) towerD].back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = hanoi_tower.tower[(char) towerO].back();
hanoi_tower.tower[(char) towerO].pop_back();
hanoi_tower.tower[(char) towerD].push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return hanoi_tower.tower['A'].size() == 0 && (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os)
{
for (size_t i = discs; i > 0; --i)
{
os << (hanoi_tower.tower['A'].size() >= i && hanoi_tower.tower['A'][i - 1] ? std::to_string(hanoi_tower.tower['A'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['B'].size() >= i && hanoi_tower.tower['B'][i - 1] ? std::to_string(hanoi_tower.tower['B'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['C'].size() >= i && hanoi_tower.tower['C'][i - 1] ? std::to_string(hanoi_tower.tower['C'][i - 1]) : "|");

os << endl;
}
}

Realmente da pra esconomizar aquele getTower. Ainda prefiro usar o ponteiro e index baseados em 0 ao invés de Dicionario.

O metodo IsGameFinished pode ser reduzido.

bool HanoiGame::GameEngine::isGameFinished()
{
return (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

Se uma das torres tiver cheias, a A vai estar vazia e não precisaremos verificar isso.

Link para o comentário
Compartilhar em outros sites

Não precisa mudar pra int, você tentou com char? Não testei, estou falando conceitualmente não deve haver problemas.

O char usei pra economizar espaço mesmo.

Economizou várias chamadas, já dá pra matar o GET e em termos de design até criar uma interface com o ponteiro cont.

Ficou bom cara.

PS: Entendi o que você fez, fiz algumas modificações, não gostei ficou feio o que eu fiz.

Agora que entendi o que você quis atingir, acho que MAP seria melhor do que o ponteiro. Não acho interessante ficar vazando conhecimento sobre a classe para fora, do tipo 'A' - tower. Se queremos ser práticos devemos usar um dicionário e não incluir esses detalhes na parte de fora da classe. Eu já fui além e coloquei do lado de dentro e não traz vantagem em economizar o GET, pois fica uma sintaxe horrorosa e usando o GET não vejo vantagem em retirar o switch por um ponteiro, estamos trocando milésimos de performance por menos compreensão imediata do código.

Acho que o MAP seria a solução ideal para indexar os valores do enum. Independente de usar 0 ou 'A' podemos continuar com o tipo char.

Fica aí minha opinião final após mexer usando esse ponteiro.

Perceba como fica mais limpo sem expor nada, nem depender de artimanhas.


#include <iostream>
#include <vector>
#include <string>
#include <map>

namespace HanoiGame
{
using namespace std;

enum class Tower : char
{
TowerA = 'A',
TowerB = 'B',
TowerC = 'C'
};

class Hanoi
{
vector<int> towerA;
vector<int> towerB;
vector<int> towerC;

public:
map<char, vector<int>> tower;

Hanoi()
{
tower['A'] = towerA;
tower['B'] = towerB;
tower['C'] = towerC;
}
};

class GameEngine
{
Hanoi hanoi_tower;
int discs;

public:
explicit GameEngine(int discs = 5);
bool moveDisc(Tower towerO, Tower towerD);
bool isValidMove(Tower towerO, Tower towerD);
bool isGameFinished();
void printGame(ostream& os);
};
}

using namespace std;
using HanoiGame::Tower;

bool checkValidTower(char tower)
{
return (tower >= 'A' && tower <= 'C');
}

void playGame()
{
HanoiGame::GameEngine game{};
char origem, destino;

do
{
game.printGame(cout);

cout << "Torre de origem:";
cin >> origem;

cout << "Torre de destino:";
cin >> destino;

origem = toupper(origem);
destino = toupper(destino);

if (!(checkValidTower(origem) && checkValidTower(destino)) || (origem == destino))
{
cout << "voce digitou uma torre invalida" << endl;
}
else
{
if (!game.moveDisc((Tower) origem, (Tower) destino))
{
cout << "Movimento Invalido!" << endl;
}
else
{
if (game.isGameFinished())
{
cout << "Parabens jogo finalizado!" << endl;
}
}
}
} while (!game.isGameFinished());

game.printGame(cout);
}

int main()
{
playGame();
}

HanoiGame::GameEngine::GameEngine(int discs)
{
this->discs = discs;

for (size_t i = discs; i > 0; --i)
{
hanoi_tower.tower['A'].push_back(i);
}
}

bool HanoiGame::GameEngine::isValidMove(Tower towerO, Tower towerD)
{
if (hanoi_tower.tower[(char)towerO].size() == 0)
{
return false;
}

if (hanoi_tower.tower[(char)towerD].size() == 0)
{
return true;
}

return hanoi_tower.tower[(char) towerO].back() < hanoi_tower.tower[(char) towerD].back();
}

bool HanoiGame::GameEngine::moveDisc(Tower towerO, Tower towerD)
{
if (isValidMove(towerO, towerD))
{
int disc = hanoi_tower.tower[(char) towerO].back();
hanoi_tower.tower[(char) towerO].pop_back();
hanoi_tower.tower[(char) towerD].push_back(disc);

return true;
}

return false;
}

bool HanoiGame::GameEngine::isGameFinished()
{
return hanoi_tower.tower['A'].size() == 0 && (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

void HanoiGame::GameEngine::printGame(ostream& os)
{
for (size_t i = discs; i > 0; --i)
{
os << (hanoi_tower.tower['A'].size() >= i && hanoi_tower.tower['A'][i - 1] ? std::to_string(hanoi_tower.tower['A'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['B'].size() >= i && hanoi_tower.tower['B'][i - 1] ? std::to_string(hanoi_tower.tower['B'][i - 1]) : "|");

os << '\t';

os << (hanoi_tower.tower['C'].size() >= i && hanoi_tower.tower['C'][i - 1] ? std::to_string(hanoi_tower.tower['C'][i - 1]) : "|");

os << endl;
}
}

Realmente da pra esconomizar aquele getTower. Ainda prefiro usar o ponteiro e index baseados em 0 ao invés de Dicionario.

O metodo IsGameFinished pode ser reduzido.

bool HanoiGame::GameEngine::isGameFinished()
{
return (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

Se uma das torres tiver cheias, a A vai estar vazia e não precisaremos verificar isso.

Link para o comentário
Compartilhar em outros sites

Realmente da pra esconomizar aquele getTower. Ainda prefiro usar o ponteiro e index baseados em 0 ao invés de Dicionario.

O metodo IsGameFinished pode ser reduzido.

bool HanoiGame::GameEngine::isGameFinished()
{
return (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

Se uma das torres tiver cheias, a A vai estar vazia e não precisaremos verificar isso.

A condição foi escrita daquela forma propositalmente.

É um exemplo didático man, nós não estamos mandando um foguete para a lua. :cool:

Concordo que certas coisas explicitas são ridiculas como if (x == true), mas acho interessante ter um guard mesmo que redundante. Previne contra manutenções erradas.

Concordo que o ponteiro é mais hardcore, mas para algo simples prefiro o MAP.

Nesse caso você se safou, pois o ponteiro é apenas referência, se fosse um recurso, você lembraria de escrever o destrutor?

E escrevendo o destrutor, você lembraria de escrever o copy constructor/assignment, o move constructor/assignment?

Você percebe como a sua sugestão não é didatica?

Link para o comentário
Compartilhar em outros sites

Realmente da pra esconomizar aquele getTower. Ainda prefiro usar o ponteiro e index baseados em 0 ao invés de Dicionario.

O metodo IsGameFinished pode ser reduzido.

bool HanoiGame::GameEngine::isGameFinished()
{
return (hanoi_tower.tower['B'].size() == discs || hanoi_tower.tower['C'].size() == discs);
}

Se uma das torres tiver cheias, a A vai estar vazia e não precisaremos verificar isso.

A condição foi escrita daquela forma propositalmente.

É um exemplo didático man, nós não estamos mandando um foguete para a lua. :cool:

Concordo que certas coisas explicitas são ridiculas como if (x == true), mas acho interessante ter um guard mesmo que redundante. Previne contra manutenções erradas.

Concordo que o ponteiro é mais hardcore, mas para algo simples prefiro o MAP.

Nesse caso você se safou, pois o ponteiro é apenas referência, se fosse um recurso, você lembraria de escrever o destrutor?

E escrevendo o destrutor, você lembraria de escrever o copy constructor/assignment, o move constructor/assignment?

Você percebe como a sua sugestão não é didatica?

Link para o comentário
Compartilhar em outros sites

A condição foi escrita daquela forma propositalmente.

É um exemplo didático man, nós não estamos mandando um foguete para a lua. :cool:

Concordo que certas coisas explicitas são ridiculas como if (x == true), mas acho interessante ter um guard mesmo que redundante. Previne contra manutenções erradas.

Concordo que o ponteiro é mais hardcore, mas para algo simples prefiro o MAP.

Nesse caso você se safou, pois o ponteiro é apenas referência, se fosse um recurso, você lembraria de escrever o destrutor?

E escrevendo o destrutor, você lembraria de escrever o copy constructor/assignment, o move constructor/assignment?

Você percebe como a sua sugestão não é didatica?

Desconheço essas expressoes copy constructor/assignment e move constructor/assignment, porém se tivesse usado new lembraria de usar delete, ou então usaria shared_ptr.

Link para o comentário
Compartilhar em outros sites

A condição foi escrita daquela forma propositalmente.

É um exemplo didático man, nós não estamos mandando um foguete para a lua. :cool:

Concordo que certas coisas explicitas são ridiculas como if (x == true), mas acho interessante ter um guard mesmo que redundante. Previne contra manutenções erradas.

Concordo que o ponteiro é mais hardcore, mas para algo simples prefiro o MAP.

Nesse caso você se safou, pois o ponteiro é apenas referência, se fosse um recurso, você lembraria de escrever o destrutor?

E escrevendo o destrutor, você lembraria de escrever o copy constructor/assignment, o move constructor/assignment?

Você percebe como a sua sugestão não é didatica?

Desconheço essas expressoes copy constructor/assignment e move constructor/assignment, porém se tivesse usado new lembraria de usar delete, ou então usaria shared_ptr.

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...