很久之前写的一个2048小游戏,最开始没考虑动画,动画后来加上去的,导致代码有点乱。
到2048分就赢,想多加点把数组扩大就行。
截图:
4个类:
Start--------------入口
MainFrame-----主类
BlockData-------动画相关
Direct------------方向
代码:
类Start:
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.UIManager;
public class Start {
public static void main(String[] args) {
// 使用Windows的界面风格
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
MainFrame mf = new MainFrame();
mf.setVisible(true);
try {
mf.getGraphics().drawImage(ImageIO.read(Start.class.getResource("logo.png")), 10, 35, 295, 370, null);
} catch (IOException e) {e.printStackTrace();}
}
}
类MainFrame:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* 游戏主界面
* @author kyda
*/
public class MainFrame extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
// 方块大小
private static final int PER_PIECE_SIZE = 70;
// 方块间距
private static final int BORDER_SIZE = 10;
// 方块显示起始坐标
private static final int GAMEAREA_X = 10, GAMEAREA_Y = 90;
// 最大阶数,根据屏幕大小设定
private static final int MAX_PIECES_DEGREE = (Toolkit.getDefaultToolkit().getScreenSize().height - 250) / PER_PIECE_SIZE;
// 方块移动次数
private static final int MOVE_TIMES = 20;
// 分数移动次数
private static final int SCORE_MOVE_TIMES = 20;
// 方块闪烁次数
private static final int FLASH_TIMES = 10;
// 所有方块数值和背景颜色
private static final int BLOCKS[][] = {{0, 0xffccc0b4}, {2, 0xffeee4da}, {4, 0xffede0c8},
{8, 0xfff2b179}, {16, 0xfff59563}, {32, 0xfff67c5f},
{64, 0xfff65e3b}, {128, 0xffedcf72}, {256, 0xffedcc61},
{512, 0xffedc850}, {1024, 0xffedc53f}, {2048, 0xffedc22e}};
// 字体名
private static final String FONTNAME = "Arial";
// 圆角半径
private static final int RADIUS = 4;
private GamePanel gamePanel;
private JPanel configPanel;
private JTextField sizeTF;
private JButton startBtn;
private JLabel stepLabel;
private int block[][]; // 方块
private int bk[][]; // 方块
private int rows; // 阶数
private boolean isMoved; // 是否有方块移动过
private int steps; // 游戏步数
private int scores; // 游戏分数
private int isWin; // 输赢状态,-1-输,0-可继续移动,1-赢
private Thread t1, t2, t3; // 三种线程,移动,闪烁,分数
private boolean t1Alive, t2Alive, t3Alive; // 三种线程是否活着
private boolean merge[]; // 储存是否合并至数组下标位置的方块,闪烁用
private int currentScore, currentScoreY; // 分数和Y坐标
private int currentSize; // 方块大小,闪烁用
private Random rand = new Random(System.currentTimeMillis());
private List list; // 方块移动信息
public MainFrame() {
setTitle("2048 by kyda");
setSize(315, 450);
//setLayout(null);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addMouseListener(new GestureListener());
gamePanel = new GamePanel();
gamePanel.addKeyListener(new GameKeyListener());
add(gamePanel, BorderLayout.CENTER);
configPanel = new JPanel();
add(configPanel, BorderLayout.SOUTH);
sizeTF = new JTextField("4");
sizeTF.setColumns(6);
sizeTF.addKeyListener(new SizeTFListener());
configPanel.add(sizeTF);
startBtn = new JButton("开始");
startBtn.addActionListener(this);
configPanel.add(startBtn);
stepLabel = new JLabel();
configPanel.add(stepLabel);
}
// 初始化方块数值
public void init() {
block = new int[rows][];
for (int row = 0; row < rows; row++) {
block[row] = new int[rows];
}
createBlock(2);
}
// 随机在空白区域创建方块,如果有空格
public void createBlock() {
int r = 0;
boolean hasBlank = false;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < rows; col++) {
if (block[row][col] == 0) {
hasBlank = true;
row = rows;
break;
}
}
}
if (!hasBlank) return;
do {
r = Math.abs(rand.nextInt() % (rows * rows));
} while (block[r / rows][r % rows] != 0);
block[r / rows][r % rows] = Math.abs(rand.nextInt() % 2) + 1;
}
// 创建指定数目的方块
public void createBlock(int count) {
while (count-- > 0) {
createBlock();
}
}
// 点击开始按钮
public void start() {
rows = 0;
try {
rows = Integer.valueOf(sizeTF.getText().trim());
if (rows < 2) {
JOptionPane.showMessageDialog(null, "大点行么?", "干", JOptionPane.INFORMATION_MESSAGE);
sizeTF.setText("");
sizeTF.requestFocus();
return;
} else if (rows > MAX_PIECES_DEGREE) {
JOptionPane.showMessageDialog(null, "小点行么,你屏幕放的下?", "干", JOptionPane.INFORMATION_MESSAGE);
sizeTF.setText("");
sizeTF.requestFocus();
return;
}
} catch (Exception e1) {
JOptionPane.showMessageDialog(null, "不要乱输入,OK?", "无语", JOptionPane.ERROR_MESSAGE);
sizeTF.setText("");
sizeTF.requestFocus();
return;
}
init();
int width = PER_PIECE_SIZE * rows + transform(GAMEAREA_X) + 10;
int height = PER_PIECE_SIZE * rows + transform(GAMEAREA_Y) + 10;
gamePanel.setSize(width, height);
steps = 0;
scores = 0;
isWin = 0;
t1Alive = false;
t2Alive = false;
t3Alive = false;
stepLabel.setText("");
setSize(width + transform(GAMEAREA_X + 5), height + 70);
repaint();
setLocationRelativeTo(null);
gamePanel.repaint();
gamePanel.requestFocus();
merge = new boolean[rows * rows];
}
@Override
public void actionPerformed(ActionEvent e) {
start();
}
// 按照方向移动方块,先将数组行列转成按上方向移动
public void moveBlocks(Direct direct) {
switch (direct) {
case UP:
isMoved = moveByUpDirect(Direct.UP);
break;
case DOWN:
reverse();
isMoved = moveByUpDirect(Direct.DOWN);
reverse();
break;
case LEFT:
transpose();
isMoved = moveByUpDirect(Direct.LEFT);
transpose();
break;
case RIGHT:
transpose();
reverse();
isMoved = moveByUpDirect(Direct.RIGHT);
reverse();
transpose();
break;
default:
break;
}
if (isMoved) {
createBlock();
steps++;
stepLabel.setText("步数:" + steps);
}
}
// 将转换后的数组按照上方向移动(由于动画信息是后来加的,比较混乱)
public boolean moveByUpDirect(Direct direct) {
int index;
int r, c;
for (int col = 0; col < rows; col++) {
index = 0;
boolean hasNext = false, isFirst = true;
if (block[0][col] != 0) {
for (int row = 1; row < rows; row++) {
if (block[row][col] != 0) {
hasNext = true;
break;
}
}
if (!hasNext) {
list.add(calculate(0, col, 0, col, block[0][col], direct));
}
}
for (int row = 1; row < rows; row++) {
if (block[row][col] == 0) continue;
if (block[index][col] == block[row][col]) {
if (isFirst) {
list.add(calculate(index, col, index, col, block[index][col], direct));
isFirst = false;
}
scores += BLOCKS[block[row][col] + 1][0];
currentScore += BLOCKS[block[row][col] + 1][0];
list.add(calculate(row, col, index, col, block[index][col], direct));
block[index][col]++;
block[row][col] = 0;
if (!isMoved) isMoved = true;
r = index;
c = col;
switch (direct) {
case DOWN:
r = rows - index - 1;
case UP:
break;
case RIGHT:
r = rows - index - 1;
case LEFT:
int tmp = r;
r = c;
c = tmp;
break;
}
merge[r * rows + c] = true;
index++;
} else {
if (block[index][col] != 0) {
if (isFirst)
list.add(calculate(index, col, index, col, block[index][col], direct));
index++;
}
block[index][col] = block[row][col];
list.add(calculate(row, col, index, col, block[index][col], direct));
isFirst = false;
if (index != row) {
block[row][col] = 0;
if (!isMoved) isMoved = true;
}
}
}
for (int row = 0; row < rows; row++) {
if (block[row][col] == 0) {
list.add(calculate(row, col, row, col, 0, direct));
}
}
}
return isMoved;
}
// 将移动信息从上方向转回原方向
public BlockData calculate(int startRow, int startCol, int endRow, int endCol, int data, Direct direct) {
BlockData bd = new BlockData(startRow, startCol, data, direct, 0);
int tmp;
switch (direct) {
case DOWN:
bd.row = rows - bd.row - 1;
case UP:
bd.distance = Math.abs(endRow - startRow);
//System.out.println("add:" + bd.row + " " + bd.col + " " + bd.speed + " " + endRow + " " + endCol);
break;
case RIGHT:
tmp= bd.row;
bd.row = bd.col;
bd.col = rows - tmp - 1;
bd.distance = Math.abs(endRow - startRow);
break;
case LEFT:
tmp = bd.row;
bd.row = bd.col;
bd.col = tmp;
bd.distance = Math.abs(endRow - startRow);
break;
default:
break;
}
return bd;
}
// 将方块每列逆转
public void reverse() {
int tmp;
for (int col = 0; col < rows; col++) {
for (int row = 0; row < rows / 2; row++) {
tmp = block[rows - row - 1][col];
block[rows - row - 1][col] = block[row][col];
block[row][col] = tmp;
}
}
}
// 对所有方块行列置换
public void transpose() {
int tmp;
for (int col = 0; col < rows; col++) {
for (int row = col; row < rows; row++) {
tmp = block[col][row];
block[col][row] = block[row][col];
block[row][col] = tmp;
}
}
}
// 复制二维数组
public int[][] copy(int[][] src) {
int len = src.length;
int[][] ret = new int[len][];
for (int i = 0; i < len; i++) {
ret[i] = new int[src[0].length];
System.arraycopy(src, 0, ret, 0, src[0].length);
}
return ret;
}
// 判断游戏输赢
public int win() {
boolean canMove = false;
if (isWin == 1) return 1;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < rows; col++) {
if (block[row][col] == BLOCKS.length - 1) { // 达到2048游戏结束
isWin = 1;
return 1;
}
if (!canMove && block[row][col] == 0) {
canMove = true;
}
}
}
if (!canMove) { // 没有空格(分开判断节约时间)
for (int row = 0; row < rows; row++) {
for (int col = 0; col < rows; col++) {
// System.out.println(row +" " + col);
canMove = canMove(row, col);
if (canMove) {
row = rows;
break;
}
}
}
}
if (canMove) {
isWin = 0;
return 0;
}
isWin = -1;
return -1;
}
// 在没有空格的情况下,判断当前方块是否可以移动
public boolean canMove(int row, int col) {
if (row > 0 && block[row - 1][col] == block[row][col])
return true;
if (row < rows - 1 && block[row + 1][col] == block[row][col])
return true;
if (col > 0 && block[row][col - 1] == block[row][col])
return true;
if (col < rows - 1 && block[row][col + 1] == block[row][col])
return true;
return false;
}
/** 计算缩放后的坐标,适应当前界面大小 */
public int transform(int src) {
if (rows == 4) return src;
return src * rows / 4;
}
// 按方向移动
public void gameMove(Direct direct) {
if (isWin != 0 || block == null) return;
bk = copy(block);
list = new LinkedList<>();
Arrays.fill(merge, false);
isMoved = false;
currentScore = 0;
currentScoreY = transform(60);
moveBlocks(direct);
win();
//gamePanel.repaint();
if (isMoved) {
/*if (t1 != null) {
t1Alive = false;
stopThread(t1);
//t1.join();
}*/
if (t1 != null && t1.isAlive()) t1.stop(); // 让线程自动停止动画效果不理想,只能想到这么干了。。
t1 = new MoveThread();
t1.start();
}
if (isMoved) {
if (t2 != null && t2.isAlive()) t2.stop();
t2 = new FlashThread();
t2.start();
}
if (currentScore != 0) {
if (t3 != null && t3.isAlive()) t3.stop();
t3 = new ScoreMoveThread();
t3.start();
}
//System.out.println(Arrays.deepToString(block));
//if (t1Alive)
// gamePanel.repaint();
}
// 效果不好
public void stopThread(Thread t) {
while (t.isAlive()) {};
}
// 监听输入框,Enter键
class SizeTFListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
start();
}
}
}
// 监听手势
class GestureListener extends MouseAdapter {
private int startX, startY;
@Override
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
if (!gamePanel.isFocusOwner()) // 获得焦点,不知道为啥不能自动获取
gamePanel.requestFocus();
}
@Override
public void mouseReleased(MouseEvent e) {
if (startX < 10 || startX > getSize().width - 10 || startY < transform(GAMEAREA_X + 70) || startY > getSize().height - 60) return;
int endX = e.getX();
int endY = e.getY();
int dx = (int) Math.abs(startX - endX);
int dy = (int) Math.abs(startY - endY);
if (dx - dy < 0 && dy > 20) {
if (startY > endY) {
gameMove(Direct.UP);
} else {
gameMove(Direct.DOWN);
}
} else if (dx - dy > 0 && dx > 20) {
if (startX > endX) {
gameMove(Direct.LEFT);
} else {
gameMove(Direct.RIGHT);
}
}
}
}
class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
@Override
public void paint(Graphics g) {
super.paint(g);
if (block == null) return;
// 背景
g.setColor(new Color(0xffbfafa2));
g.fillRoundRect(transform(GAMEAREA_X), transform(GAMEAREA_Y), PER_PIECE_SIZE * rows + GAMEAREA_X + BORDER_SIZE - 10, PER_PIECE_SIZE * rows + BORDER_SIZE, RADIUS, RADIUS);
// 标题
//g.setColor(new Color(0xffecc400));
//g.fillRect(transform(20), transform(10), transform(80), transform(70));
g.setColor(new Color(0xff776e65));
g.setFont(new Font(FONTNAME, Font.PLAIN, transform(40)));
g.drawString("2048", transform(25), transform(60));
g.setColor(new Color(0xffbfafa2));
g.fillRoundRect(transform(130), transform(10), transform(70), transform(70), RADIUS, RADIUS);
g.setColor(Color.WHITE);
g.setFont(new Font(FONTNAME, Font.BOLD, transform(12)));
g.drawString("SCORE", transform(130 + 15), transform(35));
int len = String.valueOf(scores).length();
g.setFont(new Font(FONTNAME, Font.BOLD, transform(16)));
g.drawString("" + scores, transform(130 + ((70 - len * 8) >> 1)), transform(60));
g.setColor(new Color(0xffbfafa2));
g.fillRoundRect(transform(220), transform(10), transform(70), transform(70), RADIUS, RADIUS);
g.setColor(Color.WHITE);
g.setFont(new Font(FONTNAME, Font.BOLD, transform(12)));
g.drawString("STEPS", transform(238), transform(35));
len = String.valueOf(steps).length();
g.setFont(new Font(FONTNAME, Font.BOLD, transform(16)));
g.drawString("" + steps, transform(220 + ((70 - len * 8) >> 1)), transform(60));
if (t3Alive && currentScore != 0) {
g.setColor(new Color(0xff776e65));
len = String.valueOf(currentScore).length();
g.drawString("+" + currentScore, transform(130 + ((70 - len * 8) >> 1) - 8), currentScoreY);
//return;
}
int x = 0, y = 0, size = 0, data;
g.setColor(new Color(BLOCKS[0][1]));
for (int i = 0; i < rows; i++) {
for (int j = 0; j < rows; j++) {
x = j * PER_PIECE_SIZE + transform(GAMEAREA_X) + BORDER_SIZE;
y = i * PER_PIECE_SIZE + transform(GAMEAREA_Y) + BORDER_SIZE;
g.fillRoundRect(x, y, PER_PIECE_SIZE - BORDER_SIZE, PER_PIECE_SIZE - BORDER_SIZE, RADIUS, RADIUS);
}
}
g.setFont(new Font(FONTNAME, Font.BOLD, 24));
if (t1Alive) {
for (BlockData bd : list) {
if (bd.data == 0) continue;
x = transform(GAMEAREA_X) + BORDER_SIZE + bd.x;
y = transform(GAMEAREA_Y) + BORDER_SIZE + bd.y;
size = PER_PIECE_SIZE - BORDER_SIZE;
data = bd.data;
g.setColor(new Color(BLOCKS[data][1]));
g.fillRoundRect(x, y, size, size, RADIUS, RADIUS);
if (data <= 2)
g.setColor(new Color(0xff776e65));
else
g.setColor(Color.WHITE);
if (BLOCKS[data][0] != 0) {
len = String.valueOf(BLOCKS[data][0]).length();
g.drawString("" + BLOCKS[data][0], x + ((size - len * 12) >> 1) - 1, y + (PER_PIECE_SIZE >> 1) + 4);
}
}
} else {
for (int i = rows - 1; i >= 0; i--) {
for (int j = rows - 1; j >= 0; j--) {
//if (block[i][j] == 0) continue;
x = j * PER_PIECE_SIZE + transform(GAMEAREA_X) + BORDER_SIZE;
y = i * PER_PIECE_SIZE + transform(GAMEAREA_Y) + BORDER_SIZE;
size = PER_PIECE_SIZE - BORDER_SIZE;
data = block[i][j];
if (t2Alive) {
if (merge[i * rows + j]) {
x -= currentSize;
y -= currentSize;
size += currentSize << 1;
}
}
g.setColor(new Color(BLOCKS[data][1]));
if (BLOCKS[data][0] != 0)
g.fillRoundRect(x, y, size, size, RADIUS, RADIUS);
if (data <= 2)
g.setColor(new Color(0xff776e65));
else
g.setColor(Color.WHITE);
if (BLOCKS[data][0] != 0) {
len = String.valueOf(BLOCKS[data][0]).length();
g.drawString("" + BLOCKS[data][0], x + ((size - len * 12) >> 1) - 1, y + (PER_PIECE_SIZE >> 1) + 4);
}
}
}
}
g.setColor(Color.GREEN);
if (rows < 4)
g.setFont(new Font(FONTNAME, Font.BOLD, 14));
if (isWin == 1) {
g.drawString("You Win! score:" + scores + " steps:" + steps, 20, transform(200));
} else if (isWin == -1) {
g.drawString("Uh-oh~ You Go Die!!", 20, transform(200));
}
}
}
// 游戏按键
class GameKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
gameMove(Direct.UP);
break;
case KeyEvent.VK_LEFT:
gameMove(Direct.LEFT);
break;
case KeyEvent.VK_DOWN:
gameMove(Direct.DOWN);
break;
case KeyEvent.VK_RIGHT:
gameMove(Direct.RIGHT);
break;
default:
return;
}
}
}
// 移动线程
class MoveThread extends Thread {
@Override
public void run() {
t1Alive = true;
/*for (BlockData bd : list) {
if (bd.data != 0)
System.out.println(bd);
}
System.out.println();*/
while (t1Alive) {
for(int time = 1; time <= MOVE_TIMES; time++) {
//System.out.println(time);
for (BlockData bd : list) {
//System.out.println(bd.row + " " + bd.col + " " + bd.distance);
bd.x = bd.col * PER_PIECE_SIZE;
bd.y = bd.row * PER_PIECE_SIZE;
if (bd.distance == 0) continue;
switch (bd.direct) {
case UP:
bd.y -= (bd.distance * PER_PIECE_SIZE * time) / MOVE_TIMES;
break;
case DOWN:
bd.y += (bd.distance * PER_PIECE_SIZE * time) / MOVE_TIMES;
break;
case LEFT:
bd.x -= (bd.distance * PER_PIECE_SIZE * time) / MOVE_TIMES;
break;
case RIGHT:
bd.x += (bd.distance * PER_PIECE_SIZE * time) / MOVE_TIMES;
break;
}
}
gamePanel.repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t1Alive = false;
//gamePanel.repaint();
}
}
}
// 闪烁线程
class FlashThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(10 * MOVE_TIMES);
} catch (Exception e1) {
e1.printStackTrace();
}
t2Alive = true;
while (t2Alive) {
for (int time = 1; time <= FLASH_TIMES; time++) {
currentSize = PER_PIECE_SIZE - BORDER_SIZE;
if (time < (FLASH_TIMES >>> 2)) {
currentSize = currentSize * time / (FLASH_TIMES << 3);
} else {
currentSize = currentSize * (FLASH_TIMES - time) / (FLASH_TIMES << 3);
}
gamePanel.repaint();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t2Alive = false;
//gamePanel.repaint();
}
}
}
// 分数移动线程
class ScoreMoveThread extends Thread {
@Override
public void run() {
t3Alive = true;
while (t3Alive) {
for (int time = 0; time <= SCORE_MOVE_TIMES; time++) {
if (rows < 4)
currentScoreY = transform(((currentScoreY << 2) / rows) - (20 / SCORE_MOVE_TIMES));
else
currentScoreY -= transform(20 / SCORE_MOVE_TIMES);
//gamePanel.repaint();
gamePanel.repaint(transform(130), transform(10), transform(70), transform(70));
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t3Alive = false;
//gamePanel.repaint();
gamePanel.repaint(transform(130), transform(10), transform(70), transform(70));
}
}
}
}
类BlockData:
/**
* 储存每一步方块操作的信息,用于动画
* @author kyda
*/
public class BlockData {
public int row; // 移动前行数
public int col; // 移动前列数
public int x; // 坐标x,显示时计算
public int y; // 坐标y
public int data; // 方块值
public Direct direct; // 方向
public int distance; // 移动距离
public BlockData() {
}
public BlockData(int row, int col, int data, Direct direct, int distance) {
this.row = row;
this.col = col;
this.data = data;
this.direct = direct;
this.distance = distance;
}
@Override
public String toString() {
return "["+row+","+col+","+data+","+direct+","+distance+"]";
}
}
类Direct:
public enum Direct {
UP, LEFT, DOWN, RIGHT
}