首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >扑克牌项目最新进展

扑克牌项目最新进展
EN

Code Review用户
提问于 2013-08-03 20:11:02
回答 1查看 1.8K关注 0票数 4

由于这种新方法不同于我以前的修订,因此不需要引用它们。

在接受了CR chat的指导之后,我最终利用了以下几点:

  • enums用于RankSuit而不是数组
  • namespaceenums和Card
  • to_string()s代表军衔和服装在operator<<中扮演std::strings
  • 不再将operator<<作为friend
  • 两位std::vectors为Deck级(用于在重置时保留原甲板)

我想知道是否还有更多的事情可以做,我有一些问题:

  1. 使用两个向量可以有效地保存牌面(而不是让消费代码负责将卡片退回到牌面)吗?
  2. 如果用户不知道西服的顺序是哪一种(当用卡片填满甲板时),是否应该对enums做些其他的事情?
  3. 虽然enums是内置的,但const&仍然适合Card的S构造器吗?
  4. 用户调用empty()是否足够好,以避免类内异常处理的复杂性?
  5. 两名to_string()s的排名和西服能否缩短,同时仍像一张查表?

任何其他反馈也是非常感谢的。除了这些方面之外,我希望能够适应不同的游戏(例如添加一个小丑)的整体可维护性。因此,我不想损害基本的设计,只与几个游戏相关的功能。

Card.h

代码语言:javascript
复制
#ifndef CARD_H
#define CARD_H

#include <iostream>
#include <string>

namespace card
{
    enum Rank {Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
    enum Suit {Clubs,Diamonds,Hearts,Spades};

    class Card
    {
    private:
        Rank m_rank;
        Suit m_suit;

        std::string to_string(Rank const&) const;
        std::string to_string(Suit const&) const;

    public:
        Card(Rank const&, Suit const&);
        std::string to_string() const;
        Rank rank() const {return m_rank;}
        Suit suit() const {return m_suit;}
    };
}

card::Rank& operator++(card::Rank&);
card::Suit& operator++(card::Suit&);
std::ostream& operator<<(std::ostream&, card::Card const&);

#endif

Card.cpp

代码语言:javascript
复制
#include "Card.h"
#include <stdexcept>

card::Card::Card(Rank const& rank, Suit const& suit)
: m_rank(rank), m_suit(suit) {}

std::string card::Card::to_string(Rank const& rank) const
{
    switch (rank)
    {
    case Ace:   return "A";
    case Two:   return "2";
    case Three: return "3";
    case Four:  return "4";
    case Five:  return "5";
    case Six:   return "6";
    case Seven: return "7";
    case Eight: return "8";
    case Nine:  return "9";
    case Ten:   return "T";
    case Jack:  return "J";
    case Queen: return "Q";
    case King:  return "K";
    default: throw std::logic_error("Invalid Rank");
    }
}

std::string card::Card::to_string(Suit const& suit) const
{
    switch (suit)
    {
    case Clubs:    return "C";
    case Diamonds: return "D";
    case Hearts:   return "H";
    case Spades:   return "S";
    default: throw std::logic_error("Invalid Suit");
    }
}

std::string card::Card::to_string() const
{
    return ("[" + to_string(m_rank) + to_string(m_suit) + "]");
}

card::Rank& operator++(card::Rank& r)
{
    return r = card::Rank(static_cast<int>(r)+1);
}

card::Suit& operator++(card::Suit& s)
{
    return s = card::Suit(static_cast<int>(s)+1);
}

std::ostream& operator<<(std::ostream& out, card::Card const& obj)
{
    return out << obj.to_string();
}

Deck.h

代码语言:javascript
复制
#ifndef DECK_H
#define DECK_H

#include <vector>

class Deck
{
private:
    std::vector<card::Card> m_original; // retains original cards when resetting deck
    std::vector<card::Card> m_playable; // cards are drawn from this one

public:
    typedef std::vector<card::Card>::size_type SizeType;

    void reset();
    void shuffle();
    void add(card::Card const&);
    card::Card draw();
    SizeType size() const {return m_playable.size();}
    bool empty() const {return m_playable.empty();}
};

#endif

Deck.cpp

代码语言:javascript
复制
#include "Card.h"
#include "Deck.h"
#include <algorithm>

void Deck::reset()
{
    // is this already exception-safe by itself?
    m_playable = m_original;
}

void Deck::shuffle()
{
    // my tests tell me that an empty vector will not cause this to fail
    std::random_shuffle(m_playable.begin(), m_playable.end());
}

void Deck::add(card::Card const& card)
{
    // new cards always added to both vectors
    m_original.push_back(card);
    m_playable.push_back(card);
}

card::Card Deck::draw()
{
    // still not exception-safe, unless not needed if handled in client
    card::Card topCard(m_playable.back());
    m_playable.pop_back();
    return topCard;
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2013-08-03 21:59:08

通用备注

  • to_strings for RankSuit不属于Card类。将它们从类中移出并移至card命名空间。它们也可以是客户端代码的有用函数。
  • 我个人不喜欢m_前缀。通常,您只需使用普通变量名即可。如果您必须有一个成员变量的命名方案,我认为foo_更易读。这样,我的眼睛就不必放弃m_,然后才能找到真名。
  • 为了增加可读性,我会在大括号和逗号后面放置空格: bool空() const {返回m_playable.empty();} enum适合{Club,钻石,红心,黑桃};如果您认为行太宽,可以将enum定义放在多行上。
  • 我会把S( cases )缩进一个switch里,只有单行的情况.可读性胜过一致性:开关(套装){ case俱乐部:返回"C";case钻石:返回"D";大小写心:返回"H";大小写黑桃:返回"S";默认情况:抛出std::logic_error(“无效西装”);}
  • “我的测试告诉我.”在C++中,大量内容是未指定和未定义的,我们依赖于标准和/或引用,而不是测试。在本例中,在安全方面是很容易的: void::霉运(){ if (m_playable.empty())返回;std::random_shuffle(m_playable.begin(),m_playable.end());}
  • 关于Desk::resetstd::vector::operator=的异常安全性,如果赋值操作符抛出,则基本例外安全保障:对象将处于有效状态。由于m_playable已经具备了所需的容量,所以我想不出它应该抛出什么原因。
  • Deck::draw()是异常安全的:std::list::pop_back永远不会抛出。见下文。
  • 您的Card.h头应该包括<ostream>而不是<iostream>。因为您只需要std::ostream声明,所以只需在Card.cpp中包含<iosfwd><ostream>
  • 为什么op++和op<< for Card没有出现在card名称空间中?
  • 您应该编写单元测试并记录您的接口。

回答您的问题

使用两个向量可以有效地保存牌面(而不是让消费代码负责将卡片退回到牌面)吗?

我想是的。要求客户代码返回卡太苛刻了。

如果用户不知道西服的顺序是哪一种(当用卡片填满甲板时),是否应该对enums做些其他的事情?

客户端代码应该能够很好地处理这个问题。如果它不想使用增量操作符,它只需在填充面板时定义四个循环,甚至可以实现自己的函数来迭代这些循环。

虽然enums是内置的,但const&仍然适合Card的S构造器吗?

通过enums的价值。

用户调用empty()是否足够好,以避免类内异常处理的复杂性?

是。实际上,Deck::draw()有很强的例外安全保证:如果出了问题,它将被回滚回原来的状态。在本例中,您可以免费获得此服务:std::list::pop_back()没有抛出保证。这意味着draw()中唯一可能失败的操作是调用m_playable.back(),而这不会修改容器。只要调用成功,整个函数也会成功。

两名to_string()s的排名和西服能否缩短,同时仍像一张查表?

我认为你这样做很好。您可以使用std::map,但与您的方法相比,该方法没有优势。如果你想这样做,你可以这样做:

代码语言:javascript
复制
std::string to_string(Rank rank)
{
    static const map<Rank, string> rank_names = { {Ace, "A"}, {Two, "2"}, {Three, "3"} /* ... */ };

    return rank_names[rank];
}

请注意,这有O(n log )查找时间,而您的实现可能是O(1)。还可以定义数组并将enum值用作索引。这也是O(1),但仍然没有意义--这种方法违背了抽象。

最后,我有一个问题要问你:你将如何实现一个小丑?

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/29348

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档