1. 程式人生 > >JAVA_2048小遊戲的實現練習

JAVA_2048小遊戲的實現練習

package com.lyc.java2048;

import java.awt.Graphics;
import java.awt.Image;

/**
*準備圖片資源
*/
public class Background {

private final int BG_X = 0;

private final int BG_Y = 0;

private final int FG_X = 50;  

private final int FG_Y = 140;

public void paintBG(Graphics g){
	
	g.drawImage(Resources.IMG_BG, BG_X, BG_Y, null);
	g.drawImage(Resources.IMG_FG, FG_X, FG_Y, null);
}

public void paintMap(Graphics g, int index, int i, int j){
	switch (index) {
	case 1:
		map(g,Resources.IMG_02,i,j);
		break;
	case 2:
		map(g,Resources.IMG_04,i,j);
		break;
	case 3:
		map(g,Resources.IMG_08,i,j);
		break;
	case 4:
		map(g,Resources.IMG_16,i,j);
		break;
	case 5:
		map(g,Resources.IMG_32,i,j);
		break;
	case 6:
		map(g,Resources.IMG_64,i,j);
		break;
	case 7:
		map(g,Resources.IMG_128,i,j);
		break;
	case 8:
		map(g,Resources.IMG_256,i,j);
		break;
	case 9:
		map(g,Resources.IMG_512,i,j);
		break;
	case 10:
		map(g,Resources.IMG_1024,i,j);
		break;
	case 11:
		map(g,Resources.IMG_2048,i,j);
		break;
	default:
		break;
	}
}

public void map(Graphics g, Image img, int i, int j){
	g.drawImage(img, FG_X+100*j, FG_Y+100*i, null);
}

}

package com.lyc.java2048;

import java.awt.Image;

import javax.swing.ImageIcon;

/**

  • 載入遊戲資源類
  • @author Administrator

*/
public class Resources {

 static final Image IMG_02;
 static final Image IMG_04;
 static final Image IMG_08;
 static final Image IMG_16;
 static final Image IMG_32;
 static final Image IMG_64;
 static final Image IMG_128;
 static final Image IMG_256;
 static final Image IMG_512;
 static final Image IMG_1024;
 static final Image IMG_2048;
 static final Image IMG_NUM;
 static final Image IMG_SCORE;
 static final Image IMG_HEIGHSCORE;
 static final Image IMG_BG;
 static final Image IMG_FG;

static {
	IMG_02 = getImage("res/2.png");
	IMG_04 = getImage("res/4.png");
	IMG_08 = getImage("res/8.png");
	IMG_16 = getImage("res/16.png");
	IMG_32 = getImage("res/32.png");
	IMG_64 = getImage("res/64.png");
	IMG_128 = getImage("res/128.png");
	IMG_256 = getImage("res/256.png");
	IMG_512 = getImage("res/512.png");
	IMG_1024 = getImage("res/1024.png");
	IMG_2048 = getImage("res/2048.png");
	IMG_NUM = getImage("res/num.png");
	IMG_SCORE = getImage("res/score.png");
	IMG_HEIGHSCORE = getImage("res/highscore.png");
	IMG_BG = getImage("res/bg.png");
	IMG_FG = getImage("res/fg.png");
}

static Image getImage(String path){
	
	return new ImageIcon(path).getImage();
}

}

package com.lyc.java2048;

import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JPanel;

/**

  • 顯示面板類
  • @author Administrator

*/
@SuppressWarnings(“serial”)
public class Game_2048 extends JPanel implements KeyListener{

private Background background;
private GameService gameService;

Game_2048(){
	background = new Background();
	gameService = new GameService();
}

public void paint(Graphics g){
	background.paintBG(g);
	gameService.gamePaint(g);
}

@Override
public void keyTyped(KeyEvent e) {
	
}

@Override
public void keyPressed(KeyEvent e) {
	boolean ismove = false;
	boolean isRemove = false;
	if(e.getKeyCode()==37){
		ismove=gameService.moveLeft();
		isRemove = gameService.removeLeft();
		if(ismove||isRemove){
			gameService.newBlock();
		}
	}
	else if(e.getKeyCode()==38){
		ismove=gameService.moveUp();
		isRemove = gameService.removeUp();
        if(ismove||isRemove){
        	gameService.newBlock();
		}
	}
	else if(e.getKeyCode()==39){
		ismove=gameService.moveRight();
		isRemove = gameService.removeRight();
		if(ismove||isRemove){
			gameService.newBlock();
		}
	}
	else if(e.getKeyCode()==40){
		ismove=gameService.moveDown();
		isRemove = gameService.removeDown();
		if(ismove||isRemove){
			gameService.newBlock();
		}
	}
	else if(e.getKeyCode()==27){
		int i = OptionPanel.showExitDialog();
		gameService.refreshHighscore();
		if(i==0){
			
			System.exit(0);
			
		}else{
			
		}
	}
	else if(e.getKeyCode()==112){
		int i = OptionPanel.showRestartDialog();
		gameService.refreshHighscore();
		if(i==0){
			
			gameService.restart();
		}else{
			
		}
		
	}
	repaint();
	gameService.isGameOver();//每走一步,判斷是否結束遊戲
	repaint();//解決遊戲結束後的重新開始遊戲畫面問題
}

@Override
public void keyReleased(KeyEvent e) {
	
}

}

package com.lyc.java2048;

import java.awt.Graphics;

/**

  • 遊戲分數處理類
  • @author Administrator

*/
public class Data {

private static final int SCORE_X=80;
private static final int SCORE_Y=20;
private static final int SCORE_HEIGHT_X=280;
private static final int SCORE_HEIGHT_Y=20;
private static final int NUM_SIZE = 21;//一個數字圖片的大小  寬高都是21
private static final int SCORE_SIZE = 140;// 分數圖片的寬度     最高分數圖片的寬度  都是140


private int score;
private int heightScore;

public int getScore() {
	return score;
}


public void setScore(int score) {
	this.score = score;
}


public int getHeightScore() {
	return heightScore;
}


public void setHeightScore(int heightScore) {
	this.heightScore = heightScore;
}


public void painScore(Graphics g){
	
	g.drawImage(Resources.IMG_SCORE, SCORE_X, SCORE_Y, null);//畫分數圖片
	
	g.drawImage(Resources.IMG_HEIGHSCORE, SCORE_HEIGHT_X, SCORE_HEIGHT_Y, null);//畫最高分圖片
	
	drawScore(g);//畫分數值
	
	draw_height_score(g);
}

/**
 * 畫當前分數:
 * 動態畫出分數圖片資料,實現動態居中的效果
 * 1).需要傳入一個分數資料
 * 2).計算出第一個數字的左上角x,y座標位置
 *  1.獲取圖片正中間座標位置:SCORE_X+圖片寬度/2
	2.計算出需要畫的數字的圖片寬度:數字圖片個數*21
	3.計算出左邊要畫出的位置,在中間座標位置往左偏移數字圖片寬度的一半:
	SCORE_X+圖片寬度/2-數字圖片個數*21/2
	3)計算出迴圈過程中其他數字的左上角的x和y
	第一個數字的左上角x+數字所在字串中的索引值位置*21
 */
public void drawScore(Graphics g){
	int mid_x = SCORE_X+SCORE_SIZE/2;//中間位置的x座標
	//score: 128   -> 轉換成  "128"  可以求出個數
	//轉換分數為字串資料
	String score_str = score+"";
	//求出要畫出的數字圖片寬度
	int pic_width = score_str.length()*NUM_SIZE;
	//求出第一個數字圖片的左上角位置
	int x = mid_x - pic_width/2;//x
	int y = SCORE_Y+40;//y
	
	//迴圈遍歷所有的數字,逐個畫出
	for (int i = 0; i < score_str.length(); i++) {
		//計算出分數數字字串的值
		char c = score_str.charAt(i);//從字串中,根據索引獲取出字元    如    "128".charAt(0)->  '1'
		int num_post = c-'0';
		g.drawImage(
				Resources.IMG_NUM,      //num.png數字圖片物件
				x+i*NUM_SIZE, y, //當前要畫出的數字的左上角位置
				x+i*NUM_SIZE+NUM_SIZE, y+NUM_SIZE, //當前要畫出的數字的右下角位置
				num_post*NUM_SIZE, 0, //num.png數字圖片的左上角位置
				num_post*NUM_SIZE+NUM_SIZE, NUM_SIZE, //num.png數字圖片的右下角位置
				null);
	}
	
}

public void draw_height_score(Graphics g){
	int mid_x = SCORE_HEIGHT_X+SCORE_SIZE/2;//中間位置的x座標
	//score: 128   -> 轉換成  "128"  可以求出個數
	//轉換分數為字串資料
	String score_str = heightScore+"";
	//求出要畫出的數字圖片寬度
	int pic_width = score_str.length()*NUM_SIZE;
	//求出第一個數字圖片的左上角位置
	int x = mid_x - pic_width/2;//x
	int y = SCORE_Y+40;//y
	
	//迴圈遍歷所有的數字,逐個畫出
	for (int i = 0; i < score_str.length(); i++) {
		//計算出分數數字字串的值
		char c = score_str.charAt(i);//從字串中,根據索引獲取出字元    如    "128".charAt(0)->  '1'
		int num_post = c-'0';
		g.drawImage(
				Resources.IMG_NUM,      //num.png數字圖片物件
				x+i*NUM_SIZE, y, //當前要畫出的數字的左上角位置
				x+i*NUM_SIZE+NUM_SIZE, y+NUM_SIZE, //當前要畫出的數字的右下角位置
				num_post*NUM_SIZE, 0, //num.png數字圖片的左上角位置
				num_post*NUM_SIZE+NUM_SIZE, NUM_SIZE, //num.png數字圖片的右下角位置
				null);
	}
}

}

package com.lyc.java2048;

import java.awt.Graphics;
import java.util.Random;

/**

  • 遊戲服務類,用於處理遊戲中的資料
  • @author Administrator

*/
public class GameService {
private Data data;
private int[][] gameMap;

GameService(){
	data = new Data();
	start();
}

public void gamePaint(Graphics g){
	Background background = new Background();
	data.painScore(g);
	for (int i = 0; i < gameMap.length; i++) {
		for (int j = 0; j < gameMap[i].length; j++) {
			background.paintMap(g,gameMap[i][j],i,j);
			//System.out.print(gameMap[i][j]+",");
			
		}
		//System.out.println();
	}
	System.out.println(data.getScore());//模擬顯示分數
}

public void start(){
	gameMap = new int[4][4];
	newBlock();
	newBlock();
}

public void newBlock(){
	int x,y;
	Random random = new Random();
	
	do{
		x=random.nextInt(4);
		y=random.nextInt(4);
		
	}while(gameMap[x][y]!=0);
	
	int index = random.nextInt(8);
	if(index==0){
		gameMap[x][y]=2;
	}else{
		gameMap[x][y]=1;
	}
}

/**
 * System.out.println("左移動");
 * @return
 */
public boolean moveLeft(){
	boolean ismove = false;
	
	for (int i = 0; i < 4; i++) {//遍歷4行
		for (int j = 1; j < 4; j++) {//遍歷列   列索引範圍  [1~3]   0索引位置的列左邊沒有格子,不需要移動
			int mov_i = i;
			int mov_j = j;//當前移動格子的 座標
			while(gameMap[mov_i][mov_j]!=0&&gameMap[mov_i][mov_j-1]==0){
				gameMap[mov_i][mov_j-1] = gameMap[mov_i][mov_j];//將當前方塊資料賦值給左邊位置
				gameMap[mov_i][mov_j] = 0;
				
				//移動下一個位置標記
				if(mov_j>1){
					mov_j--;
				}
				ismove = true;//有方塊移動了
			}
		}
	}
	return ismove;
}

/**
 * System.out.println("右移動");
 * @return
 */
public boolean moveRight(){
	boolean ismove = false;
	
	for (int i = 0; i < 4; i++) {//遍歷4行
		for (int j = 0; j < 3; j++) {//遍歷列   列索引範圍  [0~2]   3索引位置的列左邊沒有格子,不需要移動
			int mov_i = i;
			int mov_j = j;//當前移動格子的 座標
			while(gameMap[mov_i][mov_j]!=0&&gameMap[mov_i][mov_j+1]==0){
				gameMap[mov_i][mov_j+1] = gameMap[mov_i][mov_j];//將當前方塊資料賦值給左邊位置
				gameMap[mov_i][mov_j] = 0;
				
				//移動下一個位置標記
				if(mov_j<2){
					mov_j++;
				}
				ismove = true;//有方塊移動了
			}
		}
	}
	return ismove;
}

/**
 * System.out.println("上移動");
 * @return
 */
public boolean moveUp(){
	boolean ismove = false;
	
	for (int i = 1; i < 4; i++) {//第一行不用上移動
		for (int j = 0; j < 4; j++) {
			int mov_i = i;
			int mov_j = j;//當前移動格子的 座標
			while(gameMap[mov_i][mov_j]!=0&&gameMap[mov_i-1][mov_j]==0){
				gameMap[mov_i-1][mov_j] = gameMap[mov_i][mov_j];
				gameMap[mov_i][mov_j] = 0;
				
				if(mov_i>1){//判斷是否能夠移動下一個位置的標記
					mov_i--;
				}
				ismove = true;
			}
		}
	}
	return ismove;
}

/**
 * System.out.println("下移動");
 * @return
 */
public boolean moveDown(){
	boolean ismove = false;
	for (int i = 0; i < 3; i++) {//最後一行不用下移動
		for (int j = 0; j < 4; j++) {
			int mov_i = i;
			int mov_j = j;//當前移動格子的 座標
			while(gameMap[mov_i][mov_j]!=0&&gameMap[mov_i+1][mov_j]==0){
				gameMap[mov_i+1][mov_j] = gameMap[mov_i][mov_j];
				gameMap[mov_i][mov_j] = 0;
				
				if(mov_i<2){//判斷是否能夠移動下一個位置的標記
					mov_i++;
				}
				ismove = true;
			}
		}
	}
	return ismove;
}

public boolean removeLeft(){
	boolean isRemove = false;
	for (int i = 0; i < gameMap.length; i++) {
		for (int j = 0; j <3; j++) {
			if(gameMap[i][j]!=0 && gameMap[i][j]==gameMap[i][j+1]){
				gameMap[i][j]++;;
				gameMap[i][j+1]=0;
				isRemove=true;
				bonus(gameMap[i][j]);
			}
		}
	}
	moveLeft();
	return isRemove;
}

public boolean removeRight(){
	boolean isRemove = false;
	for (int i = 0; i < gameMap.length; i++) {
		for (int j = 3; j > 0; j--) {
			if(gameMap[i][j]!=0 && gameMap[i][j]==gameMap[i][j-1]){
				gameMap[i][j]++;;
				gameMap[i][j-1]=0;
				isRemove=true;
				bonus(gameMap[i][j]);
			}
		}
	}
	moveRight();
	return isRemove;
}


public boolean removeUp(){
	boolean isRemove = false;
	for (int i = 0; i <3 ; i++) {
		for (int j = 0; j < gameMap.length; j++) {
			if(gameMap[i][j]!=0 && gameMap[i][j]==gameMap[i+1][j]){
				gameMap[i][j]++;;
				gameMap[i+1][j]=0;
				isRemove=true;
				bonus(gameMap[i][j]);
			}
		}
	}
	moveUp();
	return isRemove;
}

public boolean removeDown(){
	boolean isRemove = false;
	for (int i = 3; i >0 ; i--) {
		for (int j = 0; j < gameMap.length; j++) {
			if(gameMap[i][j]!=0 && gameMap[i][j]==gameMap[i-1][j]){
				gameMap[i][j]++;;
				gameMap[i-1][j]=0;
				isRemove=true;
				bonus(gameMap[i][j]);
			}
		}
	}
	moveDown();
	return isRemove;
}

public void bonus(int num){
	int value = (int) Math.pow(2, num);
	data.setScore(data.getScore()+value);
}


/**
 * 重新整理最高分
 */
public void refreshHighscore(){
	if(data.getScore() >data.getHeightScore()){
		data.setHeightScore(data.getScore());//如果當前分數大於歷史最高分,則更新為最高分
	}
}

/**
 * 重新開始遊戲
 * 1.gameMap清零
 * 2.生成兩個新方塊
 * 3.score清零
 */
public void restart(){
	start();
	data.setScore(0);
}

/**
 * 是否遊戲結束
 * 1.達成2048    is2048()
 * a.重新整理最高分
 * b.彈框提示是否重新開始遊戲
 * c.重新開始遊戲   restart()
 * d.不重新開始遊戲  退出遊戲
 * 
 * 2.無法移動或消除  canMove()
 * a.重新整理最高分
 * b.彈框是否重新開始遊戲
 * c.重新開始遊戲   restart()
 * d.不重新開始遊戲  退出遊戲
 * 
 * @return
 */
public void isGameOver(){
	if(is2048()){//如果遊戲勝利
		int choice = OptionPanel.showGameOverDialog(data);//0為重新開始按鈕    1為結束遊戲
		refreshHighscore();
		/*System.out.println(choice);*/
		if(choice==0){
			restart();
		}else{
			System.exit(0);
		}
	}else if(isFull()&&!canMove()){//或者遊戲失敗
		refreshHighscore();
		int choice = OptionPanel.showGameOverDialog(data);//0為重新開始按鈕    1為結束遊戲
		System.out.println(choice);
		if(choice==0){
			restart();
		}else{
			System.exit(0);
		}
	}
}

/**
 * 是否達成2048
 * 遍歷gameMap,查詢是否有11
 */
public boolean is2048(){
	boolean is2048 = false;
	for (int i = 0; i < 4; i++) {//遍歷行
		for (int j = 0; j < 4; j++) {//遍歷列
			if(gameMap[i][j]==11){
				is2048 = true;//達成2048
				return is2048;//直接返回,結束迴圈
			}
		}
	}
	return is2048;
}



/**
 * 是否不能移動或消除
 * 1.佔滿螢幕
 * 2.不能做上下左右任意的消除
 * @return    false  不能移動      true  可以移動
 */
public boolean canMove(){
	boolean canMove = false;
	if(isFull()){//佔滿螢幕
		//判斷不能上下左右消除
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				//能上消除  並且要排除掉最下面的一行  
				if(j<3&&gameMap[i][j]==gameMap[i][j+1]){
					canMove = true;
					return canMove;
				}
				//下消除
				if(j>0&&gameMap[i][j]==gameMap[i][j-1]){
					canMove = true;
					return canMove;
				}
				//左消除
				if(i<3&&gameMap[i][j]==gameMap[i+1][j]){
					canMove = true;
					return canMove;
				}
				//右消除
				if(i>0&&gameMap[i][j]==gameMap[i-1][j]){
					canMove = true;
					return canMove;
				}
			}
		}
	}
	return canMove;
}

/**
 * 是否佔滿螢幕
 * 遍歷gameMap,全部不為0
 * @return
 */
public boolean isFull(){
	boolean isFull = true;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			if(gameMap[i][j]==0){//未佔滿螢幕
				isFull = false;
				return isFull;
			}
		}
	}
	return isFull;
}

}

package com.lyc.java2048;

import javax.swing.JOptionPane;

public class OptionPanel {

public static int showExitDialog(){
	return JOptionPane.showConfirmDialog(
			null, 
			"               退出遊戲?", 
			"遊戲提示", 
			JOptionPane.OK_CANCEL_OPTION);
}

public static int showRestartDialog(){
	return JOptionPane.showConfirmDialog(
			null, 
			"    重新開始遊戲?", 
			"遊戲提示", 
			JOptionPane.OK_CANCEL_OPTION);
}


public static int showGameOverDialog(Data data){
	/*
	 * JOptionPane.showOptionDialog
	 * (parentComponent, message, title, optionType, messageType,icon, options, initialValue)
	 * 
	 * JOptionPane:彈出要求使用者提供值或向其發出通知的標準對話方塊
	 * showOptionDialog:詢問一個確認問題,如 yes/no/cancel。
	 * parentComponent:定義父對話方塊,設定為null則預設的 Frame 用作父級
	 * message:要置於對話方塊中的描述訊息,型別是 Object[]
	 * title:對話方塊的標題
	 * optionType:定義在對話方塊的底部顯示的選項按鈕的型別  YES_NO_OPTION
	 * messageType:定義 message 的樣式   QUESTION_MESSAGE
	 * icon:要置於對話方塊中的裝飾性圖示。設定為null圖示的預設值由 messageType 引數確定
	 * options:將在對話方塊底部顯示的選項按鈕集合的更詳細描述。引數型別是 Object[] 
	 * initialValue:預設選擇    
	 */
	Object[] options = {"重新開始","退出遊戲"};
	return JOptionPane.showOptionDialog(
			null,
			"\no(╯□╰)o 遊戲結束!\n      分數為:"+data.getScore(), 
			"遊戲提示", 
			JOptionPane.YES_NO_OPTION, 
			JOptionPane.QUESTION_MESSAGE, 
			null, 
			options, 
			options[0]);
}

}

package com.lyc.java2048;

import javax.swing.JFrame;

/**

  • 遊戲啟動類
  • @author Administrator

*/
public class StartGame {

public static void main(String[] args) {

	JFrame frame = new JFrame();//建立一個視窗
	
	Game_2048 panel = new Game_2048();//自定義面板
	
	frame.add(panel);//視窗裝載自定義面板
	
	frame.addKeyListener(panel);//新增監聽器
	
	frame.setSize(505, 600);
	
	frame.setLocationRelativeTo(null);//居中
	
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//關閉視窗且關閉程式
	
	frame.setResizable(false);//窗體不可改變大小
	
	frame.setVisible(true);
}

}