题目描述&链接
21点游戏OOD设计:设计一个可以多人在线的21点游戏,一个游戏室会有一个荷官(发牌员),多名玩家,一条牌组。每一名玩家用手里的牌与发牌员比大小,谁越接近21点,谁就算赢。如果你的牌是一个Ace + 10的话,那么你就是Black Jack也就是游戏中最大牌。
题目结构设计梳理
作为一个初学者,一开始看这道题着实是没有什么思路,不知道从哪里下手,而且看了网上一些设计,写的确实很好,但是我认为功能过于完善,我并不认为在面试20分钟的时间内,能够做到那种完美的设计。我这篇文章主要的想法一个是总结我自己作为新手在接触OOD设计题目的理解,以及根据网上资源教程和自己的理解,整理了我觉得最为重要的几个核心部分。我认为会更便于像我一样的初学者进行学习。
作为OOD设计问题,归根到底无非就是类的设计以及类之间调用关系设计,另外代码结构上最后运用一些可以使用上的设计模式(Factory, Strategy等等)进行结构优化。
OOD设计首先考虑时间,地点,人物这三要素,大致会有下列这些元素:
- 时间 - 21点游戏里感觉没有什么需要考虑时间节点(如果开局,退出不算的话)
- 地点 - 应该有一个游戏室,这个游戏室可以加入发牌员,玩家,并且所有结算筹码,判断游戏输赢,都应该在游戏室内完成
- 人物 - 发牌员(负责跟玩家比大小,初始化牌组) , 玩家(拿牌,改筹码,这局游戏跟还是不跟)
整体流程可以设计成如下图所示:
1. 卡牌类
首先我们要有游戏媒介 - 纸牌,21点游戏并不需要考虑花色,纸牌唯一需要的信息就是数组(A-K) 也就是 1-13.
class Card {
private int value;
public Card(int v) {
value = v;
}
public int getValue() {
return value;
}
}
接下来,每一个玩家和发牌员都有手牌,由于手牌类我们也想加入一些功能包括:查看当前手牌合计分数,加入手牌等,所以可以将手牌建立成一个类。
class Hand {
private List<Card> hands;
public Hand() {
hands = new ArrayList<>();
}
// 添加手牌
public void addHand(Card card) {
hands.add(card);
}
// 清空手牌,准备下一局或者退出游戏
public void clear() {
hands = new ArrayList<>();
}
// 找到当前手牌<=21的最大分数
public int getBestValue() {
// 计算出当前手牌最好的分数,由于Ace可以1,11 所以手牌分数存在多种可能
// 首先枚举处手牌可以组成的所有分数
List<Integer> val = new ArrayList<>();
int aceCount = 0;
int ans = 0;
for(Card c: hands) {
if(c.getValue()==1) {
aceCount++;
} else if(c.getValue>=10) {
ans+=10;
} else {
ans+=c.getValue();
}
}
for(int i=0; i<=aceCount; i++) {
int one = i;
int eleven = aceCount-i;
val.add(ans + one*1 + eleven*11);
}
int res = -1; // if ans>21 -> return -1
for(int v: val) {
if(v<=21 && res<v) res = v;
}
return res;
}
}
2. 人物类
首先是玩家类,玩家主要的功能就是抽牌,调整筹码,选择是否继续进行游戏。
class Player {
Hand hands;
int totalBets;
int bets;
boolean stopDealing;
public Player() {
hands = new Hand();
totalBets = 1000;
stopDealing = true;
}
public void drawCard(Card card) {
hands.addCard(card);
}
public void clear() {
hands.clear();
}
public Hand getHand() {
return hands;
}
public void addBets(int v) {
// 筹码不合理,退出
if(v<=0 || v>totalBets-bets) return;
// 如果筹码合理,就添加
bets+=v;
}
public void addTotalBets(int v) {
if(v<=0) return;
totalBets+=v;
}
public int getBets() {
return bets;
}
public int getTotalBets() {
return totalBets;
}
public void stopDealing() {
stopDealing = false;
}
public boolean isDealing() {
return stopDealing;
}
}
然后是发牌员,发牌员没有Stop Dealing的选择,以及发牌员不需要出筹码。
class Dealer{
Hand hands;
int totalBets;
// 发牌员不需要有筹码,如果赢了就收钱,输了就结账即可
public Dealer() {
hands = new Hand();
totalBets = 10000;
}
public void drawCard(Card card) {
hands.addCard(card);
}
public void clear() {
hands.clear();
}
public void addTotalBets(int v) {
if(v<=0) return;
totalBets+=v;
}
public void setTotalBets(int v) {
totalBets = v;
}
public int getTotalBets() {
return totalBets;
}
public boolean isLarger(Player p) {
Hand phand = p.getHand()
return hands.getBestValue()>=phand.getBestValue();
}
}
3. 游戏室类
该OOD中最主要的设计,其中包含整个游戏规则流程的逻辑,开牌发牌逻辑,添加发牌员和玩家,核心功能都在这个大类中
class Game {
List<Card> cards;
int nextIdx;
Dealer dealer;
List<Player> playes;
public Game() {
players = new ArrayList<>();
}
public void addPlayer(Player p) {
if(players.size()<5) players.add(p);
}
public void addDealer(Dealer d) {
dealer = d;
}
public void initGame() {
if(Dealer==null || players.size()<=0) return;
// 初始化牌组,并且进行洗牌shuffle
// 四个花色 + 13张牌
for(int i=0; i<4; i++) {
for(int j=1; i<=13; j++) {
Card c = new Card(j);
cards.add(c);
}
}
// shuffle的逻辑就是随机选择在当前位置i到end之间的某个位置j将i,j进行交换
for(int i=0; i<cards.size(); i++) {
int j = Math.random()*(cards.size()-i) + i;
Card a = cards.get(i);
Card b = cards.get(j);
//交换位置
cards.set(i, b.getValue());
cards.set(j, a.getValue());
}
}
public void sendCard() {
if(cards.size()>nextIdx) return cards.get(nextIdx++);
}
public void compareResult() {
// 循环计算player结果
for(Player i: players) {
if(i.stopDealing()) continue;
if(dealer.largerThan(i)) {
dealer.setBets(i.getBets());
if(i.getTotalBets()-i.getBets()==0) {
i.setStopDealing(true);
}
//
i.setTotalBets(i.getTotalBets() - i.getBets());
} else {
// player wins
if(dealer.getBets()<i.getBets()) {
dealer.setBets(dealer.getBets() + 3*i.getBets()); //如果dealer筹码不够加筹码
}
i.setTotalBets(i.getTotalBets() + i.getBets()); // 更新总筹码
}
i.setBets(0);
}
}
}