2048游戏

前言

目的:

    想写一篇面向新手的文章,将做一个基于javaSE的2048小游戏

项目介绍

    这是一个简单的小游戏,游戏的规则很简单,你需要控制所有方块向一个方向运动,两个相同数字方块撞在一起之后合并成为他们的和,每次操作之后会随机生成一个2或者4,最终得到一个 “2048”的方块就算胜利了。

所需技术

    java基本语法、运算符与流程控制、面向对象基础、GUI

实现思路

整体思路

    将整个项目分为三层:数据层、视图层、控制层。

  • 数据层,Data类,2048游戏最主要用到的数据结构就是二维数组,使用二维数组保存当前的状态。这个类中有一些方法,方法的作用是改变二维数组的值来完成数据的上下左右移动。
  • 视图层,视图层的作用是展示数据,它只跟数据层打交道,负责把数据画出来。
  • 控制层,它是一个监听器,监听键盘的操作,根据不同的操作来调用数据层里的方法,从而改变数据的值。

第一层

/**
 * 这是一个实体类,可以通过一个二维数组保存数据
 */
public class Data{
    /**
     * 保存核心数据
     */
    int[][] Numbers;
    /**
     * 构造器
     */
    public Data(){

      Numbers = new int[4][4];
    }
    public Data(int[][] a ){
    }
    
    //方法,进行上下左右移动
    public void right() {
     
    }

    public void left() {
      
    }

    public void up() {
     
    }
    public void down() {
    }

}

第二层

import Game.Numbers;

import javax.swing.*;
import java.awt.*;

/**
 * 这是视图层,用于展示数据
 */
public class View extends JPanel {
    /**
     * 私有,用于展示的数据
     */
    private numbers;
    /**
     * 构造器
     * @param numbers
     */
    public view(Numbers numbers){
        this.numbers = numbers;
    }
    /**
     * 根据数据画图
     * @param g
     */
    public void paint(Graphics g) {

    }

    /**
     * 参数是想画的数据,作用是将数据画出来
     * @param numbers
     */
    public void showdata(Numbers numbers){
        this.numbers = numbers;
        repaint();
        
    }
}

(这是思路,具体实现时简化了)

第三层

package Game;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
 * 这是控制层,监听键盘操作,做出响应。
 */
public class Control extends JPanel implements KeyListener {
    @Override
    public void keyTyped(KeyEvent e) {
        
    }

    @Override
    public void keyPressed(KeyEvent e) {
        //再这里根据按下的键调用Numbers里的方法
        //然后将数据交给视图层,画图

    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
}

具体实现方法

数据层

   数据层是本项目最核心的类。而最核心的方法是数据的左移,即left()。你可能会问,为什么只有左移,那右、上、下移动呢,其实,如果我们实现了左移,只需对原二维数组做转置或镜像操作,再进行左移,再转置或镜像回去就能实现右、上、下移动。

left()方法的实现

                              ------->

  上面是效果图,我在这里只写一下我的思路(不只这一种)。我们将四行拆解,一行一行实现合并,先写一个方法,把一行中的空位消除。然后,从第一个元素开始遍历,检测这个元素和下一个元素能否合并,如果能就合并,直到遍历到末尾。最后再做一次空位消除操作,就得到了想要的效果。

     --clear()--->  ----eliminate---->   

public void clear(int[] Eliminated_array) {
		// 写一个清零函数,用于清除空白
		int move = 0;
		for(int i = 0;i<Eliminated_array.length;i++){
			if(Eliminated_array[i]==0){
				move++;
				continue;
			}else {
				if(move!=0){
					Eliminated_array[i-move]=Eliminated_array[i];
					Eliminated_array[i] = 0;
					merge=1;
				}
			}
		}
	}	


public void eliminate(int[] Eliminated_array) {
		
		// 给定一个一维数组,将一维数组,格式化
		clear(Eliminated_array);
		//成功完成了清零操作,进行合并
		for(int i = 0;i<Eliminated_array.length-1;i++){
			if(Eliminated_array[i]==Eliminated_array[i+1]&&Eliminated_array[i]!=0){
				Eliminated_array[i]*=2;
				Eliminated_array[i+1]=0;
			}
		}
		clear(Eliminated_array);
	}

right()方法的实现

  有了left()方法,right()实现起来就很简单,只要将保存数据的二维数组进行镜像操作,然后调用left()方法即可实现。

	/**
	 * 对二维数组进行镜像操作
	 * @return
	 */
	public void Mirror() {
		int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
		for (int i = 0; i < Numbers.length; i++) {
			for (int k = 0; k < Numbers[0].length; k++) {
				arr2[i][Numbers[0].length - k - 1] = Numbers[i][k];
			}
		}
		Numbers = arr2;
	}

	public void right() {
         Mirror();
		for (int i = 0; i < 4; i++) {
			eliminate(Numbers[i]);
		}
        Mirror();
	}

up()方法的实现

  将保存数据的二维数组转置再调用left()方法即可。


	/**
	 * 对二维数组进行转置
	 */
	public void Transposition() {
		// 矩阵转置,其实很简单,重新创建一个数组填充即可
		int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
		for (int i = 0; i < Numbers.length; i++) {
			int k = 0;// arr2的行标记
			for (int j = 0; j < Numbers[i].length; j++) {
				arr2[k][i] = Numbers[i][j];
				k++;
			}
		}
		Numbers = arr2;
	}

	public void up() {
     Transposition();
		for (int i = 0; i < 4; i++) {
			eliminate(Numbers[i]);
		}
       Transposition();
	}

down()方法的实现.

   将保存数据的二维数组转置再调用right()方法即可。

	public void down() {
		Transposition();
		right();
		Transposition();
	}

视图层

  第二层用作数据展示,很简单,直接上代码。

//View层用于展示数据
    public void paint(Graphics g) {
        super.paint(g);
        // 先画背景
        g.setColor(new Color(0x66ccff)); // 当然是蓝色了
        for (int i = 0; i < 4; i++) {
            for (int k = 0; k < 4; k++) {
                g.fillRoundRect(25 + i * 90, 120 + k * 90, 80, 80, 15, 15);
            }
        }
        // 再画数字
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (data.getNumbers()[j][i]!= 0) {
                    int FontSize = 30;
                    int MoveX = 0, MoveY = 0;
                    switch (data.getNumbers()[j][i]) {
                        case 2:
                            g.setColor(new Color(0xeee4da));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 4:
                            g.setColor(new Color(0xede0c8));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 8:
                            g.setColor(new Color(0xf2b179));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 16:
                            g.setColor(new Color(0xf59563));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 32:
                            g.setColor(new Color(0xf67c5f));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 64:
                            g.setColor(new Color(0xf65e3b));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 128:
                            g.setColor(new Color(0xedcf72));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 256:
                            g.setColor(new Color(0xedcc61));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 512:
                            g.setColor(new Color(0xedc850));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 1024:
                            g.setColor(new Color(0xedc53f));
                            FontSize = 27;
                            MoveX = -15;
                            MoveY = 0;
                            break;
                        case 2048:
                            g.setColor(new Color(0xedc22e));
                            FontSize = 27;
                            MoveX = -15;
                            MoveY = 0;
                            break;
                        default:
                            g.setColor(new Color(0x000000));
                            break;
                    }
                    g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 10, 10);
                    g.setColor(new Color(0x000000));
                    g.setFont(new Font("Arial", Font.PLAIN, FontSize));
                    g.drawString(data.Numbers[j][i] + "", 25 + i * 90 + 30 + MoveX, 120 + j * 90 + 50 + MoveY);
                }
            }
        }
        score_screen.setText("分数为" + ":" + score);
    }

控制层

思路

  控制层用于整个游戏的流程控制,核心是监听器。监听器负责响应键盘操作,当按键 按下时调用相关的方法改变数据层的数据。在这一层中需要有score全局变量记录当前分数,preData变量记录前一个状态。

随机数生成

  游戏规则规定,每移动一次会在随机地方生成一个“2”或者“4”,我们在进行流程控制时,需要判断键盘按下后当前数据是否发生改变,只有数据改变才生成随机数。方法很简单,只需记录之前状态,当键盘响应后判断之前状态和当前状态是否相同即可。

这种状态按下左键,数据没有改变,不会生成随机数

是否输掉游戏

  首先判断操作后数据是否改变,若改变则肯定还没有输掉。如果数据没有改变则判断是否满,若未满,则一定没有输。若满,就判断分别进行上下左右移动后数据是否改变,若都不改变,则输掉游戏。

/**
     *根据Data,来判断当前游戏是否输了,逻辑很简单,首先看这一回合有没有移动过,若移动了则还没有输,否则看当前是否满了,若未满肯定没有输
     * 否则上下左右移动一下,如果上下左右移动都没有变,则游戏结束
     * @param data
     * @return
     */
    public boolean isFailed(Data data){
        boolean result = true;
        System.out.println("是否满了"+data.isFull());
        if(isMove){
            result = false;
        }

        else if(!data.isFull()){
            result = false;
        }else {
            Data d = data;
            data.up();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.down();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.right();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.left();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;
        }
        return result;
    }

整体代码

package Game;

import java.util.Random;

public class Data {
	//这是一个实体类,可以通过一个二维数组保存数据
	/**
	 * 保存核心数据
	 */
	int[][] Numbers;

	/**
	 * 构造器
	 */
	public Data(){
		Numbers = new int[4][4];

	}
	public Data(int[][] numbers ){
		Numbers=numbers;
	}
	public int[][] getNumbers() {
	    return Numbers;
	}

	public void eliminate(int[] Eliminated_array) {
		// 给定一个一维数组,将一维数组,格式化
		clear(Eliminated_array);
		//成功完成了清零操作,进行合并
		for(int i = 0;i<Eliminated_array.length-1;i++){
			if(Eliminated_array[i]==Eliminated_array[i+1]&&Eliminated_array[i]!=0){
				Eliminated_array[i]*=2;
				Eliminated_array[i+1]=0;
			}
		}
		clear(Eliminated_array);
	}


	public void clear(int[] Eliminated_array) {
		// 写一个清零函数,用于清除空白
		int move = 0;
		for(int i = 0;i<Eliminated_array.length;i++){
			if(Eliminated_array[i]==0){
				move++;
				continue;
			}else {
				if(move!=0){
					Eliminated_array[i-move]=Eliminated_array[i];
					Eliminated_array[i] = 0;
				}
			}
		}
	}

	/**
	 * 对二维数组进行转置
	 */
	public void Transposition() {
		// 矩阵转置,其实很简单,重新创建一个数组填充即可
		int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
		for (int i = 0; i < Numbers.length; i++) {
			int k = 0;// arr2的行标记
			for (int j = 0; j < Numbers[i].length; j++) {
				arr2[k][i] = Numbers[i][j];
				k++;
			}
		}
		Numbers = arr2;
	}

	/**
	 * 对二维数组进行镜像操作
	 * @return
	 */
	public void Mirror() {
		int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
		for (int i = 0; i < Numbers.length; i++) {
			for (int k = 0; k < Numbers[0].length; k++) {
				arr2[i][Numbers[0].length - k - 1] = Numbers[i][k];
			}
		}
		Numbers = arr2;
	}

	public void right() {
         Mirror();
		for (int i = 0; i < 4; i++) {
			eliminate(Numbers[i]);
		}
        Mirror();
	}

	public void left() {

		for (int i = 0; i < 4; i++) {
			eliminate(Numbers[i]);
		}
	}

	public void up() {
     Transposition();
		for (int i = 0; i < 4; i++) {
			eliminate(Numbers[i]);
		}
       Transposition();
	}
	public void down() {
		Transposition();
		right();
		Transposition();
	}


	public void Random_generation_2() {
		Random r = new Random();
			if (isFull()) {
				// 先判断一下是否满了

				return;
			}
		while(true){
			int x = r.nextInt(4);
			int y = r.nextInt(4);
			//控制概率
			int flag = r.nextInt(100) + 1;
			if (Numbers[x][y] == 0) {
				if (flag < 30) {
					Numbers[x][y] = 4;
				} else {
					Numbers[x][y] = 2;
				}
				break;
			}
		}
	}

	/**
	 * 用于悔步
	 * @param a
	 */
	public void undo(int[][] a) {
            Numbers = a;
	}

	/**
	 * 判断有没有满
	 * @return
	 */
	public boolean isFull() {
		for (int i = 0; i < 4; i++) {
			for (int k = 0; k < 4; k++) {
				if (Numbers[i][k] == 0) {
					return false;
				}
			}
		}
		return true;
	}


	/**
	 * 判断preNumber和Number 是否相等
	 * @return
	 */
	public boolean isEqual(Data d){
		int[][] numbers = d.getNumbers();
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if (Numbers[i][j] != numbers[i][j]) {
					return false;
				}
			}
		}
		return true;
	}

}
package Game;

import java.awt.*;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import com.sun.glass.events.KeyEvent;


public class game extends JFrame {
    Data data;

    JLabel score_screen;
    Data preData;
    public int score;

    boolean isMove;

    public game() {
        this.setBounds(450, 100, 400, 500);
        this.setTitle("2048小游戏");
        this.setLayout(null);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        Init();
        // 创建显示分数
        score_screen = new JLabel();
        score_screen.setBounds(100, 10, 100, 30);
        score_screen.setText("分数为" + ":" + score);
        this.add(score_screen);
        score_screen.setVisible(true);


        //加监听器
        this.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(java.awt.event.KeyEvent e) {
                // TODO Auto-generated method stub
            }

            @Override
            public void keyReleased(java.awt.event.KeyEvent e) {
                // TODO Auto-generated method stub
                // 调用对应的方法即可
                preData = new Data(data.getNumbers());
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_LEFT:
                        data.left();
                        break;
                    case KeyEvent.VK_RIGHT:
                        data.right();
                        break;
                    case KeyEvent.VK_DOWN:
                        data.down();
                        break;
                    case KeyEvent.VK_UP:
                        data.up();
                        break;
                }
                if(!data.isEqual(preData)){
                    isMove = true;
                }else {
                    isMove = false;
                }
                if(isMove){
                    score++;
                    data.Random_generation_2();
                }
                //在这里判断输赢
                System.out.println("是否输了"+isFailed(data));
                if (isFailed(data)) {
                    DisplayToast();
                }
                repaint();
            }

            @Override
            public void keyPressed(java.awt.event.KeyEvent e) {
                // TODO Auto-generated method stub
            }
        });
    }

    /**
     *根据Data,来判断当前游戏是否输了,逻辑很简单,首先看这一回合有没有移动过,若移动了则还没有输,否则看当前是否满了,若未满肯定没有输
     * 否则上下左右移动一下,如果上下左右移动都没有变,则游戏结束
     * @param data
     * @return
     */
    public boolean isFailed(Data data){
        boolean result = true;
        System.out.println("是否满了"+data.isFull());
        if(isMove){
            result = false;
        }

        else if(!data.isFull()){
            result = false;
        }else {
            Data d = data;
            data.up();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.down();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.right();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;

            data.left();
            if(!data.equals(preData)){
                result = false;
            }
            data = d;
        }
        return result;
    }

    public void Init() {
        //关于init与构造器的区别,构造器的东西是只用一次的,但是init可以多次用
        //初始化numbers 清空撤销用list 清空score
        data = new Data();
        data.Random_generation_2();
        data.Random_generation_2();
        preData = data;

    }

    //View层用于展示数据
    public void paint(Graphics g) {
        super.paint(g);
        // 先画背景
        g.setColor(new Color(0x66ccff)); // 当然是蓝色了
        for (int i = 0; i < 4; i++) {
            for (int k = 0; k < 4; k++) {
                g.fillRoundRect(25 + i * 90, 120 + k * 90, 80, 80, 15, 15);
            }
        }
        // 再画数字
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (data.getNumbers()[j][i]!= 0) {
                    int FontSize = 30;
                    int MoveX = 0, MoveY = 0;
                    switch (data.getNumbers()[j][i]) {
                        case 2:
                            g.setColor(new Color(0xeee4da));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 4:
                            g.setColor(new Color(0xede0c8));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 8:
                            g.setColor(new Color(0xf2b179));
                            FontSize = 30;
                            MoveX = 0;
                            MoveY = 0;
                            break;
                        case 16:
                            g.setColor(new Color(0xf59563));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 32:
                            g.setColor(new Color(0xf67c5f));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 64:
                            g.setColor(new Color(0xf65e3b));
                            FontSize = 29;
                            MoveX = -5;
                            MoveY = 0;
                            break;
                        case 128:
                            g.setColor(new Color(0xedcf72));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 256:
                            g.setColor(new Color(0xedcc61));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 512:
                            g.setColor(new Color(0xedc850));
                            FontSize = 28;
                            MoveX = -10;
                            MoveY = 0;
                            break;
                        case 1024:
                            g.setColor(new Color(0xedc53f));
                            FontSize = 27;
                            MoveX = -15;
                            MoveY = 0;
                            break;
                        case 2048:
                            g.setColor(new Color(0xedc22e));
                            FontSize = 27;
                            MoveX = -15;
                            MoveY = 0;
                            break;
                        default:
                            g.setColor(new Color(0x000000));
                            break;
                    }
                    g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 10, 10);
                    g.setColor(new Color(0x000000));
                    g.setFont(new Font("Arial", Font.PLAIN, FontSize));
                    g.drawString(data.Numbers[j][i] + "", 25 + i * 90 + 30 + MoveX, 120 + j * 90 + 50 + MoveY);
                }
            }
        }
        score_screen.setText("分数为" + ":" + score);
    }

    public void DisplayToast() {
        JOptionPane.showMessageDialog(null, "你输了");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        // 主函数就是创建对象,然后初始化
        game UI = new game();
        // 创建一个按钮用于悔步,和一个JLable显示分数

    }

}

总结

   第一次写博客,肯定会有错误,如有发现请指出。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值