JAVA面向對象小程序(二):俄羅斯方塊s

今天,我們來使用Eclipse製作一個小遊戲《Tetris~》

資源在文末

一、遊戲機制

 

a)    7種小塊兒隨機生成

               i.         一塊放置在頂部(隨時間下降)

              ii.         另一塊置於右側,提示下一個隨機生成塊

             iii.         七種形狀:“O、L、J、I、T、S、Z”

            

        

b)   牆與邊界

               i.         小塊的移動範圍在邊框之內;

              ii.         第一個小塊下降到最底部,觸底碰“牆”,自身嵌入“牆”;

             iii.         下一小塊兒的其中某個小方格,底部碰到“牆”,自身嵌入“牆”;

 

 

c)    小塊兒的下降、左移、右移、旋轉與平移

 

              i.         添加鍵盤監聽事件

             ii.         不允許方塊的越界

             iii.         確定旋轉中心,以旋轉中心的相對位置做其餘小塊的行列號修改

 

d)   消除與積分

               i.         當某一排(col)被小方格佔滿,消除某一排

              ii.         被消除排 的上方所有小方塊整體下移

             iii.         積分累加

f)   遊戲的三種狀態:下落、暫停與重新開始

g)    遊戲結束的條件

               i.         最上方的生成區域被佔,GameOver

 

二、設計需求 / 編程思想

【遵循javabean規範】

       (1)添加兩個(至少)構造器【一個無參數,一個有參數】
       (2)屬性一般爲私有化【外部類訪問不到】
       (3)提供公有的get/set 【提供訪問途徑】
       (4)toString用來描述對象信息
           重寫toString屬性的信息
       (5)重寫equals 【對象的值】
       (6)重寫hashCode 【對象的哈希碼(地址)】

 

 

幾大部件:【我們要把這幾個傢伙抽象到類 (class) 中】
 

1.單個的小方格——class  Cell

        俄羅斯方塊中的最小單位:小方格
        * 特徵:屬性
        * row--行號 

        * col--列號
 
       * image--對應圖片
 
       
 
       * 行爲:
 
       * 向左移動
 
       * 向右移動
 
       * 向下移動

對於image屬性,我們可以添加BufferImage類的對象image

記得導包(import)

2.每個不同形狀的四小塊兒組(7種形狀)——class  X  extends Tetromino

        形狀有:O、L、J、I、T、S、Z

* Cell類繼承4塊整體(Trtromino)的屬性

* 構造器初始化

         * Cell[] cells = new Cell[4];

         * Cell(行號,列號,BufferedImage image)構造器

        整體的四種狀態(旋轉),其中S、Z、I型可以只有兩種狀態,O只有一種狀態

         *states = new State[4];

         *

*/

3.四個小方格組成一個塊兒組——class  Tetromino

 

   四格方塊作爲一個整體

   共同特徵:七種組合的父類

        cell--四個方塊(Cell 數組存放)--protected Cell[ ] cells = new Cell[4]; //4個小方格創建,初值爲null

        修飾詞protected【子類可訪問】

====================================================================

共同行爲:向左向右..
 * 屬性:
 * 4cells 
 * 行爲:
 * 重寫toString
 * 
 * moveLeft()
 * moveRight()

 * softDrop()軟下降——【按鍵盤'↓'只降一格】

=====================================================================

         四種狀態:(相對與旋轉中心)

         按照順時針順序:

   

 

4.主類——Tetris--------- extends JPanel

        此類作爲程序的入口,加載靜態資源【圖片緩衝區】

        擁有主方法main(){}

        在主方法中添加:

        1.JFrame(窗體框框)

        2.JPanel(面板&畫筆)

        (1).面板會自動調用繪製方法paint(Graphics g)
        (2).重寫paint方法,繪製圖片背景
        (3).繪製網格和嵌入牆中的方塊

        3.在Panel中調用主邏輯start( )

====================================================================

        把遊戲的主邏輯封裝入start( )方法中

        1.KeyListener(開啓鍵盤監聽)

              封裝匿名內部類  keyPressed(KeyEvent e){

                                        【注意:k要小寫】

                                        【Action後馬上重繪】
                                          repaint();

                                       };

        2.面板添加監聽事件

        3.當前對象設置成焦點

        4.設置程序睡眠【每300ms生成新的小塊兒】

三、代碼實現

 

首先強調,Leo遵循的編寫順序爲:

    (1).Cell類——小細胞

    (2).Tetromino類——四個小細胞組成一個整體

    (3).七種形狀類“O、L、J、I、T、S、Z”——繼承Tetromino的屬性和行爲

    (4).最後是主類Tetris——作爲遊戲的主邏輯程序的入口

1.Cell類

import java.awt.image.BufferedImage;
/*
 * 俄羅斯方塊中的最小單位:小方格
 * 特徵:屬性
 * 		row--行號 
 * 		col--列號
 * 		image--對應圖片
 * 
 * 		行爲:
 * 		向左
 * 		向右
 * 		向下
 */
public class Cell {
	private int row;  //行號
	private int col;  //列號
	private BufferedImage image;

	public Cell(int row, int col, BufferedImage image) {
		super();
		this.row = row;
		this.col = col;
		this.image = image;
	}

	

	public int getRow() {
		return row;
	}

	public void setRow(int row) {
		this.row = row;
	}

	public int getCol() {
		return col;
	}

	public void setCol(int col) {
		this.col = col;
	}

	public BufferedImage getImage() {
		return image;
	}

	public void setImage(BufferedImage image) {
		this.image = image;
	}

	public void left(){
		col --;
	}
	public void right(){
		col ++;
	}
	public void drop(){
		row ++; 
	}



	@Override
	public String toString() {
		return "(" + row + ", " + col + ")" ;
	}
	
}

2.Tetromino類

 

import java.util.Arrays;
/*
 * 4格方塊作爲一個整體
 * 屬性:
 * 		4cells 
 * 行爲:
 * 		重寫toString
 * 
 * 		moveLeft()
 * 		moveRight()
 * 		softDrop()軟下降,按↓箭頭只下移一個單位
 */
public class Tetromino {
	protected Cell[] cells = new Cell[4];	//4個null
	//每個方塊都向左  一個單位移動
	public void moveLeft(){
		for (int i = 0; i < cells.length; i++) {
			Cell cell = cells[i];
			cell.left();
		}
	}
	//每個方塊都向右  一個單位移動
	public void moveRight(){
		for (int i = 0; i < cells.length; i++) {
			Cell cell = cells[i];
			cell.right();
		}
	}
	//每個方塊都向下  一個單位移動
	public void softDrop(){
		//強循環數組遍歷
		for(Cell c:cells){
			c.drop();
		}
	}
	@Override
	public String toString() {
		return  Arrays.toString(cells) ;
	}
	public static Tetromino randomOne(){
		//隨機生成4格方塊
		//四小塊作爲一整體,t賦爲null
		Tetromino t = null ;
		int num = (int)(Math.random()*7);
		switch(num){
				case 0:t=new T();break;
				case 1:t=new O();break;
				case 2:t=new L();break;
				case 3:t=new J();break;
				case 4:t=new I();break;
				case 5:t=new Z();break;
				case 6:t=new S();break;
		}
		return t;
		
	}
	public static Tetromino randomOne(){
		//隨機生成4格方塊
		//四小塊作爲一整體,t賦爲null
		Tetromino t = null ;
		int num = (int)(Math.random()*7);
		switch(num){
				case 0:t=new T();break;
				case 1:t=new O();break;
				case 2:t=new L();break;
				case 3:t=new J();break;
				case 4:t=new I();break;
				case 5:t=new Z();break;
				case 6:t=new S();break;
		}
		return t;	
	}
	
	//旋轉四格方塊:順時針
	public void rotateRight(){
		//旋轉一次,計數器自增1
		count++;
		State s = state[count%state.length];
		//需要獲取軸的行號和列號
		Cell c = cells[0];
		int row = c.getRow();
		int col = c.getCol();
		cells[1].setRow(row+s.row1);
		cells[1].setCol(col+s.col1);
		cells[2].setRow(row+s.row2);
		cells[2].setCol(col+s.col2);
		cells[3].setRow(row+s.row3);
		cells[3].setCol(col+s.col3);
	}
	//旋轉四格方塊:逆時針
	public void rotateLeft(){
		//旋轉一次,計數器自減1
		count--;
		State s = state[count%state.length];
		//需要獲取軸的行號和列號
		Cell c = cells[0];
		int row = c.getRow();
		int col = c.getCol();
		cells[1].setRow(row+s.row1);
		cells[1].setCol(col+s.col1);
		cells[2].setRow(row+s.row2);
		cells[2].setCol(col+s.col2);
		cells[3].setRow(row+s.row3);
		cells[3].setCol(col+s.col3);
	}
	/*
	 * 定義內部類,用於封裝每次
	 * 旋轉後的相對於軸的其他三個小格子的行列號
	 */
	public class State{
		/*
		 * 設置8個座標int
		 * 分別存儲四格方塊元素的相對位置
		 */
		int row0,col0;
		int row1,col1;
		int row2,col2;
		int row3,col3;
		public State() {}
		public State(int row0, int col0, int row1, int col1, int row2, int col2, int row3, int col3) {
			super();
			this.row0 = row0;
			this.col0 = col0;
			this.row1 = row1;
			this.col1 = col1;
			this.row2 = row2;
			this.col2 = col2;
			this.row3 = row3;
			this.col3 = col3;
		}
	
}

3.七種形狀方塊兒 X 類——extends Tetromino

下面以“T”形狀爲例

public class T extends Tetromino{
	/*
	 * 繼承4塊整體(Trtromino)的屬性
	 * 構造器初始化
	 * T型四格方塊的位置
	 * 000000
	 *   00
	 */
	public T(){
		/*
		 * Cell[] cells = new Cell[4];
		 * Cell(行號,列號,BufferedImage image)構造器
		 */
		cells[0]= new Cell(0,4,Tetris.T);    // 初始狀態旋轉中心的座標
		cells[1]= new Cell(0,3,Tetris.T);    // 初始狀態1號小細胞..
		cells[2]= new Cell(0,5,Tetris.T);    // 初始狀態2號...
		cells[3]= new Cell(1,4,Tetris.T);    // ...
		state = new State[4];                // 【注意】,不要重新聲明State,在Tetromino.class中已聲明過了
		//第一個狀態
		state [0] = new State(0,0, 0,-1, 0,1, 1,0);
		//第二個狀態
		state [1] = new State(0,0, -1,0, 1,0, 0,-1);
		state [2] = new State(0,0, 0,1, 0,-1, -1,0);
		state [3] = new State(0,0, 1,0, -1,0, 0,1);
	}
}

 

4.Tetris主類——extends JPanel(待續)


import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/*
 * 俄羅斯方塊的主類:
 * 前提:	一塊麪板JPanel,可以嵌入窗口
 * 		面板上自帶一個畫筆,有一個功能,自動繪製。
 * 		調用了JPanel
 * 加載靜態資源
 * 
 */
import java.awt.image.BufferedImage;
import java.util.Arrays;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Tetris extends JPanel{
	/* 
	 * 俄羅斯方塊的主類
	 * 功能:	1.加載靜態資源(背景圖,7種形狀的原始圖)
	 * 		2.設置窗口
	 * 		3.重寫JPanel中的paint()方法,構造器中添加畫筆Graphics g
	 * 前提:是一塊麪板Panel,可被嵌入窗口
	 * 屬性:1.正在下落的方塊
	 * 		2.即將下落的方塊
	 * 		3.添加牆(方格),畫舉矩形
	 * 
	 */
	//屬性:正在下落的方塊
	private Tetromino currentOne = Tetromino.randomOne();
	//屬性:即將下落的方塊
	private Tetromino nextOne = Tetromino.randomOne();
	//屬性:牆 20行10列的方格
	private Cell[][] wall= new Cell[20][10];
	//屬性:統計分數
	int [] scoresPool = {0,1,3,5,10};
	private int totalScore = 0;
	private int totalLine = 0;
	//圖片加載:靜態
	private static final int CELL_SIZE = 26;
	public static BufferedImage T;
	public static BufferedImage I;
	public static BufferedImage O;
	public static BufferedImage J;
	public static BufferedImage L;
	public static BufferedImage S;
	public static BufferedImage Z;
	public static BufferedImage background;
	
	static {
		try{
			/*
			 * getResource(String url)
			 * url:加載圖片的路徑
			 * 操作:將圖片文件拖入package Tetris_Day01
			 * 相對位置是同一個包下
			 */
			T = ImageIO.read(Tetris.class.getResource("T.png"));
			I = ImageIO.read(Tetris.class.getResource("I.png"));
			O = ImageIO.read(Tetris.class.getResource("O.png"));
			J = ImageIO.read(Tetris.class.getResource("J.png"));
			L = ImageIO.read(Tetris.class.getResource("L.png"));
			S = ImageIO.read(Tetris.class.getResource("S.png"));
			Z = ImageIO.read(Tetris.class.getResource("Z.png"));
			background = ImageIO.read(Tetris.class.getResource("tetris.png"));
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	/*
	 * 重寫JPanel中的paint()方法
	 */
	public void paint(Graphics g){
		//繪製背景(圖片,橫座標,縱座標,observer)
		/*
		 * g是畫筆
		 * g.drawImage(image,x,y,null)
		 * x,y爲開始繪製時的座標
		 */
		g.drawImage(background, 0, 0, null);
		//平移座標軸
		g.translate(15, 15);
		//繪製牆
		paintWall(g);
		//繪製正在下落的4格方塊
		paintCurrentOne(g);
		//繪製下一個將要繪製的4格方塊
		paintNextOne(g);
		
	}
	/*
	 * 繪製下一個將要繪製的4格方塊
	 * 繪製到右上角相應區域
	 */
	public void paintNextOne(Graphics g) {
		//獲取Next對象的4個小方格
		Cell[] cells = nextOne.cells;
		for (Cell c : cells){
			//獲取每一個元素的行列號
			int row = c.getRow();
			int col = c.getCol();
			//橫座標
			int x = col*CELL_SIZE+260;
			int y = row*CELL_SIZE+26;
			g.drawImage(c.getImage(), x, y, null);
		}
	}
	
	public void paintCurrentOne(Graphics a){
		//Cell類型的數組指向    4方格整體的對象currentOne的cells數組
		Cell[] cells = currentOne.cells; 
		for (Cell c : cells) {
			int x = c.getCol()*CELL_SIZE;
			int y = c.getRow()*CELL_SIZE;
			a.drawImage(c.getImage(), x, y, null);
		}
	}
	public void paintWall(Graphics a){
		//外層循環控制行數
		//內層循環控制列數
		for (int i = 0; i < 20; i++) {
			for (int j = 0; j < 10; j++) {
				int x = j * CELL_SIZE;
				int y = i * CELL_SIZE;
				Cell cell = wall[i][j];
				if(cell == null)
				{
					a.drawRect(x , y, CELL_SIZE, CELL_SIZE);
				}else {
					a.drawImage(cell.getImage(),x,y,null);
				}
				
			}
		}
	}
	//所有的主邏輯
	public void start(){
		//開啓鍵盤監聽事件
		KeyListener l = new KeyAdapter(){
			
			//匿名內部類
			@Override
			public void keyPressed(KeyEvent e) {
				int code = e.getKeyCode();
				switch(code){
				case KeyEvent.VK_DOWN:
					softDropAction();break;
				case KeyEvent.VK_LEFT:
					moveLeftAction();break;
				case KeyEvent.VK_RIGHT:
					moveRightAction();break;
				case KeyEvent.VK_UP:
					rotateRightAction();break;
				case KeyEvent.VK_SPACE:
					handDropAction();
				}
				//Action後馬上重繪
				repaint();
			}
		};
		//面板添加監聽事件
		this.addKeyListener(l);
		//當前對象設置成焦點
		this.requestFocus();
		while(true){
			/*
			 * 當程序運行到此,進入睡眠狀態
			 * 睡眠時間爲300毫秒
			 */
			try {
				Thread.sleep(900);
			} catch (InterruptedException e) {
				//打斷異常
				e.printStackTrace();
			}
			//判斷可以下落
			if(canDrop()){
				currentOne.softDrop();
			}else{
				landToWall();
				//將下一個下落的四格方塊賦值給CurrentOne
				destroy();
				currentOne = nextOne;
				nextOne = Tetromino.randomOne();
			}
			/*
			 * 下落之後,重新進行繪製
			 * 纔會在看到下一步的動作
			 * repaint方法,也是JPanel類的方法
			 * 此方法調用paint
			 */
			repaint();
		}
	}
	/*
	 *滿一行就消除,上方所有方塊向下移 
	 */
	public void destroy(){
		//統計銷燬行的次數
		int lines = 0;
		
		Cell[] cells = currentOne.cells;
		for (Cell c : cells){
			//取出每個元素的行號
			int row = c.getRow();
			while (row<20){
				if(isFullLine(row)){
					lines++;
					wall[row] = new Cell[10];
					for(int i = row;i>0;i--){
						System.arraycopy(wall[i-1], 0, wall[i], 0, 10);
					}
					wall[0] = new Cell[10];
				}
				row++;
			}		
		}
		totalScore += scoresPool[lines];
		
	}
	//判斷行是否爲
	public boolean isFullLine(int row) {
		Cell[] line = wall[row];
		
		for(Cell r : line){
			if(r == null){
				return false;
			}
		}
		return true;
	
		
	}
	//順時針旋轉的動作
	public void rotateRightAction() {
		currentOne.rotateRight();
		//先判斷越界,再判斷重合
		if(outOfBounds()||coincide()){
			currentOne.rotateLeft();
		}				
	} 
	
	protected void moveRightAction() {
		currentOne.moveRight();
		//先判斷越界,再判斷重合
		if(outOfBounds()||coincide()){
			currentOne.moveLeft();
		}
		
	}
	//監聽使用left鍵控制向左的行爲
	protected void moveLeftAction() {
		currentOne.moveLeft();
		//先判斷越界,再判斷重合
		if(outOfBounds()||coincide()){
			currentOne.moveRight();	//越界,到-1,再移動回來
		}
	}
	//判斷越界
	public boolean outOfBounds(){
		Cell[] cells = currentOne.cells;
		for(Cell c : cells){
			int col = c.getCol();
			int row = c.getRow();
			if(col<0 || col>9 || row>19 || row<0){
				return true;
			}
		}
		return false;
	}
	//判斷重合
	public boolean coincide(){
		Cell[] cells = currentOne.cells;
		for(Cell c:cells){
			int row = c.getRow();
			int col = c.getCol();
			if(wall[row][col]!=null){
				return true;
			}
		}
		return false;
	}
	public void softDropAction(){
		if(canDrop()){
			currentOne.softDrop();
		}else{
			//將下一個下落的四格方塊賦值給CurrentOne
			landToWall();
			destroy();
			currentOne = nextOne;
			nextOne = Tetromino.randomOne();
		}
	}
	
	public void handDropAction(){
//		while(canDrop()){
//			
//		}
		for(;;){
			if(canDrop()){
				currentOne.softDrop();
			}else{
				break;
			}
		}
		landToWall();
		destroy();
		currentOne = nextOne;
		nextOne = Tetromino.randomOne();
	} 
	public boolean canDrop(){
		Cell[] cells = currentOne.cells;
		/*
		 * 4格拿出來,遍歷
		 */
		
		for(Cell c:cells){
			/*
			 * 獲取每個元素的行、列號
			 * 判斷:只要有一個元素的下一行有塊
			 * 或 有一個元素到最後一行
			 * 就不能下落了
			 */
			int row = c.getRow();
			int col = c.getCol();
			if(row == 19){
				return false;
			}
			if(wall[row+1][col]!=null){
				return false;
			}
		}
		return true;
	}
	public void landToWall(){
		Cell[] cells = currentOne.cells;
		/*
		 * 4格拿出來,遍歷
		 */
		for(Cell c:cells){
			int row = c.getRow();
			int col = c.getCol();
			wall[row][col] = c;
		}
	}
	public static void main(String[] args) {
																//1.創建窗口對象
		JFrame frame = new JFrame("Tetris~");	
		frame.setVisible(true);									//2.可見性
		frame.setSize(535, 580);								//3.size
		frame.setLocationRelativeTo(null); 						//4.居中
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 	//5.關閉並終止遊戲
		
		Tetris panel = new Tetris();							//6.創建遊戲界面,即面板
		frame.add(panel);										//7.將面板嵌入窗口
//		panel.setBackground(Color.yellow); 						//8.先把面板畫成yellow
		//其實調用的是JPanel中的paint()方法
		
		//遊戲的主要邏輯封裝在start()
		panel.start();
	}
}

鏈接:https://pan.baidu.com/s/1wswwkTyDRdNGs_TUKYrlPw 密碼:t5tj

將資源解壓後拷貝至package下 


    

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章