Java实现贪吃蛇游戏

网友投稿 237 2022-11-29


Java实现贪吃蛇游戏

最近java和jswing上手练习了一下贪吃蛇

先上代码 https://github.com/rgzhang2018/snake

欢迎交流和加入新的内容

用到了JSwing,下面是一些具体的思路

实现

* 蛇:

采用单链表记录首尾,整个蛇被分为lattice格子,放在map里

* 移动:

我在实现的过程中发现最难得反而是蛇的定义和实现。一直想着怎么样用单独的方法表示出蛇来,但是如果将蛇单独实现,总有些细节实现起来特别麻烦

其实蛇移动并非牵一发而动全身,其实身子是没有发生变化的,关键是两点:

a.头的移动

b.尾巴的移动

实现:

直接把蛇实现在地http://图的小格子里,不再单独设置子类或者ArrayList等,Map里加上蛇头的坐标,从而使得Map可以根据蛇头改变蛇的坐标(类似于变量交换)。为头部单独设置x,y,作为移动的方向(也可以作为静态变量x和y,不过没什么区别),为身子设置next指针,只要next.next不是尾巴,那么保持不变。如果next是尾巴,就把自己的设置为尾巴,并且改变next,使之成为普通地图块。(refresh方法)

* 控制方向:

使用键盘事件,目前仅设置了wasd四个

* 窗口设计:

view extends JPanel,控制显示,然后在Lattice里调用Graphics.draw(...)实现对每个格子的显示

下面是核心的map部分代码(包括自动移动,检测食物,增加长度等等)

importhttp:// codes.myGame.snake.cell.Lattice;

import java.util.Random;

public class Smap {

private boolean getFood = false;//如果得到食物,该指针设为true,并且在随后的autoChange里增加蛇的长度

private boolean gameOver = false;

private boolean directionChange = false;//这里标志的作用是保证在一次运动期间只会进行一次转向,使游戏更流畅

private int MAP_SIZE;

private Lattice[][] map;

private int directionX = 0;//下一次头在当前位置的哪个方向上

private int directionY = 1;//下一次头在当前位置的哪个方向上

private int[] head = new int[2];//记录当前头的位置

private int[] food = new int[2];//记录当前食物的位置

public Smap(int size) {

MAP_SIZE = size;

map = new Lattice[MAP_SIZE][MAP_SIZE];

for(int i=0;i

for (int j = 0 ;j

map[i][j] = new Lattice();

}

}

map[MAP_SIZE/2][MAP_SIZE/2].setHead(true,map[MAP_SIZE/2][MAP_SIZE/2-1]);//初始化设置一个头结点,以及他的尾节点

head[0] = MAP_SIZE/2;

head[1] = MAP_SIZE/2;

map[MAP_SIZE/2][MAP_SIZE/2-1].setRear(true,null);

this.randFood();

}

//模拟蛇的自动移动

public void autoChange(){

this.setHead();

if(food[0]==head[0] && food[1]==head[1]){//如果新的头部碰触到了食物,那么尾部增长

getFood = true;

}

if(!gameOver)this.setRear();

if(getFood)this.randFood();

directionChange = false;

}

//根据键盘事件,改变头的下一次移动方向,注意 该移动方向是仅针对头部的

//setDirection和setHead两个方法需要互斥进行,这里单线程,用synchronized即可

//(否则,如果当前头部在边界位置,连续变幻方向可能导致在setHead里发生溢出)

public synchronized void setDirection(int x,int y){

if(directionY!=y && directionX!=x &&!directionChange){

directionX = x;

directionY = y;

directionChange = true;

}

}

public boolean gameOver(){

return gameOver;//头碰到身子,证明gameOver

}

private synchronized void setHead(){

int i = head[0];

int j = head[1];

head[0] = ( head[0] + directionX + MAP_SIZE)%MAP_SIZE;

head[1] = ( head[1] + directionY + MAP_SIZE )%MAP_SIZE;

if(map[head[0]][head[1]].isBody())gameOver = true;

map[head[0]][head[1]].setHead(true,map[i][j]);

map[i][j].setBody(true,null);

map[i][j].setHead(false,null); //传入null表示不改变当前指向

}

//设置尾巴由于没法像头部那样直接设置,这里只能采用链表遍历的方式获取尾巴

private void setRear(){

if(!getFood){

Lattice temp = map[head[0]][head[1]];

while (!temp.next.isRear())temp = temp.next;

temp.next().setRear(false,null);

temp.setRear(true,null);

temp.setBody(false,null);

}

}

private void randFood(){

getFood = false;

map[food[0]][food[1]].setFood(false);//先把当前的食物取消掉

boolean flag = false;//设置下一个食物

Random random = new Random();

int x = random.nextInt(MAP_SIZE);

int y = random.nextInt(MAP_SIZE);

while (!flag){

x = random.nextInt(MAP_SIZE);

y = random.nextInt(MAP_SIZE);

if(!map[x][y].isHead() && !map[x][y].isRear() &&!map[x][y].isBody())flag = true;

}

map[x][y].setFood(true);

food[0] = x;

food[1] = y;

}

public Lattice get(int row, int col){

return map[row][col];

}

public int getMAP_SIZE() {

return MAP_SIZE;

}

}

下面是显示部分的代码

显示分为两部分,一块是利用Graphics.draw()方法实现单个单元格的绘制,另一块设置view类继承自JPanel。负责绘制图画显示

public class Lattice {

private boolean isBody = false;

private boolean isHead = false;

private boolean isFood = false;

private boolean isRear = false;

public Lattice next = null;

public void setHead(boolean bool,Lattice next){

isHead = bool;

if(next!=null)this.next = next;

}

public void setBody(boolean bool,Lattice next){

isBody = bool;

if(next!=null)this.next = next; //传入参数为null时,不改变当前的next

}

public void setRear(boolean bool,Lattice next){

isRear = bool;

this.next = next;

}

public void setFood(boolean bool){

isFood = bool;

}

public Lattice next(){

return next;

}

public boolean isHead(){

return isHead;

}

public boolean isFood(){

return isFood;

}

public boolean isRear(){

return isRear;

}

public boolean isBody(){

return isBody;

}

public void refresh(){

if(isHead){

isBody = true;

isHead = false;

// 怎么设置下一个头呢?(考虑把DirectionX,Y放到Smap里,而不是这里)

}else if(isBody){

if(next.isRear){

next.isRear = false;

isRear = true;

isBody = false;

}

}

}

// 在这里设置细胞可见

public void draw(Graphics g, int x, int y, int size) {

g.setColor(black);

g.drawRect(x, y, size, size);

if ( isHead ) {

g.setColor( red);

g.fillRect(x, y, size, size);

}else if ( isBody || isRear) {

g.setColor(black);

g.fillRect(x, y, size, size);

}else if(isFood){

g.setColor( blue);

g.fillRect(x, y, size, size);

}

}

}

view部分:

import codes.myGame.snake.cell.Lattice;

import javax.swing.*;

import java.awt.*;

public class View extends JPanel {

private static final long serialVersionUID = -5258995676212660595L;

private static final int GRID_SIZE = 32; //填充的像素数量

private Smap thisMap;

public View(Smap map) {

thisMap = map;

}

@Override

public void paint(Graphics g) {

super.paint(g);

int size = thisMap.getMAP_SIZE();

for (int row = 0; row< size; row++ ) {

for (int col = 0; col< size; col++ ) {

Lattice lattice = thisMap.get(row, col);

if ( lattice != null ) {

lattice.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//对应的格子的显示

}

}

}

}

@Override

public Dimension getPreferredSize() {//创建该div大小

return new Dimension(thisMap.getMAP_SIZE()*GRID_SIZE+1, thisMap.getMAP_SIZE()*GRID_SIZE+1);

}

}

for (int j = 0 ;j

map[i][j] = new Lattice();

}

}

map[MAP_SIZE/2][MAP_SIZE/2].setHead(true,map[MAP_SIZE/2][MAP_SIZE/2-1]);//初始化设置一个头结点,以及他的尾节点

head[0] = MAP_SIZE/2;

head[1] = MAP_SIZE/2;

map[MAP_SIZE/2][MAP_SIZE/2-1].setRear(true,null);

this.randFood();

}

//模拟蛇的自动移动

public void autoChange(){

this.setHead();

if(food[0]==head[0] && food[1]==head[1]){//如果新的头部碰触到了食物,那么尾部增长

getFood = true;

}

if(!gameOver)this.setRear();

if(getFood)this.randFood();

directionChange = false;

}

//根据键盘事件,改变头的下一次移动方向,注意 该移动方向是仅针对头部的

//setDirection和setHead两个方法需要互斥进行,这里单线程,用synchronized即可

//(否则,如果当前头部在边界位置,连续变幻方向可能导致在setHead里发生溢出)

public synchronized void setDirection(int x,int y){

if(directionY!=y && directionX!=x &&!directionChange){

directionX = x;

directionY = y;

directionChange = true;

}

}

public boolean gameOver(){

return gameOver;//头碰到身子,证明gameOver

}

private synchronized void setHead(){

int i = head[0];

int j = head[1];

head[0] = ( head[0] + directionX + MAP_SIZE)%MAP_SIZE;

head[1] = ( head[1] + directionY + MAP_SIZE )%MAP_SIZE;

if(map[head[0]][head[1]].isBody())gameOver = true;

map[head[0]][head[1]].setHead(true,map[i][j]);

map[i][j].setBody(true,null);

map[i][j].setHead(false,null); //传入null表示不改变当前指向

}

//设置尾巴由于没法像头部那样直接设置,这里只能采用链表遍历的方式获取尾巴

private void setRear(){

if(!getFood){

Lattice temp = map[head[0]][head[1]];

while (!temp.next.isRear())temp = temp.next;

temp.next().setRear(false,null);

temp.setRear(true,null);

temp.setBody(false,null);

}

}

private void randFood(){

getFood = false;

map[food[0]][food[1]].setFood(false);//先把当前的食物取消掉

boolean flag = false;//设置下一个食物

Random random = new Random();

int x = random.nextInt(MAP_SIZE);

int y = random.nextInt(MAP_SIZE);

while (!flag){

x = random.nextInt(MAP_SIZE);

y = random.nextInt(MAP_SIZE);

if(!map[x][y].isHead() && !map[x][y].isRear() &&!map[x][y].isBody())flag = true;

}

map[x][y].setFood(true);

food[0] = x;

food[1] = y;

}

public Lattice get(int row, int col){

return map[row][col];

}

public int getMAP_SIZE() {

return MAP_SIZE;

}

}

下面是显示部分的代码

显示分为两部分,一块是利用Graphics.draw()方法实现单个单元格的绘制,另一块设置view类继承自JPanel。负责绘制图画显示

public class Lattice {

private boolean isBody = false;

private boolean isHead = false;

private boolean isFood = false;

private boolean isRear = false;

public Lattice next = null;

public void setHead(boolean bool,Lattice next){

isHead = bool;

if(next!=null)this.next = next;

}

public void setBody(boolean bool,Lattice next){

isBody = bool;

if(next!=null)this.next = next; //传入参数为null时,不改变当前的next

}

public void setRear(boolean bool,Lattice next){

isRear = bool;

this.next = next;

}

public void setFood(boolean bool){

isFood = bool;

}

public Lattice next(){

return next;

}

public boolean isHead(){

return isHead;

}

public boolean isFood(){

return isFood;

}

public boolean isRear(){

return isRear;

}

public boolean isBody(){

return isBody;

}

public void refresh(){

if(isHead){

isBody = true;

isHead = false;

// 怎么设置下一个头呢?(考虑把DirectionX,Y放到Smap里,而不是这里)

}else if(isBody){

if(next.isRear){

next.isRear = false;

isRear = true;

isBody = false;

}

}

}

// 在这里设置细胞可见

public void draw(Graphics g, int x, int y, int size) {

g.setColor(black);

g.drawRect(x, y, size, size);

if ( isHead ) {

g.setColor( red);

g.fillRect(x, y, size, size);

}else if ( isBody || isRear) {

g.setColor(black);

g.fillRect(x, y, size, size);

}else if(isFood){

g.setColor( blue);

g.fillRect(x, y, size, size);

}

}

}

view部分:

import codes.myGame.snake.cell.Lattice;

import javax.swing.*;

import java.awt.*;

public class View extends JPanel {

private static final long serialVersionUID = -5258995676212660595L;

private static final int GRID_SIZE = 32; //填充的像素数量

private Smap thisMap;

public View(Smap map) {

thisMap = map;

}

@Override

public void paint(Graphics g) {

super.paint(g);

int size = thisMap.getMAP_SIZE();

for (int row = 0; row< size; row++ ) {

for (int col = 0; col< size; col++ ) {

Lattice lattice = thisMap.get(row, col);

if ( lattice != null ) {

lattice.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//对应的格子的显示

}

}

}

}

@Override

public Dimension getPreferredSize() {//创建该div大小

return new Dimension(thisMap.getMAP_SIZE()*GRID_SIZE+1, thisMap.getMAP_SIZE()*GRID_SIZE+1);

}

}

map[i][j] = new Lattice();

}

}

map[MAP_SIZE/2][MAP_SIZE/2].setHead(true,map[MAP_SIZE/2][MAP_SIZE/2-1]);//初始化设置一个头结点,以及他的尾节点

head[0] = MAP_SIZE/2;

head[1] = MAP_SIZE/2;

map[MAP_SIZE/2][MAP_SIZE/2-1].setRear(true,null);

this.randFood();

}

//模拟蛇的自动移动

public void autoChange(){

this.setHead();

if(food[0]==head[0] && food[1]==head[1]){//如果新的头部碰触到了食物,那么尾部增长

getFood = true;

}

if(!gameOver)this.setRear();

if(getFood)this.randFood();

directionChange = false;

}

//根据键盘事件,改变头的下一次移动方向,注意 该移动方向是仅针对头部的

//setDirection和setHead两个方法需要互斥进行,这里单线程,用synchronized即可

//(否则,如果当前头部在边界位置,连续变幻方向可能导致在setHead里发生溢出)

public synchronized void setDirection(int x,int y){

if(directionY!=y && directionX!=x &&!directionChange){

directionX = x;

directionY = y;

directionChange = true;

}

}

public boolean gameOver(){

return gameOver;//头碰到身子,证明gameOver

}

private synchronized void setHead(){

int i = head[0];

int j = head[1];

head[0] = ( head[0] + directionX + MAP_SIZE)%MAP_SIZE;

head[1] = ( head[1] + directionY + MAP_SIZE )%MAP_SIZE;

if(map[head[0]][head[1]].isBody())gameOver = true;

map[head[0]][head[1]].setHead(true,map[i][j]);

map[i][j].setBody(true,null);

map[i][j].setHead(false,null); //传入null表示不改变当前指向

}

//设置尾巴由于没法像头部那样直接设置,这里只能采用链表遍历的方式获取尾巴

private void setRear(){

if(!getFood){

Lattice temp = map[head[0]][head[1]];

while (!temp.next.isRear())temp = temp.next;

temp.next().setRear(false,null);

temp.setRear(true,null);

temp.setBody(false,null);

}

}

private void randFood(){

getFood = false;

map[food[0]][food[1]].setFood(false);//先把当前的食物取消掉

boolean flag = false;//设置下一个食物

Random random = new Random();

int x = random.nextInt(MAP_SIZE);

int y = random.nextInt(MAP_SIZE);

while (!flag){

x = random.nextInt(MAP_SIZE);

y = random.nextInt(MAP_SIZE);

if(!map[x][y].isHead() && !map[x][y].isRear() &&!map[x][y].isBody())flag = true;

}

map[x][y].setFood(true);

food[0] = x;

food[1] = y;

}

public Lattice get(int row, int col){

return map[row][col];

}

public int getMAP_SIZE() {

return MAP_SIZE;

}

}

下面是显示部分的代码

显示分为两部分,一块是利用Graphics.draw()方法实现单个单元格的绘制,另一块设置view类继承自JPanel。负责绘制图画显示

public class Lattice {

private boolean isBody = false;

private boolean isHead = false;

private boolean isFood = false;

private boolean isRear = false;

public Lattice next = null;

public void setHead(boolean bool,Lattice next){

isHead = bool;

if(next!=null)this.next = next;

}

public void setBody(boolean bool,Lattice next){

isBody = bool;

if(next!=null)this.next = next; //传入参数为null时,不改变当前的next

}

public void setRear(boolean bool,Lattice next){

isRear = bool;

this.next = next;

}

public void setFood(boolean bool){

isFood = bool;

}

public Lattice next(){

return next;

}

public boolean isHead(){

return isHead;

}

public boolean isFood(){

return isFood;

}

public boolean isRear(){

return isRear;

}

public boolean isBody(){

return isBody;

}

public void refresh(){

if(isHead){

isBody = true;

isHead = false;

// 怎么设置下一个头呢?(考虑把DirectionX,Y放到Smap里,而不是这里)

}else if(isBody){

if(next.isRear){

next.isRear = false;

isRear = true;

isBody = false;

}

}

}

// 在这里设置细胞可见

public void draw(Graphics g, int x, int y, int size) {

g.setColor(black);

g.drawRect(x, y, size, size);

if ( isHead ) {

g.setColor( red);

g.fillRect(x, y, size, size);

}else if ( isBody || isRear) {

g.setColor(black);

g.fillRect(x, y, size, size);

}else if(isFood){

g.setColor( blue);

g.fillRect(x, y, size, size);

}

}

}

view部分:

import codes.myGame.snake.cell.Lattice;

import javax.swing.*;

import java.awt.*;

public class View extends JPanel {

private static final long serialVersionUID = -5258995676212660595L;

private static final int GRID_SIZE = 32; //填充的像素数量

private Smap thisMap;

public View(Smap map) {

thisMap = map;

}

@Override

public void paint(Graphics g) {

super.paint(g);

int size = thisMap.getMAP_SIZE();

for (int row = 0; row< size; row++ ) {

for (int col = 0; col< size; col++ ) {

Lattice lattice = thisMap.get(row, col);

if ( lattice != null ) {

lattice.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//对应的格子的显示

}

}

}

}

@Override

public Dimension getPreferredSize() {//创建该div大小

return new Dimension(thisMap.getMAP_SIZE()*GRID_SIZE+1, thisMap.getMAP_SIZE()*GRID_SIZE+1);

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java实现贪吃蛇小游戏
下一篇:Java 异常处理小结,从入门到精通
相关文章

 发表评论

暂时没有评论,来抢沙发吧~