当前位置:   article > 正文

详解Java实现2048小游戏(JavaFX,完整源码+注释说明)_2048小游戏代码的每步解析

2048小游戏代码的每步解析

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

目录

基本界面:

类的组织:

_CardPane:

_CardMatrixPane:

_CardColor:

_GameMenuBar:

_2048Demo:

基本思路:

卡片:

卡片矩阵:

颜色:

游戏菜单:

控制器:


首先放一下效果图:

基本界面:

 

所有卡片

分数统计

尺寸为5x5或6x6

 

类的组织:

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

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

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

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

最后是_2048Demo,相当于控制器

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

下面放代码:

_CardPane:

  1. package _2048._node;
  2. import _2048._model._CardColor;
  3. import javafx.scene.control.Label;
  4. import javafx.scene.layout.BorderPane;
  5. import javafx.scene.paint.Color;
  6. import javafx.scene.shape.Rectangle;
  7. /**
  8. * 节点类——数字卡片
  9. * @author 邦邦拒绝魔抗
  10. *
  11. */
  12. //若继承自Pane类,缺少需要的setAlignment()方法
  13. //若继承自StackPane类,会出现一些绘制错误
  14. public class _CardPane extends BorderPane {
  15. private static final int RC=5;//矩形的圆角
  16. private int type;
  17. /* 类型
  18. * type=0 number=0
  19. * type=1 number=2
  20. * type=2 number=4
  21. * type=3 number=8
  22. * ...
  23. */
  24. private boolean merge=false;//是否被合并过,如果合并了,则不能继续合并,针对当前轮
  25. private Rectangle r;//圆角矩形
  26. private Label l;//数字标签
  27. /**无参构造方法*/
  28. public _CardPane() {
  29. this(0);
  30. }
  31. /**构造方法,通过下标和类型生成数字卡片*/
  32. public _CardPane(int type) {
  33. this.type=type;
  34. //圆角矩形
  35. r=new Rectangle();
  36. r.widthProperty().bind(this.widthProperty());//矩形的宽度绑定单元格宽度
  37. r.heightProperty().bind(this.heightProperty());//矩形的高度绑定单元格高度
  38. r.setArcWidth(RC);//圆角宽度
  39. r.setArcHeight(RC);//圆角高度
  40. r.setStroke(Color.BLACK);//边框颜色
  41. r.setStrokeWidth(3);//边框宽度
  42. getChildren().add(r);
  43. //数字标签
  44. l=new Label("65536");//65536是4*4情况下可能出现的最大数字
  45. setCenter(l);
  46. //绘制变化的部分
  47. draw();
  48. }
  49. /**获取数字标签对象*/
  50. public Label getLabel() {
  51. return l;
  52. }
  53. /**设置卡片类型*/
  54. public void setType(int type) {
  55. this.type=type;
  56. }
  57. /**获取卡片类型*/
  58. public int getType() {
  59. return type;
  60. }
  61. /**设置合并记录*/
  62. public void setMerge(boolean merge) {
  63. this.merge=merge;
  64. }
  65. /**获取合并记录*/
  66. public boolean isMerge() {
  67. return merge;
  68. }
  69. /**绘制单次操作中卡片变化的部分,包括颜色和显示的数字*/
  70. public void draw() {
  71. if(merge) {//突出显示已合并的卡片
  72. r.setStroke(Color.RED);//此次操作中合并,显示红色
  73. }else {
  74. r.setStroke(Color.BLACK);//此次操作中没有合并,显示黑色
  75. }
  76. r.setFill(_CardColor.CB[type]);
  77. drawNumber();
  78. }
  79. /**判断此卡片能否向调用者所给出的卡片移动或合并*/
  80. public boolean canMergeOrMove(_CardPane card) {
  81. if(type==0) {//空卡片不能移动或合并
  82. return false;
  83. }
  84. if(card.type==0) {//可以向空卡片移动
  85. return true;
  86. }
  87. return type==card.getType()&&!merge&&!card.isMerge();//不能二次合并
  88. }
  89. /**尝试向调用者所给出的卡片移动或合并,这一函数可能会修改两个卡片的属性*/
  90. public boolean tryMergeOrMoveInto(_CardPane card) {
  91. boolean canMergeOrMove=canMergeOrMove(card);
  92. if(canMergeOrMove) {//可以移动或合并
  93. if(card.getType()==0) {//移动
  94. card.setType(type);//移动数字
  95. card.setMerge(merge);//移动合并记录
  96. this.toVoid();//this成为空卡片
  97. }else {//合并
  98. card.setType(card.getType()+1);//合并数字
  99. card.setMerge(true);//设置合并记录
  100. this.toVoid();//this成为空卡片
  101. }
  102. }
  103. return canMergeOrMove;
  104. }
  105. /**刷新为空卡片*/
  106. private void toVoid() {
  107. type=0;
  108. merge=false;
  109. }
  110. private void drawNumber() {
  111. if(type==0) {//空卡片
  112. l.setText("");
  113. }else {//非空卡片需要显示数字
  114. l.setText(""+getNumber());
  115. }
  116. }
  117. /**计算需显示的数字*/
  118. public int getNumber() {
  119. return (int)Math.pow(2,type);
  120. }
  121. @Override
  122. public String toString() {
  123. return "[type="+type+", merge="+merge+"]";
  124. }
  125. }

_CardMatrixPane:

  1. package _2048._node;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import javafx.application.Application;
  5. import javafx.scene.control.Alert;
  6. import javafx.scene.control.Alert.AlertType;
  7. import javafx.scene.input.KeyCode;
  8. import javafx.scene.layout.GridPane;
  9. import javafx.scene.layout.StackPane;
  10. import javafx.scene.text.Font;
  11. /**
  12. * 节点类——卡片矩阵
  13. * @author 邦邦拒绝魔抗
  14. *
  15. */
  16. //若继承自Pane类,缺少需要的setAlignment()方法
  17. public class _CardMatrixPane extends StackPane {
  18. private Callbacks mCallbacks;
  19. private int cols;//卡片矩阵列数
  20. private int rows;//卡片矩阵行数
  21. private GridPane gridPane;//卡片矩阵容器
  22. private _CardPane[][] cps;//卡片矩阵
  23. private long score=0;//分数,初始为0
  24. private int[] mcQuantities=new int[15];//合并过的卡片数字数量,包括4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536
  25. /**回调接口*/
  26. public interface Callbacks {
  27. void afterScoreChange();//分数变化
  28. }
  29. public _CardMatrixPane(Application application) {
  30. this(4,4,application);//默认4*4
  31. }
  32. public _CardMatrixPane(int cols,int rows,Application application) {//application供回调方法使用
  33. mCallbacks=(Callbacks)application;
  34. this.cols=cols;
  35. this.rows=rows;
  36. // this.setBackground(new Background(new BackgroundFill(Color.BLUE,CornerRadii.EMPTY,Insets.EMPTY)));//测试用
  37. init();
  38. getChildren().add(gridPane);
  39. }
  40. /**获取分数*/
  41. public long getScore() {
  42. return score;
  43. }
  44. /**获取合并过的卡片数字数量*/
  45. public int[] getMcQuantities() {
  46. return mcQuantities;
  47. }
  48. private void init() {
  49. initGridPane();//初始化GridPane
  50. createRandomNumber();//在随机的空卡片上生成数字,这个方法会返回布尔值,但这里不需要
  51. }
  52. /**初始化GridPane*/
  53. private void initGridPane() {
  54. gridPane=new GridPane();
  55. // gridPane.setBackground(new Background(new BackgroundFill(Color.YELLOW,CornerRadii.EMPTY,Insets.EMPTY)));//测试用
  56. // gridPane.setGridLinesVisible(true);//单元格边框可见,测试用
  57. //对this尺寸监听
  58. widthProperty().addListener(ov->setGridSizeAndCardFont());//宽度变化,更新边长和字号
  59. heightProperty().addListener(ov->setGridSizeAndCardFont());//高度变化,更新边长和字号
  60. //单元格间隙
  61. gridPane.setHgap(5);
  62. gridPane.setVgap(5);
  63. //绘制每个单元格
  64. cps=new _CardPane[cols][rows];
  65. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  66. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  67. _CardPane card=new _CardPane(0);
  68. gridPane.add(card, i, j);
  69. cps[i][j]=card;
  70. }
  71. }
  72. }
  73. /**设置GridPane的边长,其内部单元格的尺寸和CardPane的字号*/
  74. private void setGridSizeAndCardFont(){
  75. double w=widthProperty().get();
  76. double h=heightProperty().get();
  77. double min=w<h?w:h;
  78. gridPane.setMaxWidth(min);
  79. gridPane.setMaxHeight(min);
  80. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  81. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  82. _CardPane card=cps[i][j];
  83. card.getLabel().setFont(new Font((min/14)/cols*4));//设置显示数字的尺寸
  84. //由于下面两行代码主动设置了每个单元格内cardPane的尺寸,gridPane不需要自动扩张
  85. card.setPrefWidth(min-5*(cols-1));//设置单元格内cardPane的宽度,否则它会随其内容变化,进而影响单元格宽度
  86. card.setPrefHeight(min-5*(rows-1));//设置单元格内cardPane的高度,否则它会随其内容变化,进而影响单元格高度
  87. }
  88. }
  89. }
  90. /**添加键盘监听*/
  91. public void createKeyListener() {
  92. setOnKeyPressed(e->{//键盘按下事件
  93. _CardPane maxCard=getMaxCard();//最大卡片
  94. if(maxCard.getType()==16) {//出现最大数字
  95. Alert alert=new Alert(AlertType.INFORMATION);
  96. alert.setTitle(alert.getAlertType().toString());
  97. alert.setContentText("恭喜你,游戏的最大数字为"+maxCard.getNumber()+",可在菜单栏选择重新开始\n"+
  98. "事实上,我们还尚未准备比"+maxCard.getNumber()+"更大的数字卡片,终点已至");
  99. alert.show();
  100. return;
  101. }
  102. KeyCode kc=e.getCode();
  103. switch(kc) {
  104. case UP:
  105. case W:
  106. goUp();//↑
  107. break;
  108. case DOWN:
  109. case S:
  110. goDown();//↓
  111. break;
  112. case LEFT:
  113. case A:
  114. goLeft();//←
  115. break;
  116. case RIGHT:
  117. case D:
  118. goRight();//→
  119. break;
  120. default:
  121. return;//未定义的操作
  122. }
  123. redrawAllCardsAndResetIsMergeAndSetScore();//重绘所有的卡片,并重设合并记录,更新分数
  124. boolean isFull=!createRandomNumber();//生成新的随机数字卡片,并判满,这包含了生成数字后满的情况
  125. if(isFull) {//矩阵已满,可能已经游戏结束
  126. boolean testOpe=false;//是否还能进行横向或竖向操作
  127. testOpe|=testUp();//还能进行竖向操作
  128. testOpe|=testLeft();//还能进行横向操作
  129. if(!testOpe) {//游戏结束
  130. Alert alert=new Alert(AlertType.INFORMATION);
  131. alert.setTitle(alert.getAlertType().toString());
  132. alert.setContentText("游戏结束,本次最大数字为"+maxCard.getNumber()+",可在菜单栏选择重新开始\n");
  133. alert.show();
  134. }
  135. }
  136. });
  137. }
  138. /**向上操作*/
  139. private void goUp() {
  140. boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
  141. do {
  142. mergeOrMoveExist=false;//初始为false
  143. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  144. for(int j=1;j<rows;j++) {//从第二行起向下,遍历卡片矩阵的行
  145. _CardPane card=cps[i][j];
  146. _CardPane preCard=cps[i][j-1];//前一个卡片
  147. boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
  148. mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
  149. }
  150. }
  151. }while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
  152. }
  153. /**测试是否能向上操作*/
  154. private boolean testUp() {
  155. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  156. for(int j=1;j<rows;j++) {//从第二行起向下,遍历卡片矩阵的行
  157. _CardPane card=cps[i][j];
  158. _CardPane preCard=cps[i][j-1];//前一个卡片
  159. if(card.canMergeOrMove(preCard)) {
  160. return true;//能
  161. }
  162. }
  163. }
  164. return false;//不能
  165. }
  166. /**向下操作*/
  167. private void goDown() {
  168. boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
  169. do {
  170. mergeOrMoveExist=false;//初始为false
  171. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  172. for(int j=rows-2;j>=0;j--) {//从倒数第二行起向上,遍历卡片矩阵的行
  173. _CardPane card=cps[i][j];
  174. _CardPane preCard=cps[i][j+1];//前一个卡片
  175. boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
  176. mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
  177. }
  178. }
  179. }while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
  180. }
  181. /**向左操作*/
  182. private void goLeft() {
  183. boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
  184. do {
  185. mergeOrMoveExist=false;//初始为false
  186. for(int i=1;i<cols;i++) {//从第二列起向右,遍历卡片矩阵的列
  187. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  188. _CardPane card=cps[i][j];
  189. _CardPane preCard=cps[i-1][j];//前一个卡片
  190. boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
  191. mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
  192. }
  193. }
  194. }while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
  195. }
  196. /**测试是否能向左操作*/
  197. private boolean testLeft() {
  198. for(int i=1;i<cols;i++) {//从第二列起向右,遍历卡片矩阵的列
  199. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  200. _CardPane card=cps[i][j];
  201. _CardPane preCard=cps[i-1][j];//前一个卡片
  202. if(card.canMergeOrMove(preCard)) {
  203. return true;//能
  204. }
  205. }
  206. }
  207. return false;//不能
  208. }
  209. /**向右操作*/
  210. private void goRight() {
  211. boolean mergeOrMoveExist;//矩阵的这次操作的一次遍历中是否存在移动或合并
  212. do {
  213. mergeOrMoveExist=false;//初始为false
  214. for(int i=cols-2;i>=0;i--) {//从倒数第二列起向左,遍历卡片矩阵的列
  215. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  216. _CardPane card=cps[i][j];
  217. _CardPane preCard=cps[i+1][j];//前一个卡片
  218. boolean isChanged=card.tryMergeOrMoveInto(preCard);//记录两张卡片间是否进行了移动或合并
  219. mergeOrMoveExist|=isChanged;//只要有一次移动或合并记录,就记存在为true
  220. }
  221. }
  222. }while(mergeOrMoveExist);//如果存在移动或合并,就可能需要再次遍历,继续移动或合并
  223. }
  224. /**重绘所有的卡片,并重设合并记录,并设置分数*/
  225. private void redrawAllCardsAndResetIsMergeAndSetScore() {
  226. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  227. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  228. _CardPane card=cps[i][j];
  229. card.draw();
  230. if(card.isMerge()) {//这张卡片合并过
  231. score+=card.getNumber();//计入分数
  232. mcQuantities[card.getType()-2]++;//相应的合并过的卡片数字数量+1
  233. card.setMerge(false);
  234. }
  235. }
  236. }
  237. mCallbacks.afterScoreChange();
  238. }
  239. /**获取卡片矩阵中的最大卡片*/
  240. private _CardPane getMaxCard() {
  241. _CardPane maxCard=new _CardPane();//type=0的新卡片
  242. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  243. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  244. _CardPane card=cps[i][j];
  245. if(card.getType()>maxCard.getType()) {
  246. maxCard=card;
  247. }
  248. }
  249. }
  250. return maxCard;
  251. }
  252. /**在随机的空卡片上生成新的数字,若矩阵已满,或生成数字后满,则返回false*/
  253. public boolean createRandomNumber() {
  254. List<_CardPane> voidCards=new ArrayList<>();//空卡片列表
  255. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  256. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  257. _CardPane card=cps[i][j];
  258. if(card.getType()==0) {//是空卡片
  259. voidCards.add(card);//添加到列表中
  260. }
  261. }
  262. }
  263. int len=voidCards.size();
  264. if(len==0) {//没有空卡片了,返回
  265. return false;//判满
  266. }
  267. int type;
  268. int index=(int)(Math.random()*5);//0,1,2,3,4
  269. if(index!=0) {//4/5概率
  270. type=1;//number=2
  271. // type=7;//number=128
  272. }else {//1/5概率
  273. type=2;//number=4
  274. // type=8;//number=256
  275. }
  276. int voidCardIndex=(int)(Math.random()*len);
  277. _CardPane card=voidCards.get(voidCardIndex);
  278. card.setType(type);//更新type,生成数字
  279. card.draw();//重绘此卡片
  280. if(len==1) {//只有一个空卡片,矩阵生成数字后满
  281. return false;
  282. }
  283. return true;
  284. }
  285. /**重启卡片矩阵,并在随机的空卡片上生成数字*/
  286. public void restartMatrix() {
  287. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  288. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  289. _CardPane card=cps[i][j];
  290. card.setType(0);
  291. card.draw();//重绘
  292. }
  293. }
  294. score=0;//重设分数
  295. mcQuantities=new int[15];//重设合并过的卡片数字数量
  296. mCallbacks.afterScoreChange();
  297. createRandomNumber();//在随机的空卡片上生成数字,这个方法会返回布尔值,但这里不需要
  298. }
  299. /**进行颜色测试,可在4*4矩阵中显示2至65536*/
  300. public void testColors() {
  301. for(int i=0;i<cols;i++) {//遍历卡片矩阵的列
  302. for(int j=0;j<rows;j++) {//遍历卡片矩阵的行
  303. _CardPane card=cps[i][j];
  304. int type=i*4+j+1;
  305. if(type>16) {
  306. return;
  307. }
  308. card.setType(i*4+j+1);
  309. card.draw();//重绘
  310. }
  311. }
  312. }
  313. }

_CardColor:

  1. package _2048._model;
  2. import javafx.scene.paint.Color;
  3. public class _CardColor {
  4. public static Color[] CB= {//卡片颜色
  5. Color.rgb(255,255,255),//0
  6. //235
  7. //195*2
  8. Color.rgb(235,195,195),//2
  9. Color.rgb(195,235,195),//4
  10. Color.rgb(195,195,235),//8
  11. //195
  12. //215*2
  13. Color.rgb(195,215,215),//16
  14. Color.rgb(215,195,215),//32
  15. Color.rgb(215,215,195),//64
  16. //175
  17. //225*2
  18. Color.rgb(175,225,225),//128
  19. Color.rgb(225,175,225),//256
  20. Color.rgb(225,225,175),//512
  21. //235
  22. //155*2
  23. Color.rgb(235,155,155),//1024
  24. Color.rgb(155,235,155),//2048
  25. Color.rgb(155,155,235),//4096
  26. //115
  27. //255*2
  28. Color.rgb(115,255,255),//8192
  29. Color.rgb(255,115,255),//16384
  30. Color.rgb(255,255,115),//32768
  31. Color.rgb(195,195,195),//65536
  32. };
  33. public static Color[] CF= {//数字颜色
  34. };
  35. }

_GameMenuBar:

  1. package _2048._node;
  2. import javafx.application.Application;
  3. import javafx.application.Platform;
  4. import javafx.scene.control.Alert;
  5. import javafx.scene.control.Menu;
  6. import javafx.scene.control.MenuBar;
  7. import javafx.scene.control.MenuItem;
  8. import javafx.scene.control.RadioMenuItem;
  9. import javafx.scene.control.ToggleGroup;
  10. import javafx.scene.control.Alert.AlertType;
  11. /**
  12. * 节点类——2048游戏菜单栏
  13. * @author 邦邦拒绝魔抗
  14. *
  15. */
  16. public class _GameMenuBar extends MenuBar {
  17. private Callbacks mCallbacks;
  18. private Menu scoreMenu;
  19. /**回调接口*/
  20. public interface Callbacks {
  21. void afterRestart();//重新开始
  22. void afterResetGridSize(int cols,int rows);//重设表格尺寸
  23. void afterGetMoreScoreInfo();//获取更详细的分数信息
  24. }
  25. public _GameMenuBar(Application application) {//application供回调方法使用
  26. mCallbacks=(Callbacks)application;
  27. //Game菜单
  28. Menu gameMenu=new Menu("游戏");//游戏
  29. MenuItem restartMenuItem=new MenuItem("重新开始");//重新开始
  30. restartMenuItem.setOnAction(e->mCallbacks.afterRestart());
  31. MenuItem exitMenuItem=new MenuItem("退出");//退出
  32. exitMenuItem.setOnAction(e->Platform.exit());
  33. gameMenu.getItems().addAll(restartMenuItem,exitMenuItem);
  34. //Setting菜单
  35. Menu settingMenu=new Menu("设置");//设置
  36. ToggleGroup tg=new ToggleGroup();//组
  37. RadioMenuItem r44MenuItem=new RadioMenuItem("尺寸:4x4");
  38. r44MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(4,4));
  39. RadioMenuItem r55MenuItem=new RadioMenuItem("尺寸:5x5");
  40. r55MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(5,5));
  41. RadioMenuItem r66MenuItem=new RadioMenuItem("尺寸:6x6");
  42. r66MenuItem.setOnAction(e->mCallbacks.afterResetGridSize(6 ,6));
  43. r44MenuItem.setToggleGroup(tg);
  44. r55MenuItem.setToggleGroup(tg);
  45. r66MenuItem.setToggleGroup(tg);
  46. settingMenu.getItems().addAll(r44MenuItem,r55MenuItem,r66MenuItem);
  47. r44MenuItem.setSelected(true);//默认选中4x4
  48. //Info菜单
  49. Menu infoMenu=new Menu("信息");//信息
  50. MenuItem helpMenuItem=new MenuItem("帮助");//帮助
  51. helpMenuItem.setOnAction(e->{
  52. Alert alert=new Alert(AlertType.INFORMATION);
  53. alert.setTitle(alert.getAlertType().toString());
  54. alert.setContentText("操作方式:\n"+
  55. "向上滑动:方向键↑或键W\n"+
  56. "向下滑动:方向键↓或键S\n"+
  57. "向左滑动:方向键←或键A\n"+
  58. "向右滑动:方向键→或键D\n"+
  59. "\n游戏规则:\n"+
  60. "相同数字的卡片在靠拢、相撞时会合并\n"+
  61. "在操作中合并的卡片会以红色边框凸显\n尽可能获得更大的数字!");
  62. alert.show();
  63. });
  64. MenuItem aboutUsMenuItem=new MenuItem("关于我们");//关于我们
  65. aboutUsMenuItem.setOnAction(e->{
  66. Alert alert=new Alert(AlertType.INFORMATION);
  67. alert.setTitle(alert.getAlertType().toString());
  68. alert.setContentText("游戏作者:邦邦拒绝魔抗\n他的邮箱:842748156@qq.com\n\n感谢你的游玩!");
  69. alert.show();
  70. });
  71. infoMenu.getItems().addAll(helpMenuItem,aboutUsMenuItem);
  72. //Record菜单
  73. Menu recordMenu=new Menu("记录");//记录
  74. MenuItem historyScoreMenuItem=new MenuItem("历史分数");//历史分数
  75. historyScoreMenuItem.setOnAction(e->{
  76. Alert alert=new Alert(AlertType.INFORMATION);
  77. alert.setTitle(alert.getAlertType().toString());
  78. alert.setContentText("还没有制作喵");
  79. alert.show();
  80. });
  81. recordMenu.getItems().addAll(historyScoreMenuItem);
  82. //Score菜单
  83. scoreMenu=new Menu("分数");//分数
  84. MenuItem moreScoreInfo=new MenuItem("更多分数信息");//更多分数信息
  85. moreScoreInfo.setOnAction(e->mCallbacks.afterGetMoreScoreInfo());
  86. scoreMenu.getItems().addAll(moreScoreInfo);
  87. getMenus().addAll(gameMenu,settingMenu,infoMenu,recordMenu,scoreMenu);
  88. }
  89. /**获取分数菜单*/
  90. public Menu getScoreMenu() {
  91. return scoreMenu;
  92. }
  93. }

_2048Demo:

  1. import _2048._node._CardMatrixPane;
  2. import _2048._node._GameMenuBar;
  3. import javafx.application.Application;
  4. import javafx.geometry.Insets;
  5. import javafx.scene.Scene;
  6. import javafx.scene.control.Alert;
  7. import javafx.scene.control.Alert.AlertType;
  8. import javafx.scene.layout.BorderPane;
  9. import javafx.stage.Stage;
  10. /**
  11. * 2048运行类
  12. * @author 邦邦拒绝魔抗
  13. *
  14. */
  15. public class _2048Demo extends Application implements _GameMenuBar.Callbacks,_CardMatrixPane.Callbacks {
  16. private BorderPane borderPane;
  17. private _GameMenuBar menuBar;
  18. private _CardMatrixPane cardMatrixPane;
  19. @Override
  20. public void start(Stage primaryStage) {
  21. borderPane=new BorderPane();
  22. Scene scene=new Scene(borderPane,1000,600);
  23. //Top菜单栏
  24. menuBar=new _GameMenuBar(this);//创建菜单栏,并传入Application供调用
  25. borderPane.setTop(menuBar);//顶部
  26. //Center2048卡片矩阵
  27. cardMatrixPane=new _CardMatrixPane(this);
  28. cardMatrixPane.setPadding(new Insets(5,5,5,5));//外边距
  29. borderPane.setCenter(cardMatrixPane);//中心
  30. primaryStage.setTitle("2048");
  31. primaryStage.setScene(scene);
  32. primaryStage.show();
  33. startGame();
  34. // cardMatrixPane.testColors();//颜色测试
  35. }
  36. public static void main(String[] args) {
  37. Application.launch(args);
  38. }
  39. /**开始游戏*/
  40. private void startGame() {
  41. cardMatrixPane.requestFocus();//添加焦点
  42. cardMatrixPane.createKeyListener();//添加键盘监听
  43. afterScoreChange();
  44. }
  45. @Override
  46. public void afterRestart() {
  47. cardMatrixPane.restartMatrix();
  48. }
  49. @Override
  50. public void afterResetGridSize(int cols,int rows) {
  51. cardMatrixPane=new _CardMatrixPane(cols,rows,this);
  52. cardMatrixPane.setPadding(new Insets(5,5,5,5));//外边距
  53. borderPane.setCenter(cardMatrixPane);
  54. startGame();
  55. // cardMatrixPane.testColors();//颜色测试
  56. }
  57. @Override
  58. public void afterScoreChange() {
  59. menuBar.getScoreMenu().setText("分数: "+cardMatrixPane.getScore());
  60. }
  61. @Override
  62. public void afterGetMoreScoreInfo() {
  63. int[] temp=cardMatrixPane.getMcQuantities();
  64. Alert alert=new Alert(AlertType.INFORMATION);
  65. alert.setTitle(alert.getAlertType().toString());
  66. alert.setContentText(
  67. "4的合并次数: "+temp[0]+"\n"+
  68. "8的合并次数: "+temp[1]+"\n"+
  69. "16的合并次数: "+temp[2]+"\n"+
  70. "32的合并次数: "+temp[3]+"\n"+
  71. "64的合并次数: "+temp[4]+"\n"+
  72. "128的合并次数: "+temp[5]+"\n"+
  73. "256的合并次数: "+temp[6]+"\n"+
  74. "512的合并次数: "+temp[7]+"\n"+
  75. "1024的合并次数: "+temp[8]+"\n"+
  76. "2048的合并次数: "+temp[9]+"\n"+
  77. "4096的合并次数: "+temp[10]+"\n"+
  78. "8192的合并次数: "+temp[11]+"\n"+
  79. "16384的合并次数: "+temp[12]+"\n"+
  80. "32768的合并次数: "+temp[13]+"\n"+
  81. "65536的合并次数: "+temp[14]+"\n");
  82. alert.show();
  83. }
  84. }

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

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

基本思路:

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

卡片:

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

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

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

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

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

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

卡片矩阵:

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

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

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

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

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

颜色:

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

游戏菜单:

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

控制器:

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


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


更新:最近在对代码进行重构,并尝试加入ai,完成之后会补充到这里(也可能另外写一篇)

……

更新:补图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/191956
推荐阅读
相关标签
  

闽ICP备14008679号