java實(shí)現(xiàn)2048小游戲
本文實(shí)例為大家分享了java實(shí)現(xiàn)2048小游戲的具體代碼,供大家參考,具體內(nèi)容如下
一、實(shí)現(xiàn)效果

二、實(shí)現(xiàn)代碼
Check表示格子,GameView實(shí)現(xiàn)游戲視圖界面及功能,是核心。

Check.java
import java.awt.Color;
import java.awt.Font;
// 方格類
public class Check {
public int value;
Font font1 = new Font("宋體", Font.BOLD, 46);
Font font2 = new Font("宋體", Font.BOLD, 40);
Font font3 = new Font("宋體", Font.BOLD, 34);
Font font4 = new Font("宋體", Font.BOLD, 28);
Font font5 = new Font("宋體", Font.BOLD, 22);
public Check() {
value = 0; //value為方格中數(shù)字
}
//字體顏色
public Color getForeground() {
switch (value) {
case 0:
return new Color(0xcdc1b4);//0的顏色與背景色一致,相當(dāng)于沒有數(shù)字
case 2:
case 4:
return Color.BLACK;
default:
return Color.WHITE;
}
}
//字體背景顏色,即方格顏色
public Color getBackground() {
switch (value) {
case 0:
return new Color(0xcdc1b4);
case 2:
return new Color(0xeee4da);
case 4:
return new Color(0xede0c8);
case 8:
return new Color(0xf2b179);
case 16:
return new Color(0xf59563);
case 32:
return new Color(0xf67c5f);
case 64:
return new Color(0xf65e3b);
case 128:
return new Color(0xedcf72);
case 256:
return new Color(0xedcc61);
case 512:
return new Color(0xedc850);
case 1024:
return new Color(0xedc53f);
case 2048:
return new Color(0xedc22e);
case 4096:
return new Color(0x65da92);
case 8192:
return new Color(0x5abc65);
case 16384:
return new Color(0x248c51);
default:
return new Color(0x248c51);
}
}
public Font getCheckFont() {
if (value < 10) {
return font1;
}
if (value < 100) {
return font2;
}
if (value < 1000) {
return font3;
}
if (value < 10000) {
return font4;
}
return font5;
}
}
GameView.java
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GameView{
private static final int jframeWidth = 405;//窗口寬高
private static final int jframeHeight = 530;
private static int score = 0;
Font topicFont = new Font("微軟雅黑", Font.BOLD, 50);//主題字體
Font scoreFont = new Font("微軟雅黑", Font.BOLD, 28);//得分字體
Font explainFont = new Font("宋體", Font.PLAIN,20);//提示字體
private JFrame jframeMain;
private JLabel jlblTitle;
private JLabel jlblScoreName;
private JLabel jlblScore;
private JLabel jlblTip;
private GameBoard gameBoard;
public GameView() {
init();
}
public void init() {
//1、創(chuàng)建窗口
jframeMain = new JFrame("2048小游戲");
jframeMain.setSize(jframeWidth, jframeHeight);
jframeMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframeMain.setLocationRelativeTo(null);//窗口顯示位置居中
jframeMain.setResizable(false);
jframeMain.setLayout(null);//設(shè)置絕對布局,以便后面可以用setBounds設(shè)置位置
jlblTitle = new JLabel("2048", JLabel.CENTER);
jlblTitle.setFont(topicFont);
jlblTitle.setForeground(Color.BLACK);
jlblTitle.setBounds(50, 0, 150, 60);
jframeMain.add(jlblTitle);
//2、框架窗口搭建好,則需向里面開始添加內(nèi)容
//設(shè)置字體及其顏色、位置
jlblScoreName = new JLabel("得 分", JLabel.CENTER);
jlblScoreName.setFont(scoreFont);
jlblScoreName.setForeground(Color.WHITE);
jlblScoreName.setOpaque(true);
jlblScoreName.setBackground(Color.GRAY);
jlblScoreName.setBounds(250, 0, 120, 30);
jframeMain.add(jlblScoreName);
//3、得分區(qū)(得分名+分?jǐn)?shù))
jlblScore = new JLabel("0", JLabel.CENTER);
jlblScore.setFont(scoreFont);
jlblScore.setForeground(Color.WHITE);
jlblScore.setOpaque(true);
jlblScore.setBackground(Color.GRAY);
jlblScore.setBounds(250, 30, 120, 30);
jframeMain.add(jlblScore);
//4、提示說明區(qū)
jlblTip = new JLabel("操作: ↑ ↓ ← →, 按esc鍵重新開始 ",
JLabel.CENTER);
jlblTip.setFont(explainFont);
jlblTip.setForeground(Color.DARK_GRAY);
jlblTip.setBounds(0, 60, 400, 40);
jframeMain.add(jlblTip);
//5、主游戲面板區(qū)
gameBoard = new GameBoard();
gameBoard.setBounds(0, 100, 400, 400);
gameBoard.setBackground(Color.GRAY);
gameBoard.setFocusable(true);//焦點(diǎn)即當(dāng)前正在操作的組件,也就是移動(dòng)的數(shù)字
gameBoard.setLayout(new FlowLayout());
jframeMain.add(gameBoard);
}
// 游戲面板
class GameBoard extends JPanel implements KeyListener {
private static final int CHECK_GAP = 10;//方格之間的間隙
private static final int CHECK_SIZE = 85;//方格大小
private static final int CHECK_ARC = 20;//方格弧度
private Check[][] checks = new Check[4][4];
private boolean isadd = true;
public GameBoard() {
initGame();
addKeyListener(this);
}
private void initGame() {
score = 0;
for (int indexRow = 0; indexRow < 4; indexRow++) {
for (int indexCol = 0; indexCol < 4; indexCol++) {
checks[indexRow][indexCol] = new Check();
}
}
// 最開始時(shí)生成兩個(gè)數(shù)
isadd = true;
createCheck();
isadd = true;
createCheck();
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_ESCAPE:
initGame();//重新開始游戲(初始化游戲)
break;
case KeyEvent.VK_LEFT:
moveLeft();
createCheck();//調(diào)用一次方法創(chuàng)建一個(gè)方格數(shù)字
judgeGameOver();//創(chuàng)建后判斷是否GameOver,若所有格子均滿即跳出GameOver
break;
case KeyEvent.VK_RIGHT:
moveRight();
createCheck();
judgeGameOver();
break;
case KeyEvent.VK_UP:
moveUp();
createCheck();
judgeGameOver();
break;
case KeyEvent.VK_DOWN:
moveDown();
createCheck();
judgeGameOver();
break;
default:
break;//按其他鍵沒有反應(yīng)
}
repaint();//刷新,會(huì)自動(dòng)調(diào)用paint()方法,重新繪制移動(dòng)后的圖
}
private void createCheck() {
List<Check> list = getEmptyChecks();
if (!list.isEmpty() && isadd) {
Random random = new Random();
int index = random.nextInt(list.size());
Check check = list.get(index);
// 2, 4出現(xiàn)概率3:1
int randomValue = random.nextInt(4);
check.value = ( randomValue % 3 == 0 || randomValue % 3 == 1) ? 2 : 4;//只有[0,4)中的2才能生成4
isadd = false;
}
}
// 獲取空白方格
private List<Check> getEmptyChecks() {
List<Check> checkList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (checks[i][j].value == 0) {
checkList.add(checks[i][j]);
}
}
}
return checkList;
}
//是否全部格子占滿,全部占滿則GameOver
private boolean judgeGameOver() {
jlblScore.setText(score + "");
if (!getEmptyChecks().isEmpty()) {
return false;
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//判斷是否存在可合并的方格
if (checks[i][j].value == checks[i][j + 1].value
|| checks[i][j].value == checks[i + 1][j].value) {
return false;
}
}
}
return true;
}
private void moveLeft() {
//找到一個(gè)非空格子后checks[i][j].value > 0,可分為三種情況處理
for (int i = 0; i < 4; i++) {
for (int j = 1, index = 0; j < 4; j++) {
if (checks[i][j].value > 0) {
//第一種情況:checks[i][j](非第1列)與checks[i][index]的數(shù)相等,則合并乘以2,且得分增加
if (checks[i][j].value == checks[i][index].value) {
score += checks[i][index].value *= 2;
checks[i][j].value = 0;
isadd = true;
} else if (checks[i][index].value == 0) {
//第二種:若checks[i][index]為空格子,checks[i][j]就直接移到最左邊checks[i][index]
checks[i][index].value = checks[i][j].value;
checks[i][j].value = 0;
isadd = true;
} else if (checks[i][++index].value == 0) {
//第三種:若checks[i][index]不為空格子,并且數(shù)字也不相等,若其旁邊為空格子,則移到其旁邊
checks[i][index].value = checks[i][j].value;
checks[i][j].value = 0;
isadd = true;
}
}
}
}
}
private void moveRight() {
for (int i = 0; i < 4; i++) {
for (int j = 2, index = 3; j >= 0; j--) {
if (checks[i][j].value > 0) {
if (checks[i][j].value == checks[i][index].value) {
score += checks[i][index].value *= 2;
checks[i][j].value = 0;
isadd = true;
} else if (checks[i][index].value == 0) {
checks[i][index].value = checks[i][j].value;
checks[i][j].value = 0;
isadd = true;
} else if (checks[i][--index].value == 0) {
checks[i][index].value = checks[i][j].value;
checks[i][j].value = 0;
isadd = true;
}
}
}
}
}
private void moveUp() {
for (int i = 0; i < 4; i++) {
for (int j = 1, index = 0; j < 4; j++) {
if (checks[j][i].value > 0) {
if (checks[j][i].value == checks[index][i].value) {
score += checks[index][i].value *= 2;
checks[j][i].value = 0;
isadd = true;
} else if (checks[index][i].value == 0) {
checks[index][i].value = checks[j][i].value;
checks[j][i].value = 0;
isadd = true;
} else if (checks[++index][i].value == 0){
checks[index][i].value = checks[j][i].value;
checks[j][i].value = 0;
isadd = true;
}
}
}
}
}
private void moveDown() {
for (int i = 0; i < 4; i++) {
for (int j = 2, index = 3; j >= 0; j--) {
if (checks[j][i].value > 0) {
if (checks[j][i].value == checks[index][i].value) {
score += checks[index][i].value *= 2;
checks[j][i].value = 0;
isadd = true;
} else if (checks[index][i].value == 0) {
checks[index][i].value = checks[j][i].value;
checks[j][i].value = 0;
isadd = true;
} else if (checks[--index][i].value == 0) {
checks[index][i].value = checks[j][i].value;
checks[j][i].value = 0;
isadd = true;
}
}
}
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
drawCheck(g, i, j);
}
}
// GameOver
if (judgeGameOver()) {
g.setColor(new Color(64, 64, 64, 100));//RGBA最后一個(gè)A可以視為透明度
g.fillRect(0, 0, getWidth(), getHeight());//填充矩形(游戲面板),將暗黑色填充上去
g.setColor(Color.WHITE);
g.setFont(topicFont);
FontMetrics fms = getFontMetrics(topicFont);//FontMetrics字體測量,該類是Paint的內(nèi)部類,通過getFontMetrics()方法可獲取字體相關(guān)屬性
String value = "Game Over!";
g.drawString(value, (getWidth()-fms.stringWidth(value)) / 2, getHeight() / 2);//字體居中顯示
}
}
// 繪制方格
// Graphics2D 類是Graphics 子類,擁有強(qiáng)大的二維圖形處理能力
private void drawCheck(Graphics g, int i, int j) {
Graphics2D gg = (Graphics2D) g;
//下面兩句是抗鋸齒模式,計(jì)算和優(yōu)化消除文字鋸齒,字體更清晰順滑
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
//獲取方格
Check check = checks[i][j];
//不同數(shù)字設(shè)置背景色
gg.setColor(check.getBackground());
// 繪制圓角
gg.fillRoundRect(CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j,
CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i,
CHECK_SIZE, CHECK_SIZE, CHECK_ARC, CHECK_ARC);
//繪制字體及其顏色
gg.setColor(check.getForeground());
gg.setFont(check.getCheckFont());
// 文字測量,并對文字進(jìn)行繪制
FontMetrics fms = getFontMetrics(check.getCheckFont());
String value = String.valueOf(check.value);
//使用此圖形上下文的當(dāng)前顏色繪制由指定迭代器給定的文本。
//getAscent()是FontMetrics中的一個(gè)方法,
//getDescent() 為降部
gg.drawString(value,
CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j +
(CHECK_SIZE - fms.stringWidth(value)) / 2,
CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i +
(CHECK_SIZE - fms.getAscent() - fms.getDescent()) / 2
+ fms.getAscent());//讓數(shù)字居中顯示
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
public void showView() {
jframeMain.setVisible(true);
}
}
Main.java
public class Main {
public static void main(String[] args) {
new GameView().showView();
}
}
三、重難點(diǎn)講解
3.1 數(shù)字移動(dòng)問題
數(shù)字移動(dòng)是一難點(diǎn),分三種情況,以moveLeft()為例
(1)按左鍵,若最左邊是相同的,則合并

(2)若左邊是空格,則直接移動(dòng)到最左即可

(3)若最左邊不為空格,且不相等,則看它右邊是否是空格,是則移動(dòng)到其旁邊

3.2 繪圖問題—抗鋸齒
java提供的Graphics 2D,它是Graphics 子類
Graphics2D gg = (Graphics2D) g; gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE);
上面這兩個(gè)語句實(shí)現(xiàn)的功能是消除文字鋸齒,字體更清晰順滑,可以看下圖沒有setRenderingHint和有setRenderingHint的區(qū)別

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java開發(fā)分布式服務(wù)框架Dubbo暴露服務(wù)過程詳解
這篇文章主要為大家介紹了java開發(fā)分布式服務(wù)框架Dubbo暴露服務(wù)的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
SWT(JFace)體驗(yàn)之GridLayout布局
GridLayout 布局的功能非常強(qiáng)大,也是筆者常用的一種布局方式。GridLayout是網(wǎng)格式布局,它把父組件分成一個(gè)表格,默認(rèn)情況下每個(gè)子組件占據(jù)一個(gè)單元格的空間,每個(gè)子組件按添加到父組件的順序排列在表格中。2009-06-06
spring cloud中微服務(wù)之間的調(diào)用以及eureka的自我保護(hù)機(jī)制詳解
這篇文章主要介紹了spring cloud中微服務(wù)之間的調(diào)用以及eureka的自我保護(hù)機(jī)制詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07
idea項(xiàng)目debug模式無法啟動(dòng)的解決
這篇文章主要介紹了idea項(xiàng)目debug模式無法啟動(dòng)的解決,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02

