2048游戏
Posted 竹灬鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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显示分数
}
}
总结
第一次写博客,肯定会有错误,如有发现请指出。
以上是关于2048游戏的主要内容,如果未能解决你的问题,请参考以下文章