作者簡介
作者名:編程界明世隱
簡介:CSDN博客專家,從事軟體開發多年,精通Java、JavaScript,博主也是從零開始一步步把學習成長、深知學習和積累的重要性,喜歡跟廣大ADC一起打野升級,歡迎您關注,期待與您一起學習、成長、起飛!
系列目錄
1. Java俄羅斯方塊
2. Java五子棋小游戲
3. 老Java程式員花一天時間寫了個飛機大戰
4. Java植物大戰僵尸
5. 老Java程式員花2天寫了個連連看
6. Java消消樂(天天愛消除)
7. Java貪吃蛇小游戲
8. Java掃雷小游戲
9. Java坦克大戰
10. Java迷宮小游戲
Java 2048小游戲
引言:
前幾天偶爾看到了這個數字游戲,感徑訓蠻有意思,就玩了一下,竟然贏不了,怎么玩都是輸,真是邪門了,這不作為程式員員,我玩不贏我就自己寫一個,行不行?我自己寫的,我想贏就贏,條件我自己設定,就是玩!!
效果圖

實作思路
- 繪制視窗,
- 創建選單,
- 創建所有空白卡片,
- 隨機創建一個卡片(2或者4),
- 鍵盤事件監聽(上、下、左、右鍵監聽),
- 根據鍵盤的方向,處理數字的移動合并,
- 加入成功、失敗判定,
- 處理其他收尾作業,
代碼實作
創建視窗
首先創建一個游戲表單類GameFrame,繼承至JFrame,用來顯示在螢屏上(window的物件),每個游戲都有一個視窗,設定好視窗標題、尺寸、布局等就可以,
package main;
import java.awt.Color;
import javax.swing.JFrame;
/**
*表單類
*/
public class GameFrame extends JFrame {
//構造方法
public GameFrame(){
setTitle("2048");//設定標題
setSize(370, 420);//設定表單大小
getContentPane().setBackground(new Color(66,136,83));//加上背景
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//關閉后行程退出
setLocationRelativeTo(null);//居中
setResizable(false);//不允許變大
}
}
創建面板容器GamePanel繼承至JPanel
package main;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
* 畫布類
*/
public class GamePanel extends JPanel{
private JFrame mainFrame=null;
private GamePanel panel = null;
//構造里面初始化相關引數
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
}
}
再創建一個Main類,來啟動這個視窗,用來啟動,
package main;
//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);
}
}
右鍵執行這個Main類,視窗建出來了

創建選單
private Font createFont(){
return new Font("思源宋體",Font.BOLD,18);
}
//創建選單
private void createMenu() {
//創建JMenuBar
JMenuBar jmb = new JMenuBar();
//取得字體
Font tFont = createFont();
//創建游戲選項
JMenu jMenu1 = new JMenu("游戲");
jMenu1.setFont(tFont);
//創建幫助選項
JMenu jMenu2 = new JMenu("幫助");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("新游戲");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("退出");
jmi2.setFont(tFont);
//jmi1 jmi2添加到選單項“游戲”中
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作幫助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("勝利條件");
jmi4.setFont(tFont);
//jmi13 jmi4添加到選單項“游戲”中
jMenu2.add(jmi3);
jMenu2.add(jmi4);
jmb.add(jMenu1);
jmb.add(jMenu2);
mainFrame.setJMenuBar(jmb);
//添加監聽
jmi1.addActionListener(this);
jmi2.addActionListener(this);
jmi3.addActionListener(this);
jmi4.addActionListener(this);
//設定指令
jmi1.setActionCommand("restart");
jmi2.setActionCommand("exit");
jmi3.setActionCommand("help");
jmi4.setActionCommand("win");
}
此時直接把這個代碼加入到GamePanel中,發現是會報錯的,需要實作ActionListener,并重寫actionPerformed 方法,

此時GamePanel的代碼如下:
package main;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
/*
* 畫布類
*/
public class GamePanel extends JPanel implements ActionListener{
private JFrame mainFrame=null;
private GamePanel panel = null;
//構造里面初始化相關引數
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
//創建選單
createMenu();
}
private Font createFont(){
return new Font("思源宋體",Font.BOLD,18);
}
//創建選單
private void createMenu() {
//創建JMenuBar
JMenuBar jmb = new JMenuBar();
//取得字體
Font tFont = createFont();
//創建游戲選項
JMenu jMenu1 = new JMenu("游戲");
jMenu1.setFont(tFont);
//創建幫助選項
JMenu jMenu2 = new JMenu("幫助");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("新游戲");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("退出");
jmi2.setFont(tFont);
//jmi1 jmi2添加到選單項“游戲”中
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作幫助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("勝利條件");
jmi4.setFont(tFont);
//jmi13 jmi4添加到選單項“游戲”中
jMenu2.add(jmi3);
jMenu2.add(jmi4);
jmb.add(jMenu1);
jmb.add(jMenu2);
mainFrame.setJMenuBar(jmb);
//添加監聽
jmi1.addActionListener(this);
jmi2.addActionListener(this);
jmi3.addActionListener(this);
jmi4.addActionListener(this);
//設定指令
jmi1.setActionCommand("restart");
jmi2.setActionCommand("exit");
jmi3.setActionCommand("help");
jmi4.setActionCommand("win");
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
if ("exit".equals(command)) {
Object[] options = { "確定", "取消" };
int response = JOptionPane.showOptionDialog(this, "您確認要退出嗎", "",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
System.exit(0);
}
}else if("restart".equals(command)){
}else if("help".equals(command)){
JOptionPane.showMessageDialog(null, "通過鍵盤的上下左右來移動,相同數字會合并!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
}else if("win".equals(command)){
JOptionPane.showMessageDialog(null, "得到數字2048獲得勝利,當沒有空卡片則失敗!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
}
}
}

創建Card
建立Card類
package main;
import java.awt.Graphics;
public class Card {
private int x = 0;// x坐標
private int y = 0;// y坐標
private int w = 80;// 寬
private int h = 80;// 高
private int i = 0;//下標i
private int j = 0;//下標j
private int start=10;//偏移量(固定值)
private int num=0;//顯示數字
private boolean merge=false;//當前是否被合并過,如果合并了,則不能繼續合并,針對當前輪
public Card(int i,int j){
this.i=i;
this.j=j;
}
//根據i j計算x y坐標
private void cal(){
this.x = start + j*w + (j+1)*5;
this.y = start + i*h + (i+1)*5;
}
//繪制方法
public void draw(Graphics g) {
cal();
g.fillRoundRect(x, y, w, h, 4, 4);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isMerge() {
return merge;
}
public void setMerge(boolean merge) {
this.merge = merge;
}
}
在GamePanel中加入相關引數
private final int COLS=4;//列
private final int ROWS=4;//行
private Card cards[][] = new Card[ROWS][COLS];
private String gameFlag = "start";//游戲狀態
實體化Card物件
//初始化
private void init() {
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;
}
}
}
在構造方法中呼叫
//構造里面初始化相關引數
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
//創建選單
createMenu();
//初始化
init();
}
在GamePanel中重寫paint方法,并在此方法中繪制這些卡片,
@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);
}
}
}
運行

這個黑色不是我們想要的,要根據不同的數字來設定不同的顏色,于是我們在Card修改一下,
//獲取color
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,213,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;
case 128:
color = new Color(60,180,144);
break;
case 256:
color = new Color(45,130,120);
break;
case 512:
color = new Color(9,97,26);
break;
case 1024:
color = new Color(242,177,121);
break;
case 2048:
color = new Color(223,185,0);
break;
default://默認顏色
color = new Color(92,151,117);
break;
}
return color;
}
加入數字的顯示和顏色的修改代碼,修改draw方法,
//繪制方法
public void draw(Graphics g) {
cal();
//獲取舊的顏色
Color oColor = g.getColor();
//獲取要用的顏色
Color color = getColor();
//設定畫筆顏色
g.setColor(color);
g.fillRoundRect(x, y, w, h, 4, 4);
if(num!=0){
//設定字的顏色
g.setColor(new Color(125,78,51));
Font font = new Font("思源宋體", Font.BOLD, 35);
g.setFont(font);
//轉換成String
String text = num+"";
//計算該字體文本的長度
int wordWidth = getWordWidth(font, text);
//計算出字體居中位置的X坐標
int sx = x+(w-wordWidth)/2;
//繪制
g.drawString(text, sx , y+50);
}
//恢復畫筆顏色
g.setColor(oColor);
}
//得到該字體字串的長度
public static int getWordWidth(Font font, String content) {
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
int width = 0;
for (int i = 0; i < content.length(); i++) {
width += metrics.charWidth(content.charAt(i));
}
return width;
}

修改一下Card默認的數字,試試效果


隨機創建一個數字,2或者4
- 先把Card類中 num 默認改成0
- 因為2跟4出現的比例是1:4,所以采用隨機出1-5的數字,當是1的時候就表示,當得到2、3、4、5的時候就表示要出現數字2.
- 隨機獲取i,j 就可以得到卡片的位置,割接i,j取到card實體,如果卡片沒有數字,就表示可以,否則就遞回繼續取,取到為止,
- 把剛才取到的數字,設定到card實體物件中就好了,
代碼如下:
//在隨機的空卡片創建數字2或者4
private void createRandomNumber() {
int num = 0;
Random random = new Random();
int index = random.nextInt(5)+1;//這樣取出來的就是1-5 之間的亂數
//因為2和4出現的概率是1比4,所以如果index是1,則創建數字4,否則創建數字2(1被隨機出來的概率就是1/5,而其他就是4/5 就是1:4的關系)
if(index==1){
num = 4;
}else {
num = 2;
}
//判斷如果格子已經滿了,則不再獲取,退出
if(cardFull()){
return ;
}
//獲取隨機卡片,不為空的
Card card = getRandomCard(random);
//給card物件設定數字
if(card!=null){
card.setNum(num);
}
}
//獲取隨機卡片,不為空的
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 boolean cardFull() {
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 void createKeyListener() {
KeyAdapter l = new KeyAdapter() {
//按下
@Override
public void keyPressed(KeyEvent e) {
if(!"start".equals(gameFlag)) 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;
}
}
//松開
@Override
public void keyReleased(KeyEvent e) {
}
};
//給主frame添加鍵盤監聽
mainFrame.addKeyListener(l);
}
加入滑鼠移動邏輯處理代碼
//卡片移動的方法
protected void moveCard(int dir) {
//將卡片清理一遍,因為每輪移動會設定合并標記,需重置
clearCard();
if(dir==1){//向上移動
moveCardTop(true);
}
//移動后要創建新的卡片
createRandomNumber();
//重繪
repaint();
}
//將卡片清理一遍,因為每輪移動會設定合并標記,需重置
private void clearCard() {
Card card;
for (int i = 0; i < ROWS; i++) {//i從1開始,因為i=0不需要移動
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
card.setMerge(false);
}
}
}
//向上移動
private boolean moveCardTop(boolean bool) {
boolean res = false;
Card card;
for (int i = 1; i < ROWS; i++) {//i從1開始,因為i=0不需要移動
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不為空,要移動
if(card.moveTop(cards,bool)){//向上移動
res = true;//有一個為移動或者合并了,則res為true
}
}
}
}
return res;
}
在Card類中加入向上移動的處理邏輯
- 從第2行開始移動,因為第一行不需要移動,
- 只要卡片的數字不是0,就表示要移動,
- 根據 i-1 可以獲取到上一個卡片,如果上一個卡片是空,則把當前卡片交換上去,并且遞回,因為可能要繼續往上移動,
- 如果當前卡片與上一個卡片是相同數字的,則要合并,
- 以上兩種都不是,則不做操作,
//卡片向上移動
public boolean moveTop(Card[][] cards,boolean bool) {
//設定退出條件
if(i==0){//已經是最上面了
return false;
}
//上面一個卡片
Card prev = cards[i-1][j];
if(prev.getNum()==0){//上一個卡片是空
//移動,本質就是設定數字
if(bool){//bool為true才執行,因為flase只是用來判斷能否移動
prev.num=this.num;
this.num=0;
//遞回操作(注意這里是要 prev 來 move了)
prev.moveTop(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已經合并了,則不運行再次合并,針對當然輪)
if(bool){bool為true才執行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一個的num與當前num不同,無法移動,并退出
return false;
}
}
看看效果

加入其他3個方向的代碼,首先是在GamePanel中加入幾個代碼,
//向右移動
private boolean moveCardRight(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = COLS-1; j >=0 ; j--) {//j從COLS-1開始,從最右邊開始移動遞減
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不為空,要移動
if(card.moveRight(cards,bool)){//向右移動
res = true;//有一個為移動或者合并了,則res為true
}
}
}
}
return res;
}
//向下移動
private boolean moveCardBottom(boolean bool) {
boolean res = false;
Card card;
for (int i = ROWS-1; i >=0; i--) {//i從ROWS-1開始,往下遞減移動
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不為空,要移動
if(card.moveBottom(cards,bool)){//下移動
res = true;//有一個為移動或者合并了,則res為true
}
}
}
}
return res;
}
//向左移動
private boolean moveCardLeft(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 1; j < COLS ; j++) {//j從1開始,從最左邊開始移動
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不為空,要移動
if(card.moveLeft(cards,bool)){//向左移動
res = true;//有一個為移動或者合并了,則res為true
}
}
}
}
return res;
}
在Card加入其他幾個方向的方法
//向下移動
public boolean moveBottom(Card[][] cards,boolean bool) {
//設定退出條件
if(i==3){//已經是最下面了
return false;
}
//上面一個卡片
Card prev = cards[i+1][j];
if(prev.getNum()==0){//上一個卡片是空
//移動,本質就是設定數字
if(bool){//bool為true才執行,因為flase只是用來判斷能否移動
prev.num=this.num;
this.num=0;
//遞回操作(注意這里是要 prev 來 move了)
prev.moveBottom(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已經合并了,則不運行再次合并,針對當然輪)
if(bool){bool為true才執行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一個的num與當前num不同,無法移動,并退出
return false;
}
}
//向右移動
public boolean moveRight(Card[][] cards,boolean bool) {
//設定退出條件
if(j==3){//已經是最右邊了
return false;
}
//上面一個卡片
Card prev = cards[i][j+1];
if(prev.getNum()==0){//上一個卡片是空
//移動,本質就是設定數字
if(bool){//bool為true才執行,因為flase只是用來判斷能否移動
prev.num=this.num;
this.num=0;
//遞回操作(注意這里是要 prev 來 move了)
prev.moveRight(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已經合并了,則不運行再次合并,針對當然輪)
if(bool){bool為true才執行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一個的num與當前num不同,無法移動,并退出
return false;
}
}
//向左移動
public boolean moveLeft(Card[][] cards,boolean bool) {
//設定退出條件
if(j==0){//已經是最左邊了
return false;
}
//上面一個卡片
Card prev = cards[i][j-1];
if(prev.getNum()==0){//上一個卡片是空
//移動,本質就是設定數字
if(bool){//bool為true才執行,因為flase只是用來判斷能否移動
prev.num=this.num;
this.num=0;
//遞回操作(注意這里是要 prev 來 move了)
prev.moveLeft(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已經合并了,則不運行再次合并,針對當然輪)
if(bool){bool為true才執行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一個的num與當前num不同,無法移動,并退出
return false;
}
}
修改一下moveCard方法
//卡片移動的方法
protected void moveCard(int dir) {
//將卡片清理一遍,因為每輪移動會設定合并標記,需重置
clearCard();
if(dir==1){//向上移動
moveCardTop(true);
}else if(dir==2){//向右移動
moveCardRight(true);
}else if(dir==3){//向下移動
moveCardBottom(true);
}else if(dir==4){//向左移動
moveCardLeft(true);
}
//移動后要創建新的卡片
createRandomNumber();
//重繪
repaint();
}

做到這里就基本完成了,加入其他一下輔助的東西就行了,比如重新開始、游戲勝利,游戲結束等,也就不多說了,
看到這里的大佬,動動發財的小手 點贊 + 回復 + 收藏,能【 關注 】一波就更好了,
代碼獲取方式:
幫忙文章【點贊】 +【 收藏】+【關注】+【評論】 后,加我主頁左邊的微信 或者 私聊我,我發給你!
更多精彩
1. Java俄羅斯方塊
2. Java五子棋小游戲
3. 老Java程式員花一天時間寫了個飛機大戰
4. Java植物大戰僵尸
5. 老Java程式員花2天寫了個連連看
6. Java消消樂(天天愛消除)
7. Java貪吃蛇小游戲
8. Java掃雷小游戲
9. Java坦克大戰
10. Java迷宮小游戲
相關閱讀
1. JavaWeb圖書管理系統
2. JavaWeb學生宿舍管理系統
3. JavaWeb在線考試系統
另外
為了幫助更多小白從零進階 Java 工程師,從CSDN官方那邊搞來了一套 《Java 工程師學習成長知識圖譜》,尺寸 870mm x 560mm,展開后有一張辦公桌大小,也可以折疊成一本書的尺寸,原件129元現價 29 元,先到先得,有興趣的小伙伴可以了解一下!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298357.html
標籤:java
上一篇:Redis工程實作
