俄罗斯方块(详解)

俄罗斯方块(详解)_第1张图片

几个重要的细节问题:

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;
	}
	//设置布局面板,这里要注意,要是添加按钮,文本编辑控件,都会失去焦点,使运行面板不能实现键盘监听,可以通过内部的类是其失去焦点,
	//为了避免不必要的错误,尽量不要用吧,这里慎重啊。还有记得设置这些面板布局方式为空,只有这样才能保证这些添加的控件能在规定的位置
	//正确的显示.
	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();
				
			}
			
			
		}
	}
	//方块面板。这里写成内部类一样是为了实现两个类之间的交流,而且不需要传参,这种结构很好用
	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);
	}

}








你可能感兴趣的:(java小小项目搞起来,java小游戏)