- 浏览: 76125 次
文章分类
最新评论
-
wodentt:
....为什么楼主都英文的博客
用java解决百度之星移动火柴的问题 part 1 -
wensonzhou86:
思路明确,代码清晰,通俗易懂
visit 淘宝面试题:如何充分利用多核CPU,计算很大的List中所有整数的和 -
carydeepbreathing:
代码比较干净
visit 淘宝面试题:如何充分利用多核CPU,计算很大的List中所有整数的和 -
meiowei:
yangguo 写道i don't think it is a ...
用java解决百度之星移动火柴的问题 part 2 -
yangguo:
i don't think it is a good way ...
用java解决百度之星移动火柴的问题 part 2
Recently, I've seen an interesting post: http://www.iteye.com/topic/595321 , it's a java implementation of Tetris. While being a long time game player (My first game was the gold digger on an IBM XT back in 1988, subsequently I played Koei's Romance of 3 kingdoms, from II - XI, among others), this is the first time I am trying a game.
Though I like this post a lot, the code there suffers multi-threading problem. While I don't claim I am a good game developer or an expert on multi-threading, I try to fix this problem. At the same time, I don't want to compete what-you-can-do-in-100-lines game, I am trying to write an easy-to-understand version.
Most of the code and the gif files are from the original author, I modified some of them.
The first class is the tetris block, for more details, check the wiki page: http://en.wikipedia.org/wiki/Tetris .
package my.test1; import java.awt.Image; import javax.swing.ImageIcon; public class TetrisBlock { public int type; public int orientation; public int color = 1; public int x; public int y; private static int[][][][] blockmeshs = { { {{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}},/* l */ {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}},/*-*/ { {{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},/* z */ {{0, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}}},/* z| */ { {{0, 0, 0, 0}, {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},/* xz */ {{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}},/* xz| */ { {{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}},/** []*/ { {{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}, {{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}}},/* f */ { {{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}}, {{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {1, 1, 1, 0}, {1, 0, 0, 0}, {0, 0, 0, 0}}},/* xf */ { {{0, 1, 0, 0}, {1, 1, 1, 0}, {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, 0, 0, 0}, {1, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}, {{0, 1, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}} /* t */ } }; private static Image[] images = { new ImageIcon("domaintest/pics/red.gif").getImage(), // this is just a place holder, not used. new ImageIcon("domaintest/pics/lightblue.gif").getImage(), new ImageIcon("domaintest/pics/pink.gif").getImage(), new ImageIcon("domaintest/pics/blue.gif").getImage(), new ImageIcon("domaintest/pics/orange.gif").getImage(), new ImageIcon("domaintest/pics/green.gif").getImage(), new ImageIcon("domaintest/pics/red.gif").getImage() // this is the real red image }; public static Image image(int color) { return images[color]; } public static int blocksize() { return 6; } // the length of the really used images. public boolean isOccupied(int row, int col) { return blockmeshs[type][orientation][row][col] != 0; } public void rotate() { orientation++; if (orientation >= blockmeshs[type].length) orientation = 0; } public void settle(TetrisBoard tetrisBoard) { int[][] b = blockmeshs[type][orientation]; for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { int a = b[i][j]; if (a != 0) { tetrisBoard.settle(y+i, x+j, color); } } } for (int i=y+3; i>y; i--) { tetrisBoard.removeFilledRow(i); } } public boolean canMoveDown(TetrisBoard tetrisBoard) { int[][] b = blockmeshs[type][orientation]; int yy = y + 1; for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (yy + i >= tetrisBoard.length() && b[i][j] != 0) return false; if (yy+i < tetrisBoard.length() && x+j < tetrisBoard.width() && tetrisBoard.isOccupied(yy+i, x+j) && b[i][j] != 0) return false; } } return true; } public boolean canMoveLeft(TetrisBoard tetrisBoard) { int[][] b = blockmeshs[type][orientation]; int xx = x - 1; for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (xx + j <= -1 && b[i][j] != 0) return false; if (y+i < tetrisBoard.length() && xx+j < tetrisBoard.width() && tetrisBoard.isOccupied(y+i, xx+j) && b[i][j] != 0) return false; } } return true; } public boolean canMoveRight(TetrisBoard tetrisBoard) { int[][] b = blockmeshs[type][orientation]; int xx = x + 1; for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (xx + j >= tetrisBoard.width() && b[i][j] != 0) return false; if (y+i < tetrisBoard.length() && xx+j < tetrisBoard.width() && tetrisBoard.isOccupied(y+i, xx+j) && b[i][j] != 0) return false; } } return true; } public boolean canRotate(TetrisBoard tetrisBoard) { int oo = orientation + 1; if (oo >= blockmeshs[type].length) oo = 0; int[][] b = blockmeshs[type][oo]; for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (y+i < tetrisBoard.length() && x+j < tetrisBoard.width() && tetrisBoard.isOccupied(y+i, x+j) && b[i][j] != 0) return false; } } return true; } }
The tetris block is encoded in a 4X4 matrix (look at the 1's in the matrix). All rotations of each block form an array. All such arrays form a bigger array called blockmeshs. There should be 7 images for 7 different blocks, but I just use the six gifs from the original post.
Though this class has >200 line of code, half of them are static data, so I can live with it. This class can be unit-tested without Swing.
The TetrisBoard class is:
package my.test1; public class TetrisBoard { private int length = 21; private int width = 10; public int[][] board = new int[length][width]; public int width() { return width; } public int length() { return length;} public boolean isOccupied(int row, int col) { if (row < 0 || row >= length || col < 0 || col >= width) return false; return board[row][col] != 0; } public int colorOf(int row, int col) { return board[row][col]; } public void settle(int row, int col, int color) { if (row < length && col < width) board[row][col] = color; } public void removeFilledRow(int row) { if (row >= length) return; boolean notFilled = false; for (int j=0; j<board[0].length; j++) { if (board[row][j] == 0) { notFilled = true; break; } } if (!notFilled) { for (int j=row; j>0; j--) System.arraycopy(board[j-1], 0, board[j], 0, board[0].length); } } }
These two classes are tightly coupled. I choose to put some methods in one rather than another, the main motivation is performance (multi dimension array access can be slow, though it doesn't matter much in this case).
The next class is the composition of the above two:
package my.test1; /** * Other features, such as scores, next shape, stop/restart, change speed */ import java.util.Random; public class Tetris { public TetrisBlock movingBlock = new TetrisBlock(); public TetrisBoard board = new TetrisBoard(); public boolean finished = false; private Random generator = new Random(); public synchronized void play() { if (movingBlock.canMoveDown(board)) movingBlock.y++; else { if (movingBlock.y == 0) { finished = true; } else { movingBlock.settle(board); newBlock(); } } } private void newBlock() { movingBlock = new TetrisBlock(); movingBlock.x = 3; movingBlock.y = 0; movingBlock.type = generator.nextInt(TetrisBlock.blocksize()+1); movingBlock.color = generator.nextInt(TetrisBlock.blocksize()) + 1; // should be movingBlock.orientation = 0; } }
The method play() is synchronized because the data can be modified by this class and user input (arrow keys).
Now we are done with the game logic, it's time to work on the GUI. The first class is the drawing class:
package my.test1; import java.awt.Dimension; import java.awt.Graphics; import javax.swing.JPanel; public class TetrisPanel extends JPanel { public Tetris tetris; public TetrisPanel(Tetris tetris) { super(); this.setFocusable(true); this.setPreferredSize(new Dimension(150, 315)); this.tetris = tetris; addKeyListener(new TetrisGuiListener(tetris, this)); } public void paintComponent(Graphics g) { super.paintComponent(g); g.fillRect(0, 0, 150, 315); for (int i=0; i<tetris.board.length(); i++) { for (int j=0; j<tetris.board.width(); j++) { if (tetris.board.isOccupied(i, j)) g.drawImage(TetrisBlock.image(tetris.board.colorOf(i, j)), j * 15, i * 15 , null); } } for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (tetris.movingBlock.isOccupied(i, j) && (i + tetris.movingBlock.y) <= 21 && !tetris.board.isOccupied(i + tetris.movingBlock.y, j + tetris.movingBlock.x)) g.drawImage(TetrisBlock.image(tetris.movingBlock.color), ((j + tetris.movingBlock.x) * 15), ((i + tetris.movingBlock.y) * 15) , null); } } } }
We just override the paintComponent() method with our logic. The second part of it is a little nonintuitive because we need a special logic to write the last block. My current design is to draw the partial image, but others could choose not to draw at all.
The listener class is as follows:
package my.test1; import java.awt.Component; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class TetrisGuiListener implements KeyListener { private Tetris tetris; private Component gui; public TetrisGuiListener(Tetris tetris, Component gui) { this.tetris = tetris; this.gui = gui; } public void keyPressed(KeyEvent e) { synchronized(tetris) { if (e.getKeyCode() == 65 || e.getKeyCode() == 37) { if (tetris.movingBlock.canMoveLeft(tetris.board)) tetris.movingBlock.x--; } else if (e.getKeyCode() == 68 || e.getKeyCode() == 39) { if (tetris.movingBlock.canMoveRight(tetris.board)) tetris.movingBlock.x++; } else if (e.getKeyCode() == 83 || e.getKeyCode() == 40) { if (tetris.movingBlock.canMoveDown(tetris.board)) tetris.movingBlock.y++; } else if (e.getKeyCode() == 87 || e.getKeyCode() == 38) { if (tetris.movingBlock.canRotate(tetris.board)) tetris.movingBlock.rotate(); } gui.repaint(); } } public void keyTyped(KeyEvent e) { } public void keyReleased(KeyEvent e) { } }
Again, we need to synchronize the data since this class is running on the EDT (event dispatching thread).
The next class is a wrapper of Tetris class, using SwingWorker:
package my.test1; /** * http://download.oracle.com/javase/tutorial/uiswing/concurrency/index.html * http://java.sun.com/developer/technicalArticles/javase/swingworker/ * http://www.javaworld.com/javaworld/jw-08-2007/jw-08-swingthreading.html?page=1 * http://developerlife.com/tutorials/?p=15 * http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html * http://java.sun.com/products/jfc/tsc/articles/threads/threads2.html * http://java.sun.com/products/jfc/tsc/articles/threads/threads3.html * http://stackoverflow.com/questions/1505427/multithreading-with-java-swing-for-a-simple-2d-animation * http://www.javaranch.com/journal/200410/JavaDesigns-SwingMultithreading.html * http://kenai.com/projects/trident/pages/SimpleSwingExample * http://java.sun.com/products/jfc/tsc/articles/painting/ */ import java.awt.Component; import java.util.List; import javax.swing.SwingWorker; public class TetrisGame extends SwingWorker<Tetris, Tetris> { private Tetris tetris; private Component gui; public TetrisGame(Tetris tetris, Component gui) { this.tetris = tetris; this.gui = gui; } @Override protected Tetris doInBackground() throws Exception { try { while (!tetris.finished) // && !isCancelled()) { tetris.play(); publish(tetris); try { Thread.sleep(200); } catch (Exception ex) { ex.printStackTrace(); } } return tetris; } catch (Throwable t) { t.printStackTrace(); return null; } } // This is called fro EDT @Override protected void process(List<Tetris> tetrisList) { Tetris t = tetrisList.get(tetrisList.size()-1); synchronized(t) { gui.repaint(); } } }
For more information on SwingWorker, check the links in the java doc section. This class, except the method marked, will run in a separate thread. The separate thread and EDT both modify the data in the Tetris class.
Finally, a window class to stitch everything together.
package my.test1; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class TetrisWindow extends JFrame { public TetrisWindow() { super("Tetris"); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setPreferredSize(new Dimension(160, 355)); this.setResizable(false); Tetris tetris = new Tetris(); TetrisPanel tetrisPanel = new TetrisPanel(tetris); Container content = getContentPane(); content.setLayout(new FlowLayout()); content.add(tetrisPanel); this.pack(); TetrisGame game = new TetrisGame(tetris, tetrisPanel); game.execute(); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new TetrisWindow().setVisible(true); } }); } }
Remember, we need to fire off the window from EDT too, so we use SwingUtilities.
Here is a screenshot.
Other references on this topic:
http://www.cs.unc.edu/~plato/COMP14/Assignments/tetris/tetris.html
http://www.ibm.com/developerworks/java/library/j-tetris/
http://gametuto.com/tetris-tutorial-in-c-render-independent/
http://zetcode.com/tutorials/javaswingtutorial/thetetrisgame/
发表评论
-
Excel Addin
2014-02-23 02:07 0When utilizing custom excell a ... -
Interview questions
2011-07-23 03:38 0Core: private int a; --> in ... -
How to design a bowling scoreboard
2011-07-22 23:26 1296I just saw this post: http:// ... -
obj xml
2011-05-27 23:13 0def record_parent(child, par ... -
obj xml 1
2011-05-27 23:11 0def obj2xml(obj, objname=Non ... -
visit 淘宝面试题:如何充分利用多核CPU,计算很大的List中所有整数的和
2010-07-16 00:11 1920I've seen these two: http://ww ... -
JSE links
2007-07-13 08:09 1907Tutorials: http://javaboutique. ... -
TCP, NIO, concurrency
2007-05-15 21:57 30Raw servers http://cindy.sour ... -
Exceptions
2007-03-24 22:52 31In batch modes, sometimes, we d ... -
Java enums in JDK 1.4
2007-03-04 05:59 1752Constantly, we need to define e ...
相关推荐
tetris game created with Delphi 7
nand2tetris.zip 是 Nand2Tetris 课程 所需全部软件包,版本是 Version 2.6 软件源下载地址页面 https://www.nand2tetris.org/software Nand2Tetris 是一节面向所有受众,零计算机基础,基于动手实践的从最基本...
Tetris MVC 代码包
Qt4 for Tetris,run in centos6.6
陈广 俄罗斯方框 Tetris C#代码
game tetris 俄罗斯方块 shell bash
tetris javascript 俄罗斯方块 . 用javascript写的在网页上的俄罗斯方块游戏
linux上运行的俄罗斯方块代码,代码不多可以对于初学者比较友好
Game tetris C Sharp WPF Windows
本文档 归纳总结了 Nand2Tetris 的 Projects 的课件 lecture 和 课外阅读 book 其中课外阅读book只在第一部分Part1(前六章)有。课件lecture,包括Part1 和 Part2 共12章。 另外附带了 课外阅读 的完整 《The ...
这是一个俄罗斯方块的exe,小小的,通过操作对Tetris有一个感性认识,能够在练习编写tetris的时候有一个比较好的分析途径。
本文档 归纳总结了 Nand2Tetris 的 Projects 的课件 lecture 和 课外阅读 book 其中课外阅读book只在第一部分Part1(前六章)有。课件lecture,包括Part1 和 Part2 共12章。 另外附带了 课外阅读 的完整 《The ...
一个很好的小程序呀,欢迎大家下载呀
Tetris俄罗斯方块源码 有详细的解释 下载后可以直接运行
tetris game written with java
tetris game via java code
这是经典俄罗斯方块游戏的另一个跨平台(用Java编写)实现。 它着重于有趣的多人游戏功能以及2d,3d甚至4d游戏模式。 基本的2d单人和多人游戏已经实现。
俄罗斯方块 shell版
js 版 tetris