廣工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;
}
}
}