在很长一段时间里,我总是觉得设计一个健壮的面向对象程序(尤其是在C++中)是错误的。我觉得我不懂足够的理论或算法,所以我不会学习,也不会写代码,更不用说我总是害怕批评或者看起来很业余。最近,我意识到,通过这样做和超越我的不安全感,我一直在克制自己。我通过编写普通的C++程序来建立我对语言和架构的知识,取得了更大的进步。每个人都告诉我,写一款Blackjack游戏将帮助我为更有雄心的项目建立纯粹的知识,并为我难以理解的开源项目做出贡献。
博士:我一直在创建一个Blackjack程序,以帮助我在更高级的水平上学习编程和设计。
我尝试使用以前从未使用过的东西,例如名称空间、枚举和带有操作符重载的映射。现在这是一项正在进行的工作:它只处理两个玩家,直到我让游戏正常工作,才开发出经销商逻辑,考虑为Blackjack规则创建一个派生类,以防我想要制定扑克规则。我已经知道这是个烂摊子,但我想这是学习过程的一部分,嗯?
main.cpp
#include <iostream>
#include <string>
#include "BlackJack.h"
#include "Player.h"
int main() {
BlackJack game(2); // only two players for now, requires user for both
game.play();
return 0;
}CardInfo.h
#pragma once
#include <string>
#include <unordered_map>
namespace Cards {
enum class Rank {
ONE = 1, TWO, THREE,
FOUR, FIVE, SIX,
SEVEN, EIGHT, NINE,
TEN, JACK, QUEEN, KING, ACE
};
enum class Suit {
CLUBS, DIAMONDS, HEARTS, SPADES
};
};
namespace CardData {
// TODO make a derived card class specifically for Blackjack rules
struct Card {
Cards::Rank rank;
Cards::Suit suit;
// TODO define this so the map works
bool operator<(const CardData::Card &card) const {
return this->rank < card.rank;
}
};
}Player.h
#pragma once
#include <vector>
#include <string>
#include "CardInfo.h"
class Player {
int points{ 0 };
int numAces{ 0 }; // count aces in order to deduce 1 or 11 logic in updateScore
int id;
std::vector<CardData::Card> hand;
public:
Player(int ID);
void dealtCard(CardData::Card &drawnCard);
void updateScore(int score); // call after every dealt card
bool requestHit(); // request to exchange cards
bool hasBust();
int getScore();
int getId();
};Player.cpp
#include "Player.h"
#include <string>
#include <iostream>
Player::Player(int ID) : id(ID) {}
void Player::dealtCard(CardData::Card &drawnCard) {
//Card drawnCard{ Cards::Rank::ACE, Cards::Suit::SPADES }; remove possibly
hand.push_back(drawnCard); // test insert
}
void Player::updateScore(const int score) {
if (score == 11)
numAces++;
points += score;
if (points > 21)
while (numAces > 0) {
numAces--;
points--;
}
}
bool Player::requestHit() {
char answer;
std::cout << "Player " << id << ": Hit or stick? [y|n]" << std::endl;
std::cin >> answer;
return tolower(answer) == 'y';
}
bool Player::hasBust() {
return getScore() > 21;
}
int Player::getScore() { return points; }
int Player::getId() { return id; }BlackJack.h
#pragma once
#include <set>
#include <random>
#include "Player.h"
#include <string>
class BlackJack {
int numPlayers;
static std::unordered_map<Cards::Rank, int> score;
std::vector<Player> players;
std::set<CardData::Card> inPlay; // drawn cards inserted here to prevent duplication
CardData::Card drawCard();
void hitOrStick();
void playHands();
int enumerateCard(const CardData::Card &card) const; // converts cards to score, deals with Aces and Royals
void deal(Player &player);
public:
BlackJack(int players);
void play();
};BlackJack.cpp
#include "BlackJack.h"
#include <iostream>
#include <string>
std::unordered_map<Cards::Rank, int> BlackJack::score{
{Cards::Rank::ONE, 1},{Cards::Rank::TWO, 2},{Cards::Rank::THREE, 3},
{Cards::Rank::FOUR, 4},{Cards::Rank::FIVE, 5},{Cards::Rank::SIX, 6},
{Cards::Rank::SEVEN, 7},{Cards::Rank::EIGHT, 8},{Cards::Rank::NINE, 9},
{Cards::Rank::TEN, 10},{Cards::Rank::JACK, 10},{Cards::Rank::QUEEN, 10},
{Cards::Rank::KING, 10},{Cards::Rank::ACE, 11}
};
BlackJack::BlackJack(int players = 2) : numPlayers(players) {
for (int i = 0; i < numPlayers; i++) {
this->players.push_back(Player(i));
}
}
void BlackJack::play() {
// Shuffle workflow and distribute cards
for (Player player : players) {
std::cout << "Player \'" << player.getId() << "\' These are your cards: " << std::endl;
for (int i = 0; i < 2; i++) {
deal(player);
}
}
hitOrStick();
playHands();
}
void BlackJack::deal(Player &player) {
CardData::Card drawn;
drawn = drawCard();
while (inPlay.find(drawn) != inPlay.end())
drawn = drawCard();
std::cout << score[drawn.rank] << std::endl;
player.dealtCard(drawn);
player.updateScore(enumerateCard(drawn));
inPlay.insert(drawn);
}
CardData::Card BlackJack::drawCard() {
std::uniform_int_distribution<> suitDistrib(0, 3);
std::uniform_int_distribution<> rankDistrib(1, 14);
std::random_device rd;
std::mt19937 generator(rd());
int newRank = rankDistrib(generator);
int newSuit = suitDistrib(generator);
return CardData::Card{ static_cast<Cards::Rank>(newRank), static_cast<Cards::Suit>(newSuit) };
}
int BlackJack::enumerateCard(const CardData::Card &card) const {
return score[card.rank];
}
void BlackJack::hitOrStick() {
for (Player player : players)
while (!player.hasBust() && player.requestHit())
deal(player);
}
void BlackJack::playHands() {
int winner = -1;
int maxScore = -1;
bool push = false;
for (Player player : players) {
std::cout << "Player " << player.getId() << ": " << player.getScore() << std::endl;
if (player.getScore() > maxScore) {
push = false;
winner = player.getId();
maxScore = player.getScore();
} else if (maxScore == player.getScore()) {
push = true;
}
}
if (push)
std::cout << "Push!" << std::endl;
else if (winner == 0)
std::cout << "House wins!" << std::endl;
else
std::cout << "Player " << winner << " wins!" << std::endl;
}对于我需要改进什么或者设计/清理代码有什么建议吗?如果你也有任何建议,我应该如何关注我的经销商AI或operator<过载,我真的很感激。祝你每天都好起来!
发布于 2019-06-10 08:28:28
最近,我意识到,通过这样做和超越我的不安全感,我一直在克制自己。我通过编写普通的C++程序来建立我对语言和架构的知识,取得了更大的进步。
边做边学是一种很好的方法。希望你能改进你的游戏,并再次提交审查!
主要需要Player.h吗?
将2转换为一个命名常量:
int constexpr players{2};
BlackJack game{players}; 这使得传递给你游戏的内容更加清晰。
虽然有些人真的不喜欢这样做,但如果不依赖return 0,则可以从main中省略它。编译器将为您生成它。
就我个人而言,我不介意#pragma once,只要记住它是不标准。
主观性不要缩进namespace的后面。
主观性不要在枚举中每行写一个以上的语句。
如果要使用带有自定义键的无序容器,则需要提供散列和比较函数。看看这个问题和这个从偏好链接。
类接口应该从最小限制转到最严格限制(即public、protected、private)。原因是,当有人读取您的界面时,他很可能对可以使用的公共功能感兴趣。
主观性不要漏掉大括号,因为这样会导致错误。例如:
// bad
if (foo)
// code here
// good
if (foo)
{
// code here
}我对21点并不熟悉,但是如果你问“击中还是坚持”,答案应该是“是/否”,而不是“打/棒”吗?
看看这个问题,看看它是如何实现的。
https://codereview.stackexchange.com/questions/221881
复制相似问题