首页 > 其他分享 >第二十三章《斗地主游戏》第2节:系统功能实现

第二十三章《斗地主游戏》第2节:系统功能实现

时间:2023-01-05 16:36:11浏览次数:61  
标签:第二十三章 return 游戏 斗地主 玩家 PokerType new false else

地主游戏的功能模块非常多,本小节将介绍各功能模块的实现思路及基本源代码。

23.2.1游戏服务器的启动

在server包下有一个Main类,这个类中包含main()方法,main()方法中包含启动游戏服务器的语句,游戏服务器的启动必须先于客户端的启动,否则游戏无法运行。Main类中main()方法的代码如下:

new MainServer();

可以看出:main()方法中仅是创建了一个MainServer类对象,而MainServer类对象会在其构造方法中启动一个ServerSocket,同时启动线程处理客户端与服务器的对话,实现这个过程的代码如下:

//1.创建服务器端socket
ServerSocket serverSocket=new ServerSocket(8888);
while(true)
{
//2.接收客户端的socket
Socket socket= serverSocket.accept();
//3.开启线程 处理客户端的socket
AcceptThread acceptThread=new AcceptThread(socket);
acceptThread.start();
}

23.2.2登录窗口和主界面窗口的启动

当启动了服务器之后,就可以启动用户登录窗口。在client.view包下有一个Main类,它包含main()方法,当main()方法执行后就会创建出玩家登录窗口对象。由于斗地主游戏必须够3个玩家才能开始运行,因此在本案例中,main()方法直接开启3个登录窗口以保证游戏能立刻进入运行状态,但读者必须清楚:在真实的游戏中一次只能开启一个登录窗口。Main类的main()方法代码如下:

new LoginFrame();//打开第一个玩家的登录窗口
new LoginFrame();//打开第二个玩家的登录窗口
new LoginFrame();//打开第三个玩家的登录窗口

从以上代码可以看出:main()方法只是打开了登录窗口,登录窗口的界面如图23-1所示。当玩家在登录窗口中任意输入一个昵称并单击“登录”按钮后,就会进入斗地主游戏主界面窗口。

之所以在单击“登录”按钮后会进入斗地主游戏主界面窗口,是因为“登录”按钮的监听器在监听到事件后会打开主界面窗口,并且用Socket与服务器建立连接。“登录”按钮监听器的事件响应程序如下:

//点击登录
//1.获得用户名
String uname= txtUserName.getText();
//2.创建一个socket链接服务器端
try {
Socket socket=new Socket("127.0.0.1",8888);
//3.跳转到主窗口
new MainFrame(uname,socket);
dispose();//关闭当前窗口
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

23.2.3接收客户端上线消息

每个玩家登录到斗地主游戏后,客户端要主动向服务器发送消息以通知服务器自己已经上线,这样服务器就能知道目前有哪些玩家上线,当有3个客户端连接到服务器后游戏参与者数量正好凑够,此时服务器将开启游戏。

在23.2.2小节曾讲到:玩家在登录之后会创建出一个游戏主界面窗体,也就是MainFrame类的对象,而MainFrame对象则会在自己的构造方法中启动一个线程,并用这个线程向服务器端发送消息,启动线程的代码如下:

// 启动发消息的线程
sendThread = new SendThread(socket, uname);
sendThread.start();

线程启动后发送登录消息的代码如下:

DataOutputStream dataOutputStream;
try {
dataOutputStream = new DataOutputStream(socket.getOutputStream());
while(true)
{
if(isRun==false){
break;
}
//如果消息不为null
if(msg!=null){
System.out.println("消息在发送中:"+msg);
//发送消息
dataOutputStream.writeUTF(msg);
//消息发送完毕 消息内容清空
msg=null;
}
Thread.sleep(50); //暂停 等待新消息进来
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}

服务器端也要相应的接收客户端发来的消息,接收消息的任务也是由一个线程负责完成的,线程接收消息后就知道有一个玩家已经上线,此时它要创建一个玩家对象并进行计数,接收消息及创建玩家对象的代码如下:

Player player=new Player(index++,msg);
player.setSocket(socket);
//存入玩家列表
players.add(player);
System.out.println(msg+"上线了");
System.out.println("当前上线人数:"+players.size());

23.2.4发牌

当有3个玩家上线后,系统会自动启动发牌程序,发牌是由服务器完成的,因此在表示服务器的MainServer类中专门定义了一个发牌方法deal(),该方法的实现过程如下:

public void deal()
{
//发给三个玩家
for(int i=0;i<allPokers.size();i++)
{
//最后三张留给地主牌
if(i>=51)
{
lordPokers.add(allPokers.get(i));
}
else {
//依次分发给三个玩家
if(i%3==0)
players.get(0).getPokers().add(allPokers.get(i));
else if(i%3==1)
players.get(1).getPokers().add(allPokers.get(i));
else
players.get(2).getPokers().add(allPokers.get(i));
}
}

//将玩家的信息发送到客户端
for(int i=0;i<players.size();i++)
{
try {
DataOutputStream dataOutputStream= new DataOutputStream(players.get(i).getSocket().getOutputStream());
String jsonString=JSON.toJSONString(players);
System.out.println(jsonString);
dataOutputStream.writeUTF(jsonString);
} catch (IOException e) {
e.printStackTrace();
}
}
}

23.2.5解析消息

服务器向客户端发牌的操作过程中,所有的扑克牌的组合都是以JSON字符串的形式表示的,而客户端接收到所发牌之后必须把这个JSON字符串解析为一个JSON数组,而这个JSON数组则表示收到的牌,紧接着要把JSON数组传递给玩家对象,玩家对象在接收到这一组牌后要把这些牌在放到一个ArrayList对象中,表示玩家已经拿到了所发的牌,这个过程的实现代码写在客户端接收消息的线程ReceiveThread中,其实现过程如下:

String jsonString= dataInputStream.readUTF();
java.util.List<Player> players=new ArrayList<Player>();
//System.out.println(jsonString);
//解析json字符串
//将json字符串转换为json数组
JSONArray playerJsonArray = JSONArray.parseArray(jsonString);
for(int i=0;i<playerJsonArray.size();i++)
{
//获得当个json对象--> 玩家对象
JSONObject playerJson=(JSONObject) playerJsonArray.get(i);
int id=playerJson.getInteger("id");
String name=playerJson.getString("name");
//存放扑克列表
java.util.List<Poker> pokers=new ArrayList<Poker>();
JSONArray pokerJsonArray= playerJson.getJSONArray("pokers");
for(int j=0;j<pokerJsonArray.size();j++)
{
// 每循环一次 获得一个扑克对象
JSONObject pokerJSon= (JSONObject) pokerJsonArray.get(j);
int pid=pokerJSon.getInteger("id");
String pname=pokerJSon.getString("name");
int num=pokerJSon.getInteger("num");
Poker poker=new Poker(pid,pname,num);
pokers.add(poker);
}
Player player=new Player(id,name,pokers);
players.add(player);

23.2.6显示接收到的牌

玩家在收到所发的牌之后要把这些牌显示出来,这个操作是由定义在MainFrame类的showAllPlayersInfo()方法完成的,这个方法显示牌的核心操作是把表示牌的对象Poker与每张牌的图片对应起来并把这些牌的图片显示到窗体的上,showAllPlayersInfo()方法的实现过程如下:

public void showAllPlayersInfo(java.util.List<Player> players) {
// 1.显示三个玩家的名称
// 2.显示当前玩家的扑克列表
for (int i = 0; i < players.size(); i++) {
if (players.get(i).getName().equals(uname)) {
currentPlayer = players.get(i);
}
}

java.util.List<Poker> pokers = currentPlayer.getPokers();
for (int i = 0; i < pokers.size(); i++) {
// 创建扑克标签
Poker poker = pokers.get(i);
PokerLabel pokerLabel = new PokerLabel(poker.getId(),poker.getName(), poker.getNum());
pokerLabel.turnUp(); // 显示正面图
// 添加到面板中
this.myPanel.add(pokerLabel);
this.pokerLabels.add(pokerLabel);
// 动态的显示出来
this.myPanel.setComponentZOrder(pokerLabel, 0);
// 一张一张的显示出来
GameUtil.move(pokerLabel, 300 + 30 * i, 450);
}
// 对扑克列表排序
Collections.sort(pokerLabels);
// 重新移动位置
for (int i = 0; i < pokerLabels.size(); i++) {
this.myPanel.setComponentZOrder(pokerLabels.get(i), 0);
GameUtil.move(pokerLabels.get(i), 300 + 30 * i, 450);
}
if (currentPlayer.getId() == 0) {
getLord(); // 抢地主
}
}

23.2.7按大小排列牌

玩家在接收到发的牌时,这些牌都是随机排列的,为了方便玩家出牌,必须把这些牌按大小排列一遍。排列牌是调用Collections类的sort()方法完成的,为sort()方法所传递的参数是PokerLabel对象,根据第11章所学的知识可知,如果一个类的对象如果能够被排序,这个类必须实现Comparable接口并实现其compareTo()方法,以下是PokerLabel实现compareTo()方法的过程。

public int compareTo(Object arg0) {
PokerLabel pokerLabel=(PokerLabel)arg0;
if(this.num>pokerLabel.num)
return 1;
else if(this.num<pokerLabel.num)
return -1;
else
return 0;
}

从以上代码可以看出:两张牌进行比较时,所比较的依据就是PokerLabel对象的num属性,而这个num属性就代表了牌面的点数。

23.2.8抢地主

各玩家拿到牌后,根据牌的好坏可以选择是否抢地主。抢地主操作是由MainFrame类所定义的getLord()方法完成,其最核心代码是显示倒计时并箭头玩家的鼠标操作并根据玩家鼠标所单击是“抢地主”还是“不抢”。getLord()方法的实现过程如下:

public void getLord() {
// 显示抢地主的按钮 和 定时器按钮
lordLabel1 = new JLabel();
lordLabel1.setBounds(330, 400, 104, 46);
lordLabel1.setIcon(new ImageIcon("images/bg/jiaodizhu.png"));
lordLabel1.addMouseListener(new MyMouseEvent());//①
this.myPanel.add(lordLabel1);
lordLabel2 = new JLabel();
lordLabel2.setBounds(440, 400, 104, 46);
lordLabel2.setIcon(new ImageIcon("images/bg/bujiao.png"));
lordLabel2.addMouseListener(new MyMouseEvent());//②
this.myPanel.add(lordLabel2);
//显示定时器的图标
this.timeLabel.setVisible(true);
this.setVisible(true);
// 重绘
this.repaint();
// 启动计时器的线程
countThread = new CountThread(10, this);
countThread.start();
}

在以上代码中,lordLabel1表示“抢地主”的标签,而lordLabel2则表示“不抢”,这两个标签在被单击时也要做出响应,因此需要给这两个标签添加监听器。在MainFrame类中定义了一个鼠标事件监听器,它负责监听所有的鼠标单击操作,因此程序需要在进行处理时判断玩家单击的是哪一个标签,其中负责处理玩家单击“抢地主”标签的代码如下:

if (event.getSource().equals(lordLabel1)) {
// 停止计时器
countThread.setRun(false);
isLord = true;
// 设置抢地主的按钮不可见
lordLabel1.setVisible(false);
lordLabel2.setVisible(false);
timeLabel.setVisible(false);
}

而处理“不抢”标签被单击的代码如下:

// 点击的不抢
if (event.getSource().equals(lordLabel2)) {
// 停止计时器
countThread.setRun(false);
isLord = false;
lordLabel1.setVisible(false);
lordLabel2.setVisible(false);
timeLabel.setVisible(false);
}

23.2.9显示出牌和不出牌标签并开始倒计时

抢地主步骤结束后,需要开始出牌,因此游戏主界面上必须显示出“出牌”和“不出牌”标签以及倒计时标签。显示这些标签的操作由MainFrame类的showChuPaiJabel()方法完成,其实现过程如下:

//显示出牌的标签
public void showChuPaiJabel()
{
if(prevPlayerid==currentPlayer.getId())
{
//从窗口上移除之前的出牌的列表
for(int i=0;i<showOutPokerLabels.size();i++)
{
myPanel.remove(showOutPokerLabels.get(i));
}
//清空之前出牌的列表
showOutPokerLabels.clear();
}
// 显示出牌和不出牌的按钮 和 定时器按钮
chupaiJLabel.setVisible(true);
buchuJLabel.setVisible(true);
timeLabel.setVisible(true);
this.repaint();
chuPaiThread=new ChuPaiThread(30, this);//①倒计时
chuPaiThread.start();
}

从showChuPaiJabel()方法的语句①可以看出:出来要把各种标签显示出来外,还要开始倒计时操作,这个操作是由chuPaiThread线程完成的,其倒计时代码如下:

while(time>=0 && isRun){
mainFrame.timeLabel.setText(time+"");
time--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

23.2.10分发出牌消息

任意一个玩家完成出牌操作后,他所出的牌必须被自己以及另外两个玩家看到,因此必须向服务器发送出牌消息,并由服务器在把这些消息分发给其他两位玩家。出牌消息是由SendThread线程完成的,实际上整个游戏中所有消息都是由这个线程完成的。SendThread线程发送消息的代码写在它的run()方法中,其实现过程如下:

public void run() {
DataOutputStream dataOutputStream;
try {
dataOutputStream = new DataOutputStream(socket.getOutputStream());
while (true) {
if (isRun == false) {
break;
}
//如果消息不为null
if (msg != null) {
System.out.println("消息在发送中:" + msg);
//发送消息
dataOutputStream.writeUTF(msg);
//消息发送完毕 消息内容清空
msg = null;
}
Thread.sleep(50); //暂停 等待新消息进来
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

从这段代码可以看出:SendThread线程中有一个无限循环,这个循环一直在不断等待这发送消息的任务,一旦要发送的消息不为空则立刻发送消息,因此如果想让这个线程发送消息,只需要调用SendThread类的setMsg()方法把消息传递给SendThread即可,而ChuPaiThread线程的run()方法执行以下代码完成出牌操作以及把消息传递给SendThread:

message=new Message(4,mainFrame.currentPlayer.getId(),"出牌",
changePokerLableToPoker(mainFrame.selectedPokerLabels));
//转换为json 交给 sendThread发送到服务器去
String msg=JSON.toJSONString(message);
mainFrame.sendThread.setMsg(msg);
//将当前发送出去的扑克牌 从扑克牌列表中移除
mainFrame.removeOutPokerFromPokerList();
//如果扑克列表的数量为0 代表赢了
if(mainFrame.pokerLabels.size()==0)
{
message=new Message(5, mainFrame.currentPlayer.getId(), "游戏结束", null);
msg=JSON.toJSONString(message);
try {
Thread.sleep(100);
mainFrame.sendThread.setMsg(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

23.2.11判断牌型和出牌是否符合规则

如果轮到当前玩家出牌,那么玩家不能随意出,必须符合某种牌型。在斗地主游戏中,所有牌型都已经被定义到PokerType枚举中,而判断牌型的操作由PokerRule类所定义的checkPokerType()方法完成,其源代码如下:

//判断牌型
public static PokerType checkPokerType(List<PokerLabel> list){
Collections.sort(list);
int count=list.size();
if(count==1){
//单张
return PokerType.p_1;
}else if(count==2){
//对子
if(isSame(list, count)){
return PokerType.p_2;
}
//王炸
if(isWangZha(list)){
return PokerType.p_2w;
}
return PokerType.p_error;
}else if(count==3){
//三个头
if(isSame(list, count)){
return PokerType.p_3;
}
return PokerType.p_error;
}else if(count==4){
//炸弹
if(isSame(list, count)){
return PokerType.p_4;
}
//三带一
if(isSanDaiYi(list)){
return PokerType.p_31;
}
return PokerType.p_error;
}else if(count>=5){
//顺子
if(isShunZi(list)){
return PokerType.p_n;
}else if(isSanDaiYiDui(list)){
//三代一对
return PokerType.p_32;
}else if(isLianDui(list)){
//连对
return PokerType.p_1122;
}else if(isFeiJI(list)){
//双飞or双飞双带
return PokerType.p_111222;
}else if(isFeiJIDaiChiBang1(list)){
//双飞or双飞双带
return PokerType.p_11122234;
}

else if(isFeiJIDaiChiBang2(list)){
//双飞or双飞双带
return PokerType.p_1112223344;
}
}
return PokerType.p_error;
}

玩家所出的牌型如果正确,还必须看是否符合出牌规则,如果下家的牌不大于上家则认为不符合出牌规则,这个判断操作由PokerRule类的legal()静态方法完成,其实现过程如下:

//判断是否符合出牌规则,即根据上家牌面判断本次出牌是否合规
public static boolean legal(List<PokerLabel> prevList, List<PokerLabel> currentList){
// 首先判断牌型是不是一样
PokerType paiXing = checkPokerType(prevList);
if (paiXing.equals(checkPokerType(currentList))) {
// 根据牌型来判断大小
if (PokerType.p_1.equals(paiXing)) {
// 单张
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_2w.equals(paiXing)) {
// 王炸
return false;
} else if (PokerType.p_2.equals(paiXing)) {
// 对子
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_3.equals(paiXing)) {
// 三张
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_31.equals(paiXing)) {
// 三带一
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_32.equals(paiXing)) {
// 三带一对
if (compare(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_4.equals(paiXing)) {
// 炸弹
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_n.equals(paiXing)) {
// 顺子
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_1122.equals(paiXing)) {
// 连对
if (compareLast(prevList, currentList)) {
return true;
}
return false;
} else if (PokerType.p_111222.equals(paiXing)) {
// 双飞
if (compare(prevList, currentList)) {
return true;
}
return false;
}
else if (PokerType.p_11122234.equals(paiXing)) {
// 飞机带翅膀(单张)
if (compare(prevList, currentList)) {
return true;
}
return false;
}
else if (PokerType.p_1112223344.equals(paiXing)) {
// 飞机带翅膀(对子)
if (compare(prevList, currentList)) {
return true;
}
return false;
}
}else if(currentList.size()==2){
//判断是不是王炸
if(isWangZha(currentList)){
return true;
}
return false;
} else if(currentList.size()==4){
//判断是不是炸弹
if(isSame(currentList, 4)){
return true;
}
return false;
}
return false;
}

23.2.12判断玩家输赢

判断玩家输赢其实很简单,只要某个玩家手中的牌数量等于0就认定该玩家已经赢了,如果已经赢了,则还要向服务器发送游戏结束的消息。完成判断操和发送消息的操作由ChuPaiThread类的run()方法完成,由之前的介绍可知:ChuPaiThread类的run()方法会完成很多操作,其中判断玩家是否已经赢得牌局以及发送消息的代码如下:

if(mainFrame.pokerLabels.size()==0)
{
//产生游戏结束的消息
message=new Message(5, mainFrame.currentPlayer.getId(), "游戏结束", null);
msg=JSON.toJSONString(message);
try {
Thread.sleep(100);
//发送消息
mainFrame.sendThread.setMsg(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。


标签:第二十三章,return,游戏,斗地主,玩家,PokerType,new,false,else
From: https://blog.51cto.com/mugexuetang/5991423

相关文章

  • 第二十三章《斗地主游戏》第3节:项目完整代码
    对于初学者来说,斗地主游戏是一个比较复杂的项目,它涉及的类很多,以下是这个项目所有类的源代码,源码中有两个Main.java文件,它们虽然文件名称相同,但位于不同的包下,读者在复制粘......
  • 小程序游戏开发有哪些游戏引擎可以选择?
    小游戏与小游戏引擎的关系小游戏现在囊括的范围包括微信小游戏、QQ空间小游戏、QQ玩一玩(厘米游戏)、FacebookInstantGames、各手机厂商的快应用小游戏,他们都在尝试着将社......
  • 第二十三章《斗地主游戏》第1节:斗地主项目简介
     斗地主游戏是一款3人参与的棋牌游戏,3方用一副牌(54张)展开游戏,其中一方为地主,其余两家为另一方,双方对战,先出完牌的一方获胜。用计算机程序实现的斗地主程序需要3个客户端,每......
  • 吃鸡游戏跑毒和倍镜开发
    我好像好久没有更新过博客了,emmm……觉得没有什么值得分享的东西。。。拿一个之前的存货吧。记录一下我曾经玩过一个“吃鸡的游戏”。玩了几天的吃鸡游戏,开始对这个游戏不感......
  • 圣诞树拼图游戏unity制作
    2022年圣诞节到来啦,很高兴这次我们又能一起度过~一、前言提示:使用unity来制作一个拼图游戏,图片便是圣诞树。二、创意名圣诞树拼图游戏三、效果展示圣诞树拼图游戏最终效果。......
  • Unity游戏副本地图点击图标移动功能
    本篇讲相同的功能即:点击地图中的一个位置,让图标瞬间移动到点击位置,同时3D场景中人物也可以抵达场景中对应的点击位置。如图:操作方法和之前一样:找到大地图的渲染的Rawimage。......
  • 游戏管理器(1)
    游戏管理器(1)现在的游戏中还缺少显示游戏信息的UI和游戏失败的状态提示,我们将创建一个游戏管理器来处理这些东西。1)创建GameManager.cs角本:1.UnityEngine;2.System.C......
  • 网页游戏为什么容易赚钱?(From:07073)
    网页游戏为什么容易赚钱?商业就是这样2012-10-0817:47作者:​​董晓常​​我们刚刚经历了惊讶的9月,现在又在享受一个可能更加惊讶的10月。从商业上来说,一个让人惊讶......
  • uniapp + vue 实现色弱测试小游戏
    最终的效果:点击色块中不同的色块,跳到下一关准备一些静态数据,放到js目录下,在vue文件中引入即可//在1到比1大的任意整数之间随机取一个整数exportconstgetRandom......
  • 为什么游戏公司应该选择 Cloud Spanner 来支持他们的游戏?
    普华永道最近的一份报告指出,全球游戏行业是过去几年经历显着增长的行业之一,到2026年该行业(不包括电子竞技)的价值有望达到3210亿美元。过去仅三年时间,该行业就增加了5......