1. 程式人生 > >廣工JAVA大作業——遊戲程式(俄羅斯方塊)

廣工JAVA大作業——遊戲程式(俄羅斯方塊)

效果:





1. 概述

隨著時代的發展,電子遊戲逐漸出現,早起的一些桌面小遊戲風靡全球,其中就有《俄羅斯方塊》,《俄羅斯方塊》(Tetris)是一款由俄羅斯人阿列克謝·帕基特諾夫1984年6月發明的休閒遊戲。該遊戲曾經被多家公司代理過。經過多輪訴訟後,該遊戲的代理權最終被任天堂獲得。任天堂對於俄羅斯方塊來說意義重大,因為將它與GB搭配在一起後,獲得了巨大的成功。《俄羅斯方塊》的基本規則是移動、旋轉和擺放遊戲自動輸出的各種方塊,使之排列成完整的一行或多行並且消除得分,上手簡單、老少皆宜、家喻戶曉。本文將詳述我個人開發的一款基於這款遊戲的簡易俄羅斯方塊,實現該遊戲基本功能,如自動出方塊,可翻轉、左移、右移、下降,暫停,增加難度,降低難度並且達到消行加分的功能。

2. 系統分析

 

系統分為兩大板塊,一個是方塊工廠,另一個是遊戲顯示畫板,方塊工廠用來生成方、翻轉、移動和固定,遊戲畫板用於繪畫,並加上定時器和監聽器,用於反饋使用者按鍵事件,傳遞到方塊工廠來控制方塊,並且檢查方塊是否碰撞或消行等狀態

3. 系統設計,

3.1系統目標

①在頂部生成方塊

②方塊反轉、左移、右移、下降

③方塊在特定的遊戲區域內運動

④定時器定一特定長時間作為時間間隔來觸發ActionEvent使方塊定時下落一格

⑤檢測是否碰撞(包括與圍牆,底部,已固定方塊),碰撞則停止並生成新的方塊下落

⑥檢查是否能消行,且消行要加分記錄並顯示在介面上,並且放出beep音效

⑦生成方塊前檢查最頂行有沒有方塊,有則彈框提示結束遊戲並顯示所得分數,

  按確認後重新開始遊戲。

⑧遊戲區域中,圍牆隱藏起來,方塊可運動區域有網格,右邊有分數顯示且有操作方法提示,方塊下落時藍色,固定時為綠色,便於區分。

⑨開始執行時彈對話方塊是否開始遊戲

⑩可以暫停遊戲

增加難度(下落速度增加)

⑫降低難度(下落速度減少)

⑬難度範圍為0-10

3.2系統功能結構


3.3 系統預覽


程式碼:

TetrisGame.java

package MyTetris2;

import javax.swing.JFrame;
import javax.swing.JOptionPane;


	/**
	 * 遊戲主程式
	 * @author LEUNG
	 *
	 */
public class TetrisGame extends JFrame{
	
	/**
	 * 
	 */	
	private static final long serialVersionUID = 1L;
	private GamePanel t ;
	private static int flag=0;
	
	/**
	 * 構造方法
	 */
	TetrisGame(){	
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocation(800,100);					
		setTitle("俄羅斯方塊");
		setSize(600,750);
		setResizable(true);      //不可縮放	
		
	}
	
	/**
	 * 新增畫板
	 * 開始跑Timer
	 */
	public void startGame(){
		t = new GamePanel();
		add(t);	
		addKeyListener(t);
		t.timer.start();
	}
	
	public static void main(String[] args){
		TetrisGame tetris = new TetrisGame();	
		
		tetris.setVisible(true);
		flag = JOptionPane.showConfirmDialog(tetris, 				//開始選擇對話方塊
				"按【是】開始新遊戲\n按【否】退出遊戲", "new Game", JOptionPane.YES_NO_OPTION);	
		if(flag==JOptionPane.YES_OPTION){
			tetris.startGame();
			tetris.setVisible(true);
		}
		else{
			tetris.dispose();
			System.exit(0);
		}							
		
	}
	
	
}

Block.java

package MyTetris2;


/**
 * 方塊類
 * 內含方塊的基本方法(方塊固定、新建、翻轉、左移、右移、下移)
 * @author LEUNG
 *
 */
public class Block {
	
	/**
	 * 方塊用一個三維陣列來存,分別是形狀,形態,座標(在4×4方格中,1表示要填充,0表示不填充)
	 *7種圖形分別是J,L,S,Z,T,O,I
	 *4種形態,旋轉得到的
	 */
	public final int shapes[][][] = new int[][][]{
		//J
			{{0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0},
			 {1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0},
			 {1,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0},
			 {1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0}}, 
			
		//L	
			{{1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0},
			 {1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0},
			 {1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0},
			 {0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0}},
			
		//S	
			{{0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,},
			 {1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,},
			 {0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,},
			 {1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,}},
			
		//Z	
			{{1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
			 {0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0},
			 {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
			 {0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0}},
			
		//T	
			{{0,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0},
			 {1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0},
			 {1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0},
			 {0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0}},
			
		//O
			{{1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},
			 {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},
			 {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},
			 {1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0}},
			
		//I	
			{{0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0},
			 {0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0},
			 {0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0},
			 {0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0}},		};
			 
	/**
	 * 	x,y用來記錄方塊4×4區域中的(0,0)點的位置,x,y中的(0,0)相對於map中是(1,0)	
	 */
	public int x;		
	public int y;
	public int blockType=0;	//方塊型別
	public int blockState=0;  	//方塊狀態
	public int initX = 4;				//初始位置的X,Y值
	public int initY = 0;
	
	GamePanel game;				

	/**
	 * 構造方法
	 */
	Block(GamePanel game){		//引用遊戲畫板
		 this.game = game;
	}
			
	/**
	 * 新建方塊(初始化)
	 */
	public void newblock(){
		blockType = (int)(Math.random()*1000)%7;   //範圍0-6
		blockState = (int)(Math.random()*1000)%4;  //範圍0-3
		x=initX;
		y=initY;

		
	}
	
	/**
	 * 把需要固定的方塊固定
	 * 存放在map陣列中
	 */
	public void add(){
		int i=0;
		for(int a=0;a<4;a++){
			for(int b=0;b<4;b++){
				if(game.map[x+1+b][y+a]==0){     // map[列][行]
					game.map[x+1+b][y+a]=shapes[blockType][blockState][i];
				}
				i++;
			}
		}
	}
				
	/**
	 * 右移
	 */
	public void right() {
		if(!game.isCollied(x+1,y)){
			x++;
		}
			game.repaint();
	}

	/**
	 * 下移
	 */
	public void down() {
		if(!game.isCollied(x,y+1)){
			y++;
		}else{
			add();		//碰撞到底部後,把方塊新增到畫布上去
			game.deleteLine();
			newblock();
			}	
		game.repaint();
	}

	/**
	 * 左移
	 */
	public void left() {
		if(!game.isCollied(x-1,y)){
			x--;
		}		
		game.repaint();
	}

	/**
	 * 轉換狀態
	 */
	public void turnState() {
					
		int temp = blockState;		//首先記錄本狀態
		blockState = (blockState+1)%4;
		if(game.isCollied(x,y)){
			blockState = temp;
		}
		game.repaint();
	}
}

GamePanel.java

package MyTetris2;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;



/**
 * 遊戲介面畫板
 * @author LEUNG
 *
 */
public class GamePanel extends JPanel implements KeyListener{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public int MAPCOL=13;				//遊戲畫布的列數	 
	public int MAPROW=23;				//遊戲畫布的行數     
	public int PIXEL=30;				//畫素,單位格子的長、寬
	public int score;
	public int map[][]=new int[MAPCOL][MAPROW];
	public Timer timer ;
	private int Level=5;				//初始難度5
	private Block block;				//引用Block類
	private TimerListener timerlistener;
	
	/**
	 * 內部類,被timer觸發
	 * @author LEUNG
	 *
	 */
	private class TimerListener implements ActionListener{		//實現介面
		
		@Override
		public void actionPerformed(ActionEvent e) {
		
			if(!isCollied(block.x,block.y+1)){
				block.y++;
				
				
			}else{
				block.add();		//碰撞到底部後,把方塊新增到畫布上去					
				deleteLine();
				if(true==isGameover()){
					JOptionPane.showMessageDialog(null, "Game Over\n你的分數是:"+score);
					cleanMap();
					drawWall();
					score=0;		
				}
				block.newblock();
			}				
			repaint();				//重畫
		}
	}
	

	/**
	 * 構造方法
	 */
	GamePanel(){
		block = new Block(this);	//引用Block類,引數是GamePanel類
		cleanMap();
		drawWall();
		block.newblock();
		timerlistener=new TimerListener();
		timer = new Timer(800, timerlistener);   //計時器,在指定時間間隔觸發TimerListener()
		
		
	}
	
	/**
	 * 增加難度
	 */
	private void upLevel(){
		if(Level<10){
			Level++;
			timer.setDelay(1300-100*Level);
			repaint();
		}
	}
	/**
	 * 降低難度
	 */
	private void downLevel(){
		if(Level>0){
			Level--;
			timer.setDelay(1300-100*Level);
			repaint();
		}
	}
	

	/**
	 * 圍牆內的畫面置零,相當於清除畫面
	 */
	private void cleanMap(){
		for(int i=1;i<MAPCOL-2;i++){			
			for(int j=0;j<MAPROW-2;j++){
				map[i][j]=0;
			}
		}     
	}
	
	/**
	 * 畫圍牆,用2標示為圍牆
	 * 
	 */
	private void drawWall(){			
		for(int i=0;i<MAPCOL-1;i++){
			map[i][MAPROW-2]=2;
		}
		for(int j=0;j<MAPROW-2;j++){
			map[0][j]=2;
			map[MAPCOL-2][j]=2;
		}
	}
	
	/**
	 * 得到畫筆來畫TeTirsPanel
	 * 畫面來源
	 */
	protected void paintComponent(Graphics g) {		
		
		super.paintComponent(g);		//每次呼叫repaint()都清除原先的元件,不呼叫會保留原元件
		
		for(int j=0;j<MAPROW-1;j++){
			for(int i=0;i<MAPCOL-1;i++){
			/*	if(2==map[i][j]){
					g.setColor(Color.ORANGE);
					g.fillRect(i*PIXEL, j*PIXEL, PIXEL, PIXEL);   //畫圍牆格子
				}	*/											  //現把其隱藏	
				
				//畫出固定好的方塊
				if(1==map[i][j]){
					g.setColor(Color.GREEN);
					g.fill3DRect(i*PIXEL, j*PIXEL, PIXEL, PIXEL,true);   
				}
			}
		}
		
		//畫豎線
		for(int i=1;i<MAPCOL-1;i++){	
			g.setColor(Color.BLACK);
			g.drawLine(i*PIXEL, 0, i*PIXEL, (MAPROW-2)*PIXEL);		
		}
		
		//畫橫線
		for(int j=0;j<MAPROW-1;j++){	
			g.setColor(Color.BLACK);
			g.drawLine(PIXEL*1, j*PIXEL, (MAPCOL-2)*PIXEL, j*PIXEL);
		}
		
		//畫未固定方塊,16是shapes陣列的第一維長度
		//x,y是方塊正處於的座標(提示x要加1)
		for(int i=0;i<16;i++){											
			if(1==block.shapes[block.blockType][block.blockState][i]){
				g.setColor(Color.BLUE);
				g.fill3DRect((block.x+1+i%4)*PIXEL,(block.y+i/4)*PIXEL,PIXEL,PIXEL,true);			//用shapes陣列來畫出方塊
			}
		}	

		g.setColor(Color.darkGray);
		g.setFont(new Font("黑體", Font.BOLD, 30)); 
		g.drawString("分數:"+score,MAPCOL*PIXEL,3*PIXEL-35);
		Graphics2D g2 = (Graphics2D)g;
		g2.setStroke(new BasicStroke(3.0f));
		g2.drawLine(MAPCOL*PIXEL-30, 3*PIXEL-85, MAPCOL*PIXEL+170, 3*PIXEL-85);			//橫線1
		g2.drawLine(MAPCOL*PIXEL-30, 3*PIXEL, MAPCOL*PIXEL+170, 3*PIXEL);				//橫線2
		g2.drawLine(MAPCOL*PIXEL-30, 3*PIXEL-85, MAPCOL*PIXEL-30, 3*PIXEL);				//豎線1
		g2.drawLine(MAPCOL*PIXEL+170, 3*PIXEL-85, MAPCOL*PIXEL+170, 3*PIXEL);			//豎線2	
		
		g2.drawLine(MAPCOL*PIXEL-30, 5*PIXEL-40, MAPCOL*PIXEL+170, 5*PIXEL-40);			//橫線1
		g2.drawLine(MAPCOL*PIXEL-30, 5*PIXEL+20, MAPCOL*PIXEL+170, 5*PIXEL+20);			//橫線2
		g2.drawLine(MAPCOL*PIXEL-30, 7*PIXEL+20, MAPCOL*PIXEL+170, 7*PIXEL+20);			//橫線3
		g2.drawLine(MAPCOL*PIXEL-30, 9*PIXEL+20, MAPCOL*PIXEL+170, 9*PIXEL+20);			//橫線4
		g2.drawLine(MAPCOL*PIXEL-30, 11*PIXEL+20, MAPCOL*PIXEL+170, 11*PIXEL+20);		//橫線5
		g2.drawLine(MAPCOL*PIXEL-30, 13*PIXEL+20, MAPCOL*PIXEL+170, 13*PIXEL+20);		//橫線6
		g2.drawLine(MAPCOL*PIXEL-30, 15*PIXEL+20, MAPCOL*PIXEL+170, 15*PIXEL+20);		//橫線7
		g2.drawLine(MAPCOL*PIXEL-30, 17*PIXEL+20, MAPCOL*PIXEL+170, 17*PIXEL+20);		//橫線8
		g2.drawLine(MAPCOL*PIXEL-30, 19*PIXEL+20, MAPCOL*PIXEL+170, 19*PIXEL+20);		//橫線9
		g2.drawLine(MAPCOL*PIXEL-30, 5*PIXEL-40, MAPCOL*PIXEL-30, 19*PIXEL+20);			//豎線1
		g2.drawLine(MAPCOL*PIXEL+170, 5*PIXEL-40, MAPCOL*PIXEL+170, 19*PIXEL+20);		//豎線2
		
		g.setFont(new Font("黑體", Font.BOLD, 22));
		g.drawString("操作方法", MAPCOL*PIXEL, 5*PIXEL);
		g.drawString("↑    翻轉",MAPCOL*PIXEL,7*PIXEL);
		g.drawString("↓    下降一格",MAPCOL*PIXEL,9*PIXEL);
		g.drawString("←    左移",MAPCOL*PIXEL,11*PIXEL);
		g.drawString("→    右移",MAPCOL*PIXEL,13*PIXEL);
		g.drawString("F1    暫停", MAPCOL*PIXEL, 15*PIXEL);
		g.drawString("F2  增加難度", MAPCOL*PIXEL, 17*PIXEL);
		g.drawString("F3  降低難度", MAPCOL*PIXEL, 19*PIXEL);
		g.drawString("當前難度:"+Level+"     相當於"+(double)(1300-Level*100)/1000+
				"秒下降一格", 2*PIXEL, 21*PIXEL+40);
		
	}


	@Override
	public void keyPressed(KeyEvent e) {
		switch(e.getKeyCode()){
		case KeyEvent.VK_UP:		
				block.turnState();
				break;
				
		case KeyEvent.VK_LEFT:		
				block.left();
				break;
				
		case KeyEvent.VK_DOWN:		
				block.down();
				break;
				
		case KeyEvent.VK_RIGHT:		
				block.right();
				break;
				
		case KeyEvent.VK_F1:		
				timer.stop();
				JOptionPane.showMessageDialog(null, "按確認取消暫停");
				timer.restart();
				break;
				
		case KeyEvent.VK_F2:		
				upLevel();
				break;
				
		case KeyEvent.VK_F3: 		
				downLevel();
				break;
		}
	}
	
	@Override
	public void keyTyped(KeyEvent e) {
	}

	@Override
	public void keyReleased(KeyEvent e) {	
	}
	
	/**
	 * 判斷是否碰撞
	 * @param x
	 * @param y
	 * @return boolean
	 */
	public boolean isCollied(int x,int y){			
		for(int a=0;a<4;a++){			//遍歷4×4方塊區域
			for(int b=0;b<4;b++){
				if((block.shapes[block.blockType][block.blockState][a*4+b]==1)&&(map[x+1+b][y+a]==1)){	//判斷與已有方塊是否重合
					return true;
				}else if((block.shapes[block.blockType][block.blockState][a*4+b]==1)&&(map[x+1+b][y+a]==2)){	//與圍牆 
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 判斷是否結束遊戲,判斷條件:第一行有方塊
	 * @return boolean
	 */
	public boolean isGameover(){
		for(int i=1;i<MAPCOL-3;i++){
			if(map[i][0]==1){
				return true;		
			}
		}
		return false;
	}
	
	/**
	 * 消行
	 */
	public void deleteLine(){
		int count = 0;
		for(int i=0;i<MAPROW-2;i++){
			for(int j=1;j<MAPCOL-2;j++){
				if(map[j][i]==1){
					count++;
					if(count==MAPCOL-3){			//一行都滿的話,總數為MAPCOL-3個,滿足則消行
						score+=10;
						Toolkit.getDefaultToolkit().beep();		//消行提示音
						for(int a=i;a>0;a--){			//從第i行開始
							for(int b=1;b<MAPCOL-2;b++){
								map[b][a]=map[b][a-1];   //當前行等於上一行
							}
						}
					}
				}
			}
			count=0;
		}
	}

}