刚刚完成的一个小游戏程序,写文章记录一下,如果有任何错误或者可以改进的代码请提出
另一方面也是方便自己几个月或几年后忘记时,来这里翻一翻回顾思路

首先放一下效果图:

基本界面

所有卡片

分数统计

尺寸为5x5或6x6

类的组织:

五个类,最基础的是_CardPane,继承自BorderPane,作为数字卡片。它里面有一个Rectangle,用来表示卡片的圆角矩形背景,以及一个Label来显示数字

然后是由数字卡片组成的矩阵_CardMatrixPane,继承自StackPane,它包含一个GridPane

_CardColor,里面只有一个静态的Color数组,用来搞卡片的背景颜色

_GameMenuBar作为游戏的菜单栏,继承自MenuBar

最后是_2048Demo,相当于控制器

这里类名前面加下划线是个人习惯,因为我的Eclipse项目名、包名、类名等等都会与图标重合一些,加下划线可以看的方便,如下:

下面放代码:

_CardPane:

package _2048._node;import _2048._model._CardColor;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;/*** 节点类——数字卡片* @author 邦邦拒绝魔抗* */
//若继承自Pane类,缺少需要的setAlignment()方法
//若继承自StackPane类,会出现一些绘制错误
public class _CardPane extends BorderPane {private static final int RC=5;//矩形的圆角private int type;/* 类型* type=0    number=0* type=1  number=2* type=2  number=4* type=3  number=8* ...*/private boolean merge=false;//是否被合并过,如果合并了,则不能继续合并,针对当前轮private Rectangle r;//圆角矩形private Label l;//数字标签/**无参构造方法*/public _CardPane() {this(0);}/**构造方法,通过下标和类型生成数字卡片*/public _CardPane(int type) {this.type=type;//圆角矩形r=new Rectangle();r.widthProperty().bind(this.widthProperty());//矩形的宽度绑定单元格宽度r.heightProperty().bind(this.heightProperty());//矩形的高度绑定单元格高度r.setArcWidth(RC);//圆角宽度r.setArcHeight(RC);//圆角高度r.setStroke(Color.BLACK);//边框颜色r.setStrokeWidth(3);//边框宽度getChildren().add(r);//数字标签l=new Label("65536");//65536是4*4情况下可能出现的最大数字setCenter(l);//绘制变化的部分draw();}/**获取数字标签对象*/public Label getLabel() {return l;}/**设置卡片类型*/public void setType(int type) {this.type=type;}/**获取卡片类型*/public int getType() {return type;}/**设置合并记录*/public void setMerge(boolean merge) {this.merge=merge;}/**获取合并记录*/public boolean isMerge() {return merge;}/**绘制单次操作中卡片变化的部分,包括颜色和显示的数字*/public void draw() {if(merge) {//突出显示已合并的卡片r.setStroke(Color.RED);//此次操作中合并,显示红色}else {r.setStroke(Color.BLACK);//此次操作中没有合并,显示黑色}r.setFill(_CardColor.CB[type]);drawNumber();}/**判断此卡片能否向调用者所给出的卡片移动或合并*/public boolean canMergeOrMove(_CardPane card) {if(type==0) {//空卡片不能移动或合并return false;}if(card.type==0) {//可以向空卡片移动return true;}return type==card.getType()&&!merge&&!card.isMerge();//不能二次合并}/**尝试向调用者所给出的卡片移动或合并,这一函数可能会修改两个卡片的属性*/public boolean tryMergeOrMoveInto(_CardPane card) {boolean canMergeOrMove=canMergeOrMove(card);if(canMergeOrMove) {//可以移动或合并if(card.getType()==0) {//移动card.setType(type);//移动数字card.setMerge(merge);//移动合并记录this.toVoid();//this成为空卡片}else {//合并card.setType(card.getType()+1);//合并数字card.setMerge(true);//设置合并记录this.toVoid();//this成为空卡片}}return canMergeOrMove;}/**刷新为空卡片*/private void toVoid() {type=0;merge=false;}private void drawNumber() {if(type==0) {//空卡片l.setText("");}else {//非空卡片需要显示数字l.setText(""+getNumber());}}/**计算需显示的数字*/public int getNumber() {return (int)Math.pow(2,type);}@Overridepublic String toString() {return "[type="+type+", merge="+merge+"]";}
}

_CardMatrixPane:

package _2048._node;import java.util.ArrayList;
import java.util.List;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;/*** 节点类——卡片矩阵* @author 邦邦拒绝魔抗* */
//若继承自Pane类,缺少需要的setAlignment()方法
public class _CardMatrixPane extends StackPane {private Callbacks mCallbacks;private int cols;//卡片矩阵列数private int rows;//卡片矩阵行数private GridPane gridPane;//卡片矩阵容器private _CardPane[][] cps;//卡片矩阵private long score=0;//分数,初始为0private int[] mcQuantities=new int[15];//合并过的卡片数字数量,包括4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536/**回调接口*/public interface Callbacks {void afterScoreChange();//分数变化}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)));//测试用init();getChildren().add(gridPane);}/**获取分数*/public long getScore() {return score;}/**获取合并过的卡片数字数量*/public int[] getMcQuantities() {return mcQuantities;}private void init() {initGridPane();//初始化GridPanecreateRandomNumber();//在随机的空卡片上生成数字,这个方法会返回布尔值,但这里不需要}/**初始化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 w=widthProperty().get();double h=heightProperty().get();double min=w<h?w:h;gridPane.setMaxWidth(min);gridPane.setMaxHeight(min);for(int i=0;i<cols;i++) {//遍历卡片矩阵的列for(int j=0;j<rows;j++) {//遍历卡片矩阵的行_CardPane card=cps[i][j];card.getLabel().setFont(new Font((min/14)/cols*4));//设置显示数字的尺寸//由于下面两行代码主动设置了每个单元格内cardPane的尺寸,gridPane不需要自动扩张card.setPrefWidth(min-5*(cols-1));//设置单元格内cardPane的宽度,否则它会随其内容变化,进而影响单元格宽度card.setPrefHeight(min-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()+"更大的数字卡片,终点已至");alert.show();return;}KeyCode kc=e.getCode();switch(kc) {case UP:case W:goUp();//↑break;case DOWN:case S:goDown();//↓break;case LEFT:case A:goLeft();//←break;case RIGHT:case D:goRight();//→break;default:return;//未定义的操作}redrawAllCardsAndResetIsMergeAndSetScore();//重绘所有的卡片,并重设合并记录,更新分数boolean isFull=!createRandomNumber();//生成新的随机数字卡片,并判满,这包含了生成数字后满的情况if(isFull) {//矩阵已满,可能已经游戏结束boolean testOpe=false;//是否还能进行横向或竖向操作testOpe|=testUp();//还能进行竖向操作testOpe|=testLeft();//还能进行横向操作if(!testOpe) {//游戏结束Alert alert=new Alert(AlertType.INFORMATION);alert.setTitle(alert.getAlertType().toString());alert.setContentText("游戏结束,本次最大数字为"+maxCard.getNumber()+",可在菜单栏选择重新开始\n");alert.show();}}});}/**向上操作*/private void goUp() {boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并do {mergeOrMoveExist=false;//初始为falsefor(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}}}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并}/**测试是否能向上操作*/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 void goDown() {boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并do {mergeOrMoveExist=false;//初始为falsefor(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}}}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并}/**向左操作*/private void goLeft() {boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并do {mergeOrMoveExist=false;//初始为falsefor(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}}}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并}/**测试是否能向左操作*/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 void goRight() {boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并do {mergeOrMoveExist=false;//初始为falsefor(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}}}while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并}/**重绘所有的卡片,并重设合并记录,并设置分数*/private void redrawAllCardsAndResetIsMergeAndSetScore() {for(int i=0;i<cols;i++) {//遍历卡片矩阵的列for(int j=0;j<rows;j++) {//遍历卡片矩阵的行_CardPane card=cps[i][j];card.draw();if(card.isMerge()) {//这张卡片合并过score+=card.getNumber();//计入分数mcQuantities[card.getType()-2]++;//相应的合并过的卡片数字数量+1card.setMerge(false);}}}mCallbacks.afterScoreChange();}/**获取卡片矩阵中的最大卡片*/private _CardPane getMaxCard() {_CardPane maxCard=new _CardPane();//type=0的新卡片for(int i=0;i<cols;i++) {//遍历卡片矩阵的列for(int j=0;j<rows;j++) {//遍历卡片矩阵的行_CardPane card=cps[i][j];if(card.getType()>maxCard.getType()) {maxCard=card;}}}return maxCard;}/**在随机的空卡片上生成新的数字,若矩阵已满,或生成数字后满,则返回false*/public boolean createRandomNumber() {List<_CardPane> voidCards=new ArrayList<>();//空卡片列表for(int i=0;i<cols;i++) {//遍历卡片矩阵的列for(int j=0;j<rows;j++) {//遍历卡片矩阵的行_CardPane card=cps[i][j];if(card.getType()==0) {//是空卡片voidCards.add(card);//添加到列表中}}}int len=voidCards.size();if(len==0) {//没有空卡片了,返回return false;//判满}int type;int index=(int)(Math.random()*5);//0,1,2,3,4if(index!=0) {//4/5概率type=1;//number=2
//          type=7;//number=128}else {//1/5概率type=2;//number=4
//          type=8;//number=256}int voidCardIndex=(int)(Math.random()*len);_CardPane card=voidCards.get(voidCardIndex);card.setType(type);//更新type,生成数字card.draw();//重绘此卡片if(len==1) {//只有一个空卡片,矩阵生成数字后满return false;}return true;}/**重启卡片矩阵,并在随机的空卡片上生成数字*/public void restartMatrix() {for(int i=0;i<cols;i++) {//遍历卡片矩阵的列for(int j=0;j<rows;j++) {//遍历卡片矩阵的行_CardPane card=cps[i][j];card.setType(0);card.draw();//重绘}}score=0;//重设分数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:

package _2048._model;import javafx.scene.paint.Color;public class _CardColor {public static Color[] CB= {//卡片颜色Color.rgb(255,255,255),//0//235//195*2Color.rgb(235,195,195),//2Color.rgb(195,235,195),//4Color.rgb(195,195,235),//8//195//215*2Color.rgb(195,215,215),//16Color.rgb(215,195,215),//32Color.rgb(215,215,195),//64//175//225*2Color.rgb(175,225,225),//128Color.rgb(225,175,225),//256Color.rgb(225,225,175),//512//235//155*2Color.rgb(235,155,155),//1024Color.rgb(155,235,155),//2048Color.rgb(155,155,235),//4096//115//255*2Color.rgb(115,255,255),//8192Color.rgb(255,115,255),//16384Color.rgb(255,255,115),//32768Color.rgb(195,195,195),//65536};public static Color[] CF= {//数字颜色};
}

_GameMenuBar:

package _2048._node;import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Alert.AlertType;/*** 节点类——2048游戏菜单栏* @author 邦邦拒绝魔抗**/
public class _GameMenuBar extends MenuBar {private Callbacks mCallbacks;private Menu scoreMenu;/**回调接口*/public interface Callbacks {void afterRestart();//重新开始void afterResetGridSize(int cols,int rows);//重设表格尺寸void afterGetMoreScoreInfo();//获取更详细的分数信息}public _GameMenuBar(Application application) {//application供回调方法使用mCallbacks=(Callbacks)application;//Game菜单Menu gameMenu=new Menu("游戏");//游戏MenuItem restartMenuItem=new MenuItem("重新开始");//重新开始restartMenuItem.setOnAction(e->mCallbacks.afterRestart());MenuItem exitMenuItem=new MenuItem("退出");//退出exitMenuItem.setOnAction(e->Platform.exit());gameMenu.getItems().addAll(restartMenuItem,exitMenuItem);//Setting菜单Menu settingMenu=new Menu("设置");//设置ToggleGroup tg=new ToggleGroup();//组RadioMenuItem r44MenuItem=new RadioMenuItem("尺寸:4x4");r44MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(4,4));RadioMenuItem r55MenuItem=new RadioMenuItem("尺寸:5x5");r55MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(5,5));RadioMenuItem r66MenuItem=new RadioMenuItem("尺寸:6x6");r66MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(6 ,6));r44MenuItem.setToggleGroup(tg);r55MenuItem.setToggleGroup(tg);r66MenuItem.setToggleGroup(tg);settingMenu.getItems().addAll(r44MenuItem,r55MenuItem,r66MenuItem);r44MenuItem.setSelected(true);//默认选中4x4//Info菜单Menu infoMenu=new Menu("信息");//信息MenuItem helpMenuItem=new MenuItem("帮助");//帮助helpMenuItem.setOnAction(e->{Alert alert=new Alert(AlertType.INFORMATION);alert.setTitle(alert.getAlertType().toString());alert.setContentText("操作方式:\n"+"向上滑动:方向键↑或键W\n"+"向下滑动:方向键↓或键S\n"+"向左滑动:方向键←或键A\n"+"向右滑动:方向键→或键D\n"+"\n游戏规则:\n"+"相同数字的卡片在靠拢、相撞时会合并\n"+"在操作中合并的卡片会以红色边框凸显\n尽可能获得更大的数字!");alert.show();});MenuItem aboutUsMenuItem=new MenuItem("关于我们");//关于我们aboutUsMenuItem.setOnAction(e->{Alert alert=new Alert(AlertType.INFORMATION);alert.setTitle(alert.getAlertType().toString());alert.setContentText("游戏作者:邦邦拒绝魔抗\n他的邮箱:842748156@qq.com\n\n感谢你的游玩!");alert.show();});infoMenu.getItems().addAll(helpMenuItem,aboutUsMenuItem);//Record菜单Menu recordMenu=new Menu("记录");//记录MenuItem historyScoreMenuItem=new MenuItem("历史分数");//历史分数historyScoreMenuItem.setOnAction(e->{Alert alert=new Alert(AlertType.INFORMATION);alert.setTitle(alert.getAlertType().toString());alert.setContentText("还没有制作喵");alert.show();});recordMenu.getItems().addAll(historyScoreMenuItem);//Score菜单scoreMenu=new Menu("分数");//分数MenuItem moreScoreInfo=new MenuItem("更多分数信息");//更多分数信息moreScoreInfo.setOnAction(e->mCallbacks.afterGetMoreScoreInfo());scoreMenu.getItems().addAll(moreScoreInfo);getMenus().addAll(gameMenu,settingMenu,infoMenu,recordMenu,scoreMenu);}/**获取分数菜单*/public Menu getScoreMenu() {return scoreMenu;}
}

_2048Demo:

import _2048._node._CardMatrixPane;
import _2048._node._GameMenuBar;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;/*** 2048运行类* @author 邦邦拒绝魔抗**/
public class _2048Demo extends Application implements _GameMenuBar.Callbacks,_CardMatrixPane.Callbacks {private BorderPane borderPane;private _GameMenuBar menuBar;private _CardMatrixPane cardMatrixPane;@Overridepublic void start(Stage primaryStage) {borderPane=new BorderPane();Scene scene=new Scene(borderPane,1000,600);//Top菜单栏menuBar=new _GameMenuBar(this);//创建菜单栏,并传入Application供调用borderPane.setTop(menuBar);//顶部//Center2048卡片矩阵cardMatrixPane=new _CardMatrixPane(this);cardMatrixPane.setPadding(new Insets(5,5,5,5));//外边距borderPane.setCenter(cardMatrixPane);//中心primaryStage.setTitle("2048");primaryStage.setScene(scene);primaryStage.show();startGame();
//      cardMatrixPane.testColors();//颜色测试}public static void main(String[] args) {Application.launch(args);}/**开始游戏*/private void startGame() {cardMatrixPane.requestFocus();//添加焦点cardMatrixPane.createKeyListener();//添加键盘监听afterScoreChange();}@Overridepublic void afterRestart() {cardMatrixPane.restartMatrix();}@Overridepublic void afterResetGridSize(int cols,int rows) {cardMatrixPane=new _CardMatrixPane(cols,rows,this);cardMatrixPane.setPadding(new Insets(5,5,5,5));//外边距borderPane.setCenter(cardMatrixPane);startGame();
//      cardMatrixPane.testColors();//颜色测试}@Overridepublic void afterScoreChange() {menuBar.getScoreMenu().setText("分数: "+cardMatrixPane.getScore());}@Overridepublic void afterGetMoreScoreInfo() {int[] temp=cardMatrixPane.getMcQuantities();Alert alert=new Alert(AlertType.INFORMATION);alert.setTitle(alert.getAlertType().toString());alert.setContentText("4的合并次数:      "+temp[0]+"\n"+"8的合并次数:      "+temp[1]+"\n"+"16的合并次数:         "+temp[2]+"\n"+"32的合并次数:         "+temp[3]+"\n"+"64的合并次数:         "+temp[4]+"\n"+"128的合并次数:        "+temp[5]+"\n"+"256的合并次数:        "+temp[6]+"\n"+"512的合并次数:        "+temp[7]+"\n"+"1024的合并次数:   "+temp[8]+"\n"+"2048的合并次数:   "+temp[9]+"\n"+"4096的合并次数:   "+temp[10]+"\n"+"8192的合并次数:  "+temp[11]+"\n"+"16384的合并次数:     "+temp[12]+"\n"+"32768的合并次数:     "+temp[13]+"\n"+"65536的合并次数:     "+temp[14]+"\n");alert.show();}
}

一共700多行吧,里面有几行是注释掉的,它们在测试程序时候用过,方便调试,也就没删

_CardMatrixPane的testColors()方法正常流程是用不到的,测试程序时候查看所有卡片的颜色和字号用,可以删掉

基本思路:

代码里注释已经比较详细了,所以这里并不会涉及到很多的细节

卡片:

卡片的类型和显示的数字间关系很简单,在换算方法getNumber()中也有体现,数字就是2的type次方。其中的例外是2的0次方,这时候类型0即空卡片,不显示数字

为什么要加一个类型呢,举例的话用类型作为数组的下标取颜色就很方便,数字就不够紧凑了。当然,显示时的换算是一个开销,可以搞一个专门的存储数字的数组,用类型作为下标取数字,那就不用换算了

merge用来记录在一次操作中(向上、向下、向左、向右)已经合并了的卡片,逻辑上不希望卡片间连续地合并,这也符合2048游戏的基本规则

数据绑定,矩形的尺寸和GridPane的单元格尺寸绑定,缩放页面时候会跟随变化

用红色边框突出显示已合并的卡片,这里就是方便看的,可以删掉

重写了Object类的toString()方法,它是为了debug时方便看而设置的,可以删掉,用不到

卡片矩阵:

考虑到5x5和6x6的情况,卡片矩阵的行列数是变量而非常量

写了回调方法afterScoreChange(),这是因为卡片矩阵越职去修改菜单栏的分数是不好的,所以把这项工作交给了控制器来完成

对于卡片矩阵的宽高变化设有监听器,它随之修改卡片矩阵中GridPane的尺寸,还有单元格的尺寸和显示数字的尺寸。因为GridPane需要是正方形的,它的边长便取卡片矩阵宽高中的最小值。而卡片矩阵的宽高接近于窗口的宽高

这里有一个逻辑上的问题,按照2048游戏的基本规则,如果一次操作中没有出现任何卡片的移动或合并(矩阵中还有空卡片),就不应该生成新的2或4了,但这个程序的表现是会生成的,大家可以自行修改

createRandomNumber()方法会返回一个布尔值来表示矩阵里还有没有空卡片,有时并不需要这个返回值,是因为我们认为矩阵里肯定还有空卡片,比如重新开始游戏的时候

颜色:

一开始考虑了做卡片的数字颜色,后来偷懒都用黑色了

游戏菜单:

同样,菜单栏越职去访问和修改卡片矩阵是不好的,用回调方法把这些工作交给了控制器来完成

控制器:

实现回调接口中的各个回调方法,在恰当的时机控制各个节点

想来也就这些,欢迎评论


更新:对部分代码优化,修改了分数统计的形式(改为了用表格来展示)

……

详解Java实现小游戏2048(使用JavaFX)相关推荐

  1. html+css+javascript实现小游戏2048(详解,附源代码)

    html+css+javascript实现小游戏2048(详解,附源代码) 1.上下左右的移动原理相同,这里只详细说明向上移动的方法 2.这里的上下左右由wasd四个键控制 3-小方块空的意思就是没数 ...

  2. 前端小游戏2048(一步步详解附带源代码,源码上传到csdn,可以免费下载)

    2048小游戏 2048是前端开发必经的一个小游戏,2048小游戏包含了HTML,CSS和JavaScript. 简介 <2048>,是一款益智小游戏,这款游戏是由年仅19岁的意大利程序员 ...

  3. 详解Java解析XML的四种方法

    http://developer.51cto.com  2009-03-31 13:12  cnlw1985  javaeye  我要评论(8) XML现在已经成为一种通用的数据交换格式,平台的无关性 ...

  4. java同步异步调用_详解java 三种调用机制(同步、回调、异步)

    1:同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,jsPwwCe它是一种单向调用 2:回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口: 3:异步调用:一种类似消 ...

  5. java斐波那契查找_详解Java Fibonacci Search斐波那契搜索算法代码实现

    一, 斐波那契搜索算法简述 斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术. 斐波那契搜索采用分而治之的方法,其中我们按照斐波那契数列对元素进行不均等 ...

  6. 图文详解Java环境变量配置方法

    今天动力节点java学院小编为大家介绍"图文详解Java环境变量配置方法",希望对各位小伙伴有帮助,下面就和小编一起来看看Java环境变量配置方法吧. 首先是要安装JDK,JDK安 ...

  7. java comparator相等_详解Java中Comparable和Comparator接口的区别

    详解Java中Comparable和Comparator接口的区别 发布于 2020-7-20| 复制链接 摘记: 详解Java中Comparable和Comparator接口的区别本文要来详细分析一 ...

  8. 详解 Java NIO

    详解 Java NIO 文件的抽象化表示,字节流以及字符流的文件操作等属于传统 IO 的相关内容,我们已经在前面的文章进行了较为深刻的学习了. 但是传统的 IO 流还是有很多缺陷的,尤其它的阻塞性加上 ...

  9. java中priorityqueue_详解JAVA中priorityqueue的具体使用

    Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示.本文从Queue接口函数出发,结合生动的图解,深入浅出地分析PriorityQueue每个操作的具体过程和时间复杂度, ...

最新文章

  1. 数据库和数据湖的关键概念性差异
  2. 201771010126.王燕《面向对象程序设计(Java)》第六周学习总结
  3. LOJ504「LibreOJ β Round」ZQC 的手办
  4. System.Insert - 插入字符串
  5. 人工智能大牛的新年启示:未来要看无监督学习、自然语言处理
  6. 04 组件与Props
  7. 还有人不知道什么是AndroidX的吗?文末领取面试资料
  8. markdown如何设置图片大小_不会吧,还不会用markdown排版吗
  9. 救救孩子?强制实名游戏不足四成 青少年视力保护状况堪忧
  10. c语言上级题目,C语言上级考试题目.doc
  11. 20201125 plecs更新
  12. 教育培训机构如何利用小程序招生?
  13. matlab hold all,Matlab中的命令hold on hold off | 学步园
  14. 剪刀石头布java流程图_青岛能源所基于“剪刀石头布”策略实现快速多轮基因编辑...
  15. 即时通讯IM 与系统集成
  16. laravel从入门到精通之 php excel设置单元格边框只显示竖条
  17. Fintech趣店总部(厦门)技术招聘
  18. 手机内置传感器和定位技术
  19. matlab张志涌版课后习题答案,matlab教程(张志涌)课后习题答案.doc
  20. 电销企业外呼系统如何选最合适?

热门文章

  1. js中clearInterval无效,以及setInterval中断后重新执行
  2. 第13期 《不一样的选择,不一样的世界》2017年3月 期刊
  3. 【小强推歌】---邓丽君演绎古词专集《淡淡幽情》
  4. 2048小游戏--pygame来实现
  5. rz安装 xshell_文件上传命令rz和下载命令sz的安装
  6. Question2Answer的统计添加
  7. w8计算机配置要求,安装Win8对电脑硬件的配置要求
  8. Word文件删除后怎么恢复?好用的恢复方法分享
  9. 最动听的声音 2019年农历腊月二十六
  10. 直播电商购物消费者满意度在线调查报告(一)