工作越来越忙了,没有多少时间看书学习了,下班挤出时间写一个贪吃蛇的小游戏。当然还有许多瑕疵和不足,权当萌新学习交流之用。(吐槽:当年家人为什么要阻挡我学习计算机,报什么鬼会计。话说有没有自学编程的一起交流交流)
这个贪吃蛇有以下几个类:
GameFrame(主游戏窗口类)
StartPanel(开始界面)
游戏开始界面:
3.GameHelp(上图的帮助)
4.Info(上图的关于)
5.OerationList(游戏运行后右侧信息栏,不要在意类名,打错了没改-。-)
游戏运行画面如下:
6.Location(坐标类,必要时还会记录信息)
7.Snake(蛇的类,里面包含一个内部类,方向枚举类OritentionEum)
8.Food(食物类)
1.GameFrame类代码如下:
package贪吃蛇;importjava.awt.Canvas;importjava.awt.Color;importjava.awt.Font;importjava.awt.Frame;importjava.awt.Graphics;importjava.awt.Image;importjava.awt.event.KeyAdapter;importjava.awt.event.KeyEvent;importjava.awt.event.WindowAdapter;importjava.awt.event.WindowEvent;import贪吃蛇.Snake.OritentionEum;//游戏的主窗口
public classGameFrame {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;privateSnake sk ;privateFood fd;//窗口初始化
public voidframeinit() {
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,inty) {if(f!=null) {
f.addWindowListener(newWindowAdapter() {public voidwindowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setLocation(x, y);//设置组件在桌面显示的位置
f.setSize(fWidth, fHeight); //设置组件的大小 ,也可用setBounds同时设置位置和大小
f.setVisible(true);
}
}//游戏开始窗口
public voidcreateGame() {
f.setVisible(false);int x=f.getX();int y =f.getY();
f= new Frame("贪吃蛇游戏开始");
sk= new Snake(); //蛇
fd = newFood();this.setAtt(x,y); //为了随时更新位置
f.setLayout(null);
gc= newGameCanvas();
gc.canvasInit();//所以
gc.setBounds(0, 0, fWidth*2/3, fHeight); //gc宽度为300,高度为450
gc.addKeyListener(newKeyAdapter() {public voidkeyPressed(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= newOerationList().listInit();
ol.setBounds(310, 75, fWidth/4, fHeight*2/3);
f.add(ol);new updateTimeThread().start(); //更新时间线程
new updateCanvasThread().start();//绘画线程更新
}public intgetWidth() {returnf.getWidth();
}public intgetHeight() {returnf.getHeight();
}public int getX() { //返回当前窗口的X坐标
returnf.getX();
}public intgetY() {returnf.getY();
}//绘画线程
class updateCanvasThread extendsThread{public voidrun() {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 extendsThread{public voidrun() {while(true) {try{
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
ol.updateTime();
}
}
}//画布
class GameCanvas extendsCanvas{publicGameCanvas canvasInit() {this.setVisible(true);return this;
}public voidupdate(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 voidpaint(Graphics g) {
}
}public static voidmain(String[] args) {
GameFrame game= newGameFrame();
game.frameinit();
}
}
2.startpanel代码如下:
package贪吃蛇;importjava.awt.Button;importjava.awt.GridLayout;//panel是容器,默认为FlowLayout布局,不能独立存在//该Panel主要容纳三个button,分别是 开始游戏 帮助 关于//Panel最后会被加入Frame//使用了布局管理,就不能使用绝对定位了。
importjava.awt.Panel;
@SuppressWarnings("serial")public class StartPanel extendsPanel{privateButton startGame;privateButton help;privateButton info;private GameHelp gh; //点击帮助按钮弹出的Frame
private Info ifo; //关于的窗口
private GameFrame gf; //与主窗口关联,游戏开始按钮 的时间监听 执行的动作 与其相关
publicStartPanel(GameFrame game) {super();
gf=game;
}publicStartPanel 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= newGameHelp();
gh.helpInit(gf.getX(),gf.getY(),gf.getHeight());}elsegh.helpInit(gf.getX(),gf.getY(),gf.getHeight());
});
info= new Button("关于");//给关于按钮添加监听
info.addActionListener(e->{if (ifo==null) {
ifo= newInfo();
ifo.infoInit(gf.getX(),gf.getY(),gf.getHeight());}elseifo.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贪吃蛇;importjava.awt.Frame;importjava.awt.Graphics;importjava.awt.event.WindowAdapter;importjava.awt.event.WindowEvent;
@SuppressWarnings("serial")public class GameHelp extendsFrame{public void helpInit(int x,int y,intheight) {this.setBounds(x+height, y, height/2, height/2);this.setName("帮助");this.addWindowListener(newWindowAdapter() {//重写closing方法
public voidwindowClosing(WindowEvent e) {
setVisible(false);//close();//匿名内部类也是内部类,可以直接调用外部类的方法和成员
}
});this.setVisible(true);this.repaint(); //paint方法只能是系统调用,我们只能通过repaint来间接调用paint
}//这里其实应该用一个好的图片(带帮助)直接画上最好
public voidpaint(Graphics g) {
g.drawString("贪吃蛇游戏帮助", 50 , 50);
}
}
package贪吃蛇;//info的窗口
importjava.awt.Frame;importjava.awt.Graphics;importjava.awt.event.WindowAdapter;importjava.awt.event.WindowEvent;
@SuppressWarnings("serial")public class Info extendsFrame{public void infoInit(int x,int y,intheight) {this.setBounds(x+height, y+height/2, height/2,height/2);this.setName("关于");this.addWindowListener(newWindowAdapter() {//重写closing方法
public voidwindowClosing(WindowEvent e) {
Info.this.setVisible(false);//close();//匿名内部类也是内部类,可以直接调用外部类的方法和成员
}
});this.setVisible(true);this.repaint(); //paint方法只能是系统调用,我们只能通过repaint来间接调用paint
}//这里其实应该用一个好的图片(带帮助)直接画上最好
public voidpaint(Graphics g) {
g.drawString("作者:CP", 50 , 50);
}
}
4.location类(如果直接粘贴复制运行的话,注意修改package)
package贪吃蛇;import贪吃蛇.Snake.OritentionEum;public classLocation {//坐标类,不光记录坐标位置,必要的时候还记录方向信息
private intx;private inty;privateOritentionEum oritention;public Location(int x,inty) {this.x =x;this.y=y;
}public Location(int x,inty,OritentionEum oritention) {this(x, y);this.oritention =oritention;
}publicLocation(Location la,OritentionEum oritention) {this(la.getX(), la.getY());this.oritention =oritention;
}public intgetX() {returnx;
}public intgetY() {returny;
}public voidrecordOritention(OritentionEum oritention) {this.oritention =oritention;
}publicOritentionEum getRecordOritention() {return this.oritention;
}
}
5.OerationList类
package贪吃蛇;importjava.awt.Choice;importjava.awt.Font;importjava.awt.GridLayout;importjava.awt.Label;importjava.awt.Panel;importjava.awt.TextField;importjava.time.LocalTime;
@SuppressWarnings("serial")public class OerationList extendsPanel{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 = newChoice();//private LocalTime lt = LocalTime.now();//获取当前时间 ,java8最新时间包Time
publicOerationList 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;
}publicString getScore() {returnscore.getText();
}publicString getSpeed() {returnspeed.getSelectedItem();
}public voidupdateScore() {int i =Integer.parseInt(score.getText());
i+=5;
score.setText(String.valueOf(i));
}public voidupdateTime() {
time.setText(LocalTime.now().toString().split("\\.")[0]);//split是用正则表达式的 其中.表示任意字符 所以 .要用两个转义字符才能表示.本身
}
}
6.snake类
package贪吃蛇;importjava.awt.Color;importjava.awt.Graphics;importjava.util.LinkedList;public classSnake {//private int[][] snakeMap = new int[45][30]; 45行30列//将蛇身的坐标加入一个队列,从蛇尾巴开始加
public LinkedList snake = new LinkedList<>();//方向数组,用来确定蛇尾巴的方向 ,每次Frame监听到有效的方向按键,则记录位置并记录方向
public LinkedList 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 LinkedListgetSnake(){this.growTail();returnsnake;
}//获取蛇尾位置
publicLocation getSnakeTail() {returnsnake.getFirst();
}//获取蛇头位置
publicLocation getSnakeHead() {returnsnake.getLast();
}//设置蛇头
public voidsetSnakeHead(Location loc) {this.snakeHead =loc;
}//蛇运动, Location的数组行索引其实对应的是画布的Y坐标
public voidsnakeCrawl() {
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,intrate) {
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 voidsetOritention(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;}
}
}
}//获取当前蛇头的方向
publicOritentionEum getHeadOritention() {returnoe;
}//获取蛇尾方向,方向队列不为空,就按照队列First方向,否则就按照蛇头方向
publicOritentionEum getTailOritention() {if(!oritention.isEmpty()) {
Location la= oritention.getFirst(); //蛇头转向的标记点//Location tail = this.getSnakeTail();
returnla.getRecordOritention();
}else return this.getHeadOritention();
}//蛇吃食物,同时在蛇尾增加长度(需要根据方向来确定增加在哪里)
public booleaneatFood(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 voidgrowTail() {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 booleancrashed() {
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
Location sla=snake.get(i);if(sla.getX()==x && sla.getY()==y) //蛇与自身碰撞了
flag = true;
}returnflag;
}else return true;
}//蛇运行方向枚举类
enumOritentionEum{//4个枚举方向的枚举实例
UP,DOWN,LEFT,RIGHT;
}public static voidmain(String [] args) {
Snake sk= newSnake();for(Location i : sk.snake) {
System.out.println(i.getX()+" "+i.getY());
}
System.out.println(sk.snake.getLast().getY());
}
}
7.food类
package贪吃蛇;importjava.awt.Color;importjava.awt.Graphics;importjava.util.Random;//定义食物的类,显示和随机生成
public classFood {private boolean iseat = false; //默认没有被吃
Location foodla = new Location(20,20); //食物的初始位置
public void drawFood(Graphics g,intrate) {
g.setColor(Color.RED);
g.fillRect(foodla.getY()*rate+10, foodla.getX()*rate+50, rate, rate);
}//获取食物的坐标
publicLocation getFoodLocation() {returnfoodla;
}//设定食物坐标
public voidsetLocation(Location x) {this.foodla =x;
}//食物是否被吃
public booleanisEat() {returniseat;
}//设定食物是否被吃状态
public void setEat(booleanb) {this.iseat =b;
}//如果食物被吃了,就更新食物坐标,食物坐标在(90,60)的范围且不与蛇身重合
public voidupdateFood() {//这里只是随机产生的坐标,有可能食物在蛇身子上,因为不想用遍历的方式来获取哪些坐标是空白的,有时间记得回来改
Random am = newRandom();
foodla= new Location(am.nextInt(70),am.nextInt(50));
}
}
另:游戏结束会面如下:
工作一年,从x=x+1都理解不了的萌新,到能自己刷leetcode(只是刷了简单级别的67题和几道medium的题),真羡慕那些初中就接触代码的人。自己也在一直补习组成原理、操作系统和网络的相关知识。数据结构也在看,看的是C语言版的,然后自己用java实现栈(链式和数组式),队列,avl树,B树等。希望自己坚持下来,解算法题也是会上瘾的,哈哈。