目录
游戏介绍
游戏玩法
全代码
(1)Main类:
(2)GamePanel类
(3)Card类
(4)GameFrame类
项目设计思路
1.绘制一个窗口
2.创建菜单
3.创建所有空白卡片
(1)我们先写一个(Card)类定义卡片
(2)然后再创建卡片
4.随机创建一个卡片 (2或者4)
5.键盘事件监听 (上、下、左、右键监听)
6.根据键盘的方向,处理数字的移动合并
7.加入成功、失败判定
游戏介绍
《2048》 是一款比较流行的数字游戏,最早于2014年3月20日发行。原版2048首先在GitHub上发布,原作者是Gabriele Cirulli,后被移植到各个平台。
游戏玩法
每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。
全代码
(1)Main类:
public class Main { public static void main(String[] args) { GameFrame frame = new GameFrame(); GamePanel panel = new GamePanel(frame); frame.add(panel); frame.setVisible(true); } }
(2)GamePanel类
import com.sun.rowset.internal.Row; import javax.swing.*; import javax.swing.plaf.FontUIResource; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.Random; public class GamePanel extends JPanel implements ActionListener { private JFrame frame = null; private GamePanel panel = null; private static final int RowS = 4; private static final int COLS = 4; private Card[][] cards = new Card[RowS][COLS]; private String gameFalg = "start"; public GamePanel(JFrame frame){ this.setLayout(null); this.setOpaque(false); this.frame = frame; this.panel = this; //创建菜单 createMenu(); //创建卡片 createCard(); //随机一个卡片 createRandomNum(); //创建键盘监听 createKeyLinsener(); } //创建键盘监听 private void createKeyLinsener() { KeyAdapter keyAdapter = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if(!"start".equals(gameFalg)) { return; } int key = e.getKeyCode(); switch (key){ //上 case KeyEvent.VK_UP: case KeyEvent.VK_W: moveCard(1); break; //友 case KeyEvent.VK_RIGHT: case KeyEvent.VK_D: moveCard(2); break; //下 case KeyEvent.VK_DOWN: case KeyEvent.VK_S: moveCard(3); break; //左 case KeyEvent.VK_LEFT: case KeyEvent.VK_A: moveCard(4); break; } } }; frame.addKeyListener(keyAdapter); } //按方向移动卡片 private void moveCard(int i) { //清理卡片的合并标记 clearCard(); if(i == 1) { moveCardTop(); }else if(i == 2) { moveCardRight(); }else if(i == 3) { moveCardBotton(); }else if(i == 4) { moveCardLeft(); } //新创建卡片 createRandomNum(); //重绘画布 repaint(); //判断游戏是否结束 gameOverNot(); } //判断游戏是否结束 private void gameOverNot() { if(isWin()) { gameWin(); }else if(cardIsFull()) { //位置满 //if(moveCardTop(false)|| moveCardBotton(false) || moveCardLeft(false) || moveCardRight(false)) { // return; //}else { //游戏失败 gameOver(); //} } } private void gameWin() { gameFalg = "end"; UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); JOptionPane.showMessageDialog(frame,"你成功了,太棒啦!"); } private void gameOver() { gameFalg = "end"; UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); JOptionPane.showMessageDialog(frame,"失败了,再接再厉!"); } private boolean isWin() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() == 64) { return true; } } } return false; } //清理卡片的合并标记 private void clearCard() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; card.setMerge(false); } } } private void moveCardTop() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveTop(cards); } } } } private void moveCardBotton() { Card card; for (int i = RowS-2; i >= 0; i--) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveDown(cards); } } } } private void moveCardLeft() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 1; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveLeft(cards); } } } } private void moveCardRight() { Card card; for (int i = 0; i < RowS; i++) { for (int j = COLS-2; j >= 0; j--) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveRight(cards); } } } } //随机一个卡片 private void createRandomNum() { int num = 0; Random random = new Random(); int index = random.nextInt(5)+1; if(index == 1) { num = 4; }else { num = 2; } //如果格子满了,不取了 if(cardIsFull()) { return; } //取到卡片 Card card = getRandomCard(random); //设置卡片数字 if(card != null) { card.setNum(num); } } private boolean cardIsFull() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() == 0) { return false; } } } return true; } private Card getRandomCard(Random random) { int i = random.nextInt(RowS); int j = random.nextInt(COLS); Card card = cards[i][j]; if(card.getNum() == 0) { //空卡片返回 return card; } //没找到递归,继续找 return getRandomCard(random); } //创建卡片 private void createCard() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = new Card(i, j); cards[i][j] = card; } } } @Override public void paint(Graphics g) { super.paint(g); //绘制卡片 drawCard(g); } //绘制卡片 private void drawCard(Graphics g) { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; card.draw(g); } } } //创建字体 private Font createFont() { return new Font("思源宋体",Font.BOLD,18); } //创建菜单 private void createMenu() { //创建JMenuBar JMenuBar jmb = new JMenuBar(); //创建字体 Font font = createFont(); JMenu jMenu1 = new JMenu("游戏"); //创建子项 JMenuItem jMenuItem1 = new JMenuItem("新游戏"); JMenuItem jMenuItem2 = new JMenuItem("退出"); //添加子项 jMenu1.add(jMenuItem1); jMenu1.add(jMenuItem2); //使用上面的字体 jMenu1.setFont(font); jMenuItem1.setFont(font); jMenuItem2.setFont(font); JMenu jMenu2 = new JMenu("帮助"); //创建子项 JMenuItem jMenuItem3 = new JMenuItem("操作帮助"); JMenuItem jMenuItem4 = new JMenuItem("胜利条件"); //添加子项 jMenu2.add(jMenuItem3); jMenu2.add(jMenuItem4); //使用上面的字体 jMenu2.setFont(font); jMenuItem3.setFont(font); jMenuItem4.setFont(font); jmb.add(jMenu1); jmb.add(jMenu2); frame.setJMenuBar(jmb); //添加时间鉴定 jMenuItem1.addActionListener(this); jMenuItem2.addActionListener(this); jMenuItem3.addActionListener(this); jMenuItem4.addActionListener(this); //设置指令 jMenuItem1.setActionCommand("restart"); jMenuItem2.setActionCommand("exit"); jMenuItem3.setActionCommand("help"); jMenuItem4.setActionCommand("win"); } @Override public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if("restart".equals(command)) { System.out.println("新游戏"); restart(); }else if("exit".equals(command)) { System.out.println("退出"); Object[] optioons = {"确定","取消"}; int ret = JOptionPane.showConfirmDialog(this,"你确定要退出游戏吗?", "", JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE,null); System.exit(ret); }else if("help".equals(command)) { System.out.println("帮助"); JOptionPane.showConfirmDialog(null,"通过键盘上下左右来移动,相同数字会合并!!!", "提示", JOptionPane.INFORMATION_MESSAGE); }else if("win".equals(command)) { System.out.println("胜利"); JOptionPane.showConfirmDialog(null,"数字到2048会胜利,当没有空位时则失败!!!", "提示", JOptionPane.INFORMATION_MESSAGE); } } private void restart() { } }
(3)Card类
import java.awt.*; public class Card { private int x = 0;//x坐标 private int y = 0;//y坐标 private int high = 85;//高 private int width = 85;//宽 private int i = 0;//下标 private int j = 0;//下标 private int start = 0;//偏移量 private int num = 0;//数字 private boolean merge = false;//是否合并 Card(int i, int j) { this.i = i; this.j = j; cal(); } private void cal() { this.x = start + j*width + (j+1)*5; this.y = start + i*high + (i+1)*5; } //卡片的绘制 public void draw(Graphics g) { Color color = getColor(); Color oColor = g.getColor(); //设置新颜色 g.setColor(color); g.fillRoundRect(x, y, width, high, 4, 4); //绘制数字 if(num != 0) { g.setColor(new Color(125,78,51)); Font font = new Font("思源宋体",Font.BOLD,35); g.setFont(font); String text = num + ""; int textLen = getWordWidth(font,text,g); int tx = x + (width-textLen)/2; int ty = y + 50; g.drawString(text,tx,ty); } //还原颜色 g.setColor(oColor); } //得到字体字符串长度 public static int getWordWidth(Font font, String conten, Graphics g) { FontMetrics metrics = g.getFontMetrics(font); int width = 0; for (int i = 0; i < conten.length(); i++) { //width += metrics.charsWidth(conten.charAt(i)); } return width; } //获取颜色 private Color getColor(){ Color color = null; //根据num设置颜色 switch (num) { case 2: color = new Color(238,244,234); break; case 4: color = new Color(222,236,200); break; case 8: color = new Color(174,231,130); break; case 16: color = new Color(142,201,75); break; case 32: color = new Color(111,148,48); break; case 64: color = new Color(76,174,124); break; default: color = new Color(92,151,117); break; } return color; } public void setNum(int num) { this.num = num; } public int getNum() { return this.num; } public void setMerge(boolean merge) { this.merge = merge; } //向上移动 public void moveTop(Card[][] cards) { //设定递归退出条件 if(i == 0) { return; } //上一个卡片 Card prev = cards[i-1][j]; if(prev.getNum() == 0) { //交换上去 prev.num = this.num; this.num = 0; //移到最上 prev.moveTop(cards); }else if(prev.getNum() == num && !prev.merge){ //合并 prev.merge = true; prev.num = this.num * 2; this.num = 0; } } //向下移动 public void moveDown(Card[][] cards) { //设定递归退出条件 if(i == 3) { return; } //上一个卡片 Card prev = cards[i+1][j]; if(prev.getNum() == 0) { //交换上去 prev.num = this.num; this.num = 0; //移到最上 prev.moveDown(cards); }else if(prev.getNum() == num && !prev.merge){ //合并 prev.merge = true; prev.num = this.num * 2; this.num = 0; } } //向左移动 public void moveLeft(Card[][] cards) { //设定递归退出条件 if(j == 0) { return; } //上一个卡片 Card prev = cards[i][j-1]; if(prev.getNum() == 0) { //交换上去 prev.num = this.num; this.num = 0; //移到最上 prev.moveLeft(cards); }else if(prev.getNum() == num && !prev.merge){ //合并 prev.merge = true; prev.num = this.num * 2; this.num = 0; } } //向右移动 public void moveRight(Card[][] cards) { //设定递归退出条件 if(j == 3) { return; } //上一个卡片 Card prev = cards[i][j+1]; if(prev.getNum() == 0) { //交换上去 prev.num = this.num; this.num = 0; //移到最上 prev.moveRight(cards); }else if(prev.getNum() == num && !prev.merge){ //合并 prev.merge = true; prev.num = this.num * 2; this.num = 0; } } }
(4)GameFrame类
import javax.swing.*; import java.awt.*; public class GameFrame extends JFrame { public GameFrame(){ setTitle("2028"); setSize(370,420); getContentPane().setBackground(new Color(66, 136, 83));//设置窗口背景 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭后进程退出 setLocationRelativeTo(null);//居中 setResizable(false);//大小不可变 } }
项目设计思路
1.绘制一个窗口
我们可以先给这个项目一个默认的窗口,小编的供大家参考,大家可以根据爱好自行设计
我们java中绘制窗口的时候要用到Jframe,以便利用和main函数代码简洁,这里 我们写一个类专门用来绘制窗口GameFrame(继承与JFrame)
import javax.swing.*; import java.awt.*; public class GameFrame extends JFrame { public GameFrame(){ setTitle("2028"); setSize(370,420); getContentPane().setBackground(new Color(66, 136, 83));//设置窗口背景 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭后进程退出 setLocationRelativeTo(null);//居中 setResizable(false);//大小不可变 } }
2.创建菜单
创建菜单的时候要用到JPanel,在这里我们专门写一个类GamePanel用来创建菜单(可以参考上面的总代买进行参考)
从下面图片可知 我们菜单主要有 游戏(中有新游戏和退出) 和 帮助(中有操作帮助和胜利条件) 。
//创建字体 private Font createFont() { return new Font("思源宋体",Font.BOLD,18); } //创建菜单 private void createMenu() { //创建JMenuBar JMenuBar jmb = new JMenuBar(); //创建字体 Font font = createFont(); JMenu jMenu1 = new JMenu("游戏"); //创建子项 JMenuItem jMenuItem1 = new JMenuItem("新游戏"); JMenuItem jMenuItem2 = new JMenuItem("退出"); //添加子项 jMenu1.add(jMenuItem1); jMenu1.add(jMenuItem2); //使用上面的字体 jMenu1.setFont(font); jMenuItem1.setFont(font); jMenuItem2.setFont(font); JMenu jMenu2 = new JMenu("帮助"); //创建子项 JMenuItem jMenuItem3 = new JMenuItem("操作帮助"); JMenuItem jMenuItem4 = new JMenuItem("胜利条件"); //添加子项 jMenu2.add(jMenuItem3); jMenu2.add(jMenuItem4); //使用上面的字体 jMenu2.setFont(font); jMenuItem3.setFont(font); jMenuItem4.setFont(font); jmb.add(jMenu1); jmb.add(jMenu2); frame.setJMenuBar(jmb); //添加时间鉴定 jMenuItem1.addActionListener(this); jMenuItem2.addActionListener(this); jMenuItem3.addActionListener(this); jMenuItem4.addActionListener(this); //设置指令 jMenuItem1.setActionCommand("restart"); jMenuItem2.setActionCommand("exit"); jMenuItem3.setActionCommand("help"); jMenuItem4.setActionCommand("win"); } 3.创建所有空白卡片 (1)我们先写一个(Card)类定义卡片 public class Card { private int x = 0;//x坐标 private int y = 0;//y坐标 private int high = 85;//高 private int width = 85;//宽 private int i = 0;//下标 private int j = 0;//下标 private int start = 0;//偏移量 private int num = 0;//数字 private boolean merge = false;//是否合并 Card(int i, int j) { this.i = i; this.j = j; cal(); } private void cal() { this.x = start + j*width + (j+1)*5; this.y = start + i*high + (i+1)*5; } //卡片的绘制 public void draw(Graphics g) { Color color = getColor(); Color oColor = g.getColor(); //设置新颜色 g.setColor(color); g.fillRoundRect(x, y, width, high, 4, 4); //绘制数字 if(num != 0) { g.setColor(new Color(125,78,51)); Font font = new Font("思源宋体",Font.BOLD,35); g.setFont(font); String text = num + ""; int textLen = getWordWidth(font,text,g); int tx = x + (width-textLen)/2; int ty = y + 50; g.drawString(text,tx,ty); } //还原颜色 g.setColor(oColor); } //得到字体字符串长度 public static int getWordWidth(Font font, String conten, Graphics g) { FontMetrics metrics = g.getFontMetrics(font); int width = 0; for (int i = 0; i < conten.length(); i++) { //width += metrics.charsWidth(conten.charAt(i)); } return width; } //获取颜色 private Color getColor(){ Color color = null; //根据num设置颜色 switch (num) { case 2: color = new Color(238,244,234); break; case 4: color = new Color(222,236,200); break; case 8: color = new Color(174,231,130); break; case 16: color = new Color(142,201,75); break; case 32: color = new Color(111,148,48); break; case 64: color = new Color(76,174,124); break; default: color = new Color(92,151,117); break; } return color; } public void setNum(int num) { this.num = num; } public int getNum() { return this.num; } public void setMerge(boolean merge) { this.merge = merge; } }
(2)然后再创建卡片
//创建卡片 private void createCard() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = new Card(i, j); cards[i][j] = card; } } }
4.随机创建一个卡片 (2或者4)
在窗口中创建卡片后,我们想让卡片上生成2,4随机数字
//随机一个卡片 private void createRandomNum() { int num = 0; Random random = new Random(); int index = random.nextInt(5)+1; if(index == 1) { num = 4; }else { num = 2; } //如果格子满了,不取了 if(cardIsFull()) { return; } //取到卡片 Card card = getRandomCard(random); //设置卡片数字 if(card != null) { card.setNum(num); } } private boolean cardIsFull() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() == 0) { return false; } } } return true; } private Card getRandomCard(Random random) { int i = random.nextInt(RowS); int j = random.nextInt(COLS); Card card = cards[i][j]; if(card.getNum() == 0) { //空卡片返回 return card; } //没找到递归,继续找 return getRandomCard(random); }
5.键盘事件监听 (上、下、左、右键监听)
//创建键盘监听 private void createKeyLinsener() { KeyAdapter keyAdapter = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if(!"start".equals(gameFalg)) { return; } int key = e.getKeyCode(); switch (key){ //上 case KeyEvent.VK_UP: case KeyEvent.VK_W: moveCard(1); break; //友 case KeyEvent.VK_RIGHT: case KeyEvent.VK_D: moveCard(2); break; //下 case KeyEvent.VK_DOWN: case KeyEvent.VK_S: moveCard(3); break; //左 case KeyEvent.VK_LEFT: case KeyEvent.VK_A: moveCard(4); break; } } }; frame.addKeyListener(keyAdapter); }
6.根据键盘的方向,处理数字的移动合并
//按方向移动卡片 private void moveCard(int i) { //清理卡片的合并标记 clearCard(); if(i == 1) { moveCardTop(); }else if(i == 2) { moveCardRight(); }else if(i == 3) { moveCardBotton(); }else if(i == 4) { moveCardLeft(); } } private void moveCardTop() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveTop(cards); } } } } private void moveCardBotton() { Card card; for (int i = RowS-2; i >= 0; i--) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveDown(cards); } } } } private void moveCardLeft() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 1; j < COLS; j++) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveLeft(cards); } } } } private void moveCardRight() { Card card; for (int i = 0; i < RowS; i++) { for (int j = COLS-2; j >= 0; j--) { card = cards[i][j]; if(card.getNum() != 0) { //不是空白就移动 card.moveRight(cards); } } } }
7.加入成功、失败判定
//判断游戏是否结束 private void gameOverNot() { if(isWin()) { gameWin(); }else if(cardIsFull()) { //位置满 //if(moveCardTop(false)|| moveCardBotton(false) || moveCardLeft(false) || moveCardRight(false)) { // return; //}else { //游戏失败 gameOver(); //} } } private void gameWin() { gameFalg = "end"; UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); JOptionPane.showMessageDialog(frame,"你成功了,太棒啦!"); } private void gameOver() { gameFalg = "end"; UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); UIManager.put("Optionpane.buttonFont",new FontUIResource(new Font("思源宋体",Font.ITALIC,18))); JOptionPane.showMessageDialog(frame,"失败了,再接再厉!"); } private boolean isWin() { Card card; for (int i = 0; i < RowS; i++) { for (int j = 0; j < COLS; j++) { card = cards[i][j]; if(card.getNum() == 64) { return true; } } } return false; }