《C++游戏编程入门》第10章 继承与多态:Blackjack
10.1 继承简介
从已有类派生新类,自动继承已有类的数据成员与成员函数。
可重用性的好处:
- 减少工作量。功能函数不必再次编写。
- 减少错误。
- 代码更加清晰。
10.simple_boss.cpp
#include <iostream>
using namespace std;
class Enemy
{
public:
int m_Damage;
Enemy();
void Attack() const;
};
Enemy::Enemy()
: m_Damage(10)
{
}
void Enemy::Attack() const
{
cout << "Attack inflicts " << m_Damage << " damage points!\n";
}
class Boss : public Enemy//基类派生,构造、析构、拷贝构造、拷贝赋值运算符未被继承
{
public:
int m_DamageMultiplier;
Boss();
void SpecialAttack() const;
};
Boss::Boss()
: m_DamageMultiplier(3)
{
}
void Boss::SpecialAttack() const
{
cout << "Special Attack inflicts " << (m_DamageMultiplier * m_Damage);
cout << " damage points!\n";
}
int main()
{
cout << "Creating an enemy.\n";
Enemy enemy1;
enemy1.Attack();
cout << "\nCreating a boss.\n";
Boss boss1;
boss1.Attack();
boss1.SpecialAttack();
return 0;
}
10.2 继承访问权的控制
public、protected、private只为派生类提供基类成员的必要的访问权。
- public:可被程序中所有代码访问。
- protected:本类和派生类访问。
- private:只能本类访问。
10.simple_boss2.cpp
#include <iostream>
using namespace std;
class Enemy
{
public:
Enemy();
void Attack() const;
protected:
int m_Damage;
};
Enemy::Enemy()
: m_Damage(10)
{
}
void Enemy::Attack() const
{
cout << "Attack inflicts " << m_Damage << " damage points!\n";
}
class Boss : public Enemy
{
public:
Boss();
void SpecialAttack() const;
private:
int m_DamageMultiplier;
};
Boss::Boss()
: m_DamageMultiplier(3)
{
}
void Boss::SpecialAttack() const
{
cout << "Special Attack inflicts " << (m_DamageMultiplier * m_Damage);
cout << " damage points!\n";
}
int main()
{
cout << "Creating an enemy.\n";
Enemy enemy1;
enemy1.Attack();
cout << "\nCreating a boss.\n";
Boss boss1;
boss1.Attack();
boss1.SpecialAttack();
return 0;
}
10.3 调用与重写基类成员函数
派生类中可重写基类的成员函数或者调用。
10.overriding_boss.cpp
#include <iostream>
using namespace std;
class Enemy
{
public:
Enemy(int damage = 10);
virtual void Taunt() const; // 声明虚基类成员函数
virtual void Attack() const; // 重写函数声明成虚函数
private:
int m_Damage;
};
Enemy::Enemy(int damage)
: m_Damage(damage)
{
}
void Enemy::Taunt() const
{
cout << "The enemy says he will fight you.\n";
}
void Enemy::Attack() const
{
cout << "Attack! Inflicts " << m_Damage << " damage points.";
}
class Boss : public Enemy
{
public:
Boss(int damage = 30);
virtual void Taunt() const; // virtual可省略
virtual void Attack() const override; // override建议增加
};
Boss::Boss(int damage)
: Enemy(damage) // 调用基类构造函数
{
cout << "damage in Boss(int damage): " << damage << endl;
}
void Boss::Taunt() const // override base class member function
{
cout << "The boss says he will end your pitiful existence.\n";
}
void Boss::Attack() const // override base class member function
{
Enemy::Attack(); // 调用基类成员函数
cout << " And laughs heartily at you.\n";
}
int main()
{
cout << "Enemy object:\n";
Enemy anEnemy;
anEnemy.Taunt();
anEnemy.Attack();
cout << "\n\nBoss object:\n";
Boss aBoss;
aBoss.Taunt();
aBoss.Attack();
return 0;
}
10.4 派生类中调用父类赋值运算符与拷贝构造函数
Boss& operator=(const Boss& b)
{
Enemy::operator=(b);
}
Boss(const Boss& b)
: Enemy(b)
{
}
10.5 多态简介
运行时,对象类型决定实际调用的成员函数。
虚函数通过引用和指针来产生多态行为。
虚析构函数作用:编译器在派生类中插入对基类虚析构函数的调用。
10.polymorphic_bad_guy.cpp
#include <iostream>
using namespace std;
class Enemy
{
public:
Enemy(int damage = 10);
virtual ~Enemy();
void virtual Attack() const;
protected:
int *m_pDamage;
};
Enemy::Enemy(int damage)
{
m_pDamage = new int(damage);
}
Enemy::~Enemy()
{
cout << "In Enemy destructor, deleting m_pDamage.\n";
delete m_pDamage;
m_pDamage = nullptr;
}
void Enemy::Attack() const
{
cout << "An enemy attacks and inflicts " << *m_pDamage << " damage points.";
}
class Boss : public Enemy
{
public:
Boss(int multiplier = 3);
virtual ~Boss();
void virtual Attack() const;
protected:
int *m_pMultiplier;
};
Boss::Boss(int multiplier)
{
m_pMultiplier = new int(multiplier);
}
Boss::~Boss()
{
cout << "In Boss destructor, deleting m_pMultiplier.\n";
delete m_pMultiplier;
m_pMultiplier = nullptr;
}
void Boss::Attack() const
{
cout << "A boss attacks and inflicts " << (*m_pDamage) * (*m_pMultiplier)
<< " damage points.";
}
int main()
{
cout << "Calling Attack() on Boss object through pointer to Enemy:\n";
Enemy *pBadGuy = new Boss();//基类指针指向派生类
pBadGuy->Attack();
cout << "\n\nDeleting pointer to Enemy:\n";
delete pBadGuy;
pBadGuy = nullptr;
return 0;
}
10.6 使用抽象类
纯虚函数是不需要定义的函数。
不能实例化的基类,抽象类(含纯虚函数)。
派生抽象类时,重写纯虚函数(未完全重写时仍是抽象类)。
10.abstract_creature.cpp
#include <iostream>
using namespace std;
class Creature // 抽象类
{
public:
Creature(int health = 100);
virtual void Greet() const = 0; // 纯虚函数
virtual void DisplayHealth() const;
protected:
int m_Health;
};
Creature::Creature(int health)
: m_Health(health)
{
}
void Creature::DisplayHealth() const
{
cout << "Health: " << m_Health << endl;
}
class Orc : public Creature
{
public:
Orc(int health = 120);
virtual void Greet() const override;
};
Orc::Orc(int health)
: Creature(health)
{
}
void Orc::Greet() const // 重写纯虚函数
{
cout << "The orc grunts hello.\n";
}
int main()
{
Creature *pCreature = new Orc();
pCreature->Greet();
pCreature->DisplayHealth();
delete pCreature;
pCreature = nullptr;
return 0;
}
10.7 Blackjack游戏
21点,A为1或11(玩家决定),J、Q、K为10。
开始,玩家和庄家分别发两张牌,玩家牌可见,庄家一张牌可见。
接着,可要牌,超过21,输。
最后,庄家小于等于16,必须要牌,比较庄家与玩家点数。
10.blackjack.cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
#include <ctime>
using namespace std;
class Card // 扑克牌
{
public:
enum rank
{
ACE = 1,
TWO,
THREE,
FOUR,
FIVE,
SIX,
SEVEN,
EIGHT,
NINE,
TEN,
JACK,
QUEEN,
KING
};
enum suit
{
CLUBS,
DIAMONDS,
HEARTS,
SPADES
};
Card(rank r = ACE, suit s = SPADES, bool ifu = true)
: m_Rank(r), m_Suit(s), m_IsFaceUp(ifu) {}
int GetValue() const // 获取点数, 0 - 11
{
int value = 0; // 牌朝下为0
if (m_IsFaceUp)
{
value = m_Rank;
if (value > 10) // 人脸牌,点数为10
value = 10;
}
return value;
}
void Flip() // 翻牌
{
m_IsFaceUp = !m_IsFaceUp;
}
friend ostream &operator<<(ostream &os, const Card &aCard);
private:
rank m_Rank; // 大小
suit m_Suit; // 花色
bool m_IsFaceUp; // 是否正面朝上
};
class Hand // 持有牌
{
public:
Hand()
{
m_Cards.reserve(7);
}
virtual ~Hand()
{
Clear();
}
void Add(Card *pCard)
{
m_Cards.push_back(pCard);
}
void Clear()
{
for (vector<Card *>::iterator iter = m_Cards.begin(); iter != m_Cards.end(); ++iter)
delete *iter;
m_Cards.clear();
}
int GetTotal() const // 获取牌的最大点数,aces取1或11
{
if (m_Cards.empty() || m_Cards[0]->GetValue() == 0) // 无牌或者牌朝下
return 0;
int total = 0; // 所有牌点数加起来,假定Ace点数为1
for (vector<Card *>::const_iterator iter = m_Cards.begin(); iter != m_Cards.end(); ++iter)
total += (*iter)->GetValue();
if (total <= 11) // 点数和够低,Ace才可能取11
{
for (vector<Card *>::const_iterator iter = m_Cards.begin(); iter != m_Cards.end(); ++iter)
{
if ((*iter)->GetValue() == Card::ACE) // 是否存在Ace
{
total += 10; // 1变11,增加10
break;
}
}
}
return total;
}
protected:
vector<Card *> m_Cards;
};
class GenericPlayer : public Hand // 一般玩家
{
friend ostream &operator<<(ostream &os, const GenericPlayer &aGenericPlayer);
public:
GenericPlayer(const string &name = "")
: m_Name(name) {}
virtual ~GenericPlayer() {}
virtual bool IsHitting() const = 0; // 是否要牌
bool IsBusted() const
{
return GetTotal() > 21;
}
void Bust() const
{
cout << m_Name << " busts.\n";
}
protected:
string m_Name;
};
class Player : public GenericPlayer // 人类
{
public:
Player(const string &name = "")
: GenericPlayer(name) {}
virtual ~Player() {}
virtual bool IsHitting() const
{
cout << m_Name << ", do you want a hit? (Y/N): ";
char response;
cin >> response;
return (response == 'y' || response == 'Y');
}
void Win() const
{
cout << m_Name << " wins.\n";
}
void Lose() const
{
cout << m_Name << " loses.\n";
}
void Push() const
{
cout << m_Name << " pushes.\n";
}
};
class House : public GenericPlayer // 计算机
{
public:
House(const string &name = "House") : GenericPlayer(name) {}
virtual ~House() {}
virtual bool IsHitting() const
{
return GetTotal() <= 16;
}
void FlipFirstCard() // 翻转第一张牌
{
if (!m_Cards.empty())
m_Cards[0]->Flip();
else
cout << "No card to flip!\n";
}
};
class Deck : public Hand // 堆牌,洗牌和发牌
{
public:
Deck()
{
m_Cards.reserve(52);
Populate();
}
virtual ~Deck() {}
void Populate() // 生成标准52张牌
{
Clear();
for (int s = Card::CLUBS; s <= Card::SPADES; ++s)
for (int r = Card::ACE; r <= Card::KING; ++r)
Add(new Card(static_cast<Card::rank>(r), static_cast<Card::suit>(s)));
}
void Shuffle() // 乱序
{
// random_device rd;
// shuffle(m_Cards.begin(), m_Cards.end(), default_random_engine(rd()));//非确定性随机整数作为随机种子
unsigned seed = chrono::system_clock::now().time_since_epoch().count();
shuffle(m_Cards.begin(), m_Cards.end(), default_random_engine(seed)); // 时间作为随机种子
}
void Deal(Hand &aHand) // 分发一张牌给手牌
{
if (!m_Cards.empty())
{
aHand.Add(m_Cards.back());
m_Cards.pop_back();
}
else
{
cout << "Out of cards. Unable to deal.";
}
}
void AdditionalCards(GenericPlayer &aGenericPlayer) // 给通用玩家发牌
{
cout << endl;
// 只要通用玩家未超过21点,且想要牌
while (!(aGenericPlayer.IsBusted()) && aGenericPlayer.IsHitting())
{
Deal(aGenericPlayer);
cout << aGenericPlayer << endl;
if (aGenericPlayer.IsBusted())
aGenericPlayer.Bust();
}
}
};
class Game // 游戏
{
public:
Game(const vector<string> &names)
{
for (vector<string>::const_iterator pName = names.begin(); pName != names.end(); ++pName)
m_Players.push_back(Player(*pName)); // 添加人类玩家
}
~Game() {}
void Play()
{
srand(static_cast<unsigned int>(time(0))); // 随机种子
m_Deck.Populate(); // 生成牌
m_Deck.Shuffle(); // 洗牌
vector<Player>::iterator pPlayer;
for (int i = 0; i < 2; ++i) // 所有玩家发两张牌
{
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
m_Deck.Deal(*pPlayer);
m_Deck.Deal(m_House);
}
m_House.FlipFirstCard(); // 计算机第一张牌朝下隐藏
// 显示所有玩家手牌
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
cout << *pPlayer << endl;
cout << m_House << endl;
cout << endl;
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
m_Deck.AdditionalCards(*pPlayer); // 给每一位玩家发牌
m_House.FlipFirstCard(); // 计算机第一张牌朝上显示
cout << m_House;
m_Deck.AdditionalCards(m_House); // 给计算机发牌
if (m_House.IsBusted()) // 计算机超过21点
{
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
if (!(pPlayer->IsBusted()))
pPlayer->Win();
}
else
{
// 比较玩家与庄家的点数
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
if (!pPlayer->IsBusted())
if (pPlayer->GetTotal() > m_House.GetTotal())
pPlayer->Win();
else if (pPlayer->GetTotal() < m_House.GetTotal())
pPlayer->Lose();
else
pPlayer->Push();
}
// 移除所有玩家的牌
for (pPlayer = m_Players.begin(); pPlayer != m_Players.end(); ++pPlayer)
pPlayer->Clear();
m_House.Clear();
}
private:
Deck m_Deck;
House m_House;
vector<Player> m_Players;
};
ostream &operator<<(ostream &os, const Card &aCard)
{
const string RANKS[] = {"0", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
const string SUITS[] = {"c", "d", "h", "s"};
if (aCard.m_IsFaceUp)
os << RANKS[aCard.m_Rank] << SUITS[aCard.m_Suit];
else
os << "XX";
return os;
}
ostream &operator<<(ostream &os, const GenericPlayer &aGenericPlayer)
{
os << aGenericPlayer.m_Name << ":\t";
if (!aGenericPlayer.m_Cards.empty())
{
for (vector<Card *>::const_iterator pCard = aGenericPlayer.m_Cards.begin(); pCard != aGenericPlayer.m_Cards.end(); ++pCard)
os << *(*pCard) << "\t";
if (aGenericPlayer.GetTotal() != 0)
cout << "(" << aGenericPlayer.GetTotal() << ")";
}
else
os << "<empty>";
return os;
}
int main()
{
cout << "\t\tWelcome to Blackjack!\n\n";
int numPlayers = 0;
while (numPlayers < 1 || numPlayers > 7)
{
cout << "How many players? (1 - 7): ";
cin >> numPlayers;
}
vector<string> names;
string name;
for (int i = 0; i < numPlayers; ++i)
{
cout << "Enter player name: ";
cin >> name;
names.push_back(name);
}
cout << endl;
// 游戏循环
Game aGame(names);
char again = 'y';
while (again != 'n' && again != 'N')
{
aGame.Play();
cout << "\nDo you want to play again? (Y/N): ";
cin >> again;
}
return 0;
}