工作越来越忙了,没有多少时间看书学习了,下班挤出时间写一个贪吃蛇的小游戏。当然还有许多瑕疵和不足,权当萌新学习交流之用。(吐槽:当年家人为什么要阻挡我学习计算机,报什么鬼会计。话说有没有自学编程的一起交流交流)
这个贪吃蛇有以下几个类:
- GameFrame(主游戏窗口类)
- StartPanel(开始界面)
游戏开始界面:
3.GameHelp(上图的帮助)
4.Info(上图的关于)
5.OerationList(游戏运行后右侧信息栏,不要在意类名,打错了没改-。-)
游戏运行画面如下:
6.Location(坐标类,必要时还会记录信息)
7.Snake(蛇的类,里面包含一个内部类,方向枚举类OritentionEum)
8.Food(食物类)
1.GameFrame类代码如下:
package 贪吃蛇; import java.awt.Canvas; import java.awt.Color; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import 贪吃蛇.Snake.OritentionEum; //游戏的主窗口 public class GameFrame { private Frame f ; //其实GameFrame直接继承Frame窗口就好 private int fLocationX = 300; private int fLocationY = 50; private int fWidth = 450; //窗口宽 private int fHeight=450; //窗口高 private StartPanel sp; //开始界面Panel private GameCanvas gc; //贪吃蛇画布 private OerationList ol; //相关信息 private int rate = 5; private Snake sk ; private Food fd; //窗口初始化 public void frameinit() { f = new Frame("游戏窗口"); f.setLayout(null); //不使用布局管理,Panel用绝对定位 sp = new StartPanel(this).panelInit(); //sp =sp.panelInit(); sp.setBounds(fWidth/3, 100, fWidth/3, fHeight/2); //这个是相对于sp的上一级的component的 f.add(sp); this.setAtt(fLocationX,fLocationY); } //窗口的属性,单独拿出来,在切换到游戏运行时还要用 public void setAtt(int x,int y) { if(f!=null) { f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setLocation(x, y); //设置组件在桌面显示的位置 f.setSize(fWidth, fHeight); //设置组件的大小 ,也可用setBounds同时设置位置和大小 f.setVisible(true); } } //游戏开始窗口 public void createGame() { f.setVisible(false); int x=f.getX(); int y = f.getY(); f = new Frame("贪吃蛇游戏开始"); sk = new Snake(); //蛇 fd = new Food(); this.setAtt(x,y); //为了随时更新位置 f.setLayout(null); gc = new GameCanvas(); gc.canvasInit(); //所以 gc.setBounds(0, 0, fWidth*2/3, fHeight); //gc宽度为300,高度为450 gc.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { switch(e.getKeyCode()) { case KeyEvent.VK_UP: sk.setOritention(OritentionEum.UP);break; case KeyEvent.VK_DOWN: sk.setOritention(OritentionEum.DOWN);break; case KeyEvent.VK_LEFT: sk.setOritention(OritentionEum.LEFT);break; case KeyEvent.VK_RIGHT: sk.setOritention(OritentionEum.RIGHT);break; } } }); gc.setFocusable(true); //画布获取焦点 f.add(gc); ol = new OerationList().listInit(); ol.setBounds(310, 75, fWidth/4, fHeight*2/3); f.add(ol); new updateTimeThread().start(); //更新时间线程 new updateCanvasThread().start();//绘画线程更新 } public int getWidth() { return f.getWidth(); } public int getHeight() { return f.getHeight(); } public int getX() { //返回当前窗口的X坐标 return f.getX(); } public int getY() { return f.getY(); } //绘画线程 class updateCanvasThread extends Thread{ public void run() { while(true) { gc.repaint(); String sp = ol.getSpeed(); try { switch(sp) { case "一级" : Thread.sleep(100);break; case "二级" : Thread.sleep(300);break; case "三级" : Thread.sleep(500);break; } } catch(Exception e) {e.printStackTrace();} } } } //更新界面线程 class updateTimeThread extends Thread{ public void run() { while(true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ol.updateTime(); } } } //画布 class GameCanvas extends Canvas{ public GameCanvas canvasInit() { this.setVisible(true); return this; } public void update(Graphics g) { Image img = this.createImage(fWidth*2/3,fHeight); //不能直接创建Image对象 Graphics gg = img.getGraphics(); gg.setColor(Color.GRAY); //灰色 gg.fillRect(0, 0, fWidth*2/3, fHeight); gg.setColor(Color.BLACK); gg.drawRect(10, 50, 250, 350); //起点10,,50,宽 250 高350 70行 50列的矩阵 if(!sk.crashed()) { if(sk.eatFood(fd)) { //蛇如果吃到食物就尾部增长 fd.updateFood(); sk.growTail(); ol.updateScore(); } sk.drawSnake(gg,rate); //画蛇的时候传入一个rate来控制大小 fd.drawFood(gg, rate); } else { //游戏结束画面 gg.setFont(new Font("微软雅黑",2,15)); //设置字体 gg.setColor(Color.WHITE); gg.fillRect(60, 175, 150, 40); gg.setColor(Color.RED); gg.drawString("游戏结束 得分"+ol.getScore(), 80 , 200); } g.drawImage(img, 0, 0, this); paint(g); } public void paint(Graphics g) { } } public static void main(String[] args) { GameFrame game = new GameFrame(); game.frameinit(); } }
2.startpanel代码如下:
package 贪吃蛇; import java.awt.Button; import java.awt.GridLayout; //panel是容器,默认为FlowLayout布局,不能独立存在 //该Panel主要容纳三个button,分别是 开始游戏 帮助 关于 //Panel最后会被加入Frame //使用了布局管理,就不能使用绝对定位了。 import java.awt.Panel; @SuppressWarnings("serial") public class StartPanel extends Panel{ private Button startGame; private Button help; private Button info; private GameHelp gh; //点击帮助按钮弹出的Frame private Info ifo; //关于的窗口 private GameFrame gf; //与主窗口关联,游戏开始按钮 的时间监听 执行的动作 与其相关 public StartPanel(GameFrame game) { super(); gf = game; } public StartPanel panelInit() { startGame = new Button("开始游戏"); help = new Button("帮助"); //因为 startPanel已经设置了布局管理,所以setSize,setLocation,setBounds就失效了。 //给startGame 按钮增加监听,直接用lambda ,ActionListener是个函数式接口 startGame.addActionListener(e->{ gf.createGame(); }); //给“帮助”按钮加上点击监听 help.addActionListener(e->{ if (gh==null) { gh = new GameHelp(); gh.helpInit(gf.getX(),gf.getY(),gf.getHeight());} else gh.helpInit(gf.getX(),gf.getY(),gf.getHeight()); }); info = new Button("关于"); //给关于按钮添加监听 info.addActionListener(e->{ if (ifo==null) { ifo = new Info(); ifo.infoInit(gf.getX(),gf.getY(),gf.getHeight());} else ifo.infoInit(gf.getX(),gf.getY(),gf.getHeight()); }); this.setLayout(new GridLayout(3,1,25,25)); //网格布局,三行一列 this.add(startGame); this.add(help); this.add(info); this.setVisible(true); return this; } }
3.help和info
package 贪吃蛇; import java.awt.Frame; import java.awt.Graphics; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @SuppressWarnings("serial") public class GameHelp extends Frame{ public void helpInit(int x,int y,int height) { this.setBounds(x+height, y, height/2, height/2); this.setName("帮助"); this.addWindowListener(new WindowAdapter() { //重写closing方法 public void windowClosing(WindowEvent e) { setVisible(false);//close(); //匿名内部类也是内部类,可以直接调用外部类的方法和成员 } }); this.setVisible(true); this.repaint(); //paint方法只能是系统调用,我们只能通过repaint来间接调用paint } //这里其实应该用一个好的图片(带帮助)直接画上最好 public void paint(Graphics g) { g.drawString("贪吃蛇游戏帮助", 50 , 50); } }
package 贪吃蛇; //info的窗口 import java.awt.Frame; import java.awt.Graphics; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @SuppressWarnings("serial") public class Info extends Frame{ public void infoInit(int x,int y,int height) { this.setBounds(x+height, y+height/2, height/2,height/2 ); this.setName("关于"); this.addWindowListener(new WindowAdapter() { //重写closing方法 public void windowClosing(WindowEvent e) { Info.this.setVisible(false);//close(); //匿名内部类也是内部类,可以直接调用外部类的方法和成员 } }); this.setVisible(true); this.repaint(); //paint方法只能是系统调用,我们只能通过repaint来间接调用paint } //这里其实应该用一个好的图片(带帮助)直接画上最好 public void paint(Graphics g) { g.drawString("作者:CP", 50 , 50); } }
4.location类(如果直接粘贴复制运行的话,注意修改package)
package 贪吃蛇; import 贪吃蛇.Snake.OritentionEum; public class Location { //坐标类,不光记录坐标位置,必要的时候还记录方向信息 private int x; private int y; private OritentionEum oritention; public Location(int x,int y) { this.x = x; this.y= y; } public Location(int x,int y,OritentionEum oritention) { this(x, y); this.oritention = oritention; } public Location(Location la,OritentionEum oritention) { this(la.getX(), la.getY()); this.oritention = oritention; } public int getX() { return x; } public int getY() { return y; } public void recordOritention(OritentionEum oritention) { this.oritention = oritention; } public OritentionEum getRecordOritention() { return this.oritention; } }
5.OerationList类
package 贪吃蛇; import java.awt.Choice; import java.awt.Font; import java.awt.GridLayout; import java.awt.Label; import java.awt.Panel; import java.awt.TextField; import java.time.LocalTime; @SuppressWarnings("serial") public class OerationList extends Panel{ private Label l = new Label("时间:"); private TextField time = new TextField(15); private Label l1 = new Label("得分:"); private TextField score = new TextField("0",15); private Label l2 = new Label("速度:"); private Choice speed = new Choice(); //private LocalTime lt = LocalTime.now(); //获取当前时间 ,java8最新时间包Time public OerationList listInit() { l.setFont(new Font("宋体",Font.PLAIN,15)); l1.setFont(new Font("宋体",Font.PLAIN,15)); l2.setFont(new Font("宋体",Font.PLAIN,15)); time.setFont(new Font("宋体",Font.PLAIN,15)); score.setFont(new Font("宋体",Font.PLAIN,15)); speed.setFont(new Font("宋体",Font.PLAIN,15)); //游戏速度选择框 speed.add("一级"); speed.add("二级"); speed.add("三级"); //时间框显示 this.updateTime(); //得分要根据吃食物数量来定 //全部加到ol中 this.add(l); this.add(time); this.add(l1); this.add(score); this.add(l2); this.add(speed); //this.setBounds((int)(grWidth*0.75), 0, (int)(grWidth*0.25), grHeight); this.setLayout(new GridLayout(6,1,0,25)); //6行一列,水平间距50 ,垂直间距60 this.setVisible(true); return this; } public String getScore() { return score.getText(); } public String getSpeed() { return speed.getSelectedItem(); } public void updateScore() { int i = Integer.parseInt(score.getText()); i +=5; score.setText(String.valueOf(i)); } public void updateTime() { time.setText(LocalTime.now().toString().split("\.")[0]); //split是用正则表达式的 其中.表示任意字符 所以 .要用两个转义字符才能表示.本身 } }
6.snake类
package 贪吃蛇; import java.awt.Color; import java.awt.Graphics; import java.util.LinkedList; public class Snake { //private int[][] snakeMap = new int[45][30]; 45行30列 //将蛇身的坐标加入一个队列,从蛇尾巴开始加 public LinkedList<Location> snake = new LinkedList<>(); //方向数组,用来确定蛇尾巴的方向 ,每次Frame监听到有效的方向按键,则记录位置并记录方向 public LinkedList<Location> oritention = new LinkedList<>(); private boolean hasCrawl = true; //初始化数组, { snake.add(new Location(2,3)); //First remove snake.add(new Location(2,4)); snake.add(new Location(2,5)); snake.add(new Location(2,6)); snake.add(new Location(2,7)); //Last 蛇头 } private OritentionEum oe = OritentionEum.RIGHT; //方向默认开始向右 private Location snakeHead = snake.getLast(); private boolean isEat = false; //是否吃到 //获取蛇身,将蛇尾是否增长计入里面 public LinkedList<Location> getSnake(){ this.growTail(); return snake; } //获取蛇尾位置 public Location getSnakeTail() { return snake.getFirst(); } //获取蛇头位置 public Location getSnakeHead() { return snake.getLast(); } //设置蛇头 public void setSnakeHead(Location loc) { this.snakeHead = loc; } //蛇运动, Location的数组行索引其实对应的是画布的Y坐标 public void snakeCrawl() { OritentionEum oe = this.getHeadOritention(); Location snakeHead = this.getSnakeHead(); switch(oe) { case DOWN : snake.addLast(new Location(snakeHead.getX()+1,snakeHead.getY()));break; case UP : snake.addLast(new Location(snakeHead.getX()-1,snakeHead.getY()));break; case RIGHT : snake.addLast(new Location(snakeHead.getX(),snakeHead.getY()+1));break; case LEFT : snake.addLast(new Location(snakeHead.getX(),snakeHead.getY()-1));break; } snake.remove(); this.hasCrawl = true; //蛇走完一部才设置为true,这样才允许方向的设置 if(!oritention.isEmpty()) { Location tail = this.getSnakeTail(); Location la = oritention.getFirst(); //蛇头转向的标记点 if(tail.getX() == la.getX() && tail.getY()==la.getY()) { //蛇尾走到拐点 oritention.remove(); } } } //画蛇 public void drawSnake(Graphics g,int rate) { g.setColor(Color.YELLOW); for(Location i: snake) { //g.fillRect(i.getX()*rate, i.getY()*rate, rate, rate); g.fillRect(i.getY()*rate+10,i.getX()*rate+50, rate, rate); } this.snakeCrawl(); } //设置方向,方向为右的时候只能设置方向为上下,为上下的时候只能设置方向为左右 //设置方向的时候同时将将方向加入方向队列用来对蛇尾方向进行标记 //存在一种情况,方向已经设置但是由于线程更新间隔大于按键监听间隔, //所以可能存在当蛇在横着走的时候可能先设置上或者下成功,但这时候绘画线程却没有更新(),在这极短的时间内又重新设置了左或者右 //这时候就会发现蛇可能在水平方向来回运动的情况 //设置一个标识变量 flag 设置方向的时候将其设置为false,蛇crawl之后设置为true public void setOritention(OritentionEum oe) { if(this.hasCrawl) { OritentionEum currentOe = this.getHeadOritention(); //获取当前蛇的方向 if(currentOe == OritentionEum.RIGHT || currentOe == OritentionEum.LEFT) { if(oe==OritentionEum.UP || oe==OritentionEum.DOWN) {oritention.add(new Location(this.getSnakeHead(),this.getHeadOritention())); this.oe = oe; this.hasCrawl=false;} } else{ if(oe==OritentionEum.RIGHT || oe==OritentionEum.LEFT) {oritention.add(new Location(this.getSnakeHead(),this.getHeadOritention())); this.oe=oe;this.hasCrawl=false;} } } } //获取当前蛇头的方向 public OritentionEum getHeadOritention() { return oe; } //获取蛇尾方向,方向队列不为空,就按照队列First方向,否则就按照蛇头方向 public OritentionEum getTailOritention() { if(!oritention.isEmpty()) { Location la = oritention.getFirst(); //蛇头转向的标记点 //Location tail = this.getSnakeTail(); return la.getRecordOritention(); } else return this.getHeadOritention(); } //蛇吃食物,同时在蛇尾增加长度(需要根据方向来确定增加在哪里) public boolean eatFood(Food d) { Location fla=d.getFoodLocation(); //食物坐标 Location sla = this.getSnakeHead(); //蛇头坐标 if(sla.getX()==fla.getX() && fla.getY() == sla.getY()) { isEat = true; d.setEat(true); //更新食物里面是否被吃的状态 return true; } return false; } //在蛇尾增加一个单位的长度 //如果方向队列为空,则按照当前蛇前进的方向来反向增加 //否则就按照蛇尾方向队列oritention的First元素来反向增加蛇尾长度 public void growTail() { if(this.isEat) { OritentionEum oe = this.getTailOritention(); //System.out.println(oe); Location la = this.getSnakeTail(); switch(oe) { case UP: snake.addFirst(new Location(la.getX()+1,la.getY()));break; case DOWN: snake.addFirst(new Location(la.getX()-1,la.getY()));break; case LEFT: snake.addFirst(new Location(la.getX(),la.getY()+1));break; case RIGHT: snake.addFirst(new Location(la.getX(),la.getY()-1));break; } //增长完后复位 this.isEat = false; } } // 碰撞游戏结束 public boolean crashed() { Location la = this.getSnakeHead(); int x = la.getX(),y=la.getY(); //只要蛇头没撞上就OK //分为撞墙和撞蛇身 if(x>=0 && x<=69 && y>=0 && y<=49) { //在画布范围里 boolean flag = false; for(int i=0;i<snake.size()-1;i++) { Location sla = snake.get(i); if(sla.getX()==x && sla.getY()==y) //蛇与自身碰撞了 flag = true; } return flag; } else return true; } //蛇运行方向枚举类 enum OritentionEum{ //4个枚举方向的枚举实例 UP,DOWN,LEFT,RIGHT; } public static void main(String [] args) { Snake sk = new Snake(); for(Location i : sk.snake) { System.out.println(i.getX()+" "+i.getY()); } System.out.println(sk.snake.getLast().getY()); } }
7.food类
package 贪吃蛇; import java.awt.Color; import java.awt.Graphics; import java.util.Random; //定义食物的类,显示和随机生成 public class Food { private boolean iseat = false; //默认没有被吃 Location foodla = new Location(20,20); //食物的初始位置 public void drawFood(Graphics g,int rate) { g.setColor(Color.RED); g.fillRect(foodla.getY()*rate+10, foodla.getX()*rate+50, rate, rate); } //获取食物的坐标 public Location getFoodLocation() { return foodla; } //设定食物坐标 public void setLocation(Location x) { this.foodla = x; } //食物是否被吃 public boolean isEat() { return iseat; } //设定食物是否被吃状态 public void setEat(boolean b) { this.iseat = b; } //如果食物被吃了,就更新食物坐标,食物坐标在(90,60)的范围且不与蛇身重合 public void updateFood() { //这里只是随机产生的坐标,有可能食物在蛇身子上,因为不想用遍历的方式来获取哪些坐标是空白的,有时间记得回来改 Random am = new Random(); foodla = new Location(am.nextInt(70),am.nextInt(50)); } }
另:游戏结束会面如下:
工作一年,从x=x+1都理解不了的萌新,到能自己刷leetcode(只是刷了简单级别的67题和几道medium的题),真羡慕那些初中就接触代码的人。自己也在一直补习组成原理、操作系统和网络的相关知识。数据结构也在看,看的是C语言版的,然后自己用java实现栈(链式和数组式),队列,avl树,B树等。希望自己坚持下来,解算法题也是会上瘾的,哈哈。