51testing 发表于 2007-11-16 14:52:01

JAVA实现游戏编程[知识帖]

一提起JAVA,可能大多数人都会想起如下几个词汇。运行慢,解释执行,绝对不适合桌面开发。更不要说开发游戏了。其实这些都是对JAVA的偏见,JAVA刚出来的时候你这样说还是可以的,但是现在的JAVA已经是今非昔比了。
    本文就将带领大家用JAVA设计出一个儿时熟悉的游戏:水果机。让大家对JAVA有一个全新的认识,让大家知道其实不是JAVA不行,是我们不会用而已,实现后,该程序运行的截图如下

http://www.blogjava.net/images/blogjava_net/hadeslee/a.jpg
<!---->1.
<!---->JAVA2D简介SUN的API对JAVA2D的描述是这样的:
Java 2D API(Application Programming Interface,应用程序接口)是一套用于高级图形和图像处理的类。它将艺术线条、文本和图像囊括在一个综合模型中。该 API 提供了对图像复合和 alpha 通道图像的广泛支持,同时也提供了一套进行精确颜色空间定义和转换的类,以及大量面向显示的图像操作符。这些类以 java.awt 和 java.awt.image 包附件的形式提供(而不是单独的包)。
其实Java 2D API是JFC (Java Fundation Classes)的一员,主要的使用对象是java.awt.Graphics2D,它增强了传统的AWT( Abstract Windowing Toolkit )的描绘功能。在 JDK1.2中开始支持Java 2D 的使用。透过Java 2D API ,我们程序员可以轻松地描绘出任意的几何图形、运用不同的填色效果、对图形做旋转( rotate)、缩放( scale)、扭曲( shear)等。通过Graphics2D我们可以简单地利用不同类型的线或是填色效果绘出统计图,以区分出不同的资料。当然, 2D API还有许多增强SWING能力的部分,比如处理图片的效果、对于任意的几何图形可以做碰撞检测、图形重叠混色计算(composite)等功能。总之,JAVA2D使JAVA的图形处理能力得到了很大的提升,当然如果要单纯处理图片,可以利用JAVA的JAI(Java Advanced Imaging),还可以使用JMF框架来处理视频以及音频,更可以使用JAVA3D来渲染3D的场景,随着JAVA爱好者的努力,JAVA在编程领域已经越来越强大了。

<!---->2.
<!---->本文所涉及到的知识点为了制作出本游戏,我们需要了解如下知识点:
1,JAVA线程模型

了解JAVA里面线程的工作原理。
2,java.util包

了解JAVA里面的一些工具类,更有利于我们编程的效率。
3,java.awt包

了解JAVA的抽象工具集,因为它是构建我们窗口的基本元素
4,java.awt.event包

了解JAVA的事件模型以及处理事件的方法。
5,javax.swing

此包是最新的JAVA界面包,利用了良好的MVC架构,有很强的扩展性,此包包括了所有的轻量级的界面的实现。

另外还有简单的声音片段处理,因为我们在游戏的过程中是充满了声音效果的,还有JDK1.5新增的泛型枚举等的编程。

如果要加上联网功能的话,还需要掌据java.net包,java.io包,用以连接网络和读写输入输出流,我们今天只做一个单机版的,大家如果有兴趣的话可以自己下载源代码更改成一个联机版的。

51testing 发表于 2007-11-16 14:52:20

<!---->3.      <!---->游戏的开发与编码
在我们开发这个游戏之前,我们先讲一个这个游戏的实现所采用的方法,那就是经典的MVC模式,因为在开发游戏的时候,结构很重要,必须要理清楚每一块负责什么,每一个类负责什么,而MVC模式正好就是解决这种问题的很好的方案,我们可以把游戏的运行流程交由一个类去统一调度,游戏的呈现也就是绘图用专门一个类去负责,而绘图所需的数据可以从一个模型类里面去取,控制的类负责更改模型里面的数据并调用视图类去更新当前的视频,这样整个游戏的流程就很清晰明了。所以我们设计了如下几个类,它们之间互相交互,形成整个游戏的框架。

1,ClientControl

       顾名思义,这个类就是我们的控制端类,它负现整个游戏的流程控制以及事件处理。它在MVC里面的角色是C。

2,ClientModel

       它就是我们程序运行的时候,放数据的地方,它存放的数据并不是一般的数据,而是需要双方一起交互的数据,它只是做为一个桥梁,连接控制端和视图端的纽带。它是MVC的角色里面是M.。

3,ClientView

       它是我们今天需要重点讲解的地方,它是我们MVC里面的视图的实现,它负责呈现整个游戏的界面,并且它也受控制端ClientControl的支配,由ClientControl请求它重绘。它重绘的时候,一些数据将从ClientModel里面去取。

       那么我们重点来看一看ClientView的代码:

/*
* ClientView.java
*
* Created on 2007年10月2日, 下午2:00
* 此类专门负责视图的实现,此类中须定义从模型中
* 取出数据并重绘的方法
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package com.hadeslee.apple.client;

import com.hadeslee.apple.common.Bet;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import java.util.Vector;

/**
*
* @author lbf
*/
public class ClientView extends JPanel {

    private ClientModel cm; //模型类的一个对象
    private volatile boolean isStar;
    private Image starA; //表示当前星星的图片
    private Image[] star; //星星的数组
    private int x1;
    private int y1; //星星的座标
    private Image bg2; //表示底衬的那层
    private Image ratio; //赔率底衬的那层
    private int x;
    private int length; //表示跑马灯的位置

    /** Creates a new instance of ClientView */
    public ClientView(ClientModel cm) {
      this.cm = cm;
      initOther();
      x = 646;
      new RunStar().start();
      new Draw().start();
    }

    //初始化视图类的一些参数
    private void initOther() {
      try {
            star = new Image;
            MediaTracker mt = new MediaTracker(this);
            for (int i = 0; i < 3; i++) {
                star = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/star/" + (i + 1) + ".png"));
                mt.addImage(star, i);
            }
            bg2 = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/bg2.png"));
            ratio = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/ratio.png"));
            mt.addImage(bg2, 4);
            mt.addImage(ratio, 5);
            
            mt.waitForAll();

            starA = star;
            //把默认的鼠标改成我们自定义的鼠标形式,以配合主题
            Image icon = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/login/icon.png"));
            Cursor cu = Toolkit.getDefaultToolkit().createCustomCursor(icon, new Point(0, 0), "my");
            this.setCursor(cu);
      } catch (InterruptedException ex) {
            Logger.getLogger(ClientView.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    //覆盖的方法
    protected void paintComponent(Graphics g) {
      //先调用父类的方法,清除以前画的内容
      super.paintComponent(g);
      //然后设置一些提示,比如屏幕抗锯齿,以及文字抗锯齿
      Graphics2D gd = (Graphics2D) g;
      gd.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      gd.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
      //画背景
      g.drawImage(cm.getBg(), 0, 0, this);
      g.drawImage(bg2, 0, 0, this); //再画第二个背景
      //画赔率的闪动
      if (cm.isRunning()) {
            drawRatio(g);
      }
      //再画桌面
      drawTable(g);
      //如果现在正在跑,那么就调用跑的那个方法
      if (cm.isRunning()) {
            drawRunning(g);
      }
      //如果现在正在赌剪刀石头布,那么就调用比倍的方法
      if (cm.isBetting()) {
            drawBetting(g);
      }
      //画出永远存在的星星
      if (isStar) {
            g.drawImage(starA, x1, y1, this);
      }
      //画出跑马灯,提示文字
      drawTip(g);
    }

    private void drawTip(Graphics g) {
      g.setFont(new Font("宋体", Font.PLAIN, 22));
      g.setColor(Color.RED);
      g.drawString(cm.getInfo().getTip(), x, 25);
      FontMetrics fm = g.getFontMetrics();
      length = (int) fm.getStringBounds(cm.getInfo().getTip(),g).getWidth();
    }

    //画出赔率
    private void drawRatio(Graphics g) {
      RatioA ra = cm.getRa();
      RatioB rb = cm.getRb();
      if (ra != null) {
            g.drawImage(ratio, ra.x, ra.y, this);
      }
      if (rb != null) {
            g.drawImage(ratio, rb.x, rb.y, this);
      }
    }

    //画出正在跑的方法
    private void drawRunning(Graphics g) {
      Vector<;PP> ps = cm.getP();
      for (PP p : ps) {
            g.drawImage(p.current, p.x, p.y, this);
      }
    }

    //画出跑完的方法
    private void drawRunOver(Graphics g) {
      Vector<;PP> ps = cm.getP();
      for (PP p : ps) {
            g.drawImage(p.current, p.x, p.y, this);
      }
    }

    //画出正在比倍的方法
    private void drawBetting(Graphics g) {
      g.drawImage(cm.getPKBG(), 172, 39, this);
      g.drawImage(cm.getPkA(), 267, 245, this);
      g.drawImage(cm.getPkB(), 386, 247, this);
    }

    //画桌面以及桌面上的一些信息
    private void drawTable(Graphics g) {
      g.drawImage(cm.getTable(), 0, 0, this);
      drawMoney(g);
      drawBet(g);
    }

    //画下注的那九格下注数字
    private void drawBet(Graphics g) {
      Bet b = cm.getBet();
      drawNumber(80, 570, 12, 19, b.getBet(1), g, b.getWin(1));
      drawNumber(183, 570, 12, 19, b.getBet(2), g, b.getWin(2));
      drawNumber(252, 570, 12, 19, b.getBet(3), g, b.getWin(3));
      drawNumber(318, 570, 12, 19, b.getBet(4), g, b.getWin(4));
      drawNumber(424, 570, 12, 19, b.getBet(5), g, b.getWin(5));
      drawNumber(527, 570, 12, 19, b.getBet(6), g, b.getWin(6));
      drawNumber(597, 570, 12, 19, b.getBet(7), g, b.getWin(7));
      drawNumber(664, 570, 12, 19, b.getBet(8), g, b.getWin(8));
      drawNumber(767, 570, 12, 19, b.getBet(9), g, b.getWin(9));
    }

    //画有余额,赢的钱,用户ID,大小彩金等的方法
    private void drawMoney(Graphics g) {
      //画余额和赢的钱
      int allMoney = cm.getAllMoney();
      int winMoney = cm.getWinMoney();
      if (allMoney < 10000) {
            drawNumber(762, 88, 24, 38, allMoney, g);
      } else {
            drawNumber(762, 94, 18, 28, allMoney, g);
      }
      if (winMoney < 10000) {
            drawNumber(129, 86, 24, 38, winMoney, g);
      } else {
            drawNumber(129, 90, 18, 28, winMoney, g);
      }
      drawNumber(740, 208, 12, 19, cm.getId(), g); //画ID号
      //画大彩金和小彩金
      int smallBonus = cm.getInfo().getSmallBonus();
      int bigBonus = cm.getInfo().getBigBonus();
      if (smallBonus < 10000) {
            drawNumber(760, 390, 24, 38, smallBonus, g);
      } else {
            drawNumber(760, 396, 18, 28, smallBonus, g);
      }
      if (bigBonus < 10000) {
            drawNumber(128, 390, 24, 38, bigBonus, g);
      } else {
            drawNumber(128, 396, 18, 28, bigBonus, g);
      }
    }

    //定义两个重载的方法,分别针对于图放大的图片和一般大小的图片
    private void drawNumber(int startX, int startY, int num, Graphics g, boolean isWin) {
      drawNumber(startX, startY, 24, 38, num, g, isWin);
    }

    private void drawNumber(int startX, int startY, int width, int height, int num, Graphics g) {
      drawNumber(startX, startY, width, height, num, g, false);
    }

    private void drawNumber(int startX, int startY, int width, int height, int num, Graphics g, boolean isWin) {
      String ns = Integer.toString(num);
      int i = 0;
      for (int start = ns.length() - 1; start >= 0; start--) {
            i++;
            char c = ns.charAt(start);
            int index = c - 48;
            if (isWin) {
                g.drawImage(cm.getWinNumber(index), startX - (i * width), startY, width, height, this);
            } else {
                g.drawImage(cm.getNumber(index), startX - (i * width), startY, width, height, this);
            }
      }
    }

//此类专门用于后台调用重绘线程
    private class Draw extends Thread {

      public void run() {
            while (true) {
                try {
                  x -= 5;
                  if (x + length < 0) {
                        x = 800;
                  }
                  Thread.sleep(200);
                  repaint(x,0,length+20,30);
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            }
      }
    }

//此类专门用于跑星星的闪动
    private class RunStar extends Thread {

      private int total;

      public RunStar() {
            isStar = true;
            x1 = 339;
            y1 = 106;
      }

      public void run() {
            int index = 0;
            while (true) {
                try {
                  Thread.sleep(100);
                  if (index < star.length - 1) {
                        starA = star[++index];
                  } else {
                        starA = star;
                        index = 0;
                        total++;
                  }
                  if (total > 1) {
                        isStar = false;
                        repaint();
                        total = 0;
                        x1 = (int) (Math.random()*100-50) + 339;
                        y1 = (int) (Math.random()*100-50) + 106;
                        int sleep = (int) (Math.random()*3000) + 1000;
                        Thread.sleep(sleep);
                        isStar = true;
                  }else{
                        repaint(x1,y1,150,150);
                  }
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            }
      }
    }
}

代码其实不长,二百多行而已,我们先来看看如下几个代码片段:

Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/bg2.png"));

这句话有两个需要我们注意的地方:

一是我们如何把图片导入程序当中,二是我们如果把图片打包进JAR包,然后如何得到它们的URL。

我们先讲第一个,如何把图片导入程序中,在这里我们用的是Toolkit的方法createImage,它确实是一个很实用的方法,它是一个重载的方法,可以传入很多种参数,除了可以传入URL之处,还可以有如下的重载方法:

Image
createImage(byte[] imagedata)
          创建一幅图像,该图像对存储在指定字节数组中的图像进行解码。

abstractImage
createImage(byte[] imagedata, int imageoffset, int imagelength)
          创建一幅图像,该图像对存储在指定字节数组中指定偏移量和长度处的图像进行解码。

abstractImage
createImage(ImageProducer producer)
          使用指定的图像生成器创建一幅图像。

abstractImage
createImage(String filename)
          返回从指定文件获取像素数据的图像。

abstractImage
createImage(URL url)
          返回一幅图像,该图像从指定 URL 获取像素数据。




有一点需要注意的是,它的createImage是一个异步的方法,也就是说我们调用了这个方法以后,程序会立即返回,并不会等到图片完全加载进内存之后才返回,所以当我们用这种方法加载比较大的图片的时候,如果图片又没有完全进入内存,而我们却去draw它,这个时候就会出现撕裂的情况,大大影响了我们程序的性能以及可玩性,那怎么办呢?

办法有两种,一种是像我们在程序里实现的一样,用一个媒体跟踪器来跟踪我们要加载的图片,然后调用一个同步方法等待它们全部加载进入内存之后才继续往下运行,这样就可以保存在初始化以后,所需要用到的图片确实都全部加载进内存了,这样画的时候,才能保证效果。如下所示:



MediaTracker mt = new MediaTracker(this);



mt.addImage(star, i);

...
mt.waitForAll();



我们生成一个媒体跟踪器,然后把我们需要跟踪的图片放到里面去,然后等待所有的图片加载,mt.waitForAll()方法是会抛出一个InterruptedException的方法。我们需要捕获处理它。

    另外一种办法就是利用javax.imageio.ImageIO的方法,它的read方法可以同步的把图片完全读入内存,某些情况下这是更方便的方法,因为使用它免去了加媒体跟踪器的代码。javax.imageio.ImageIO的read方法也有很多重载的版本,它的方法如下:

static BufferedImage
read(File input)
          返回一个 BufferedImage,作为使用 ImageReader(它是从当前已注册 ImageReader 中自动选择的)解码所提供 File 的结果。

static BufferedImage
read(ImageInputStream stream)
          返回一个 BufferedImage,作为使用 ImageReader(它是从当前已注册 ImageReader 中自动选择的)解码所提供 ImageInputStream 的结果。

static BufferedImage
read(InputStream input)
          返回一个 BufferedImage,作为使用 ImageReader(它是从当前已注册 ImageReader 中自动选择的)解码所提供 InputStream 的结果。

static BufferedImage
read(URL input)
          返回一个 BufferedImage,作为使用 ImageReader(它是从当前已注册 ImageReader 中自动选择的)解码所提供 URL 的结果。


所以我们用read方法的话,会显得更加方一些,但是为什么我们在程序当中不使用它,而使用再加繁琐的Toolkit加上MediaTracker的方法呢?因为ImageIO读入内存的图片在呈现的过程中会有如下缺点:

<!---->1,<!---->当加载的图片是动态的gif图片的时候,图片在呈现的时候,将没有动画效果,它只会读取第一帧。

<!---->2,<!---->当加载的图片是半透明的时候,图片在呈现的时候,会比用Toolkit加载进来的图片更耗CPU。

所以我们选择了用Toolkit而不是ImageIO,当我们没有用到以上两种情况的图片的时候,是完全可以用ImageIO来加载图片的。

    图片导入程序中的问题解决了,我们现在来看一看如何把图片打包进JAR包,然后又如何在程序运行的时候把JAR包里面的资源提取出来。在这里我们用的是一个很有用的方法getResource(),它是定义在Class类里面的,当我们把我们的的图片提取出来的时候,可以用相对路径也可以用绝对路来来提取,当我们用相对路径的时候,路径就是相对于当前的class文件所在目录的路径,如果是用绝对路径的时候,路径就是从JAR内部的根目录开始算的。把图片等一些资源打入JAR包有很多好处,一是可以实现资源的初步隐藏,二是可以利用JAR的特性对文件进行一些压缩,因为JAR包就是一个压缩包,只不过后缀名改了而已。

    下面我们再来看一下paintComponent方法,它是一个重写的方法,它重写了父类JPanel里面的paintComponent方法,一般来说,当我们要绘制一些内容的时候,都是采用重写此方法的办法,在以前AWT的编程中,对重量型组件进行重写,一般重写的是paint方法,所以在用轻量级组件的时候,这一点要注意,最好不要再重写paint方法了,而是改为重写paintComponent。在它里面我们看到如下三句:



Graphics2D gd = (Graphics2D) g;

      gd.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      gd.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);



它的意思就是设置图形上下文在绘制的时候,要注意哪些方面,我们可以利用这个方法给图形上下文一些提示。在本文里面我们提示了两点,一点是图片抗锯齿打开,二是文本抗据齿我们也打开,除了这两个之外还要很多提示我们可以设置的,在兴趣的朋友可以查看java.awt.RenderingHints这个类。利用这个特性,我们可以使我们呈现的界面更加完美,不过完美是需要代价的,呈现的越清晰越完美就越需要更多的CPU的运算,所以当电脑的性能不太好的时候,我们可以把这两个提示去掉,让JVM自行把握绘制的质量。

    还有一点我们要注意的地方,那就是我们调用repaint的地方。在我们需要重绘的时候,我们可以调用repaint方法,它会发送一个重绘的请求,那个会把这个请求放到重绘线程里面去,在我们调用repaint的时候,有很重要的一点就是尽量不要去调用repaint的默认方法,而要调用repaint(int x,int y,int width,int height)方法,因为它只会请求重绘某一个区域,而repaint()则会重绘整个区域,所以为了性能着想,最好不要重绘整个区域,当你开发了有关JAVA2D的程序后,你会发现,程序的大部份CPU都耗在重绘上面,所以优化重绘区域对于优化整个程序的性能是很有效果的。

51testing 发表于 2007-11-16 14:53:32

下面我们再来看看ClientModel类,这个类负责保存游戏中的一些数据,这些数据可能需要Control类去更改然后需要由View类去呈现的,说白了,它就是一个包含有很多setter/getter的类.它的代码如下:

/*
* ClientModel.java
*
* Created on 2007年10月2日, 下午2:02
* 此类封装了一些公共的数据,为视图类提供模型
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.hadeslee.apple.client;

/**
*
* @author lbf
*/
import com.hadeslee.apple.common.Bet;
import com.hadeslee.apple.common.INFO;
import java.awt.Image;
import java.util.Vector;
import
static com.hadeslee.apple.common.Constants.*;
public
class ClientModel {
    private Image bg;//表示背景图片的对象

private Image table;//表示下注桌面的对象
    //private Image runA,runB;//表示跑动的图像,A表示正在跑的,B表示跑停了的,仅当火车的时候B才有用,其余为null
    // private PP[] p;//表示当前的位置枚举表现形式,当长度为一时,则为普通情况,其余为特殊情况

private Vector<PP> v;//表示当前的位置枚举表现形式,当长度为一时,则为普通情况,其余为特殊情况

private Bet bet;//表示当前或者上一局下注的对象

private INFO info;//表示当前的INFO对象,里面封装了要全速走的步数,大彩金,小彩金数

private
int allMoney,winMoney;//表示当前用户所有的钱和本局赢的钱

private
int id;//表示当前用户的ID号

private Image[] number,winNumber;//表示数字的图像普通数组和赢了的数组

private
volatile
boolean Running,Betting,hasRunB;//表示现在是否正在跑或者正在加时赌,是否有RunB

private Image pkA,pkB;//表示两张正在PK的图

private
int ratioA,ratioB;//表示A和B国的陪率

private RatioA ra;//表增添A或B的枚举

private RatioB rb;
    private Image pk;//表示PK的背景图

private String infos;//表示跑马灯的文字信息

/** Creates a new instance of ClientModel */
public ClientModel() {
      v=new Vector<PP>();
      ra=RatioA.A_20;
      rb=RatioB.B_10;
    }
    public Image getPKBG(){
      return pk;
    }
    public
void setPKBG(Image bg){
      pk=bg;
    }
    public Image getBg() {
      return bg;
    }
    public
int getRatioA(){
      return ratioA;
    }
    public
int getRatioB(){
      return ratioB;
    }
    public
void setRatioA(int a){
      ratioA=a;
    }
    public
void setRatioB(int b){
      ratioB=b;
    }
    public
void setBg(Image bg) {
      this.bg = bg;
    }
   
    public Image getTable() {
      return table;
    }
   
    public
void setTable(Image table) {
      this.table = table;
    }
   
   /* public Image getRunA() {
      return runA;
    }
   
   
    public void setRunA(Image run) {
      this.runA = run;
    }
   
    public Image getRunB(){
      return runB;
    }
    public void setRunB(Image run){
      runB=run;
    }
    */
public Vector<PP> getP() {
      Vector<PP> vec=new Vector<PP>();
      for(PP p: v){
            vec.add(p);
      }
      return vec;
    }
   
    public
void addP(PP p){
      v.add(p);
    }
    public
void setP(PP p){
      if(v.size()>0){
            v.remove(v.size()-1);
      }
      v.add(p);
    }
    public
void clearP(){
      v.removeAllElements();
    }
   
    public Bet getBet() {
      return bet;
    }
   
    public
void setBet(Bet bet) {
      this.bet = bet;
    }
   
    public INFO getInfo() {
      return info;
    }
   
    public
void setInfo(INFO info) {
      this.info = info;
    }
   
    public
int getAllMoney() {
      return allMoney;
    }
   
    public
void setAllMoney(int allMoney) {
      this.allMoney = allMoney;
    }
   
    public
int getWinMoney() {
      return winMoney;
    }
   
    public
void setWinMoney(int winMoney) {
      this.winMoney = winMoney;
    }
   
    public
int getId() {
      return id;
    }
   
    public
void setId(int id) {
      this.id = id;
    }
   
    public Image[] getNumber() {
      return number;
    }
   
    public
void setNumber(Image[] number) {
      this.number = number;
    }
    public Image getNumber(int i){
      return number;
    }
    public
void setWinNumber(Image[] winNumber){
      this.winNumber=winNumber;
    }
    public Image getWinNumber(int i){
      return winNumber;
    }
    public Image[] getWinNumber(){
      return winNumber;
    }
   
    public
boolean isRunning() {
      return Running;
    }
   
    public
void setRunning(boolean Running) {
      this.Running = Running;
    }
   
    public
boolean isBetting() {
      return Betting;
    }
   
    public
void setBetting(boolean Betting) {
      this.Betting = Betting;
    }
    public
void setHasRunB(boolean b){
      hasRunB=b;
    }
    public
boolean getHasRunB(){
      return hasRunB;
    }
   
    public Image getPkA() {
      return pkA;
    }
   
    public
void setPkA(Image pkA) {
      this.pkA = pkA;
    }
   
    public Image getPkB() {
      return pkB;
    }
   
    public
void setPkB(Image pkB) {
      this.pkB = pkB;
    }

    public RatioA getRa() {
      return ra;
    }

    public
void setRa(RatioA ra) {
      this.ra = ra;
    }

    public RatioB getRb() {
      return rb;
    }

    public
void setRb(RatioB rb) {
      this.rb = rb;
    }
    //得到A国是否中奖

public
boolean isAWin(){
      for(PP p:v){
            if(p.country==COUNTRY_A)
                return
true;
      }
      return
false;
    }
    public
boolean isBWin(){
      for(PP p:v){
            if(p.country==COUNTRY_B)
                return
true;
      }
      return
false;
    }
}


多它的好处就是,一个改一个呈现,分工明确,并且它里面也可以包含一些简单的逻辑,这一点就是仁者见仁了:)

51testing 发表于 2007-11-16 14:56:30

下面我们再来看看ClientControl类,这个类代码量是最多的,这个类负责整体的游戏流程和游戏逻辑,由于写这个游戏的时候并没有很好的思考,所以代码可能会比较乱:(,希望大家不要介意

/*
* ClientControl.java
*
* Created on 2007年10月2日, 下午2:02
* 此类是客户端中的关键类,此类专门负责整个程序的
* 流程和表现,负责对外通讯
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.hadeslee.apple.client;

/**
*
* @author lbf
*/
import com.hadeslee.apple.common.Bet;
import com.hadeslee.apple.common.INFO;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JFrame;
import
static com.hadeslee.apple.common.Constants.*;

public
class ClientControl extends Thread implements KeyListener {

    private ClientView cv; //表示视图的类

private ClientModel cm; //表示模型的类

private ExecutorService es; //表示一个线程池的对象

private AudioClip[] betAu; //表示下注时的一个音频对象

private AudioClip onceMore;
    private AudioClip xiu;
    private AudioClip loop;
    private AudioClip runA;
    private AudioClip runB;
    private AudioClip win;
    private AudioClip lose;
    private Bet bet; //缓存的上一局的下注记录

private PP p = PP.p1;
    private
volatile
boolean canBet =
true;
    private
volatile
boolean isRunning;
    private
volatile
boolean isBetting;
    private
volatile
boolean isBetted;
    private
volatile
boolean canMove =
true; //表示此时是否可以下注,是否正在附加赌

private Image[] pkA;
    private Image[] pkB; //表示两个人物的图像数组

private
int result; //表示PK的结果,定义在Constants类里面

private Control con; //处理控制的类

private
int allBet =
1;
    private
int allWin =
1; //表示此人所有下注,所有赢的数,此数用于控制,不能赋0,除法会异常

private AudioClip temp; //中间变量,用于放歌用的

private
volatile
boolean go;
    private
volatile
boolean showMoney;
    private
volatile
boolean blinkRatio; //用以控制闪动线程的

private
volatile
boolean isFullScreen; //指示此时是否是全屏

private JFrame jf; //表示此游戏的窗口

/** Creates a new instance of ClientControl */
public ClientControl() {
    }

    public ClientControl(int id, int allMoney) {
      cm =
new ClientModel();
      cv =
new ClientView(cm);
      cm.setId(id);
      cm.setAllMoney(allMoney);
      cm.setInfo(new INFO());
      try {
            cm.getInfo().setBigBonus(10000);
            cm.getInfo().setSmallBonus(1000);
            cm.getInfo().setTip("欢迎使用单机版!!谢谢!");
      } catch (Exception exe) {
            exe.printStackTrace();
      }
      initModel();
      initWindow();
      initOther();
      doShowAllMoney();
    }

    //初始化其它东西,比如声音片断

private
void initOther() {
      con =
new Control();
      betAu =
new AudioClip[10];
      for (int i =
1; i <
10; i++) {
            betAu = Applet.newAudioClip(this.getClass().getResource("sound/bet/bet"
+ i +
".au"));
      }
      //初始化所有要使用的声音片段
      loop = Applet.newAudioClip(this.getClass().getResource("sound/loop.au"));
      runA = Applet.newAudioClip(this.getClass().getResource("sound/runA.au"));
      runB = Applet.newAudioClip(this.getClass().getResource("sound/runB.au"));
      onceMore = Applet.newAudioClip(this.getClass().getResource("sound/wu.au"));
      xiu = Applet.newAudioClip(this.getClass().getResource("sound/oncemore.au"));
      win = Applet.newAudioClip(this.getClass().getResource("sound/win/win.au"));
      lose = Applet.newAudioClip(this.getClass().getResource("sound/win/lose.au"));
      bet =
new Bet();
    }

    //初始化全屏

private
void initWindow() {
      jf =
new JFrame("苹果机");
      jf.getContentPane().add(cv, BorderLayout.CENTER);
      jf.setSize(800, 600);
      jf.setUndecorated(true);

      jf.setVisible(true);
      jf.setAlwaysOnTop(true);
      jf.setLocationRelativeTo(null);//一条简便的语句,设置窗口居中显示
      jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      jf.addKeyListener(this);
    }

    //初始化模型

private
void initModel() {
      initImage();
      initModelOther();
    }

    //初始化图像

private
void initImage() {
      MediaTracker mt =
new MediaTracker(cv);
      int index =
0;
      Image[] number =
new Image[10];
      Image[] winNumber =
new Image[10];
      for (int i =
0; i <
10; i++) {
            number = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/number1/"
+ i +
".png"));
            winNumber = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/number2/"
+ i +
".png"));
            mt.addImage(number, index++);
            mt.addImage(winNumber, index++);
      }
      Image bg = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/bg.jpg"));
      Image table = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/table.png"));
      Image pkBg = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/pk.jpg"));
      mt.addImage(bg, index++);
      mt.addImage(table, index++);
      mt.addImage(pkBg, index++);
      pkA =
new Image[3];
      pkB =
new Image[3];
      for (int i =
0; i <
3; i++) {
            pkA = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/pk/A"
+ i +
".png"));
            pkB = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("pic/game/pk/B"
+ i +
".png"));
            mt.addImage(pkA, index++);
            mt.addImage(pkB, index++);
      }
      try {
            mt.waitForAll();
      } catch (Exception exe) {
            exe.printStackTrace();
      }
      cm.setBg(bg);
      cm.setNumber(number);
      cm.setWinNumber(winNumber);
      //cm.setRunA(PP.p1.other);
      cm.setTable(table);
      cm.setPkA(pkA[0]);
      cm.setPkB(pkB[0]);
      cm.setPKBG(pkBg);
    }

    //初始化模型其它部份

private
void initModelOther() {
      Bet bet =
new Bet();
      cm.setBet(bet);
      cm.addP(PP.p1);
      //初始化线程池
      es = Executors.newCachedThreadPool();
    }

    //专门起一个线程用于更新余额的方法

private
void doShowAllMoney() {
      es.execute(new Runnable() {

            public
void run() {
                showMoney =
true;
                while (showMoney) {
                  try {
                        Thread.sleep(80);
                        cv.repaint(10, 661, 73, 107, 65);
                  } catch (Exception exe) {
                        exe.printStackTrace();
                  }
                }
            }
      });
    }

    //专门处理下注的方法

private
synchronized
void doBet(int i) {
      if (!isBetted) {
            isBetted =
true;
            canMove =
false;
            cm.getBet().clearWin();
            //doShowAllMoney();
      }
      cm.setAllMoney(cm.getAllMoney() -
1);
      int bet = cm.getBet().getBet(i);
      cm.getBet().setBet(i, bet +
1);
      betAu.play();
    }

    //专门用来设置赔率的方法

private
void setRatio() {
      int rad = (int) (Math.random()*100) +
1;
      if (rad >
95) {
            cm.setRatioA(40);
      } else
if (rad >
85) {
            cm.setRatioA(30);
      } else {
            cm.setRatioA(20);
      }
      rad = (int) (Math.random()*100) +
1;
      if (rad >
95) {
            cm.setRatioB(20);
      } else
if (rad >
85) {
            cm.setRatioB(15);
      } else {
            cm.setRatioB(10);
      }
    }

    //专门处理开始跑的方法

private
void doRun() {
      try {
            //此时先把赔率设置好
            setRatio();
            isRunning =
true;
            canBet =
false;
            canMove =
false;
            showMoney =
false;
            INFO inf = cm.getInfo();
            inf.setBigBonus(cm.getBet().getTotal() /
50
+ inf.getBigBonus());
            inf.setSmallBonus(cm.getBet().getTotal() /
100
+ inf.getSmallBonus());
            cv.repaint();
            es.execute(new Runnable() {

                public
void run() {
                  runRun();
                }
            });
      } catch (Exception exe) {
            exe.printStackTrace();
      }
    }

    public
void keyPressed(KeyEvent ke) {
      int allMoney = cm.getAllMoney();
      int code = ke.getKeyCode();
      //先看看是不是CTRL+F,如果是的话,就表示全屏或者退出全屏了

if (KeyEvent.VK_F == code && (ke.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) == KeyEvent.CTRL_DOWN_MASK) {
            if (isFullScreen) {
                GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
                try {
                  gd.setFullScreenWindow(null);
                  isFullScreen=false;
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            } else {
                //得到图形设备,也就是显示器
                GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
                try {
                  gd.setFullScreenWindow(jf);
                  gd.setDisplayMode(new DisplayMode(800, 600, 16, 60));
                  isFullScreen=true;
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            }
            return;
      }
      //如果可以下注并且余额大于0才处理键盘事件

if (allMoney >
0
&& canBet) {
            if (KeyEvent.VK_1 == code) {
                doBet(1);
                cv.repaint(24, 566, 64, 31);
            } else
if (KeyEvent.VK_2 == code) {
                doBet(2);
                cv.repaint(129, 566, 64, 31);
            } else
if (KeyEvent.VK_3 == code) {
                doBet(3);
                cv.repaint(197, 566, 64, 31);
            } else
if (KeyEvent.VK_4 == code) {
                doBet(4);
                cv.repaint(264, 566, 64, 31);
            } else
if (KeyEvent.VK_5 == code) {
                doBet(5);
                cv.repaint(369, 566, 64, 31);
            } else
if (KeyEvent.VK_6 == code) {
                doBet(6);
                cv.repaint(474, 566, 64, 31);
            } else
if (KeyEvent.VK_7 == code) {
                doBet(7);
                cv.repaint(542, 566, 64, 31);
            } else
if (KeyEvent.VK_8 == code) {
                doBet(8);
                cv.repaint(610, 566, 64, 31);
            } else
if (KeyEvent.VK_9 == code) {
                doBet(9);
                cv.repaint(712, 566, 64, 31);
            }
      }
      if (KeyEvent.VK_LEFT == code && canMove) {
            if (cm.getAllMoney() >
0) {
                if (temp !=
null) {
                  temp.stop();
                }
                go =
false;
                canBet =
false;
                cm.setAllMoney(cm.getAllMoney() -
1);
                cm.setWinMoney(cm.getWinMoney() +
1);
                cv.repaint(30, 75, 107, 65);
            }
      } else
if (KeyEvent.VK_RIGHT == code && canMove) {
            if (cm.getWinMoney() >
0) {
                if (temp !=
null) {
                  temp.stop();
                }
                go =
false;
                cm.setAllMoney(cm.getAllMoney() +
1);
                cm.setWinMoney(cm.getWinMoney() -
1);
                cv.repaint(30, 75, 107, 65);
                if (cm.getWinMoney() ==
0) {
                  canBet =
true;
                  cm.setBet(new Bet());
                  cm.setBetting(false);
                  isBetting =
false;
                  cv.repaint(0, 30, 800, 600);
                }
            }
      }
    }

    public
void keyTyped(KeyEvent ke) {
    }

    public
void keyReleased(KeyEvent ke) {
      int code = ke.getKeyCode();
      if (KeyEvent.VK_ESCAPE == code) {
            System.exit(0);
      }
      //如果是回车键则分为几种可能,只分两种可能,有分和没分,有分就下分,没分就跑

if (KeyEvent.VK_ENTER == code) {
            if (isRunning) {
                ke.consume();
            } else {
                doEnter();
            }
      } else
if (KeyEvent.VK_NUMPAD1 == code || KeyEvent.VK_Q == code) {
            if (cm.getWinMoney() >
0
&& isBetting) {
                isBetting =
false;
                result = A_WIN;
            }
      } else
if (KeyEvent.VK_NUMPAD3 == code || KeyEvent.VK_P == code) {
            if (cm.getWinMoney() >
0
&& isBetting) {
                isBetting =
false;
                result = B_WIN;
            }
      }
      //要按键能出来PK界面,必须赢的钱要大于0,否则赌的没意义

if (KeyEvent.VK_B == code) {
            if (cm.isBetting()) {
                //如果此时在PK,则无条件不显示
                cm.setBetting(false);
                isBetting =
false;
                cv.repaint(0, 30, 800, 600);
            } else
if (cm.getWinMoney() >
0
&& canMove) {
                //设置为可以PK

if (temp !=
null) {
                  temp.stop();
                }
                go =
false;
                cm.setBetting(true);
                doPK();
                cv.repaint(0, 30, 800, 600);
            }
      }
    }

    //定义一个同步方法以防止两个线程共同起动跑

private
synchronized
void doEnter() {
      //当前的可用分为0时,按回车就跑
      result =
0;
      cm.setBetting(false);
      isBetting =
false;
      go =
false;
      if (temp !=
null) {
            temp.stop();
      }
      if (cm.getWinMoney() ==
0) {
            cm.clearP();
            resetPP(); //无论怎么样,都要在开始把PP的记录重置
            //如果已下注了,那就取下注的为准

if (canBet) {

                if (isBetted) {
                  bet = cm.getBet();
                  doRun();
                  isBetted =
false;
                } else {
                  if (cm.getAllMoney() >= bet.getTotal() && bet.getTotal() >
0) {
                        bet.clearWin();
                        cm.setBet(bet);
                        cm.setAllMoney(cm.getAllMoney() - bet.getTotal());
                        doRun();
                  }
                }
                cv.repaint(0, 30, 800, 600);
            }
      } else {
            allWin += cm.getWinMoney();
            allBet += cm.getBet().getTotal();
            cm.setAllMoney(cm.getAllMoney() + cm.getWinMoney());
            cm.setWinMoney(0);
            cm.setBet(new Bet());
            canBet =
true;
            cv.repaint(0, 30, 800, 600);
      }
    }

    //起PK线程,并且设置相当的布尔量
    //使之可以PK

private
void doPK() {
      isBetting =
true; //此时才可以将这个设为真,因为不会再跑了,所以可以押大小了
      //doShowAllMoney();
      cm.setBetting(true);
      es.execute(new Runnable() {

            public
void run() {
                pkRun();
            }
      });
    }

    //处理PK的线程体

private
void pkRun() {
      int indexA =
0;
      int indexB =
0;
      while (isBetting) {
            try {
                Thread.sleep(80);
                int rand = (int) (Math.random()*100);
                if (rand %
2
==
0) {
                  if (indexA < pkA.length -
1) {
                        cm.setPkA(pkA[++indexA]);
                  } else {
                        indexA =
0;
                        cm.setPkA(pkA[0]);
                  }
                } else {
                  if (indexB < pkB.length -
1) {
                        cm.setPkB(pkB[++indexB]);
                  } else {
                        indexB =
0;
                        cm.setPkB(pkB[0]);
                  }
                }
                cv.repaint(269, 274, 254, 132);
            } catch (Exception exe) {
                exe.printStackTrace();
            }
      }
      if (Math.random() *
10
>
1) {
            while (indexA == indexB) {
                indexA = (int) (Math.random()*3);
            }
      }
      cm.setPkA(pkA);
      cm.setPkB(pkB);
      cv.repaint(238, 258, 330, 152);
      handleResult(indexA, indexB);
    }

    //处理结果的一个方法,针对传进来的A和B的下标,判断谁赢谁输

private
void handleResult(int a, int b) {
      int current =
0;
      //当A为布时

if (a ==
0) {
            if (b ==
0) {
                current = AB_TIE;
            } else
if (b ==
1) {
                current = A_WIN;
            } else
if (b ==
2) {
                current = B_WIN;
            }
      } else
if (a ==
1) {
            if (b ==
0) {
                current = B_WIN;
            } else
if (b ==
1) {
                current = AB_TIE;
            } else
if (b ==
2) {
                current = A_WIN;
            }
      } else
if (a ==
2) {
            if (b ==
0) {
                current = A_WIN;
            } else
if (b ==
1) {
                current = B_WIN;
            } else
if (b ==
2) {
                current = AB_TIE;
            }
      }
      //当两者相等,则说明猜中了

if (result == current || current == AB_TIE) {
            win.play();
            cm.setWinMoney(cm.getWinMoney() *
2);
            cv.repaint(30, 75, 107, 65);
            try {
                Thread.sleep(1000);
                isBetting =
true;
                result =
0;
                pkRun();
            } catch (Exception exe) {
                exe.printStackTrace();
            }
      } else
if (result ==
0) { //如果此时等于0,则说明没有猜拳
            //showMoney=false;
      } else {
            canBet =
true;
            lose.play();
            cm.setWinMoney(0);
            //showMoney=false;

try {
                Thread.sleep(1000);
                cm.setBetting(false);
                isBetting =
false;
                cm.setBet(new Bet());
                cv.repaint(0, 30, 800, 600);
            } catch (Exception exe) {
                exe.printStackTrace();
            }
      }
      result =
0;
    }

    //计算当前的路径出发总共跑SUM步得到的结果

private PP fromIndex(PP from, int sum, boolean isRun) {
      if (isRun) {
            int all = sum;
            PP[] ps = PP.values();
            int index = (all + p.ordinal()) %
26;
            return ps;
      } else {
            int all =
5
+
7
+ sum;
            PP[] ps = PP.values();
            int index = (all + p.ordinal()) %
26;
            return ps;
      }
    }

    private PP fromIndex(PP from, int sum) {
      int all =
5
+
7
+ sum;
      PP[] ps = PP.values();
      int index = (all + p.ordinal()) %
26;
      return ps;
    }

    //跑大四喜的实现

private
void doDaSiXi() {
      es.execute(new Runnable() {

            public
void run() {
                runDaSiXi();
            }
      });
    }

    //跑大四喜的方法体实现

private
void runDaSiXi() {
    }

    //随机跑的时间用到的线程执行体

private
void runRun() {
      doBlinkRatio();
      RatioA ra = RatioA.A_20;
      RatioB rb = RatioB.B_10;
      int total = con.getSteps(AUTO_CONTROL);
      int sum =
0; //表示已经跑的数
      //PP p=PP.p1;//初始开始跑的位置
      cm.setRunning(true); //把模型设为正在跑的模式
      cm.setP(p);
      //p.setImage(yellow);

int sleepTime =
100;
      try {
            //先跑五个点做为起步
            runA.play();
            for (int i =
1; i <
6; i++) {
                Thread.sleep(200);
                p = p.next();
                ra = ra.next();
                rb = rb.next();
                cm.setRa(ra);
                cm.setRb(rb);
                cm.setP(p);
                //p.setImage(yellow);
                //cv.repaint(178,50,465,413);
                cv.repaint(p.x -
100, p.y -
100, 230, 230);
            }
            loop.loop();
            while (sum++
< total) {
                Thread.sleep(20);
                p = p.next();
                cm.setP(p);
                ra = ra.next();
                rb = rb.next();
                cm.setRa(ra);
                cm.setRb(rb);
                //p.setImage(yellow);
                //cv.repaint(178,50,465,413);
                cv.repaint(p.x -
100, p.y -
100, 230, 230);
            }
            loop.stop();
            runB.play();
            //后面七个点为停步

for (int i =
0; i <
7; i++) {
                p = p.next();
                cm.setP(p);
                //p.setImage(yellow);
                Thread.sleep(sleepTime);
                sleepTime +=
10;
                ra = ra.next();
                rb = rb.next();
                cm.setRa(ra);
                cm.setRb(rb);
                //cv.repaint(178,50,465,413);
                cv.repaint(p.x -
100, p.y -
100, 230, 230);
            }

            PP pps = p;
            //doBlinkRun(pps);
            doRunOver(p);
      } catch (Exception exe) {
            exe.printStackTrace();
      }
    }

    //根据赔率得到当前的位置

private RatioA getRa(int ratio) {
      RatioA[] ras = RatioA.values();
      for (RatioA rs : ras) {
            if (rs.ratio == ratio) {
                return rs;
            }
      }
      return
null;
    }

    private RatioB getRb(int ratio) {
      RatioB[] rbs = RatioB.values();
      for (RatioB rs : rbs) {
            if (rs.ratio == ratio) {
                return rs;
            }
      }
      return
null;
    }

    //处理根据国数得到当前的统一陪率

private
int getRatio(int country) {
      if (COUNTRY_A == country) {
            return cm.getRatioA();
      } else
if (COUNTRY_B == country) {
            return cm.getRatioB();
      } else {
            throw
new RuntimeException("出了个不该出的错误!");
      }
    }

    //确实是否亮赔率的灯

private
void doCheckBlinkRatio() {
      if (cm.isAWin()) {
            cm.setRa(getRa(cm.getRatioA()));
      } else {
            cm.setRa(null);
      }
      if (cm.isBWin()) {
            cm.setRb(getRb(cm.getRatioB()));
      } else {
            cm.setRb(null);
      }
      cv.repaint(0, 30, 800, 600);
    }

    //处理跑完的场景,根据中得不同的下标,比如,跑火车,中彩金等

private
void doRunOver(PP p) throws Exception {
      doShowAllMoney();
      //如果下标大于0,则表示中了正规的注

if (p.getIndex() >
0) {
            canMove =
true;
            doBlinkRun(p);
            doCheckBlinkRatio();
            cm.getBet().setWin(p.getIndex(), true);
            int winMoney =
0;
            if (p.country >
0) {
                winMoney = bet.getBet(p.getIndex()) * p.country;
            } else {
                winMoney = bet.getBet(p.getIndex()) * getRatio(p.country);
            }
            cm.setWinMoney(winMoney);
            cv.repaint(30, 75, 107, 65);
            isRunning =
false;
            p.audio.play();
            Thread.sleep(800);
            if (cm.getWinMoney() >
0) {
                playMusic();
                //doPK();
            } else {
                if (!isRunning) {
                  cm.setBet(new Bet());
                  canBet =
true;
                }
            }
            cv.repaint(0, 30, 800, 600);
      } else {
            //如果下标等于ONCE_MORE,则表示开火车

if (p.getIndex() == ONCE_MORE) {
                doOnceMore();
            } else
if (p.getIndex() == SONG_DENG) {
                cm.setRa(getRa(cm.getRatioA()));
                cm.setRb(getRb(cm.getRatioB()));
                p.audio.play();
                Thread.sleep(1000);
                onceMore.play();
                Thread.sleep(1500);
                doSongDeng(p);
            } else
if (p.getIndex() == SMALL_BONUS) {
                p.audio.play();
                Thread.sleep(1000);
                cm.getInfo().setSmallBonus(0);
                cv.repaint();
            } else
if (p.getIndex() == BIG_BONUS) {
                p.audio.play();
                Thread.sleep(1000);
                cm.getInfo().setBigBonus(0);
                cv.repaint();
            }
            //下面的语句是为了调试程序而用的
            //isBetting=true;
      }
    }

    //专门用来播声音的方法

private
void playMusic() {
      int NO = (int) (Math.random()*10) +
1;
      if (isBetting) {
      } else {
            if (temp !=
null) {
                temp.stop();
            }
            temp = Applet.newAudioClip(this.getClass().getResource("sound/music/music"
+ NO +
".au"));
            temp.loop();
      }
    }

    //专起一个线程用于更新赔率的闪灯

private
void doBlinkRatio() {
      es.execute(new Runnable() {

            public
void run() {
                blinkRatio =
true;
                while (!canMove) {
                  try {
                        Thread.sleep(40);
                        cv.repaint(141, 496, 518, 32);
                        if (!blinkRatio) {
                            break;
                        }
                  } catch (Exception exe) {
                        exe.printStackTrace();
                  }
                }
            }
      });
    }

    //专门用以跑开火车的方法

private
void doOnceMore() throws Exception {
      //doBlinkRatio();
      // blinkRatio=false;
      p.audio.play();
      PP startP = p;
      RatioA ra = RatioA.A_20;
      RatioB rb = RatioB.B_10;
      int total = (int) (Math.random()*3) +
3; //随机产生火车的节数

final AudioClip wu = Applet.newAudioClip(this.getClass().getResource("sound/oncemore/wu.au"));
      final AudioClip run = Applet.newAudioClip(this.getClass().getResource("sound/oncemore/run.au"));
      final AudioClip stop = Applet.newAudioClip(this.getClass().getResource("sound/oncemore/stop.au"));
      //先处理一个一个的出来

for (int i =
0; i < total; i++) {
            Thread.sleep(1000);
            stop.play();
            cm.addP(startP = startP.next());
            cv.repaint(178, 50, 465, 413);
      }
      es.execute(new Runnable() {

            public
void run() {
                try {
                  wu.play();
                  Thread.sleep(3500);
                  run.loop();
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            }
      });
      int steps = (int) (Math.random()*26) +
60;
      int stepAll = steps;
      //在此控制火车的头尾,也就是说不能中到50-100倍的那个灯
      PP pto = fromIndex(p, steps + total, true);
      PP pwe = fromIndex(p, steps, true);
      //当总长度大于15时,必须会一头一尾的溢出

if (total >
15) {
            while (!(pto.ordinal() <
15
&& pwe.ordinal() >
17)) {
                steps++;
                pto = fromIndex(p, steps + total, true);
                pwe = fromIndex(p, steps, true);
            }
      } else {
            while (!(pto.ordinal() <
15
|| pwe.ordinal() >
17)) {
                steps++;
                pto = fromIndex(p, steps + total, true);
                pwe = fromIndex(p, steps, true);
            }
      }
      while (steps >
0) {
            //p.setImage(yellow);
            Thread.sleep(200);
            Vector<PP> v = cm.getP();
            cm.clearP();
            ra = ra.next();
            rb = rb.next();
            cm.setRa(ra);
            cm.setRb(rb);
            if (stepAll /
2
== steps) {
                wu.play();
            }
            for (int i =
0; i < v.size(); i++) {
                cm.addP(v.get(i).next());
            }
            steps--;
            cv.repaint(178, 50, 465, 413);
      }
      blinkRatio =
false;
      cm.setRa(getRa(cm.getRatioA()));
      cm.setRb(getRb(cm.getRatioB()));
      cv.repaint(141, 496, 518, 32);
      run.stop();
      //先只留一个车头,然后一个一个地出来
      Vector<PP> v = cm.getP();
      cm.clearP();
      for (int i = v.size() -
1; i >=
0; i--) {
            PP p = v.get(i);
            if (p.index >
0) {
                cm.getBet().setWin(p.index, true);
                stop.play();
                int winMoney =
0;
                if (p.country >
0) {
                  winMoney = bet.getBet(p.getIndex()) * p.country;
                } else {
                  winMoney = bet.getBet(p.getIndex()) * getRatio(p.country);
                }
                cm.setWinMoney(winMoney + cm.getWinMoney());
            }
            cm.addP(p);
            //doBlinkRun(p);
            //doCheckBlinkRatio();
            cv.repaint(0, 30, 800, 600);
            Thread.sleep(1000);
      }
      for (PP pp : v) {
            doBlinkRun(pp);
      }
      p = v.get(v.size() -
1);
      canMove =
true;

      if (cm.getWinMoney() >
0) {
            playMusic();
            //doPK();
      } else {
            cm.setBet(new Bet());
            canBet =
true;
            cv.repaint(0, 30, 800, 600);
      }
      isRunning =
false;
    }

    //专门用以跑送灯的方法

private
void doSongDeng(PP ps) {
      p = ps;
      blinkRatio =
false;
      //即使中到火车也不一定跑,跑的概率有70%

if (Math.random() *
100
>
30) {
            int total = (int) (Math.random()*3) +
2;
            while (total >
0) {
                try {
                  int steps = (int) (Math.random()*20) +
20;
                  xiu.play();
                  for (int i =
0; i < steps; i++) {
                        Thread.sleep(20);
                        p = p.next();
                        cm.setP(p);
                        cv.repaint(p.x -
100, p.y -
100, 230, 230);
                  }
                  //如果是已经中过的,或者大奖,或者彩金等等,皆不中

while (p.isWin() || p.index <
0
|| p.index ==
5) {
                        steps++;
                        Thread.sleep(20);
                        p = p.next();
                        cm.setP(p);
                        cv.repaint(p.x -
100, p.y -
100, 230, 230);
                  }
                  final PP pps = p;
                  //doBlinkRun(pps);
                  cm.addP(p);
                  p.setWin(true);
                  if (p.index >
0) {
                        cm.getBet().setWin(p.index, true);
                        p.audio.play();
                        int winMoney =
0;
                        if (p.country >
0) {
                            winMoney = bet.getBet(p.getIndex()) * p.country;
                        } else {
                            winMoney = bet.getBet(p.getIndex()) * getRatio(p.country);
                        }
                        cm.setWinMoney(winMoney + cm.getWinMoney());
                        cv.repaint(0, 30, 800, 600);
                  }
                  total--;
                  Thread.sleep(1500);
                } catch (Exception exe) {
                  exe.printStackTrace();
                }
            }
            for (PP pp : cm.getP()) {
                doBlinkRun(pp);
            }
            //doCheckBlinkRatio();

if (cm.getWinMoney() >
0) {
                playMusic();
                //doPK();
            } else {
                cm.setBet(new Bet());
                canBet =
true;
                cv.repaint(0, 30, 800, 600);
            }
      } else {
            cm.setBet(new Bet());
            canBet =
true;
            cv.repaint(0, 30, 800, 600);
      }
      canMove =
true;
      isRunning =
false;
    }

    //把PP里面的元素全部还原

private
void resetPP() {
      PP[] ps = PP.values();
      for (PP p : ps) {
            p.setWin(false);
            p.setImage(p.other);
      }
    }

    //闪动的线程方法实现

private
void doBlinkRun(final PP p) {
      es.execute(new Runnable() {

            public
void run() {
                blinkRun(p);
            }
      });
    }

    //终点闪动的线程体

private
void blinkRun(PP p) {
      int total =
0;
      go =
true;
      while (go) {
            try {
                Thread.sleep(300);
                total++;
                p.setImage(total %
2
==
0
?
null : p.other);
                cv.repaint(p.x, p.y, 60, 60);
            } catch (Exception exe) {
                exe.printStackTrace();
            }
      }
      p.setImage(p.other);
      cv.repaint(0, 30, 800, 600);
    }

//专门定义一个类来,把一些控制的机制封装在里面

private
class Control {

      public Control() {
      }

      //通过传入参数的值得到该返回多少步
      //此方法是对外的统一接口

public
int getSteps(int style) {
            if (COMMON == style) {
                return getCommon();
            } else
if (WIN_MIN == style) {
                return getWinMin();
            } else
if (WIN_MAX == style) {
                return getWinMax();
            } else
if (OPEN_MAX == style) {
                return getOpenMax();
            } else
if (OPEN_MIN == style) {
                return getOpenMin();
            } else
if (OPEN_BIG_BONUS == style) {
                return getOpenBigBonus();
            } else
if (OPEN_SMALL_BONUS == style) {
                return getOpenSmallBonus();
            } else
if (OPEN_SONG_DENG == style) {
                return getOpenSongDeng();
            } else
if (OPEN_ONCE_MORE == style) {
                return getOpenOnceMore();
            } else
if (AUTO_CONTROL == style) {
                return getAutoControl();
            } else
if (OPEN_LOSE == style) {
                return getOpenLose();
            } else {
                return (int) (Math.random()*26) +
10;
            }
      }

      //内部方法,实现普通跑的步数

private
int getCommon() {
            int total = (int) (Math.random()*26) +
50;

            //如果此时随机跑中得是大奖,则不一定给

if (fromIndex(p, total).index ==
5) {
                if (Math.random() *
100
>
1) {
                  total +=
3;
                }
            }
            //如果此时中了大小彩金,则也不一定给

if (fromIndex(p, total).index == BIG_BONUS || fromIndex(p, total).index == SMALL_BONUS) {
                //if(Math.random()*10000>1){
                total++;
                //}
            }
            return total;
      }

      //实现取赢最小的方案

private
int getWinMin() {
            Bet bet = cm.getBet();
            int index =
0;
            int win = Integer.MAX_VALUE;
            int[] wins =
new
int[10];
            for (int i =
1; i <
10; i++) {
                PP p = PP.p1;
                while (p.index != i) {
                  p = p.next();
                }
                if (p.country >
0) {
                  wins = bet.getBet(p.getIndex()) * p.country;
                } else {
                  wins = bet.getBet(p.getIndex()) * getRatio(p.country);
                }
                if (wins <= win) {
                  if (wins == win) {
                        if (Math.random() *
10
>
5) {
                            win = wins;
                            index = i;
                        }
                  } else {
                        win = wins;
                        index = i;
                  }
                }
            }
            return getTotal(p, index);
      }

      //实现赢得最多方案

private
int getWinMax() {
            Bet bet = cm.getBet();
            int index =
0;
            int win = Integer.MIN_VALUE;
            int[] wins =
new
int[10];
            for (int i =
1; i <
10; i++) {
                PP p = PP.p1;
                while (p.index != i) {
                  p = p.next();
                }
                if (p.country >
0) {
                  wins = bet.getBet(p.getIndex()) * p.country;
                } else {
                  wins = bet.getBet(p.getIndex()) * getRatio(p.country);
                }
                if (wins > win) {
                  win = wins;
                  index = i;
                }
            }
            return getTotal(p, index);
      }

      //实现开最大的那个方案(50或100倍)

private
int getOpenMax() {
            return getTotal(p, 5);
      }

      //实现开最小的那个方案(2或5倍)

private
int getOpenMin() {
            int rad = (int) (Math.random()*10);
            if (rad >
5) {
                return getTotal(p, 1);
            } else {
                return getTotal(p, 9);
            }
      }

      //实现只要让玩家输即可,不一定让玩得输最多

public
int getOpenLose() {
            int betAll = cm.getBet().getTotal();
            //用一个哈希表来存储输的下标和输的金额
            EnumMap<PP, Integer> lose =
new EnumMap<PP, Integer>(PP.class);
            for (int i =
1; i <
10; i++) {
                int wins =
0;
                PP p = PP.p1;
                while (p.index != i) {
                  p = p.next();
                }
                if (p.country >
0) {
                  wins = bet.getBet(p.getIndex()) * p.country;
                } else {
                  wins = bet.getBet(p.getIndex()) * getRatio(p.country);
                }
                if (wins < betAll) {
                  //如果赢的小于下注的,则输的个数加1
                  lose.put(p, betAll - wins);
                }
            }
            PP last = PP.p1; //最后要得到的位置
            Set<PP> keys = lose.keySet();
            System.out.println("会输的PP的个数是:"
+ keys.size());
            //分三种方式去取想要的位置

int MyRad = (int) (Math.random()*100) +
1;
            if (MyRad >
85) {
                //第一种方式用权重去取到底开什么
                System.out.println("用权重去取开什么牌了");
                double value =
0;
                Iterator<PP> it = keys.iterator();
                while (it.hasNext()) {
                  PP now = it.next();
                  double myValue = lose.get(now) *
1.0
/ now.random;
                  if (myValue >= value) {
                        value = myValue;
                        last = now;
                  }
                }
            } else
if (MyRad >
30) {
                //第二种是概率去取到底开什么
                System.out.println("用概率去取什么牌了");
                int rand = (int) (Math.random()*100) +
1; //产生一个1-100的随机数
                System.out.println("产生的随机数是:"
+ rand);
                //然后把要开的位置按概率从小到大排,尽量满足概率小的开
                PP[] ps = keys.toArray(new PP);
                for (int i =
0; i < keys.size(); i++) {
                  for (int j = i +
1; j < keys.size(); j++) {
                        if (ps.random > ps.random) {
                            PP temp = ps;
                            ps = ps;
                            ps = temp;
                        }
                  }
                }
                System.out.println("排列前的顺序是:"
+ keys);
                System.out.println("排列后的顺序是:");
                for (PP now : ps) {
                  System.out.print(now +
",");
                }
                System.out.println();
                //双重循环,已经把概率小的排在前面了

boolean isFind =
false; //一个布尔量,看有没有当前随机数匹配的

for (PP now : ps) {
                  if (now.random > rand) {
                        System.out.println("匹配到的是:"
+ now +
"它的概率是:"
+ now.random *
2);
                        last = now;
                        isFind =
true;
                        break;
                  }
                }
                if (!isFind) {
                  System.out.println("没有匹配到,随机找一个");
                  int index = (int) (Math.random()*keys.size());
                  last = ps;
                }
            } else {
                //第三种是直接以概率来取
                System.out.println("直接取概率");
                PP[] ps = keys.toArray(new PP);
                int index = (int) (Math.random()*keys.size());
                last = ps;
            }
            System.out.println("===============================================");
            return getTotal(p, last.index);
      }

      //表示开大彩金的实现

private
int getOpenBigBonus() {
            return getTotal(p, BIG_BONUS);
      }

      //表示开小彩金的实现

private
int getOpenSmallBonus() {
            return getTotal(p, SMALL_BONUS);
      }

      //表示开出送灯的实现

private
int getOpenSongDeng() {
            return getTotal(p, SONG_DENG);
      }

      //表示开出跑火车的实现

private
int getOpenOnceMore() {
            return getTotal(p, ONCE_MORE);
      }

      //实现到传入的位置,应该全速跑的数量total

private
int getTotal(PP from, int index) {
            int sum = (int) (Math.random()+26) +
30;
            while (fromIndex(from, sum).index != index) {

                sum++;
            }
            return sum;
      }

      //实现智能自动控

private
int getAutoControl() {
            System.out.println("总下注是:"
+ allBet);
            System.out.println("总赢是:"
+ allWin);
            int rad = (int) (Math.random()*100);
            System.out.println("随机数是:"
+ rad);
            //输了九成了,这个时候送个大的

if (allBet / allWin >
10) {
                System.out.println("输了九成了");
                if (rad >
95) {
                  return getSteps(WIN_MAX);
                } else
if (rad >
90) {
                  return getSteps(OPEN_SONG_DENG);
                } else
if (rad >
80) {
                  return getSteps(OPEN_ONCE_MORE);
                } else
if (rad >
30) {
                  return getSteps(COMMON);
                } else {
                  return getSteps(OPEN_LOSE);
                }
            } else
if (allBet / allWin >
5) {
                System.out.println("输了八成了");
                if (rad >
98) {
                  return getSteps(WIN_MAX);
                } else
if (rad >
95) {
                  return getSteps(OPEN_SONG_DENG);
                } else
if (rad >
90) {
                  return getSteps(OPEN_ONCE_MORE);
                } else
if (rad >
30) {
                  return getSteps(COMMON);
                } else {
                  return getSteps(OPEN_LOSE);
                }
            } else
if (allBet > allWin) {
                System.out.println("输了。。。。。。");
                if (rad >
98) {
                  return getSteps(WIN_MAX);
                } else
if (rad >
95) {
                  return getSteps(OPEN_SONG_DENG);
                } else
if (rad >
90) {
                  return getSteps(OPEN_ONCE_MORE);
                } else
if (rad >
10) {
                  return getSteps(COMMON);
                } else {
                  return getSteps(OPEN_LOSE);
                }
            } else
if (allBet < allWin) {
                System.out.println("赢了。。。。。。");
                if (rad >
50) {
                  return getSteps(OPEN_LOSE);
                } else {
                  return getSteps(COMMON);
                }
            } else {
                System.out.println("出鬼了。。。。");
                return getSteps(COMMON);
            }
      }
    }
}

51testing 发表于 2007-11-16 14:57:24

三个大类ClientView,ClientModel,ClientControl就讲到这里.登录窗口以及播放声音的实现,还有公共包面的一些共用类,以及枚举的各种实现,各位可以自行下载源代码查看,欢迎大家参与讨论。本工程是用NetBeans开发的,由于工程里面用了很多声音以及图片,所以工程比较大,有二十多M,所以上传不了,有需要的朋友,可以在下面留言,我会用Email的形式发给大家.

<!---->4.
<!---->对未来的展望JAVA经过十几年的发展,到今天已经成为世界上使用人数最多的语言,这得益于它的平台所提供的功能
的全面,从机顶盒到手机到电脑桌面到企业级的大型应用,JAVA都可以做到。SUN一直都很看重JAVA在桌面上的成绩,每次版本的更新,都会花大力气在构造桌面应用的程序包上,最近又推出了javafx来丰富桌面的开发,相信在JAVA开源以后,有这么多JAVA爱好者以及开源社区的支持,JAVA的明天一定会更加美好。

xiayan18 发表于 2008-1-4 10:09:22

很久没来了.
不知斑竹好贴如云.
细细读来须每贴必顶,恳请发个包xyyn@163.com.谢谢
页: [1]
查看完整版本: JAVA实现游戏编程[知识帖]