目录
游戏介绍
游戏玩法
全代码
(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;
}