老Java程序员花两天做了个消消乐(天天爱消除)
Posted 编程界明世隐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了老Java程序员花两天做了个消消乐(天天爱消除)相关的知识,希望对你有一定的参考价值。
老Java程序员花两天做了个消消乐(天天爱消除)
引言:
一直就想做一个消消乐,这次正好找到了素材,就自己琢磨写了一个,我觉得这个游戏难点就在消除、以及消除后的下落,其他的地方也就还好,这次做完了写个文章大家唠一波。
效果图
实现思路
1.绘制窗口、按钮、边框等。
2.实现Card类,用来代表每一个小图形。
3.创建下标集合,因图片下标是0-5,所以用随机函数随机出下标,用来代表不同的图形,并依次添加打集合indexs中。
4.对此集合进行随机排序处理。
5.创建二维数组9行8列,根据集合的下标和二维数组对应的下标实例化各个卡片,并对应放在二维数组中。
6.设定卡片交换—当前卡片的上下左右才能交换。
7.判断横向、纵向是否超过3个相同的,是则消除。
8.消除后对应的卡片下落。
代码实现
创建窗口
首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。
/*
* 游戏窗体类
*/
public class GameFrame extends JFrame {
public GameFrame() {
setTitle("消消乐");//设置标题
setSize(386, 440);//设定尺寸
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
setLocationRelativeTo(null); //设置居中
setResizable(false); //不允许修改界面大小
}
}
创建面板容器GamePanel继承至JPanel
package main;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
* 画布类
*/
public class GamePanel extends JPanel{
GamePanel gamePanel=this;
private JFrame mainFrame=null;
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
mainFrame = frame;
mainFrame.setVisible(true);
}
@Override
public void paint(Graphics g) {
}
}
再创建一个Main类,来启动这个窗口,用来启动。
public class Main {
//主类
public static void main(String[] args) {
GameFrame frame = new GameFrame();
GamePanel panel = new GamePanel(frame);
frame.add(panel);
frame.setVisible(true);//设定显示
}
}
右键执行这个Main类,窗口建出来了
创建菜单及菜单选项
创建菜单
//初始化按钮
private void initMenu(){
// 创建菜单及菜单选项
jmb = new JMenuBar();
JMenu jm1 = new JMenu("游戏");
jm1.setFont(new Font("黑体", Font.BOLD, 15));// 设置菜单显示的字体
JMenu jm2 = new JMenu("帮助");
jm2.setFont(new Font("黑体", Font.BOLD, 15));// 设置菜单显示的字体
JMenuItem jmi1 = new JMenuItem("开始新游戏");
JMenuItem jmi2 = new JMenuItem("退出");
jmi1.setFont(new Font("黑体", Font.BOLD, 15));
jmi2.setFont(new Font("黑体", Font.BOLD, 15));
JMenuItem jmi3 = new JMenuItem("操作说明");
jmi3.setFont(new Font("黑体", Font.BOLD, 15));
JMenuItem jmi4 = new JMenuItem("胜利条件");
jmi4.setFont(new Font("黑体", Font.BOLD, 15));
jm1.add(jmi1);
jm1.add(jmi2);
jm2.add(jmi3);
jm2.add(jmi4);
jmb.add(jm1);
jmb.add(jm2);
mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
jmi1.addActionListener(this);
jmi1.setActionCommand("Restart");
jmi2.addActionListener(this);
jmi2.setActionCommand("Exit");
jmi3.addActionListener(this);
jmi3.setActionCommand("help");
jmi4.addActionListener(this);
jmi4.setActionCommand("win");
}
实现ActionListener并重写方法actionPerformed
actionPerformed方法的实现
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
if ("Exit".equals(command)) {
Object[] options = { "确定", "取消" };
int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
System.exit(0);
}
}else if("Restart".equals(command)){
if(!"end".equals(gamePanel.gameFlag)){
JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
}else {
restart();
}
}else if("help".equals(command)){
JOptionPane.showMessageDialog(null, "鼠标点击选中后,与相邻的切换,超过3个成行或者成列则消除!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
}else if("win".equals(command)){
JOptionPane.showMessageDialog(null, "300秒3000分胜利,否则失败!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
}
}
初始化图片
将所有要用到的图片初始化,方便待会使用
public class ImageValue {
//小卡片
public static List<BufferedImage> itemImageList = new ArrayList<BufferedImage>();
//路径
public static String ImagePath = "/images/";
//将图片初始化
public static void init(){
String path = "";
//图片初始化
for(int i=0;i<=5;i++){
try {
path = ImagePath +"tile_"+ i+".png";
itemImageList.add(ImageIO.read(ImageValue.class.getResource(path)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
初始化下标集合
1.初始化下标值,随机从6张图片中选取,下标[0-5]。
2.8列9行,当集合的长度满足72则跳出while循环。
3.使用 Collections.shuffle 对集合进行随机排序(其实不排序也行,本身也是随机来的)。
//随机排序
private void sortImage() {
Collections.shuffle(indexs);
}
//初始化下标值
private void initIndexs() {
Random random = new Random();
int n ;
while(true){//
n = random.nextInt(6);//随机从6张图片下标中选取[0-5]
indexs.add(n);
if(indexs.size()==72){
break;
}
}
}
绘制卡片
drawImage介绍
此例中,因为图片是合在一起的,需要裁剪
所以要用以下方法:
g.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer)
其中img是Image图片对象,而d开头的都是destinetiong,s开头的是source。
也就是说分别定义好来源和目标的两个矩形的左上角和右下角的点,它会自动帮你剪裁和适应。
public class Card {
private int x = 0;//对应行
private int y = 0;//对应列
private int dx = 0;//图形显示左上角x位置
private int odx = 0;//图形更新后显示左上角x位置
private int dy = 0;//图形显示左上角y位置
private int ody = 0;//图形更新后显示左上角y位置
private int dir = 1;//方向
private int width =32;//宽
private int height = 32;//高
private int pIndex = 0;//对应素材图片下标
private int index = 0;//对应图片下标值
private int type = 1;//1:10张的 2:20张的
private BufferedImage image = null;//图片对象
private GamePanel panel=null;//GamePanel
private boolean alive=true;//是否存活
private boolean selected = false;//是否选中
private int moveFlag=0;//移动标示 0 不移动 1 横向移动 2纵向移动
private int speed=15;//移动速度
public Card(int x,int y,int pIndex,GamePanel panel){
this.x=x;
this.y=y;
this.dx = 40+y*(32+3)+10;
this.dy = 35+x*(32+3)+10;
this.panel=panel;
this.pIndex=pIndex;
this.image = ImageValue.itemImageList.get(pIndex);
}
//绘制
public void draw(Graphics g) {
int index = this.index;
//index 默认是0,就是从图片中截取第一个
int sx1 = index*32;
int sy1 = 0;
//截取的右下角计算
int sx2 = (index+1)*32;
int sy2 = 32;
g.drawImage(this.image,dx, dy,dx+width,dy+height,sx1,sy1,sx2,sy2 ,null );
}
}
创建一个Card实例,并将它设置给二维数组第一个元素
//初始化卡片
private void initCards() {
Card card = new Card(0, 0, 0, this);
cards[0][0]=card;
}
paint方法绘制一个边框,并把二维数组的card绘制
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制边框
BasicStroke bs_2=new BasicStroke(3,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER);
Graphics2D g_2d=(Graphics2D)g;
g_2d.setColor(new Color(0,191,255));
g_2d.setStroke(bs_2);
g_2d.drawRect(38, 32, 305, 334);
Card card;
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card!=null){
card.draw(g);
}
}
}
在Card类中加入线程,更新index,因为index的变更会更新裁剪位置,此方法要在构造函数中调用。
private void rock() {
new Thread(new Runnable() {
@Override
public void run() {
while (alive) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
if(index==10){
index=0;
}
}
}
}).start();
}
在GamePanel中开启主线程,用来repaint,即可看到动画。
private class RefreshThread implements Runnable {
@Override
public void run() {
while (true) {
if ("start".equals(gameFlag)) {
repaint();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
}
}
}
启动线程
gameFlag="start";
//主线程启动
mainThread = new Thread(new RefreshThread());
mainThread.start();
将卡片补齐,修改initCards方法
//初始化卡片
private void initCards() {
Card card;
int index = 0 ;
int temp=0;
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
temp = Integer.valueOf(String.valueOf(indexs.get(index)));
card = new Card(i, j, temp, this);
cards[i][j]=card;
index++;
}
}
}
此时效果:
加入点击事件
Card类中加入isPoint方法用来判断鼠标点击是否在范围内。
//判断鼠标是否卡片范围内
boolean isPoint(int x,int y){
//大于左上角,小于右下角的坐标则肯定在范围内
if(x>this.dx && y >this.dy
&& x<this.dx+this.width && y <this.dy+this.height){
return true;
}
return false;
}
GamePanel加入方法判断交换的位置,必须在其上下方、或者左右方才允许交换。(当然如果选择交换的本身是一样的,则同样不允许交换)
以上是关于老Java程序员花两天做了个消消乐(天天爱消除)的主要内容,如果未能解决你的问题,请参考以下文章
华为OD机试 - 消消乐游戏(Java) | 机试题+算法思路+考点+代码解析 2023