JAVA学习,写的一个2048小游戏
程序员文章站
2022-04-09 10:48:29
...
很久之前写的一个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<BlockData> 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 }