俄罗斯方块(详解)
2015-06-16 18:25
369 查看
几个重要的细节问题:
1、控件的布局问题:只有将布局方式设置为null,才能通过setBounds设置大小和位置
2、分层模式,内部类方式:各个类之间的相互的交流
3、事件的监听:键盘监听,失去焦点问题
4、碰壁问题:多画一行和一列
5、移动:判断是否碰壁方法不动,通过不同方式和所传的参数的不同,从而实现各种动作
package tetris3; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.Timer; import javax.swing.border.TitledBorder; import tetris2.NextBlock; public class Tetris extends JFrame { public static boolean isPlay=true;//设置是否点击暂停 Model tPanel;//方块面板 JMenuItem jitemPause ;//暂停菜单项 int grade = 0, rank = 1,numb = 1;//初始化基本数据 JLabel score, level; NextBlock nextBlockPanel;//下一块的面板 Color bgColor = Color.black, blockColor = Color.yellow;//颜色 public Tetris() { super("俄罗斯方块"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭控件 this.setSize(378, 496); this.setLocationRelativeTo(null);//设置窗口居中 setPanel();//布局 addMenu();// 添加菜单 this.setResizable(false);//设置窗口大小不能改变 } //设置下一块的颜色 public void setNextBlock(){ this.nextBlockPanel.repaint(); } //设置移动块的颜色 public void setBlockColor(Color c){ tPanel.setBlockColor(c); } //获取已经堆积块的颜色 public Color getModelColor(){ return tPanel.ModelColor; } //设置背景颜色 public void setBGColor(Color c){ bgColor=c; tPanel.setBackground(bgColor); } //设置已堆积块的颜色 public void setModelColor(Color c){ tPanel.setModelColor(c); } //设置分数 public void setScore(int s){ score.setText(""+s); } //设置下一块的颜色 public void setNextBg(Color c){ nextBlockPanel.setBg(c); } //设置等级 public void setGrade(int s){ level.setText(""+s); } //控制暂停菜单项中的暂停和继续间的切换 public void setPause(boolean b){ String str; if(b){ str="continue"; }else{ str="pause"; } jitemPause.setText(str); } //获取下一块的信息 public NextBlock getNextBlock(){ this.nextBlockPanel.repaint(); return this.nextBlockPanel; } //<span style="color:#ff0000;">设置布局面板,这里要注意,要是添加按钮,文本编辑控件,都会失去焦点,使运行面板不能实现键盘监听,可以通过内部的类是其失去焦点, //为了避免不必要的错误,尽量不要用吧,这里慎重啊。还有记得设置这些面板布局方式为空,只有这样才能保证这些添加的控件能在规定的位置 //正确的显示</span>. public void setPanel() { nextBlockPanel = new NextBlock(); tPanel = new Model(); tPanel.setBackground(bgColor); this.getContentPane().add(tPanel); this.addKeyListener(tPanel.listener); JPanel controlPanel = new JPanel(); controlPanel.setLayout(null); controlPanel.setBorder(new TitledBorder("Control And Show")); JLabel nextBlockShape = new JLabel("Next Block Shape"); nextBlockShape.setBounds(10, 20, 100, 20); controlPanel.add(nextBlockShape); nextBlockPanel.setBounds(0, 50, 120, 80); nextBlockPanel.repaint(); controlPanel.add(nextBlockPanel); JLabel scoreLabel = new JLabel("Score"); scoreLabel.setBounds(40, 120, 100, 60); controlPanel.add(scoreLabel); score = new JLabel("0"); score.setBounds(15, 160, 108, 20); controlPanel.add(score); JLabel levelLabel = new JLabel("Level"); levelLabel.setBounds(40, 160, 100, 100); controlPanel.add(levelLabel); level = new JLabel("1"); level.setBounds(15, 220, 108, 20); controlPanel.add(level); JLabel jl1=new JLabel("注意自我保护,"); jl1.setBounds(15, 280, 108, 30); JLabel jl2=new JLabel("适度游戏益脑,"); jl2.setBounds(15, 310, 108, 30); JLabel jl3=new JLabel("合理安排时间,"); jl3.setBounds(15, 340, 108, 30); JLabel jl4=new JLabel("享受健康生活。"); jl4.setBounds(15, 370, 108, 30); controlPanel.add(jl1); controlPanel.add(jl2); controlPanel.add(jl3); controlPanel.add(jl4); this.getContentPane().add(controlPanel); JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tPanel, controlPanel);// 水平分隔窗格,左右各添加一个面板 split.setDividerLocation(241);// 设置水平分隔条的位置 split.setEnabled(false);// 设置分隔条不能变动 this.getContentPane().add(split);// 框架内容窗格添加分隔窗格 } //添加菜单 private void addMenu() { JMenuBar menubar = new JMenuBar(); this.setJMenuBar(menubar); JMenu game = new JMenu("Game"); JMenu information = new JMenu("Information"); JMenuItem informationOne = new JMenuItem("Author:xjj"); JMenuItem informationTwo = new JMenuItem( "Blog:http://blog.csdn.net/u011479875"); information.add(informationOne); information.add(informationTwo); JMenuItem jitemNew = new JMenuItem("New Game"); jitemNew.setActionCommand("new"); jitemNew.addActionListener(new MenuListener()); jitemPause = new JMenuItem("Pause"); jitemPause.setActionCommand("pause"); jitemPause.addActionListener(new MenuListener()); JMenuItem jitemSetBlcokGb = new JMenuItem("BlockColor"); jitemSetBlcokGb.setActionCommand("blcokbg"); jitemSetBlcokGb.addActionListener(new MenuListener()); JMenuItem jitemBg = new JMenuItem("BackgroundColor"); jitemBg.setActionCommand("bg"); jitemBg.addActionListener(new MenuListener()); JMenuItem jitemModel = new JMenuItem("ModelColor"); jitemModel.setActionCommand("model"); jitemModel.addActionListener(new MenuListener()); JMenuItem jitemExit = new JMenuItem("Exit"); jitemExit.setActionCommand("exit"); jitemExit.addActionListener(new MenuListener()); game.add(jitemNew); game.add(jitemPause); game.addSeparator();// 菜单里设置分隔线 game.add(jitemSetBlcokGb); game.add(jitemBg); game.add(jitemModel); game.addSeparator();// 菜单里设置分隔线 game.add(jitemExit); menubar.add(game); menubar.add(information); } //监听类,这里写成内部类的形式的好处是,能够调用直接调用上面的一下成员变量,和方法,避免传参。 class MenuListener implements ActionListener { public MenuListener() { } public void actionPerformed(ActionEvent e) { if(e.getActionCommand().equalsIgnoreCase("exit")){ System.exit(EXIT_ON_CLOSE); } if(e.getActionCommand().equalsIgnoreCase("pause")){ if(Tetris.isPlay){ tPanel.timer.stop(); }else{ tPanel.timer.start(); } setPause(Tetris.isPlay); Tetris.isPlay=!Tetris.isPlay; } if(e.getActionCommand().equalsIgnoreCase("blcokbg")){ Color c=JColorChooser.showDialog(tPanel, "颜色", Color.black); if(c.equals(bgColor)){ JOptionPane.showMessageDialog(tPanel, "背景色不能和块色相同"); return ; } setBlockColor(c); } if(e.getActionCommand().equalsIgnoreCase("bg")){ Color c=JColorChooser.showDialog(tPanel, "颜色", Color.black); if(c.equals(blockColor) || c.equals(getModelColor())){ JOptionPane.showMessageDialog(tPanel, "背景色不能和块色相同"); return ; } setBGColor(c); } if(e.getActionCommand().equalsIgnoreCase("model")){ Color c=JColorChooser.showDialog(tPanel, "颜色", Color.black); if(c.equals(bgColor)){ JOptionPane.showMessageDialog(tPanel, "背景色不能和块色相同"); return ; } setModelColor(c); } if(e.getActionCommand().equalsIgnoreCase("new")){ grade = 0;rank = 1;numb = 1; setScore(0);setGrade(1); tPanel.newGame(); tPanel.repaint(); } } } //<span style="color:#ff0000;">方块面板。这里写成内部类一样是为了实现两个类之间的交流,而且不需要传参,这种结构很好用</span> class Model extends JPanel{ public TimeListener listener = new TimeListener();//监听器 public int blockType=0,turnState=0,x=0,y,score=0,delay=1000,level=1; Timer timer;//定时器 Color ModelColor=Color.red,BlockColor=Color.blue; NextBlock nextBlock;//下一块块 public int[][] map = new int[100][100];//地图 int[][][] shapes = new int[][][] {//几种图形,这里也可以使用二维数组,只不过要换成16进制形式,然后通过位运算计算 // I { { 1, 1, 1, 1, 0, 0, 0, 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 }, { 1, 1, 1, 1, 0, 0, 0, 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 } }, // 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 } }, // 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 } }, // 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 } }, // 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 } }, // T { { 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 }, { 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 } } }; public Model() { nextBlk(); newGame(); timer = new Timer(delay, listener); timer.start(); } //设置堆积块颜色 public void setModelColor(Color c) { this.ModelColor=c; repaint(); } //设置移动块颜色 public void setBlockColor(Color c) { this.BlockColor=c; repaint(); } //初始化数据 public void newGame() { blockType=0; turnState=0; x=4;y=0; score=0; delay=1000; level=1; map = new int[100][100]; //这里将框架向为扩充一行和一列,这样对后面判断是否越界很方便 for(int i=0;i<12;i++){ map[i][22]=1; } for(int i=0;i<22;i++){ map[12][i]=1; } } private void nextBlk() { this.x=4; this.y=0; this.nextBlock=getNextBlock(); setNext(); if(crash(x, y, blockType, turnState)==0){//如果一出来就撞了,游戏结束 JOptionPane.showMessageDialog(null, "Game Over!!!"); timer.stop(); } } //设置下一块信息 public void setNext(){ this.blockType=this.nextBlock.getBlockType(); this.turnState=this.nextBlock.getTurnState(); } //画图 protected void paintComponent(Graphics g) { super.paintComponent(g); // g.fill3DRect(220, 420, 20, 20, true); //画俄罗斯方块已经“堆积的方块” g.setColor(ModelColor); for(int i=0;i<12;i++){ for(int j=0;j<22;j++){ if(map[i][j]==1){ g.fill3DRect(i*20, j*20, 20, 20, true); } } } //画移动的块 g.setColor(BlockColor); for(int j=0;j<16;j++){ if(shapes[blockType][turnState][j]==1){ g.fill3DRect((j%4+x)*20, (j/4+y)*20, 20, 20,true); } } } //向下运行,如果碰撞,则结束这个块的运行,并生成下一个块,否则继续向下走 private void down(){ System.out.println(x+" "+y); if(crash(x,y+1,blockType,turnState)==0){ add(x,y,blockType,turnState); setNextBlock(); nextBlk(); }else{ y++; } repaint(); } //向左移动 public void left() { if(x>=0){ x -= crash(x-1,y,blockType,turnState); repaint(); } } //向右移动 public void right() { if(x<12){ x += crash(x+1,y,blockType,turnState); repaint(); } } //旋转,为了保证在左右靠墙是都能转动,所以要对下一状态进行预判,要是下一块不安全,则将4*4的块模型整体向后退一不,然后再 //旋转,但是要注意,要是横条是 要退3步 public void turn() { if( crash(x,y,blockType,(turnState+1)%4)==0 ){ if(blockType==0&&crash(x-3,y,blockType,(turnState+1)%4)==1){ x-=3; turnState =(turnState+1)%4; }else if(crash(x-1,y,blockType,(turnState+1)%4)==1){ x-=1; turnState =(turnState+1)%4; } }else{ turnState =(turnState+1)%4; } repaint(); } //判断是否碰撞,要是返回0,否则返回1,这里返回1和0是有讲究的,是为了左右运行时,可以直接加上这个返回值就行了,碰撞加0 //没有,加1. private int crash(int x, int y, int blockType, int turnState) { for(int a=0;a<4;a++){ for(int b=0;b<4;b++){ if(x==-1){//这里是判断左移是是否碰壁 return 0; } if((shapes[blockType][turnState][a*4+b] & map[x+b][y+a]) ==1){//这里通过位运算可以很快速度得到结果,必须同时为1才算碰撞,即, //是块和块碰撞 return 0; } } } return 1; } //向已堆积块添加块 private void add(int x, int y, int blockType, int turnState) { for(int a=0;a<4;a++){ for(int b=0;b<4;b++){ if(shapes[blockType][turnState][a*4+b]==1){ map[x+b][y+a]=1; } } } tryDelLine();//分数和等级 } //控制分数和等级,同时消的越多,分数加的越多,等差数列,等级的升高,伴随着速度的加快 public void tryDelLine() { int count=1; for(int b=0;b<22;b++){ int c=1; for(int a=0;a<13;a++){ c&=map[a][b]; } if(c==1){ if(count==1){ score +=10; }else{ score +=10*count; } count++; setScore(score); for(int d=b;d>0;d--){ for(int e=0;e<12;e++){ map[e][d]=map[e][d-1]; } } //※更改游戏难度(加快下落速度) if(score>100*numb){ level++; setGrade(level); delay -= delay; if(delay==100){ delay=100; } timer.setDelay(delay); } } } } //这个内部类是实现事件的监听,也是方便类与类之间的联系 class TimeListener extends KeyAdapter implements ActionListener{ public void actionPerformed(ActionEvent e) { if(!Tetris.isPlay){ return ; } down(); } public void keyPressed(KeyEvent e) { if(!Tetris.isPlay){ return ; } switch(e.getKeyCode()){ case KeyEvent.VK_DOWN: down();break; case KeyEvent.VK_LEFT: left();break; case KeyEvent.VK_RIGHT: right();break; case KeyEvent.VK_UP: turn(); break; } } } } public static void main(String[] args) { Tetris te = new Tetris(); te.setVisible(true); } }
相关文章推荐
- mysql多实例,my.cnf 4G conf配置安装配置
- 【转载】恼人的函数指针(二):指向类成员的指针
- JavaScript 的 parseInt 取整
- AngularJS学习小结
- HTML-CSS
- 154在屏幕中绘图时设置透明度(扩展知识:为图片视图添加点击手势识别器,来实现点击事件操作)
- Ternary Search Tree Java实现
- SRS配置采集(ingest)
- OpenCms创建网站的过程示意图——专用OpenCms人们刚开始学习
- jsp防盗链代码
- Android Binder机制理解
- 有趣的笔试题(1)
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- Tiny210编译和烧写u-boot步骤
- poj2583---Series Determination
- Kafka实战-实时日志统计流程
- response.getWriter().write()与out.print()的区别
- cocos2d-x3.3获取时间
- Ehcache 整合Spring 使用页面、对象缓存
- Solr与Mysql简单集成