1. 程式人生 > >俄羅斯方塊的小實現

俄羅斯方塊的小實現

最近在老師帶領下做了一個俄羅斯方塊的小遊戲~最後執行介面是這樣的:小方格和背景的圖片都是現成的,大致思路是:代表每個小方格移動的Cell類->組成好的塊(四格小方格組成的大塊)的類Tetromino->表示每個具體圖形的位置和顏色的類(一共七個)->進行遊戲具體操作的類TetrisCell類比較簡單,屬性有行、列和屬性,提供相應的get、set函式,以及每個小塊的向左、向右、向下移動。Tetromino類也同樣,整合了Cell類,其中的方法實現四格方塊整體的向左、向右、向下移動,以及隨機生成一個正在下落的塊和將要下落的塊。Tetris類就比較複雜啦..首先是初始介面。介面需要繼承JPanel類,重寫paint方法實現在面板上自動繪製圖形,JFrame與JPanel的區別我在網上查詢到的是:Jpanel不是頂級視窗,不能直接輸出。它必須放在像JFrame這樣的頂級視窗上才能輸出
JFrame只是一個介面,也就是個框架,要想把控制元件放在該介面中,必須把控制元件放在JPanel中,然後再把JPanel放在JFrame中,JPanel作為一個容器使用。表示小方塊和背景的圖片需要放到和程式一個包下,設定為靜態,每次初次載入程式即可載入完成,沒有卡頓的可能性。
public static  BufferedImage T;   //T形狀的方塊 以下以此類推
public static  BufferedImage I;
新增圖片的過程也是靜態的,載入一次即可。
static {
   try {
	T=ImageIO.read(Tetris.class.getResource("T.png"));  //其他以此類推
	background=ImageIO.read(Tetris.class.getResource("tetris.png"));
	}catch(Exception e)
	{
	       e.printStackTrace();
	}
}
然後是繪製格子。
if(cell==null) {
	g.drawRect(x, y, CELL_SIZE, CELL_SIZE);}  //x表示橫座標
else {
        g.drawImage(cell.getImage(),x,y,null);}	
通過兩層for迴圈實現繪製格子,在內層迴圈中如果wall[i][j]位置為空,則繪製一個空的格子,如果不空,則繪製該位置應有的格子圖片,即為了在每個塊落到底部時能被繪製出來。然後是繪製正在和即將下落的方塊。
需要呼叫在Tetromino類中寫好的隨機獲取某個方塊的函式(隨機0-6,每個數字代表一個圖形),獲取後確定了圖形的形狀和初始位置,繪製出來即可。接下來是圖形的下落過程。
重寫paint方法,在方法中繪製牆、正在下落和即將下落的方塊。paint方法將被自動呼叫,不需要顯式呼叫。到現在玩遊戲前的頁面就做好了。

Tetris類的屬性有正在下落、即將下落的四格方塊組成的圖形(有七種)和牆(方塊在牆上移動,牆為20*10的方格)。

private Tetromino currentOne=Tetromino.randomOne();
private Tetromino  nextOne=Tetromino.randomOne();
private Cell[][] wall=new Cell[20][10]; //設定牆為20*10的方格組合 private static final int CELL_SIZE=26; //寬度為26接下來是玩遊戲的方法,也是遊戲的主要邏輯。定義start方法,在其中建立一個鍵盤監聽器物件,接受鍵盤的下左右事件,並重新繪製新位置的圖形(repaint方法即可實現,repaint()通過呼叫執行緒再由執行緒去呼叫update()方法清除當前顯示並再呼叫paint()方法進行繪製下一個需要顯示的內容.這樣就起到了一種圖片的交替顯示從而在視角上形成了動畫,update()即用來清除當前顯示並呼叫paint()方法)。為了使程式的執行速度看起來慢一些(即為了防止塊下落的過快而人來不及反應),設定了程式的休眠時間為300毫秒。也需要判斷是否能繼續下落,即下一行的格有沒有方塊或者有沒有到達底部,如果沒有則繼續下落,如果有則停止下落,在牆上的相應位置設定有方塊(即非null),並更新下一個圖形。再呼叫repaint函式,使鍵盤沒有任何操作時,形仍能繼續下落。在鍵盤有操作時,需要判斷是按了哪個鍵,這件事鍵盤監聽的物件即可做到。不同的鍵選擇不同的操作。在判斷沒有左右出界和即將移動的地方沒有其他方塊後,呼叫Tetromino中的移動函式,移動整個圖形,如果不再能改變位置,則在牆上表明位置然後繪製這個圖形。(在此處需要在鍵盤產生時間後立刻改變牆的位置,在下次畫圖時能直接畫到移動到的位置,如果按很多次下而不實時改變牆中的位置,則會影響遊戲體驗,方塊並沒有因為按動的頻率加快而速度加快,不符合遊戲人的需要)。在移動時,先向該移動的方向移動,如果出界或重合,則再向反方向移動一次。判斷左右出界時,如果列小於0或者大於9則出界,判斷即將移動的位置有無方塊判斷牆的對應位置是否為空即可。判斷這個時不能先判斷有無重合,因為在這種操作中可能存在列號為-1時,判斷重合函式會報錯,如果把判斷出界放在前面,即會捕捉這個錯誤,或語句也不會繼續執行。上述即是目前簡單實現方塊移動的過程,之後還會實現圖形的旋轉~附全部程式碼:Cell類:
import java.awt.image.BufferedImage;
/**
 * 俄羅斯方塊中的最小單位-方格
 * 屬性:row col image
 * 行為(方法):left() right() down()
 */
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 Cell() {
		super();
	}
	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;
	}
	@Override
	public String toString() {
		return "Cell [row=" + row + ", col=" + col + ", image=" + image + "]";
	}
	
	/* 向左移動*/
	public void left()
	{
		col--;
	}
	/* 向右移動*/
	public void right()
	{
		col++;
	}
	/* 向下移動*/
	public void drop()
	{
		row++;
	}
}
Tetromino類:
/**
 *  四格方塊
 *  屬性:
 *    --cells ---四個方塊
 *  行為;
 *    moveLeft()
 *    moveRight()
 *    softDrop()
 *
 */
public class Tetromino {
	protected Cell[] cells=new Cell[4];
	public void moveLeft()
	{
		for(Cell c:cells)
			c.left();
	}
	public void moveRight()
	{
		for(int i=0;i<4;i++)
			cells[i].right();
	}
	public void softDrop()
	{
		for(int i=0;i<4;i++)
			cells[i].drop();
	}
	/*
	 * 隨機生成一個四格方塊
	 */
	public static Tetromino randomOne() {
		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 I();break;
		case 3: t=new J();break;
		case 4: t=new L();break;
		case 5: t=new S();break;
		case 6: t=new Z();break;
		}
		return t;
	}
}
Tetris類:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

/*
 * 俄羅斯方塊的主類
 * 載入靜態資源
 * 前提:必須是面板JPanel,可以嵌入視窗
 * 面板上自帶一個畫筆,有一個功能:自動繪製
 * 其實是呼叫了JPanel裡的paint()
 */
public class Tetris extends JPanel{
	/*
	 * 屬性:正在下落的四格方塊
	 *      將要下落的四格方塊
	 */
	private Tetromino  currentOne=Tetromino.randomOne();
	private Tetromino  nextOne=Tetromino.randomOne();
	/*
	 * 屬性:牆 20*10的方格  寬度為26
	 */
	private Cell[][] wall=new Cell[20][10];
	private static final int CELL_SIZE=26;
	
	public static  BufferedImage T;   //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 {
			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();
		}
	}
	public void paintWall(Graphics g) {
		//外層迴圈控制行數
		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) {
					g.drawRect(x, y, CELL_SIZE, CELL_SIZE);
				}
				else {
					g.drawImage(cell.getImage(),x,y,null);
				}		
			}
		}
		
	}
	public void paintCurrentOne(Graphics g)
		{
			Cell[] cells=currentOne.cells;
				for(int i=0;i<cells.length;i++)
				{
					int x=cells[i].getCol()*CELL_SIZE;
					int y=cells[i].getRow()*CELL_SIZE;
					g.drawImage(cells[i].getImage(), x, y, null);
					}
			}
	//繪製即將下落的另一個
	public void paintNextOne(Graphics g)
	{
		Cell[] cells=nextOne.cells;
		for(Cell cell:cells)
		{
			int col=cell.getCol();
			int row=cell.getRow();
			int x=col*CELL_SIZE+260;
			int y=row*CELL_SIZE+26;
			g.drawImage(cell.getImage(), x, y, null);
			}
	}
	//重寫JPanel中的paint方法
	public void paint(Graphics g)
	{
		/*
		 * 背景  g:畫筆  g.drawImage(image,x,y,null)
		 * image要繪製的圖片
		 * x y 為開始繪製的橫縱座標
		 * 
		 */
		g.drawImage(background, 0, 0,null);
	    //平移座標軸
		g.translate(15, 15);
		//繪製牆
		paintWall(g);
		//繪製正在下落的四格方塊
		paintCurrentOne(g);
		//繪製即將下落的四格方塊
		paintNextOne(g);
	}
	/*
	 *封裝了遊戲的主要邏輯 
	 */
	public void start()
	{
		//開啟鍵盤監聽事件
		KeyListener listener=new KeyAdapter() {
			/*
			 * keyPressed()是鍵盤按鈕按下去所呼叫的方法
			 */
			@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;
				}
				repaint();
	
			}
		};
		//面板新增事件監聽物件listener
		this.addKeyListener(listener);
		//面板物件設定成焦點
		this.requestFocus();
		
		while(true)
		{
			/*
			 * 當程式執行到此,會進入休眠狀態
			 * 睡眠時間為200毫秒,單位為毫秒
			 * 300毫秒之後,會自動執行後續程式碼
			 */
			try {
				Thread.sleep(300);
			}catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			if(canDrop())
			{
				currentOne.softDrop();
			}
			else {
				LandToWall();
				currentOne=nextOne;
				nextOne=Tetromino.randomOne();
			}
			/*
			 * 下落之後,需要重新繪製,才會看到下落後的位置
			 * repaint方法發也是JPanel類中 提供的
			 * 此方法呼叫paint方法
			 */
			repaint();
		}
	}
	/*
	 * 使用down控制四格方塊的下落
	 */
	public void softDropAction() {
		if(canDrop())
		{
			currentOne.softDrop();
		}
		else {
			LandToWall();
			currentOne=nextOne;
			nextOne=Tetromino.randomOne();
		}
	}
	/*
	 * 使用left鍵控制向左的行為
	 */
	public void moveLeftAction() {
		//沒出界或者沒和左面的方塊重合
		currentOne.moveLeft();;
		if(outOfBounds()||coincide())
		{
			currentOne.moveRight();;
		}
		}
	private boolean coincide() {
		Cell[] cells=currentOne.cells;
		for(Cell cell:cells) {
			int row=cell.getRow();
			int col=cell.getCol();
			if(wall[row][col]!=null)
			{
				return true;
			}
		}
		return false;
	}
	private boolean outOfBounds() {
		Cell[] cells=currentOne.cells;
		for(Cell cell:cells) {
			int col=cell.getCol();
			if(col<0||col>9)
				return true;
		}
		return false;
	}
	public void moveRightAction() {
		//沒出界或者右面的方塊重合
		currentOne.moveRight();
		//不可以改變這兩個函式的位置  因為coincide裡的陣列不允許引數為-1的時候
		if(outOfBounds()||coincide())
		{
			currentOne.moveLeft();
		}
}
	
	/*
	 * 判斷是否下落
	 */
	public boolean canDrop()
	{
		Cell[] cells=currentOne.cells;
		for(Cell cell:cells) {
			/*
			 * 獲取每個元素的行號和列號
			 * 判斷
			 * 只要有一個元素的下一行有方塊
			 * 或者只要有一個元素到達最後一行
			 * 就不能再下落了
			 */
			int row=cell.getRow();
			int col=cell.getCol();
			if(row==19) {
				return false;
			}
			if(wall[row+1][col]!=null) {
				return false;
			}
		}
		return true;
 	}
	/*
	 * 當不能再下落時,需要將四格方塊,嵌入到牆中
	 * 也就是儲存在二維陣列中相應位置中
	 */
	public void LandToWall() {
		Cell[] cells=currentOne.cells;
		for(Cell cell:cells) {
			int row=cell.getRow();
			int col=cell.getCol();
			wall[row][col]=cell;
		}
	} 
	
	//啟動遊戲的入口
	public static void main(String[] args) {
		//1、建立一個視窗物件
		JFrame frame=new JFrame("俄羅斯方塊");
		//建立遊戲介面即面板
		Tetris panel=new Tetris();
		//將面板嵌入視窗
		frame.add(panel);
		//2、設定為可見
		frame.setVisible(true);
		//3、設定視窗大小尺寸
		frame.setSize(535, 600);
		//4、設定視窗居中
		frame.setLocationRelativeTo(null);
		//5、設定視窗關閉,即程式終止
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		panel.setBackground(Color.yellow);
		panel.start();
		
	}
	
}
各個圖形對應的位置: