Card
类怎么用?怎么把牌加入vector<card>
?A:这里给一个例子:
Card c1("A"), c2("2"), c3("3"); // 定义三个Card类的实例
// 大于号小于号怎么用?
if (c1 > c2) cout << "true" << endl;
else cout << "false" << endl; // 这里会输出false
if (c3 < c2) cout << "true" << endl; // 这里会输出true
else cout << "false" << endl;
// vector<Card> 怎么用?
vector<Card> cards; // 初始化一个空的vector,类似数组,但初始长度是0
cards.push_back(c1); // c1被加入cards的末尾,现在数组长度为1
cards.push_back(Card("JOKER"); // cards中又加入了一张大王,现在数组长度为2
cout << cards[0] << endl; // 输出第一张牌,此时是A
cout << cards[1] << endl; // 输出第一张牌,此时是JOKER
cout << cards[2] << endl; // 报错!你下标越界了。因为cards里只有两张牌
sort(cards.begin(), cards.end(), greater<Card>()); // 将cards中的牌从大到小排序。如果你要排序的vector<Card>名为hand,就把cards改成hand
// 输出cards里面的所有牌,你可以这样写:
// 注:你总是可以用xx.size()得到vector的大小
for (int i = 0; i < cards.size(); ++i)
cout << cards[i] << " ";
// 也可以这样写:
// 注:如果你好奇这种写法,可以搜索“迭代器”的相关资料;如果你弄不明白,可以跳过
for (vector<Card>::iterator i = cards.begin(); i != cards.end(); ++i)
cout << *i << " ";
showHand()
。A:showHand()
全称是DDZPlayer::showHand()
,是DDZPlayer
的成员函数。
DDZPlayer
的其他成员函数,例如你在实现DDZPlayer::legal(...)
,想要输出当前对象的手牌,你可以直接调用——直接写
showHand();
DDZPlayer
的成员函数里,例如,你在调试run()
函数,想在run()
函数中输出看看某个玩家的手牌,你可以这样调用:// run()函数中,players[0]、players[1]、players[2]分别是三个玩家的指针。
players[0]->showHand(); // 输出players[0]的手牌
DDZHumanPlayer
的成员函数,你会发现,虽然没有实现过DDZHumanPlayer::showHand()
,但是它还是可以被调用(参见DDZHumanPlayer::play()
的实现)。这是由于DDZHumanPlayer
继承自DDZPlayer
,所以DDZPlayer
的成员函数它可以直接用。不仅是showHand()
函数,所有别的函数和变量都能直接用。DDZPlayer
的所有函数和变量都能直接用,为什么重写了一个DDZHumanPlayer::play()
?因为我希望DDZHumanPlayer::play()
完成的功能与DDZPlayer::play()
不一样。A:不,你能写得了。需要你完成的部分不涉及面向对象的特性、继承、重载等等内容,你只需要用提供好的变量和函数拼装成要求你完成的部分就行了。你还需要阅读一下代码,尝试理解一点面向对象的工作机制。如果哪里读不懂,请直接咨询助教,我们会帮你解答。
A:题目:我们提供了一辆汽车,缺一个轮子,大家看一下,体会一下这个汽车是怎么设计的,并自己把这个轮子装上,把车开起来。
同学1:老师,你这个汽车设计太过时了,是我小学的时候玩的,我能从头搞一辆坦克出来,不用你的破车了行不?
老师:可以,鼓励。
同学2:老师,你这个汽车的结构我看不懂,我有辆自行车也能跑起来,不用你的车行不?
老师:……不建议。
上周的题目虽然没有困难的知识点,但是如果不能良好地组织代码,实现起来将会比较麻烦。
本周的题目旨在提供一个面向对象程序设计的第一印象,让大家体会面向对象程序设计中的类的设计和功能的划分。因此,这里提供一个代码框架,请大家基于这个代码框架,将上周的第二题以面向对象的方式完成。
通过合理的设计,程序的逻辑可以十分清晰。程序的main()
函数作为顶层的流程,实现只需要三行:
int main() {
DDZPlayer p1("Alice"), p2("Bob"), p3("Charlie");
DDZGame g(&p1, &p2, &p3);
g.run();
}
DDZGame
类打包了游戏运行的玩家和状态信息,并具有控制游戏流程的功能。其成员变量和函数声明如下(一些辅助函数和变量没有列出):
class DDZGame {
private:
vector<DDZPlayer*> players; // 保存三个玩家的指针
public:
DDZGame(DDZPlayer *p1, DDZPlayer *p2, DDZPlayer *p3); // 构造函数
void run(); // 执行游戏流程
};
DDZGame::run()
函数控制了游戏的整个流程。纵观游戏的流程,可以发现DDZPlayer
类需要和DDZGame
类有一些交互(下图红字标出),也就是DDZPlayer
类需要实现的函数功能。
因此,我们依次可以设计出DDZPlayer
类的成员变量和函数(一些辅助函数和变量没有列出):
class DDZPlayer {
protected:
string name; // 玩家名
int position; // 你的位置编号,0为地主,1为地主下家,2为地主上家
vector<Card> hand; // 手牌
public:
DDZPlayer(string name); // 构造函数,初始化玩家名
virtual void draw(Card card); // 将cards中的牌加入手牌
virtual void draw(vector<Card> cards); // 将cards中的牌加入手牌
virtual void setPosition(int pos); // 初始化用,决定地主后设置
virtual void observed(int pos, vector<Card> cards); // 观测到玩家出牌
virtual vector<Card> play(); // 轮到自己时决定出什么牌
bool leftNoCard(); // 返回是否打完了牌?
};
此外,框架中还实现了一个Card
类,继承自std::string
。你可以把它当作string
使用,但是Card
类的成员变量可以使用>
/<
来比较牌面的大小。
class Card : public string {
public:
Card(const char* str) :string(str) {};
Card() :string() {};
Card(string str) :string(str) {};
static vector<Card> get_new_deck();
// 重载操作符,使得牌面可以比较大小
bool operator <(const Card &other) const;
bool operator >(const Card &other) const;
};
这道题目的目标有两个:
DDZPlayer::play()
函数。如果正确完成,程序将执行模拟牌局的功能。对于阅读代码,建议你按照下面的流程进行:
main
函数开始,读懂main
函数。main
函数中调用的DDZGame::run()
是主要流程,读懂这个函数的大致流程DDZGame::run()
函数,当看到里面调用的DDZPlayer
类的成员函数时,跳到相应的函数阅读。play()
函数之前,你应该已经大致弄懂了Card
、DDZPlayer
、DDZGame
三个类。在完成代码的过程中,请注意:
借助面向对象程序设计的特性,前面的程序可以轻易地扩展出不同种类的玩家类,使游戏变得丰富。例如:
DDZHumanPlayer
,其出牌策略play()
是将手牌输出到命令行,然后从命令行读取一组要打出的牌。加入两个DDZPlayer
和一个DDZHumanPlayer
,模拟牌局就变成了单机游戏。这个类的其他功能,应该和DDZPlayer
类保持一致。DDZSmartPlayer
,把出牌策略play()
替换成一个高级策略,游戏就不再弱智了。这个类的其他功能,应该和DDZPlayer
类保持一致。程序框架中DDZHumanPlayer
已经基本实现好了,但是判断出牌合法性的DDZPlayer::legal(...)
函数还没有完成。注意legal(...)
函数虽然是DDZPlayer
类的成员函数,当其正确实现后,继承自DDZPlayer
类的DDZHumanPlayer
也可以直接使用这个函数。当你正确完成这个函数后,将main2()
函数中的内容写到主函数中,一个单机版的斗地主游戏就可以运行了。
int main() {
string name;
cout << "Please input your name:" << endl;
getline(cin, name);
DDZPlayer p1("Alice"), p2("Bob");
DDZHumanPlayer p3(name);
DDZGame g(&p1, &p2, &p3);
g.run();
}
这道题目实现的具体要求如下:
DDZPlayer::legal(cards)
函数,判断出牌的正确性。main2
函数的内容填到main
函数中,程序就变成了单机斗地主(弱智版)。把程序调试正确。DDZHumanPlayer
对DDZPlayer
的继承,弄明白哪些函数和变量由于继承,被重用而没有多写一遍DDZHumanPlayer
是DDZPlayer
的派生类,所以可以直接传给DDZGame
类作为玩家而不用写特殊判断;在DDZGame
类调用play()
函数时又实质上执行了不同的出牌策略(多态)。