前言
2014年Gabriele Cirulli利用周末的时间写2048这个游戏的程序,仅仅只是好玩而已。他想用一种不同的视觉展现效果和更快速的动画来创造属于自己的游戏版本。
游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想。
主要需求
每次控制所有方块向同一个方向运动,两个相同数字的方块撞在一起之后合并成为他们的和,每次操作之后会在空白的方格处随机生成一个2或者4,最终得到一个“2048”的方块就算胜利了。如果16个格子全部填满并且相邻的格子都不相同也就是无法移动的话,那么游戏就会结束。
主要设计
1、游戏面板生成显示
2、方块设计
3、键盘监听,方向键控制数字移动
4、数字移动逻辑算法处理
5、数字累加到2048,游戏胜利
功能截图
游戏开始
移动效果
代码实现
界面布局类
public class Game2048View implements ActionListener{Block[] blocks;//方块JPanel myJPanel;//主面板JPanel jp1,jp2;//子面板//int moveFlag;// 用于累计移动的次数boolean numFlag;// 用于判断是否还能加入新的数字JLabel scroeValue;//显示分数public Game2048View(JFrame myJFrame){blocks=new Block[16];//moveFlag=0;numFlag=true;this.myJPanel=(JPanel)myJFrame.getContentPane();///获取内容面板setJp1();myJPanel.add(jp1,BorderLayout.NORTH);setJp2();myJPanel.add(jp2, BorderLayout.CENTER);myJFrame.addKeyListener(new Game2048Logic(this,myJFrame,blocks,numFlag,scroeValue));}public void addc(JPanel jp1,Component component, GridBagConstraints gbc,int gridwidth,int gridheight, int weightx,int weighty,int gridx,int gridy) {//此方法用来添加控件到容器中gbc.gridwidth=gridwidth;//该方法是设置组件水平所占用的格子数,如果为0,就说明该组件是该行的最后一个gbc.gridheight=gridheight;//该方法是设置组件垂直所占用的格子数gbc.weightx=weightx;//该方法设置组件水平的拉伸幅度,如果为0就说明不拉伸,不为0就随着窗口增大进行拉伸,0到1之间gbc.weighty=weighty;//该方法设置组件垂直的拉伸幅度,如果为0就说明不拉伸,不为0就随着窗口增大进行拉伸,0到1之间gbc.gridx=gridx;gbc.gridy=gridy;gbc.fill=GridBagConstraints.BOTH;jp1.add(component,gbc);}public void setJp1() {GridBagLayout gbLayout=new GridBagLayout();jp1=new JPanel(gbLayout);JPanel Jtitle=new JPanel();JLabel title=new JLabel("2048");title.setFont(new Font("font", Font.PLAIN, 45));//类型、风格、大小title.setHorizontalAlignment(JLabel.LEFT);//jLabel的文本左右对齐属性设置方法,对齐方式Jtitle.add(title);jp1.add(Jtitle);JPanel Jscroe=new JPanel(new GridLayout(2, 1));//new GridLayout(2, 1)为网格布局样式。其中的参数“2”“1”分别为网格的“行数”和“列数”。JLabel scroe=new JLabel("Scroe");scroe.setFont(new Font("font", Font.PLAIN, 16));scroe.setHorizontalAlignment(JLabel.CENTER);scroeValue=new JLabel("0");scroeValue.setFont(new Font("font", Font.PLAIN, 16));scroeValue.setHorizontalAlignment(JLabel.CENTER);Jscroe.add(scroe);Jscroe.add(scroeValue);jp1.add(Jscroe);JPanel Jnoite=new JPanel();JLabel noite=new JLabel("方向键移动数字累加至2048");noite.setFont(new Font("font", Font.PLAIN, 14));noite.setHorizontalAlignment(JLabel.LEFT);Jnoite.add(noite);jp1.add(Jnoite);JPanel JnewGame=new JPanel();JButton newGame=new JButton("New Game");newGame.setHorizontalAlignment(JButton.CENTER);newGame.addActionListener(this);JnewGame.add(newGame);jp1.add(JnewGame);GridBagConstraints gbc=new GridBagConstraints();addc(jp1, Jtitle, gbc, 3, 2, 60, 60, 0, 0);addc(jp1, Jscroe, gbc, 0, 2, 40, 60, 3, 0);addc(jp1, Jnoite, gbc, 3, 1, 60, 40, 0, 2);addc(jp1, JnewGame, gbc, 0, 1, 40, 40, 3, 2);}public void setJp2() {addBlock();initBlock();initBlock();}/** * 添加方块 */public void addBlock(){jp2=new JPanel();/* * setLayout是对当前组件设置为流式布局.组件在窗体中从左到右依次排列 如果排到行的末尾 换行排列* GridLayout(int rows, int cols, int hgap, int vgap)创建具有指定行数和列数的网格布局。rows - 该 rows 具有表示任意行数的值cols - 该 cols 具有表示任意列数的值hgap - 水平间距vgap - 垂直间距 */jp2.setLayout(new GridLayout(4, 4, 5, 5));for (int i = 0; i < blocks.length; i++) {blocks[i]=new Block();blocks[i].setHorizontalAlignment(JLabel.CENTER);// 不透明的标签blocks[i].setOpaque(true);// 设置控件不透明jp2.add(blocks[i]);}}/** * 初始化方块 */public void initBlock(){while (numFlag) {int index=(int) (Math.random()*16);if (blocks[index].getText().trim().equals("")) {blocks[index].setValue("2");break;} else {continue;}}}/** * 获得第一个子面板的高度 */public int getMyJPanelHeidth() {return jp1.getSize().height;}@Overridepublic void actionPerformed(ActionEvent e) {// TODO 自动生成的方法存根newGame();}/** * 重新开始游戏 */public void newGame() {for (int i = 0; i < blocks.length; i++) {blocks[i].setValue("");}numFlag=true;scroeValue.setText("0");initBlock();initBlock();}}
业务逻辑类
public class Game2048Logic implements KeyListener{Block[] blocks;boolean numFlag;// 用于判断是否还能加入新的数字JLabel scroeValue;//显示分数int blocksarr[]=new int[4];//保存一行/列方块中的数值JFrame myJFrame;int scroe=0;Game2048View game2048View;//初始化键盘事件public Game2048Logic(Game2048View game2048View, JFrame myJFrame, Block[] blocks,boolean numFlag,JLabel scroeValue) {// TODO 自动生成的构造函数存根this.blocks=blocks;this.numFlag=numFlag;this.scroeValue=scroeValue;this.myJFrame=myJFrame;this.game2048View=game2048View;}//初始化按钮事件public Game2048Logic() {// TODO 自动生成的构造函数存根}public boolean getnumFlag() {return numFlag;}@Overridepublic void keyPressed(KeyEvent e) {// TODO 自动生成的方法存根int[] blocksarr=getBlock();switch (e.getKeyCode()) {case KeyEvent.VK_UP:colBlock("up");hasEmptyBlock();if (Arrays.equals(blocksarr, getBlock())) {} else {refershBlock();}isGameFail("up");break;case KeyEvent.VK_DOWN:colBlock("down");hasEmptyBlock();if (Arrays.equals(blocksarr, getBlock())) {} else {refershBlock();}isGameFail("down");break;case KeyEvent.VK_LEFT:rowBlock("left");hasEmptyBlock();if (Arrays.equals(blocksarr, getBlock())) {} else {refershBlock();}isGameFail("left");break;case KeyEvent.VK_RIGHT:rowBlock("right");hasEmptyBlock();if (Arrays.equals(blocksarr, getBlock())) {} else {refershBlock();}isGameFail("right");break;default:break;}scroeValue.setText(""+scroe);win();}/** * 垂直方向方块移动处理函数 */public void colBlock(String direction){int tmp1=0;int tmp2=0;int index=0;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (blocks[tmp1].getText().trim().equals("")) {tmp1+=4;if (tmp1>=16) {break;} else {continue;}} else {blocksarr[index]=Integer.parseInt(blocks[tmp1].getText().trim());index+=1;tmp1+=4;if (tmp1>=16 || index>=4) {break;} else {continue;}}}switch (direction) {case "up":blocksarr=handleBlocksarr(blocksarr);break;case "down":blocksarr=reverseArr(handleBlocksarr(reverseArr(blocksarr)));break;default:break;}for (int n = 0; n < blocksarr.length; n++) {if (blocksarr[n]==0) {blocks[tmp2].setText("");blocks[tmp2].setBackground(Color.gray);} else {blocks[tmp2].setValue(blocksarr[n]+"");}tmp2+=4;}index=0;tmp1=i+1;tmp2=i+1;//清空数组blockarrfor (int n = 0; n < blocksarr.length; n++) {blocksarr[n]=0;}}}/** * 水平方向方块移动处理函数 */public void rowBlock(String direction) {int tmp1=0;int tmp2=0;int index=0;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (blocks[tmp1].getText().trim().equals("")) {tmp1+=1;if (tmp1>=16) {break;} else {continue;}} else {blocksarr[index]=Integer.parseInt(blocks[tmp1].getText().trim());index+=1;tmp1+=1;if (tmp1>=16 || index>=4) {break;} else {continue;}}}switch (direction) {case "left":blocksarr=handleBlocksarr(blocksarr);break;case "right":blocksarr=reverseArr(handleBlocksarr(reverseArr(blocksarr)));break;default:break;}for (int n = 0; n < blocksarr.length; n++) {if (blocksarr[n]==0) {blocks[tmp2].setText("");blocks[tmp2].setBackground(Color.gray);} else {blocks[tmp2].setValue(blocksarr[n]+"");}tmp2+=1;}index=0;//清空数组blockarrfor (int n = 0; n < blocksarr.length; n++) {blocksarr[n]=0;}}}/** * 处理并返回一个数组 */public int[] handleBlocksarr(int[] blocksarr) {int index=0;int[] result=new int[4];for (int i = 0; i < blocksarr.length; i++) {//排序if (blocksarr[i]!=0) {result[index]=blocksarr[i];index++;} }if (index==0 || index==1) {for (int i = index; i < result.length; i++) {result[i]=0;}} else {for (int i = 0; i < blocksarr.length; i++) {blocksarr[i]=0;}switch (index) {case 2:if (result[0]==result[1]) {blocksarr[0]=result[0]+result[1];scroe+=result[0]*2;} else {blocksarr=result;}break;case 3:if (result[0]==result[1]) {blocksarr[0]=result[0]+result[1];scroe+=result[0]*2;blocksarr[1]=result[2];} else {if (result[1]==result[2]) {blocksarr[0]=result[0];blocksarr[1]=result[1]+result[2];scroe+=result[1]*2;} else {blocksarr=result;}}break;case 4:if (result[0]==result[1]) {blocksarr[0]=result[0]+result[1];scroe+=result[0]*2;if (result[2]==result[3]) {blocksarr[1]=result[2]+result[3];scroe+=result[2]*2;} else {blocksarr[1]=result[2];blocksarr[2]=result[3];}} else {if (result[1]==result[2]) {blocksarr[0]=result[0];blocksarr[1]=result[1]+result[2];scroe+=result[1]*2;blocksarr[2]=result[3];} else {blocksarr[0]=result[0];blocksarr[1]=result[1];if (result[2]==result[3]) {blocksarr[2]=result[2]+result[3];scroe+=result[2]*2;} else {blocksarr=result;}}}break;default:break;}result=blocksarr;}return result;}/** * 反转数组eg:45000 --> 00054 */public int[] reverseArr(int[] arr) {int[] tmp=new int[arr.length];int index=arr.length-1;for (int i = 0; i < arr.length; i++) {tmp[index]=arr[i];index--;}return tmp;}/** * 刷新方块,添加新出现的2或4 */public void refershBlock(){if (numFlag==false) {}while (numFlag) {int index=(int) (Math.random()*16);if (blocks[index].getText().trim().equals("")) {if (Math.random()<0.8) {blocks[index].setValue("2");} else {blocks[index].setValue("4");}break;} else {continue;}}}/** * 判断是否有空的方块 */public void hasEmptyBlock() {for (int i = 0; i < blocks.length; i++) {if (blocks[i].getText().trim().equals("")) {this.numFlag=true;break;} else {this.numFlag=false;}}}/** * 判断游戏是否失败 */public void isGameFail(String direction) {boolean result=true;//true代表失败 false代表没有失败int tmp=0;if (numFlag == false) { // 表示没有空的方块switch (direction) {case "up":case "down":for (int i = 0; i < 4; i++) {tmp=i*4;for (int j = 0; j < 3; j++) {if (blocks[tmp].getText().trim().equals(blocks[tmp+1].getText().trim())) {result = false;//游戏未失败break;} else {tmp++;}}if (result==false) {break;}}break;case "left":case "right":for (int i = 0; i < 4; i++) {for (int j = 0; j < 3; j++) {if (blocks[tmp].getText().trim().equals(blocks[tmp+4].getText().trim())) {result = false;//游戏未失败break;} else {tmp+=4;if (tmp>=16) {break;} else {continue;}}}tmp=i+1;if (result==false) {break;}}break;default:break;}} else {result=false;}if (result==true) {JOptionPane.showMessageDialog( null , "Game Over",null , JOptionPane.ERROR_MESSAGE) ;game2048View.newGame();} else {}}/** * 判断游戏是否成功,即成功累加至2048 */public void win() {for (int i = 0; i < blocks.length; i++) {if (blocks[i].getText().trim().equals("2048")) {JOptionPane.showMessageDialog( null , "YOU ARE WIN",null , JOptionPane.ERROR_MESSAGE) ;game2048View.newGame();break;} }}/** * 获得全部方块内容,用于对比 */public int[] getBlock() {int[] blocksarr=new int[16];for (int i = 0; i < blocks.length; i++) {if (blocks[i].getText().trim().equals("")) {blocksarr[i]=0;} else {blocksarr[i]=Integer.parseInt(blocks[i].getText().trim());}}return blocksarr;}@Overridepublic void keyReleased(KeyEvent e) {// TODO 自动生成的方法存根}@Overridepublic void keyTyped(KeyEvent e) {// TODO 自动生成的方法存根}}
总结
通过此次的《2048游戏》游戏实现,让我对swing的相关知识有了进一步的了解,对java这门语言也有了比以前更深刻的认识。
java的一些基本语法,比如数据类型、运算符、程序流程控制和数组等,理解更加透彻。java最核心的核心就是面向对象思想,对于这一个概念,终于悟到了一些。
源码获取
源码下载地址:传送门–>
点赞,关注博主后,私聊博主免费获取
需要技术指导,写项目程序,等更多服务请联系博主
今天是持续写作的第 14 / 100 天。
可以关注我,点赞我、评论我、收藏我啦。