心得体会
这一个游戏的编写复杂度有点超乎我的想象,跟着老师写了一个早上+下午,虽说最后还有点懵,但也懂了很多之前没懂的东西,比如什么时候需要新建一个类,三目运算符的使用等。又想起古文里说的“求其上者,得其中;求其中者,得其下”,就是说得到的东西总会比自己理想的差一层次,编写超出自己实力程序肯定会使自己有所收获,加油吧。
1. 目的
- 一、了解单例设计模式
- 二、进一步熟悉数组的使用
- 三、完成扑克游戏的编写
2. 内容简概
- 一、了解单例设计模式
- 二、了解数组的引用
(以下为扑克牌游戏编写的具体步骤)
- 三、用封装实现文本输出
- 四、定义Poker类
- 五、生成一副牌
- 六、初始化玩家信息
- 七、实现管理底注功能
- 八、实现发牌功能
- 九、实现弃牌和下注功能
- 十、奖励获胜玩家
- 十一、游戏主程序
- 十二、游戏主程序
3. 具体内容
-
一、了解单例设计模式
1. 什么是单例设计模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
具体实现需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
2. 为什么要用它?什么时候用到它呢?
(1)一般情况下,当我们想使用这个类时,会使用new关键字,而new这个关键字是比较耗资源的,所以,如果在某一次实例化new好后不用再重复该动作,就能节省很多资源了。
(2)需要频繁的进行创建和销毁的对象。
(3)创建对象时耗时过多或耗费资源过多,但又经常用到的对象。
3.单例模式分为饿汉单例模式和懒汉单例模式
(1)饿汉单例模式:静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例模式。
(2)懒汉单例模式:要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例模式。
4.如何编写?
(1)饿汉单例模式
定义:使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。
优点:实现起来简单,没有多线程同步问题。
缺点:在某些特定条件下会耗费内存。
class Poker{
//定义一个静态的成员变量,并用static、final修饰
public static final Poker shared = new Poker();
//构造方法私有化
private Poker(){}
// 提供给外部一个访问的方法——静态方法返回该实例
public static Poker getInstance() {
return shared;
}
(2)懒汉单例设计模式
定义:调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
优点:在某些特定条件下会节约了内存。
缺点:在多线程情形下,synchronized方法通常效率低
class Player{
public int count;
//定义一个静态的成员变量,并用static修饰
private static Player shared = null;
//构造方法私有化
private Player(){}
//提供给外部一个访问的方法——静态方法返回该实例
public static Player getInstance(){
Object b = new Object();
synchronized (b){
if (shared == null){
//在多线程情形下,保证了“懒汉模式”的线程安全
shared = new Player();
}
}
return shared;
}
}
-
二、了解数组(ArrayList)的引用
1. 什么是ArrayList?
ArrayList<E>
中,E
表示泛型,ArrayList
是一个泛型类。
ArrayList
相当于C++ 的指针,用于存储对象。与数组不同,数组一旦创建,长度固定,但是ArrayList的长度是动态的,不受限制,可以存储任意多的对象,但是只能存储对象,不能存储基本数据类型。
2. 如何使用ArrayList类?
class Test2{
public static void main(String[] args){
ArrayList<Person> people = new ArrayList<>();
//获取数组元素个数
people.size();
//添加数据
Person xw = new Person();
people.add(xw);
Person zs = new Person();
people.add(zs);
//访问数据
Person xw2 = people.get(0);
xw2.name = "小王";
System.out.println(xw.name);
//System.out.println(xw2.name);
}
}
class Person{
public String name;
}
3.运行结果分析
不论是xw.name
还是xw2.name
,输出结果都为“小王”,这是因为改变数组里面对象的属性变量,原始对象的值也跟着改变,因为大家都是指向同一个内存空间。
游戏编写
游戏规则:
(1)由玩家选择游戏人数(由于平台和水平限制,需要一人扮演多人玩游戏)。
(2)游戏的底注为10,每个人初始筹码为1000。
(3)功能有看牌、弃牌和下注,一人下注后则显示为跟注。
(4)下注金额不能大于筹码,若跟注金额大于筹码,系统默认为弃牌
(5)弃牌后只剩一人时该玩家为赢家,游戏剩两人时最多局数为4局,游戏为三人时不限制局数。
我们再来先来回顾一下这个扑克牌游戏的程序结构
-
三、用封装实现文本输出
1.分析一下我们可能会用到的文本类型:
可以看出,我们可能需要单行或多行文字,可能需要换行和分隔符,故我们想到为此新建一个Utils
类,专门管理一些常用功能。
2. 接收用户输入
在显示游戏界面后,用户肯定要进行多次操作,接收用户输入也是一个常用功能,故我们也将其写入Utils
类.
public class Utils {
//如果不需要保存数据 没有成员变量
//提供静态方法 访问方便
public static void showText(boolean hasStar,boolean lineBreak,String...contents){
//判断是否需要显示分割线
System.out.print(hasStar?"*******************************\n":"" );
//判断输出的内容是多行还是一行
if (contents.length == 1){
System.out.print(contents[0]);
//有分割线换行
System.out.print(hasStar?"\n":"");
}
else{
//输出带编号的多行数据
for (int i = 0; i < contents.length; i++){
System.out.println((i+1)+"."+contents[i]);
}
}
System.out.print(hasStar?"*******************************\n":"" );
//判断是否需要换行
System.out.print(lineBreak?"\n":"");
}
//接收用户输入
public static int getInput(){
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
}
-
四、定义Poker类
1. 用
Constant
类管理常量数据
欢迎界面后,就想到应该要有一副牌,故我们新建一个Poker
类,用来管理牌的点数和花色。由于点数和花色是固定的,不变的常量,故我们再新建一个Constant
类用来管理常量数据。我们先在Constant
类中写好扑克的点数和花色。
public class Constant {
//用数组保存牌的点数和花色
public static final String[] DOTS = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
//保存固定的几个花色 黑红梅方
public static final PokerType[] TYPES = {PokerType.SPADES,PokerType.HEARTS,PokerType.CLUBS,PokerType.DIAMONDS};
//保存默认的玩家姓名
public static final String[] DEFAULT_NAMES = {"刘德华","周润发","张家辉","周星驰"};
//设置默认的金币
public static final int MONEY = 1000;
//每局消耗的金币数
public static final int BASE = 10;
}
2. 用
PokerType
类管理花色
由于花色的ASCII码值不连贯,若用其来比较大小不方便,故我们新建一个PokerType
类,专门用来管理和比较它的花色,使用setter/getter方法虽然有点麻烦,但能使其封装性更好。关于如何快速创建setter/getter方法,在我之前的文章有讲到 ☛ setter/getter方法
public class PokerType {
public static final PokerType SPADES = new PokerType("♠",4);
public static final PokerType HEARTS = new PokerType("♥",3);
public static final PokerType CLUBS = new PokerType("♣",2);
public static final PokerType DIAMONDS = new PokerType("♦",1);
//用简单的数字表示,使其比较更简单
private String pic;
private int id;
//不让默认的构造方法被覆盖
public PokerType(){}
//提供一个构造方法
//默认的构造方法就被屏蔽了
public PokerType(String pic,int id){
this.pic = pic;
this.id = id;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
3. 用
Poker
类管理每一张牌的点数和花色
编写好点数和花色后,我们需要将其在这个类拼接起来,使其输出如“K♥”“A♣”的效果
public class Poker {
private String dot;
private PokerType type;
public Poker(){}
public Poker(String dot,PokerType type){
this.dot = dot;
this.type = type;
}
//setter/getter方法
public void setDot(String dot){
this.dot = dot;
}
public String getDot(){
return dot;
}
public PokerType getType() {
return type;
}
public void setType(PokerType type) {
this.type = type;
}
public Player compareTo(Player player){
this.type = type;
}
public boolean bigerThan(Poker poker){
int mIndex = Arrays.binarySearch(Contant.DOTS,this.dot);
int oIndex = Arrays.binarySearch(Contant.DOTS,poker.dot);
if (mIndex != oIndex){
//点数不同 直接比较
return mIndex > oIndex;
}
else {
//点数相同 比较花色
return this.type.getId() > poker.type.getId();
}
}
}
-
五、生成一副牌并洗牌
1.用
PokerManager
类管理牌的操作
我们讲过PokerManager
类用来管理生成一副牌、洗牌和发牌,上面我们已经用Poker
类生成了52张单牌(无鬼牌),先在我们要生成一副完整的牌,则需要新建一个PokerManager
类。
public class PokerManager {
//保存一副牌
private ArrayList<Poker> pokers = new ArrayList<>();
//创建静态的变量
public static final PokerManager manager = new PokerManager();
//私有化构造方法
private PokerManager(){}
//定义一个方法 生成一副牌
public void deal(){
//遍历整个点数的数组
for (int i = 0; i < Contant.DOTS.length; i++){
//获取对应的点数
String dot = Contant.DOTS[i];
//生成四种花色
for (int j = 0; j < Contant.TYPES.length; j++){
//创建一张牌
Poker poker = new Poker(dot,Contant.TYPES[j]);
//将这张牌保存起来
pokers.add(poker);
}
}
//洗牌
Collections.shuffle(pokers);
}
//显示一副牌
public void show(){
for (Poker poker:pokers){
//K♣
System.out.print(poker.getDot()+poker.getType().getPic()+" ");
}
System.out.println();
}
-
六、初始化玩家信息
1. 用
Player
类管理玩家属性
在生成一副牌后,我们想到这个游戏还需要玩家,故我们先来写好玩家的各个属性,使其输出效果如“1号玩家 :张家辉 筹码1000 10♠”
public class Player {
public String name;
public int id;
public int money;
public Poker poker;
public boolean hasDiscard;//是否弃牌
public Player(){
}
public Player(String name,int id,int money){
this.name = name;
this.id = id;
this.money = money;
}
@Override
//当打印一个对象的时候 就会默认去调用对象的toString方法
//如果当前类里面没有实现这个方法 就到父类里面去查找
//object里面默认实现就是打印对象的首地址
public String toString() {
//1号玩家 张家辉 金币:1000
String pkString = "";
if (poker != null){
pkString = " "+poker.getDot()+poker.getType().getPic();
}
return id +"号玩家 " + name + " 筹码:"+ money + " " + getPokerString();
}
public String getPokerString(){
String pkString = "";
if (poker != null){
pkString = poker.getDot()+poker.getType().getPic();
}
return pkString;
}
-
七、实现管理底注功能
1. 用
GameCenter
类管理底注、记录每局金额
玩家和牌都配备好后,先不着急发牌,我们现来设置底注,在这里,我们规定底注为10,新建一个GameCenter
类。
public class GameCeter {
//记录这一局的筹码
private int totalMoney;
//开始游戏
public void start(){
System.out.println("游戏开始,请打底:");
PlayerManager manager = PlayerManager.manager;
//扣除底注
manager.betAll(Contant.BASE);
manager.show();
-
八、实现发牌功能
1. 发牌
现在我们可以发牌了,发牌和洗牌是PokerManager
类管理的,故我们在PokerManager
类中编写方法。
public void dealCards(ArrayList<Player> players){
for (int i = 0; i < players.size(); i++){
Player player = players.get(i);
//将数组里面对应的扑克牌给对应的玩家
player.poker = pokers.get(i);
}
}
}
-
九、实现弃牌和下注功能
1. 弃牌和下注
这两个功能联系比较紧密,故在此放到一起讲。若上一个玩家弃牌,则轮到下一个玩家下注,涉及到选择和循环,我们用for循环和switch语句来实现该功能。
(1)Player类中
需要判断玩家的下注金额是否足够
/**打底 &下注
* @param count 下注金额
* @return -1:失败 >0 成功
*/
public int bet(int count){
//判断自己的金币 是否大于下注金额
if (money >= count){
money -= count;
return count;
}
else {
return -1;
}
}
public void add(int count){
money += count;
}
}
(2)PlayerManager类
需要记录每局总金额、判断是否下注成功(可能余额不足)、使每个玩家轮流下注以及统计剩余玩家人数
/**下注总金额
* @param count 每局消耗的金币
* @return -1:失败 >0 成功
*/
public int betAll(int count){
for (Player player:players){
int result = player.bet(count);
if (result == -1){
return -1;
}
}
//返回总共的下注金额
return count * players.size();
}
/**
* 获取当前下注的玩家
* @return 玩家对象
*/
public Player currentPlayer(){
return players.get(currentPlayerIndex);//获取玩家编号
}
/**
* 当前剩余玩家数
* @return
*/
public int leftPlayerCount(){
int total = 0;
for (int i = 0; i < players.size(); i++){
Player player = players.get(i);
//没弃牌且有钱 才能玩
if (player.hasDiscard == false && player.money > 0){
total++;
}
}
return total;
}
/**
* 查找下一个下注的人
*/
public void changeNext(){
int i = currentPlayerIndex;
if (i == players.size()-1){
i = 0;
}
else{
i++;
}
//查找下一个可以参与的玩家
for (; i < players.size(); i++){
Player player = players.get(i);
if (player.hasDiscard == false && player.money > 0){
currentPlayerIndex = i;
return;
}
}
}
-
十、奖励获胜玩家
1.用
PlayerManager
类判断获胜玩家
奖励获胜玩家属于玩家管理系统的工作,故我们在PlayerManager
类中写一个奖励方法
public void awardWinner(int total){
Player winner;
int available = leftPlayerCount();
if (available == 1){
//只有一个玩家了 这就是赢家
changeNext();
winner = currentPlayer();
}
else {
//需要比较这两个玩家的牌
Player w1 = null;
Player w2 = null;
for (int i = 0; i < players.size(); i++){
Player player = players.get(i);
if (player.hasDiscard == false){
if (w1 == null){
w1 = player;
}
else {
w2 = player;
}
}
}
boolean result = w1.poker.bigerThan(w2.poker);
if (result){
winner = w1;
}
else {
winner = w2;
}
}
System.out.println(winner.id+"号玩家赢得胜利!赢得:"+ total +"金币");
winner.add(total);
}
}
-
十一、将
PlayerManager
类和PokerManager
类联合
1.用
GameCenter
类将管理类拼接
根据结构分析可知,GameCenter
类管理PlayerManager
类和PokerManager
类,新建一个GameCenter
类。
public class GameCeter {
//记录这一局的筹码
private int totalMoney;
//开始游戏
public void start(){
System.out.println("游戏开始,请打底:");
PlayerManager manager = PlayerManager.manager;
//扣除底注
manager.betAll(Contant.BASE);
manager.show();
//发牌
System.out.println("开始发牌");
PokerManager.manager.dealCards(manager.players);
PlayerManager.manager.show();
int time = 0;//记录如果是两个人的次数
boolean isFirst = true;
int betMoney = 0;
while (true){
//获取当前玩家信息
Player player = manager.currentPlayer();
//提示选择操作
System.out.println("请" + player.id + "号玩家选择操作:");
Utils.showText(true,true,new String[] {"看牌","弃牌",isFirst?"下注":"跟注"});
int choice = Utils.getInput();
boolean flag = false;
switch (choice){
case 1:
//看牌
System.out.println(player.getPokerString());
flag = true;
break;
case 2:
//弃牌
System.out.println(player.id + "号玩家弃牌!");
player.hasDiscard = true;
break;
default:
//下注
if (isFirst){
while (true) {
System.out.print("请输入下注金额:");
betMoney = Utils.getInput();
int result = player.bet(betMoney);
if (result == -1) {
//下注不成功
System.out.print("余额不足 ");
} else {
//下注成功
isFirst = false;
totalMoney += betMoney;
break;
}
}
}
else {
//跟注
int result = player.bet(betMoney);
if (result == -1){
player.hasDiscard = true;
}
else {
System.out.println("下注成功!");
totalMoney += betMoney;
}
}
break;
}
if (flag == false){
//计算当前还有多少人可以参与
int available = manager.leftPlayerCount();
if (available == 1){
//本局结束
break;
}
if (available == 2){
time++;
if (time == 4){
//两个回合结束 结束游戏
break;
}
}
//time == 3 切换到下一个人
manager.changeNext();
manager.show();
}
}
manager.awardWinner(totalMoney);
}
}
-
十二、游戏主程序
用
MyClass
类管理游戏主程序
其实MyClass
类大可不必最后创建,在编写欢迎界面时就可以创建了,这样我们可以写一点运行一次,防止程序出现多处错误。但在这里不便于分开讲,所以才汇总到最后。
public class MyClass {
public static void main(String[] args){
//欢迎界面
Utils.showText(true,true,new String[]{"欢迎参加扑克游戏"});
//生成一副牌
PokerManager.manager.deal();
//显示一副牌
PokerManager.manager.show();
//提示输入玩家人数
Utils.showText(false,false,new String[]{"请输入参与人数:"});
//接收用户输入
int count = Utils.getInput();
//初始化玩家
PlayerManager.manager.initPlayer(count);
//显示玩家
//PlayerManager.manager.show();
//开始游戏
GameCeter ceter = new GameCeter();
ceter.start();
}
}
4.扑克牌游戏运行效果
结语
通过运行结果可以看出,这个游戏还是有不少BUG的,但因为是初步学习,暂时先不考虑太多,以免程序太复杂,待日后能力够了后,我会再继续完善这一个程序。麻雀虽小,五脏俱全,这也算是一个完整的游戏,难度中等偏上,适合新手磨练。