首页 > 编程语言 >基于JAVA的2048小游戏的二次开发

基于JAVA的2048小游戏的二次开发

时间:2024-03-05 16:25:06浏览次数:17  
标签:JAVA 数字 卡片 int 小游戏 new 二次开发 import card

引言
《2048Numberpuzzlegame》是一款数字益智游戏,而《2048》的初始数字则是由2+2组成的基数4。在操作方面的不同则表现为一步一格的移动,变成更为爽快的一次到底。相同数字的方框在靠拢、相撞时会相加。系统给予的数字方块不是2就是4,玩家要想办法在这小小的16格范围中凑出「2048」这个数字方块。
游戏规则很简单,每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。系统给予的数字方块不是2就是4,玩家要想办法在这小小的16格范围中凑出“2048”这个数字方块。
游戏的画面很简单,一开整体16个方格大部分都是灰色的,当玩家拼图出现数字之后就会改变颜色,整体格调很是简单。
在玩法规则也非常的简单,一开始方格内会出现2或者4等这两个小数字,玩家只需要上下左右其中一个方向来移动出现的数字,所有的数字就会向滑动的方向靠拢,而滑出的空白方块就会随机出现一个数字,相同的数字相撞时会叠加靠拢,然后一直这样,不断的叠加最终拼凑出2048这个数字就算成功。
分析
类:
最基础的是CardPane,继承自BorderPane,作为数字卡片。
然后是由数字卡片组成的矩阵CardMatrixPane,继承自StackPane
CardColor,里面只有一个静态的Color数组,用来搞卡片的背景颜色
settings类,红框标识工具
源代码:
CardPane:




Settings:

import javafx.beans.property.SimpleBooleanProperty;

public final class _Settings {
	public static SimpleBooleanProperty NEEDHL=new SimpleBooleanProperty();//默认合并卡片红色边框
}

CardMatrixPane:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import javafx.application.Application;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;


public class _CardMatrixPane extends StackPane {
	private Callbacks mCallbacks;
	private int cols;//卡片矩阵列数
	private int rows;//卡片矩阵行数
	private GridPane gridPane;//卡片矩阵容器
	private _CardPane[][] cps;//卡片矩阵
	private final Random rand=new Random();
	private int[] mcQuantities=new int[15];//合并过的卡片数字数量,包括4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536
	
	/**回调接口*/
	
	public _CardMatrixPane(Application application) {
		this(4,4,application);//默认4*4
	}
	
	public _CardMatrixPane(int cols,int rows,Application application) {//application供回调方法使用
		mCallbacks=(Callbacks)application;
		this.cols=cols;
		this.rows=rows;
//		this.setBackground(new Background(new BackgroundFill(Color.BLUE,CornerRadii.EMPTY,Insets.EMPTY)));//测试用
		initGridPane();//初始化GridPane
		createRandomNumber();//在随机的空卡片上生成数字,这个方法会返回布尔值
		getChildren().add(gridPane);
	}
	
	
	/**初始化GridPane*/
	private void initGridPane() {
		gridPane=new GridPane();
//		gridPane.setBackground(new Background(new BackgroundFill(Color.YELLOW,CornerRadii.EMPTY,Insets.EMPTY)));//测试用
//		gridPane.setGridLinesVisible(true);//单元格边框可见,测试用
		
		//对this尺寸监听
		widthProperty().addListener(ov->setGridSizeAndCardFont());//宽度变化,更新边长和字号
		heightProperty().addListener(ov->setGridSizeAndCardFont());//高度变化,更新边长和字号
		//单元格间隙
		gridPane.setHgap(5);
		gridPane.setVgap(5);
		//绘制每个单元格
		cps=new _CardPane[cols][rows];
		for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
			for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
				_CardPane card=new _CardPane(0);
				gridPane.add(card,i,j);
				cps[i][j]=card;
			}
		}
	}
	
	/**设置GridPane的边长,其内部单元格的尺寸和CardPane的字号*/
	private void setGridSizeAndCardFont(){
		double minSide=Math.min(widthProperty().get(),heightProperty().get());
		gridPane.setMaxWidth(minSide);
		gridPane.setMaxHeight(minSide);
		for(_CardPane[] row:cps) {
			for(_CardPane card:row) {
				card.getLabel().setFont(new Font((minSide/14)/cols*4));//设置显示数字的尺寸
				//由于下面两行代码主动设置了每个单元格内cardPane的尺寸,gridPane不需要自动扩张
				card.setPrefWidth(minSide-5*(cols-1));//设置单元格内cardPane的宽度,否则它会随其内容变化,进而影响单元格宽度
				card.setPrefHeight(minSide-5*(rows-1));//设置单元格内cardPane的高度,否则它会随其内容变化,进而影响单元格高度
			}
		}
	}
	
	/**添加键盘监听*/
	public void createKeyListener() {
		setOnKeyPressed(e->{
			_CardPane maxCard=getMaxCard();//最大卡片
			if(maxCard.getType()==16) {//出现最大数字
				Alert alert=new Alert(AlertType.INFORMATION);
				alert.setTitle(alert.getAlertType().toString());
				alert.setContentText("恭喜你,游戏的最大数字为"+maxCard.getNumber()+",可在菜单栏选择重新开始\n"+
						"事实上,我们还尚未准备比"+maxCard.getNumber()+"更大的数字卡片,终点已至");
				return;
			}
			KeyCode kc=e.getCode();
			boolean suc=false;
			switch(kc) {
			case UP:
			case W:
				suc=goUp();//↑
				break;
			case DOWN:
			case S:
				suc=goDown();//↓
				break;
			case LEFT:
			case A:
				suc=goLeft();//←
				break;
			case RIGHT:
			case D:
				suc=goRight();//→
				break;
			default://尚未定义的操作
				return;
			}
			redrawAllCardsAndResetIsMergeAndSetScore();//重绘所有的卡片,并重设合并记录,更新分数
			if(!suc) {//失败的操作
				return;
			}
			boolean isFull=!createRandomNumber();//生成新的随机数字卡片,并判满,这包含了生成数字后满的情况
			if(isFull) {//矩阵已满,可能已经游戏结束
				boolean canMove=testUp()||testLeft();
				if(!canMove) {//游戏结束
					Alert alert=new Alert(AlertType.INFORMATION);
					alert.setTitle(alert.getAlertType().toString());
					alert.setContentText("游戏结束,本次最大数字为"+maxCard.getNumber()+",可在菜单栏选择重新开始\n");
				}
			}
		});
	}
	
	/**向上操作,返回操作成功与否*/
	private boolean goUp() {
		boolean suc=false;
		boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
		do {
			mergeOrMoveExist=false;//初始为false
			for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
				for(int j=1;j<rows;j++) {//从第二行起向下,遍历卡片矩阵的行
					_CardPane card=cps[i][j];
					_CardPane preCard=cps[i][j-1];//前一个卡片
					boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
					mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
				}
			}
			suc|=mergeOrMoveExist;
		}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
		return suc;
	}
	
	/**测试是否能向上操作*/
	private boolean testUp() {
		for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
			for(int j=1;j<rows;j++) {//从第二行起向下,遍历卡片矩阵的行
				_CardPane card=cps[i][j];
				_CardPane preCard=cps[i][j-1];//前一个卡片
				if(card.canMergeOrMove(preCard)) {
					return true;//能
				}
			}
		}
		return false;//不能
	}
	
	/**向下操作,返回操作成功与否*/
	private boolean goDown() {
		boolean suc=false;
		boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
		do {
			mergeOrMoveExist=false;//初始为false
			for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
				for(int j=rows-2;j>=0;j--) {//从倒数第二行起向上,遍历卡片矩阵的行
					_CardPane card=cps[i][j];
					_CardPane preCard=cps[i][j+1];//前一个卡片
					boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
					mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
				}
			}
			suc|=mergeOrMoveExist;
		}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
		return suc;
	}
	
	/**向左操作,返回操作成功与否*/
	private boolean goLeft() {
		boolean suc=false;
		boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
		do {
			mergeOrMoveExist=false;//初始为false
			for(int i=1;i<cols;i++) {//从第二列起向右,遍历卡片矩阵的列
				for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
					_CardPane card=cps[i][j];
					_CardPane preCard=cps[i-1][j];//前一个卡片
					boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
					mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
				}
			}
			suc|=mergeOrMoveExist;
		}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
		return suc;
	}
	
	/**测试是否能向左操作*/
	private boolean testLeft() {
		for(int i=1;i<cols;i++) {//从第二列起向右,遍历卡片矩阵的列
			for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
				_CardPane card=cps[i][j];
				_CardPane preCard=cps[i-1][j];//前一个卡片
				if(card.canMergeOrMove(preCard)) {
					return true;//能
				}
			}
		}
		return false;//不能
	}
	
	/**向右操作,返回操作成功与否*/
	private boolean goRight() {
		boolean suc=false;
		boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
		do {
			mergeOrMoveExist=false;//初始为false
			for(int i=cols-2;i>=0;i--) {//从倒数第二列起向左,遍历卡片矩阵的列
				for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
					_CardPane card=cps[i][j];
					_CardPane preCard=cps[i+1][j];//前一个卡片
					boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
					mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
				}
			}
			suc|=mergeOrMoveExist;
		}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
		return suc;
	}
	
		mCallbacks.afterScoreChange();
	}
	
	/**获取卡片矩阵中的最大卡片*/
	private _CardPane getMaxCard() {
		_CardPane maxCard=new _CardPane();//type=0的新卡片
		for(_CardPane[] row:cps) {
			for(_CardPane card:row) {
				if(card.getType()>maxCard.getType()) {
					maxCard=card;
				}
			}
		}
		return maxCard;
	}
	
	/**在随机的空卡片上生成新的数字,若矩阵已满,或生成数字后满,则返回false*/
	public boolean createRandomNumber() {
		List<_CardPane> voidCards=new ArrayList<>();//空卡片列表
		
		for(_CardPane[] row:cps) {
			for(_CardPane card:row) {
				if(card.getType()==0) {//是空卡片
					voidCards.add(card);//添加到列表中
				}
			}
		}
		int len=voidCards.size();
		if(len==0) {//没有空卡片了,返回
			return false;//判满
		}
		_CardPane card=voidCards.get((int)(rand.nextDouble()*len));
		card.setType(rand.nextInt(5)!=0?1:2);//设置type
		card.draw();
		return len!=1;//len==1,也满
	}
	
	/**重启卡片矩阵,并在随机的空卡片上生成数字*/
	public void restartMatrix() {
		for(_CardPane[] row:cps) {
			for(_CardPane card:row) {
				card.setType(0);
				card.draw();//重绘
			}
		}
		mcQuantities=new int[15];//重设合并过的卡片数字数量
		mCallbacks.afterScoreChange();
		createRandomNumber();//在随机的空卡片上生成数字,这个方法会返回布尔值,但这里不需要
	}

	/**进行颜色测试,可在4*4矩阵中显示2至65536*/
	public void testColors() {
		for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
			for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
				_CardPane card=cps[i][j];
				int type=i*4+j+1;
				if(type>16) {
					return;
				}
				card.setType(i*4+j+1);
				card.draw();//重绘
			}
		}
	}
	}

CardColor:

原来的游戏页面

不足
只有单纯的游戏,并且画面过于简洁,增加了分数的计算和记录
额外增加了菜单栏类和卡片的美化

改动:
卡片的美化:

分数的记录:

菜单栏的增设:

最终的画面呈现:

项目结语:
通过对2048小游戏的二次开发,我发现即使很简单的小游戏里面也有很大的学问,需要注意操作的流畅性,用户界面的美化,相关内容的记录;在多次查找相关内容后进行综合,才能够利用好已学知识。在修改的过程,我也询问了原作者相关的内容。获得了很大的帮助,对项目有了更深层次的掌握。当修改时,我们要注意到前后的衔接,类名、成员、方法的命名规范,最好借助思维图帮助自身梳理。这次的项目改进令我受益匪浅,对JAVA语言的使用也有了提升。

标签:JAVA,数字,卡片,int,小游戏,new,二次开发,import,card
From: https://www.cnblogs.com/2252703xx/p/18054299

相关文章

  • 基于JAVA的康威生命游戏二次开发
    引言:       康威生命游戏(Conway'sGameofLife)是一种基于细胞自动机的零玩家游戏,由数学家约翰·康威(JohnConway)于1970年创建。这个游戏并不是传统意义上的游戏,而是一种模拟生命演变的规则系统。康威生命游戏的“宇宙”是一个由无限的二维网格组成的平面,每个格子被称为......
  • linux下的java部署
    jar命令简介java部署jar包可以使用java-jar命令,比如:java-jardemo.jar执行上述命令后,JAR包中的程序将在Linux系统中运行。注:在运行JAR包之前,确保你的JAR文件是可执行的,并且包含了正确的类和依赖项。如果JAR包依赖于其他库或配置文件,确保它们也在正确的位置可用。......
  • Java数组
    Java数组数组是一种容器,可以用来存储同种类型的多个值。数组的定义两种形式int[]arrayintarray[]数组的初始化在内存中为数组开辟空间,并将数据存入容器的过程。静态初始化//完整格式int[]array=newint[]{11,22,33};//简化格式int[]array={11,22,33};......
  • Java学习笔记——第六天
    案例练习案例一:买飞机票需求用户购买机票时,机票原价会按照是淡季还是旺季,是头等舱还是经济舱的情况进行相应的优惠,优惠方案如下:5-10月为旺季,头等舱9折,经济舱8.5折;11月到来年4月为淡季,头等舱7折,经济舱6.5折,请开发程序计算出用户当前机票的优惠价。分析方法是否需要接收数据?......
  • JAVA项目 贪吃蛇游戏二次开发
    基于java实现贪吃蛇小游戏,主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。原代码地址:https://gitee.com/jay_musu/games-and-tools.gitpackagecom.snake.view;importjava.awt.Color;importjava.awt.EventQueue;importjava.awt.Font;importjava.awt......
  • java 异常初识
    什么是异常◆实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格式不对,你要读取数据库的数据,数据可能是空的等。我们的程序再跑着,内存或硬盘可能满了。等等◆软件程序在运行过程中,非......
  • JAVA基础--JavaDos生成文档
    JavaDos生成文档法一:通过命令行生成信息输入(例子)/***@authorAAA*@version1.0*@since1.8*/publicclassDos{Stringname;/***@authorAAA*@paramname*@return*@throwsException*///方法前输入/**则会自......
  • javaweb04-maven&web入门
    maven依赖管理:管理项目依赖的jar包,避免版本冲突统一项目结构:提供标准统一的项目结构标准的项目构建:标准跨平台的自动化项目构建方式maven坐标groupid:定义当前项目隶属组织名称artifactid:定义当前项目名称version:定义当前项目版本号依赖依赖传递排除依赖<exclusion>......
  • javaweb03-前端工程
    Ajax异步的Javascript和XML数据交换异步交互:在不重载页面的情况下,与服务器交换数据并更新部分网页Axios入门前后端分离开发需求分析->接口定义->前后端并行开发->测试->前后端联调开发YAPI接口管理平台前端工程化规范化、标准化前端开发环境准备vue-cli脚手架Vu......
  • JAVA基础--包机制
    JAVA包机制(使用IntellijIDEA)语法格式注意:该语句位于第一行,不可删除或改变位置packagepkg1[.pag2[.pag3...]];packagecom.baidu.www;命名规则一般利用公司域名倒置作为包名例如:www.baidu.com,包名写为:com.baidu.www导入包为了能够使用某一包内的成员,需要在Java程序中明......