Java簡易俄羅斯方塊
阿新 • • 發佈:2019-01-04
目前學到Swing第二章,這兩天沒有學新知識,寫了個俄羅斯方塊。
寫之前想不好怎麼實現,找來別人的程式看了一下,再加上自己的想法,做了下面這個半成品,接下來可以加上各種選單、按鈕貼圖等美化,都是些錦上添花的動作,繁瑣但不難。
我覺得寫俄羅斯方塊,難點在於如何將方塊的形狀(七種)、狀態(四種翻轉)、動作(左、右、下落)等提煉成陣列。 這裡採用四維陣列來表示每一種方塊:【種類】【翻轉】【X座標】【Y座標】,每種方塊都畫在一個4*4的大方塊中,把這個大方塊作為一個整體進行移動。再寫了一個方法遍歷大方塊,判斷該方塊的位置是否合法。
有了合適的模型,再把每個步驟轉化成程式碼,就不難了。
import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JCheckBox; import javax.swing.Timer; import javax.swing.JOptionPane; public class KingTetris{ // 20行,10列 private final int ROW = 20; private final int COL = 10; // 每個方塊邊長,單位為畫素 private final int LEN = 35; // 遊戲區域的左、上邊距 private final int LEFT_MARGIN = LEN*2; private final int UP_MARGIN = LEN; // 面布大小,單位為畫素 private final int AREA_WIDTH = LEN*22; private final int AREA_HEIGHT = LEN*22; // 是否需要網格 private boolean showGrid = true; // 是否彩色,將來可以作為貼圖控制 private boolean isColor = true; // 得分 private int score = 0; // 畫布 private MyCanvas drawArea = new MyCanvas(); // 視窗 private JFrame f = new JFrame("俄羅斯方塊"); // 畫圖用的image private BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB); private Graphics g = image.createGraphics(); // 陣列,用於儲存背景 private int[][] map = new int[COL][ROW]; // 陣列,用於儲存顏色 private Color[] color = new Color[]{Color.green, Color.red, Color.orange, Color.blue, Color.cyan, Color.yellow, Color.magenta, Color.gray}; //預設灰色 private final int DEFAULT = 7; private Color[][] mapColor = new Color[COL][ROW]; //元件的橫座標 int wordX = LEN*14;// 元件的橫座標 int wordY = LEN*9; // 字的初始縱座標 //shape的四個引數 private int type, state, x, y, nextType, nextState; //如果剛開始遊戲,由於無nextType,先給type等隨機一個,下為首次開始遊戲的標誌 private boolean newBegin = true; // 用陣列來代表不同形狀的下墜物,四維分別是型別Type、旋轉狀態State、橫座標X、縱座標Y。畫示意圖即可得出座標 // 方塊共有7種,分別以S、Z、L、J、I、O、T這7個字母的形狀來命名 private int[][][][] shape = new int[][][][]{ // S的四種翻轉狀態: { { {0,1,0,0}, {1,1,0,0}, {1,0,0,0}, {0,0,0,0} }, { {0,0,0,0}, {1,1,0,0}, {0,1,1,0}, {0,0,0,0} }, { {0,1,0,0}, {1,1,0,0}, {1,0,0,0}, {0,0,0,0} }, { {0,0,0,0}, {1,1,0,0}, {0,1,1,0}, {0,0,0,0} } }, // Z: { { {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} }, { {0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} } }, // L: { { {0,0,0,0}, {1,1,1,0}, {0,0,1,0}, {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,0,0}, {0,1,1,1}, {0,0,0,0} }, { {0,0,1,0}, {0,0,1,0}, {0,1,1,0}, {0,0,0,0} } }, // J: { { {0,0,0,0}, {0,0,1,0}, {1,1,1,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,1,1,1}, {0,1,0,0}, {0,0,0,0} }, { {0,1,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,0,0} } }, // I: { { {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} }, { {0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0} } }, // O: { { {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,1,1,0}, {0,1,1,0}, {0,0,0,0} } }, // T: { { {0,1,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, { {0,0,0,0}, {1,1,1,0}, {0,1,0,0}, {0,0,0,0} }, { {0,1,0,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0} }, { {0,1,0,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0} } }, }; /** * 初始化介面 */ private void init(){ drawArea.setPreferredSize(new Dimension(AREA_WIDTH, AREA_HEIGHT)); f.add(drawArea); JCheckBox gridCB = new JCheckBox("顯示網格",true); JCheckBox colorCB = new JCheckBox("彩色方塊", false); gridCB.setBounds(wordX, wordY-LEN,LEN,LEN); colorCB.setBounds(wordX, wordY-2*LEN,LEN,LEN); // paintArea(); // 加鍵盤監聽器 f.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent e){ switch (e.getKeyCode()) { case KeyEvent.VK_UP: turn(); break; case KeyEvent.VK_LEFT: left(); break; case KeyEvent.VK_RIGHT: right(); break; case KeyEvent.VK_DOWN: down(); break; } } }); Timer timer = new Timer(1000, new timerListener()); newShape(); timer.start(); // 視窗顯示在螢幕正中 // Toolkit是抽象類,只能用getDefaultToolkit()方法來獲取例項。 // getScreenSize()方法返回的是一個Dimension物件,還須用getWidth()獲取寬度 f.pack(); int screenSizeX = (int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(); int screenSizeY = (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight(); int fSizeX = (int)f.getSize().getWidth(); int fSizeY = (int)f.getSize().getHeight(); f.setResizable(false);// 禁止改變Frame大小 f.setBounds((screenSizeX-fSizeX)/2, (screenSizeY-fSizeY)/2, fSizeX,fSizeY ); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } /** * 繪圖 */ private void paintArea(){ // 預設黑色,填充白色 g.setColor(Color.white); g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT); // 方格線 // 先畫最外圍 g.setColor(Color.gray); for (int offset = 0; offset <= 2; offset++){ g.drawRect(LEFT_MARGIN-offset, UP_MARGIN-offset, COL*LEN+offset*2, ROW*LEN+offset*2); } // 如果showGrid為true則顯示網格 if(showGrid){ g.setColor(Color.gray); // 9條豎線 for (int i = 1 ; i <= 9; i++){ g.drawLine(LEFT_MARGIN+LEN*i, UP_MARGIN, LEFT_MARGIN+LEN*i, UP_MARGIN+ROW*LEN); } // 19條橫線 for(int i = 1; i <= 19; i++){ g.drawLine(LEFT_MARGIN, UP_MARGIN+LEN*i, LEFT_MARGIN+COL*LEN, UP_MARGIN+LEN*i); } } // 右上角顯示下一個shape int offset2 = 3;// 邊框粗細 int col = 4;// 右上角方框的列數 int row = 4;// 行數 g.setColor(Color.gray); g.setFont(new Font("Microsoft YaHei Mono", Font.BOLD, 20)); g.drawString("下一個:", wordX, LEN*2); int nextX = wordX; int nextY = LEN*2; //暫不畫方框 // for (int offset = 0; offset <= 2; offset++){ // g.drawRect(nextX-offset+10, nextY+10-offset, col*LEN+offset*2, row*LEN+offset*2); // } //畫下一次出現的下墜方塊 g.setColor(isColor?color[nextType]:color[DEFAULT]); for(int i = 0; i < 4; i++){ for(int j = 0; j < 4; j++){ if (shape[nextType][nextState][i][j]==1) { g.fill3DRect(nextX+10+i*LEN, nextY+10+j*LEN, LEN, LEN,true); } } } g.setColor(Color.gray); g.setFont(new Font("Times", Font.BOLD, 15)); g.drawString("玩法:", wordX, wordY+LEN*2); g.drawString("上箭頭:翻轉", wordX, wordY+LEN*3); g.drawString("左箭頭:左移", wordX, wordY+LEN*4); g.drawString("右箭頭:右移", wordX, wordY+LEN*5); g.drawString("下箭頭:下落", wordX, wordY+LEN*6); g.setFont(new Font("Times", Font.BOLD, 25)); g.drawString("得分:" + score, wordX, wordY+LEN*8); //畫下墜物shape g.setColor(isColor?color[type]:color[DEFAULT]); for(int i = 0; i < 4; i++){ for(int j = 0; j < 4; j++){ if (shape[type][state][i][j]==1) { g.fill3DRect(LEFT_MARGIN+(x+i)*LEN, UP_MARGIN+(y+j)*LEN, LEN, LEN,true); } } } //畫背景map for(int i = 0; i < COL; i++){ for(int j = 0; j < ROW; j++){ if (map[i][j] == 1) { g.setColor(mapColor[i][j]); g.fill3DRect(LEFT_MARGIN+i*LEN, UP_MARGIN+j*LEN, LEN, LEN,true); } } } drawArea.repaint(); } /** * 自定義畫布,重寫paint()方法 */ private class MyCanvas extends JPanel{ public void paint(Graphics g){ g.drawImage(image, 0, 0, null); } } /** * 判斷位置是否合法 */ private boolean check(int type, int state, int x, int y){ for(int i = 0; i < 4; i++){ for(int j = 0; j < 4; j++){ if ( (shape[type][state][i][j] == 1) && ( (x+i>=COL) || (x+i<0 ) || (y+j>=ROW) || (map[x+i][y+j]==1) ) ) { return false; } } } return true; } /** * 判斷遊戲是否結束 */ private boolean isGameOver(int type, int state, int x, int y){ return !check(type, state, x, y); } /** * 新建方塊 */ private void newShape(){ Random rand = new Random(); if(newBegin){ type = rand.nextInt(7); state = rand.nextInt(4); newBegin = false; } else{ type = nextType; state = nextState; } nextType = rand.nextInt(7); nextState = rand.nextInt(4); x = 3; y = 0; // 如果遊戲已結束,則重新開始 if(isGameOver(type, state, x, y)){ JOptionPane.showMessageDialog(f, "GAME OVER!"); newGame(); } paintArea(); } /** * 新建遊戲 */ private void newGame(){ newMap(); score = 0; newBegin = true; } /** * 清空背景圖 */ private void newMap(){ for(int i = 0; i < COL; i++){ Arrays.fill(map[i],0); } } /** * 消行 */ private void delLine(){ boolean flag = true; int addScore = 0; for(int j = 0; j < ROW; j++){ flag = true; for( int i = 0; i < COL; i++){ if (map[i][j]==0){ flag = false; break; } } if(flag){ addScore += 10; for(int t = j; t > 0; t--){ for(int i = 0; i <COL; i++){ map[i][t] = map[i][t-1]; } } } } score += addScore*addScore/COL; } /** * 計時器所用的事件監聽器 */ private class timerListener implements ActionListener{ public void actionPerformed(ActionEvent e){ if(check(type, state , x, y+1) ){ y = y +1; } else{ add(type, state, x, y); delLine(); newShape(); } paintArea(); } } /** * 把shape存到map的add方法 */ private void add(int type, int state, int x, int y){ for(int i = 0; i < 4; i++){ for(int j = 0; j < 4 ; j++){ if((y+j<ROW)&&(x+i<COL)&&(x+i>=0)&&(map[x+i][y+j]==0)){ map[x+i][y+j]=shape[type][state][i][j]; mapColor[x+i][y+j]=color[isColor?type:DEFAULT]; } } } } /** * 下面為四個方向鍵對應的方法 */ private void turn(){ int tmpState = state; state = (state + 1)%4; if (!check(type,state, x, y )) { state = tmpState; //不能轉就什麼都不做 } paintArea(); } private void left(){ if(check(type,state, x-1, y)){ --x; } paintArea(); } private void right(){ if (check(type,state, x+1, y)) { ++x; } paintArea(); } private void down(){ if (check(type,state, x, y+1)) { ++y; } //如果下不去則固定之 else{ add(type, state, x, y); delLine(); newShape(); } paintArea(); } /** * 主函式 */ public static void main(String[] args){ new KingTetris().init(); } }