Java实现经典游戏推箱子的示例代码

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Java实现经典游戏推箱⼦的⽰例代码
⽬录
前⾔
主要设计
功能截图
代码实现
核⼼类
声⾳播放类
总结
前⾔
《推箱⼦》推箱⼦是⼀个古⽼的游戏,⽬的是在训练你的逻辑思考能⼒。

在⼀个狭⼩的仓库中,要求把⽊箱放到指定的位置,稍不⼩⼼就会出现箱⼦⽆法移动或者通道被堵住的情况,所以需要巧妙的利⽤有限的空间和通道,合理安排移动的次序和位置,才能顺利的完成任务。

游戏是⽤java语⾔实现,采⽤了swing技术进⾏了界⾯化处理,设计思路⽤了⾯向对象思想。

主要需求
控制搬运⼯上下左右移动,来将箱⼦推到指定地点
主要设计
1、游戏⾯板⽣成显⽰
2、地图⽣成算法
3、⼈物移动算法
4、播放背景⾳乐
5、箱⼦移动算法
6、全部箱⼦移动到指定位置,才算游戏过关
功能截图
游戏开始
移动效果
游戏过关
代码实现
核⼼类
public class GameFrame extends JFrame implements
ActionListener, MouseListener, KeyListener {// 实现动作事件监听器、⿏标事件监听器、键盘事件监听器
// 当前的关卡数,默认为第⼀关,从1开始计数
private int grade = 1;
// row,column记载⼈的位置,分别表⽰⼆维数组中的⾏号和列号,即map[row][column]确定⼈的位置
private int row = 7, column = 7;
// leftX,leftY记载左上⾓图⽚的位置,避免图⽚从(0,0)坐标开始,因为是图⽚填充,从(0,0)开始不⾏
private int leftX = 50, leftY = 50;
// 记载地图的总共有多少⾏、多少列
private int mapRow = 0, mapColumn = 0;
// 记载屏幕窗⼝的宽度和⾼度
private int width = 0, height = 0;
private boolean acceptKey = true;
// 程序所需要⽤到的图⽚
private Image pics[] = null;// 图⽚数据
private byte[][] map = null;// 地图数据
private ArrayList list = new ArrayList();
private SoundPlayerUtil soundPlayer;// 播放声⾳⼯具类
/* 常量,即游戏中的资源 */
private final static int WALL = 1;// 墙
private final static int BOX = 2;// 箱⼦
private final static int BOX_ON_END = 3;// 放到⽬的地的箱⼦
private final static int END = 4;// ⽬的地
private final static int MAN_DOWN = 5;// 向下的⼈
private final static int MAN_LEFT = 6;// 向左的⼈
private final static int MAN_RIGHT = 7;// 向右的⼈
private final static int MAN_UP = 8;// 向上的⼈
private final static int GRASS = 9;// 通道
private final static int MAN_DOWN_ON_END = 10;// 站在⽬的地向下的⼈
private final static int MAN_LEFT_ON_END = 11;// 站在⽬的地向左的⼈
private final static int MAN_RIGHT_ON_END = 12;// 站在⽬的地向右的⼈
private final static int MAN_UP_ON_END = 13;// 站在⽬的地向上的⼈
private final static int MOVE_PIXEL = 30;// 表⽰每次移动30像素
/**
* 在构造⽅法GameFrame0中,调⽤initMap()法来初始化本关grade游戏地图,清空悔棋信
* 息列表list,同时播放MIDI背景⾳乐。

*/
public GameFrame() {
// 游戏窗⼝的⼀些基本设置
setTitle("推箱⼦游戏");
setSize(600, 600);
setVisible(true);
setLocation(300, 20);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
contentPane.setLayout(null);
contentPane.setBackground(Color.black);
// 其他设置,初始化窗⼝的宽度和⾼度赋值给width和height
this.width = getWidth();
this.height = getHeight();
// 初始化图⽚资源
getPics();
// 初始化地图数据
initMap();
// 注册事件监听器
setFocusable(true);
addKeyListener(this);
addMouseListener(this);
// 播放⾳乐
initSound();
}
/**
* initMap()⽅法的作⽤是初始化本关grade 游戏地图,清空悔棋信息列表list。

调⽤
* getMapSizeAndPosition(⽅法获取游戏区域⼤⼩及显⽰游戏的左上⾓位置( leftX, leftY )。

*/
public void initMap() {
// 获取当前关卡的地图数据
map = MapFactory.getMap(grade);
// 清除上⼀关保存的回退地图数据,即清空list集合的内容
list.clear();
// 初始化地图⾏列数和左上⾓起始坐标位置
getMapSizeAndPosition();
// 获取⾓⾊的坐标位置
getManPosition();
}
/**
* getManPosition()⽅法的作⽤是获取⼯⼈的当前位置(row,column)。

*/
public void getManPosition() {
// 即遍历地图数组map中存在那个值等于MANXXX(MAN_DOWN表⽰向下的⼈;MAN_UP表⽰向上的⼈)的情况,即表⽰该位置是⼈站⽴的位置,这个由地图数据扫描得出 for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
if (map[i][j] == MAN_DOWN || map[i][j] == MAN_DOWN_ON_END
|| map[i][j] == MAN_UP || map[i][j] == MAN_UP_ON_END
|| map[i][j] == MAN_LEFT || map[i][j] == MAN_LEFT_ON_END
|| map[i][j] == MAN_RIGHT || map[i][j] == MAN_RIGHT) {
// 保存⼈的位置,i表⽰第⼏⾏,j表⽰第⼏列,⽽且是从0开始的
this.row = i;
this.column = j;
break;
}
}
}
}
/**
* getMapSizeAndPosition()⽅法⽤来获取游戏区域⼤⼩及显⽰游戏的左上⾓位置( lefX, leftY )。

*/
private void getMapSizeAndPosition() {
// 初始化mapRow和mapColumn,表⽰地图的⾏列数
this.mapRow = map.length;
this.mapColumn = map[0].length;
// 初始化leftX和leftY,即计算左上⾓的位置,
this.leftX = (width - map[0].length * MOVE_PIXEL) / 2;
this.leftY = (height - map.length * MOVE_PIXEL) / 2;
}
/**
* getPics()⽅法⽤来加载要显⽰的图⽚
*/
public void getPics() {
// 创建长度为13的数组,即有⼗三张图⽚
pics = new Image[13];
// 然后循环将每张图⽚读取保存到pics数组中
for (int i = 0; i < 13; i++) {
pics[i] = Toolkit.getDefaultToolkit().getImage("src\\images\\pic_" + (i + 1) + ".png");
}
}
/**
* 初始化播放的⾳乐
*/
public void initSound() {
// 调⽤SoundPlayerUtil类中的⽅法播放⾳乐
soundPlayer = new SoundPlayerUtil();
soundPlayer.loadSound("src\\sounds\\music.wav");
soundPlayer.playSound(true);// 循环播放
}
/**
* grassOrEnd()⽅法判断⼈所在的位置是通道GRASS还是⽬的地END
*
* @param man
* @return
*/
public byte grassOrEnd(byte man) {
if (man == MAN_DOWN_ON_END || man == MAN_LEFT_ON_END || man == MAN_RIGHT_ON_END || man == MAN_UP_ON_END) {
return END;
}
return GRASS;
}
/**
* ⼈物向上移动
*/
private void moveUp() {
// 如果上⼀位是WALL,则不能移动
if (map[row - 1][column] == WALL) {
return;
}
// 如果上⼀位是BOX或BOX_ON_END,需要考虑上⼀位的上⼀位是什么情况
if (map[row - 1][column] == BOX || map[row - 1][column] == BOX_ON_END) {
// 那么就需要考虑上⼀位的上⼀位情况,若上上⼀位是END或GRASS,则向上⼀步,其他情况不⽤处理
if (map[row - 2][column] == END || map[row - 2][column] == GRASS) {
// 要保留当前信息,以便回退上⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte boxTemp = (byte) ((byte) map[row - 2][column] == END ? BOX_ON_END : BOX);
byte manTemp = (byte) (map[row - 1][column] == BOX ? MAN_UP : MAN_UP_ON_END);
// 箱⼦变成temp,箱⼦往前移动⼀步
map[row - 2][column] = boxTemp;
// ⼈变成MAN_UP,往上⾛⼀步
map[row - 1][column] = manTemp;
// 将⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
row--;
}
} else {
// 上⼀位为GRASS或END,⽆需考虑上上⼀步,其他情况不⽤处理
if (map[row - 1][column] == GRASS || map[row - 1][column] == END) {
// 保留当前这⼀步的信息,以便回退上⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte temp = (byte) (map[row - 1][column] == END ? MAN_UP_ON_END : MAN_UP);
// ⼈变成temp,⼈往上⾛⼀步
map[row - 1][column] = temp;
// ⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
row--;
}
}
}
/**
* ⼈物向下移动,其中(row,column)是当前⾓⾊站的位置,⽽map[row,column]表⽰当前⾓⾊
*/
private void moveDown() {
// 如果下⼀位是WALL,则不能移动
// 所以map[row+1][column]表⽰当前⾓⾊的下⼀步,也就是下⼀关图像块
if (map[row + 1][column] == WALL) {
return;
}
// 如果下⼀位是箱⼦BOX或放到⽬的地的箱⼦BOX_ON_END(即如果下⼀位是箱⼦,⽽不管是什么类型的箱⼦,都可以推动箱⼦),需要考虑下位的下⼀位是什么情况
if (map[row + 1][column] == BOX || map[row + 1][column] == BOX_ON_END) {
// 那么就需要考虑下⼀位的下⼀位情况(即箱⼦的下⼀位是什么,决定着箱⼦是否可以向前移动),若下下⼀位是⽬的地END或通道GRASS,则表⽰箱⼦可以向下移动⼀步,其他情况不⽤处理 // map[row+2][column]表⽰箱⼦的下⼀步是什么
if (map[row + 2][column] == END || map[row + 2][column] == GRASS) {
// 下⾯的代码就是箱⼦向前移动⼀步,⼈移动原来箱⼦的位置
// 要保留当前⼈和地图信息,以便回退下⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
// 判断箱⼦的下⼀步是否是⽬的地,如果是⽬的地,那么箱⼦的下⼀步就应该变成BOX_ON_END(放在⽬的地的箱⼦),如果不是⽬的地那应该还只是普通箱⼦BOX
byte boxTemp = (byte) ((byte) map[row + 2][column] == END ? BOX_ON_END : BOX);
// 判断⼈的下⼀步是否是箱⼦
byte manTemp = (byte) (map[row + 1][column] == BOX ? MAN_DOWN : MAN_DOWN_ON_END);
// 箱⼦变成temp,箱⼦往下移动⼀步
map[row + 2][column] = boxTemp;
// ⼈变成MAN_UP,往下⾛⼀步
map[row + 1][column] = manTemp;
// 将⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
row++;
}
} else {
// 执⾏到这⾥,表⽰⼈的下⼀步不是箱⼦,那么要么是通道要么是终点
// 下⼀位为GRASS或END,⽆需考虑下下⼀步,其他情况不⽤处理
if (map[row + 1][column] == GRASS || map[row + 1][column] == END) {
// 保留当前这⼀步的信息,以便回退下⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte temp = (byte) (map[row + 1][column] == END ? MAN_DOWN_ON_END : MAN_DOWN);
// ⼈变成temp,⼈往下⾛⼀步
map[row + 1][column] = temp;
// ⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
row++;
}
}
}
/**
* ⼈物向左移动
*/
private void moveLeft() {
// 如果左⼀位是WALL,则不能移动
if (map[row][column - 1] == WALL) {
return;
}
// 如果左⼀位是BOX或BOX_ON_END,需要考虑左⼀位的左⼀位是什么情况
if (map[row][column - 1] == BOX || map[row][column - 1] == BOX_ON_END) {
// 那么就需要考虑左⼀位的左⼀位情况,若左左⼀位是END或GRASS,则向左⼀步,其他情况不⽤处理 if (map[row][column - 2] == END || map[row][column - 2] == GRASS) {
// 要保留当前信息,以便回退左⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte boxTemp = (byte) ((byte) map[row][column - 2] == END ? BOX_ON_END : BOX);
byte manTemp = (byte) (map[row][column - 1] == BOX ? MAN_LEFT : MAN_LEFT_ON_END);
// 箱⼦变成temp,箱⼦往前移动⼀步
map[row][column - 2] = boxTemp;
// ⼈变成MAN_UP,往左⾛⼀步
map[row][column - 1] = manTemp;
// 将⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
column--;
}
} else {
// 左⼀位为GRASS或END,⽆需考虑左左⼀步,其他情况不⽤处理
if (map[row][column - 1] == GRASS || map[row][column - 1] == END) {
// 保留当前这⼀步的信息,以便回退左⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte temp = (byte) (map[row][column - 1] == END ? MAN_LEFT_ON_END : MAN_LEFT);
// ⼈变成temp,⼈往左⾛⼀步
map[row][column - 1] = temp;
// ⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
column--;
}
}
}
/**
* ⼈物向右移动
*/
private void moveRight() {
// 如果右⼀位是WALL,则不能移动
if (map[row][column + 1] == WALL) {
return;
}
// 如果右⼀位是BOX或BOX_ON_END,需要考虑右位的右⼀位是什么情况
if (map[row][column + 1] == BOX || map[row][column + 1] == BOX_ON_END) {
// 那么就需要考虑右⼀位的右⼀位情况,若右右⼀位是END或GRASS,则向右⼀步,其他情况不⽤处理 if (map[row][column + 2] == END || map[row][column + 2] == GRASS) {
// 要保留当前信息,以便回退右⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte boxTemp = (byte) ((byte) map[row][column + 2] == END ? BOX_ON_END : BOX);
byte manTemp = (byte) (map[row][column + 1] == BOX ? MAN_RIGHT : MAN_RIGHT_ON_END); // 箱⼦变成temp,箱⼦往右移动⼀步
map[row][column + 2] = boxTemp;
// ⼈变成MAN_UP,往右⾛⼀步
map[row][column + 1] = manTemp;
// 将⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
column++;
}
} else {
// 右⼀位为GRASS或END,⽆需考虑右右⼀步,其他情况不⽤处理
if (map[row][column + 1] == GRASS || map[row][column + 1] == END) {
// 保留当前这⼀步的信息,以便回退右⼀步
Map currMap = new Map(row, column, map);
list.add(currMap);
byte temp = (byte) (map[row][column + 1] == END ? MAN_RIGHT_ON_END : MAN_RIGHT);
// ⼈变成temp,⼈往右⾛⼀步
map[row][column + 1] = temp;
// ⼈刚才站的地⽅变成GRASS或者END
map[row][column] = grassOrEnd(map[row][column]);
// ⼈离开后修改⼈的坐标
column++;
}
}
}
/**
* 验证玩家是否过关,如果有⽬的地END值或⼈直接站在⽬的地则没有成功
*
* @return 如果已经通关则返回true,否则返回false
*/
public boolean isFinished() {
for (int i = 0; i < mapRow; i++) {
for (int j = 0; j < mapColumn; j++) {
if (map[i][j] == END || map[i][j] == MAN_DOWN_ON_END || map[i][j] == MAN_UP_ON_END || map[i][j] == MAN_LEFT_ON_END || map[i][j] == MAN_RIGHT_ON_END) { return false;
}
}
}
return true;
}
// 使⽤双缓冲技术解决动画闪烁问题
private Image iBuffer;
private Graphics gBuffer;
/**
* 重写绘制整个游戏区域的图形
*
* @param g
*/
@Override
public void paint(Graphics g) {
if (iBuffer == null) {
iBuffer = createImage(width, height);
gBuffer = iBuffer.getGraphics();
}
// 清空屏幕原来的绘画
gBuffer.setColor(getBackground());
gBuffer.fillRect(0, 0, width, height);
for (int i = 0; i < mapRow; i++) {
for (int j = 0; j < mapColumn; j++) {
// 画出地图,i表⽰⾏数,j表⽰列数
if (map[i][j] != 0) {
// 这⾥要减1是因为图⽚的名称序号不对应,应该从0开始,但是从1开始的
gBuffer.drawImage(pics[map[i][j] - 1], leftX + j * MOVE_PIXEL, leftY + i * MOVE_PIXEL, 30, 30, this);
}
}
}
gBuffer.setColor(Color.RED);
gBuffer.setFont(new Font("楷体_2312", Font.BOLD, 30));
gBuffer.drawString("现在是第", 150, 140);
gBuffer.drawString(String.valueOf(grade), 310, 140);
gBuffer.drawString("关", 360, 140);
g.drawImage(iBuffer, 0, 0, this);
/* 下⾯的代码是未使⽤双缓冲技术会导致动画闪烁的代码 */
/*g.clearRect(0, 0, width, height);
for (int i = 0; i < mapRow; i++) {
for (int j = 0; j < mapColumn; j++) {
// 画出地图,i表⽰⾏数,j表⽰列数
if (map[i][j] != 0) {
// 这⾥要减1是因为图⽚的名称序号不对应,应该从0开始,但是从1开始的
g.drawImage(pics[map[i][j] - 1], leftX + j * MOVE_PIXEL, leftY + i * MOVE_PIXEL, 30, 30, this);
}
}
}
g.setColor(Color.RED);
g.setFont(new Font("楷体_2312", Font.BOLD, 30));
g.drawString("现在是第", 150, 140);
g.drawString(String.valueOf(grade), 310, 140);
g.drawString("关", 360, 140);*/
}
@Override
public void actionPerformed(ActionEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
// 当按键盘上的按键时触发的事件
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:// 上⽅向键
moveUp();// 向上移动
break;
case KeyEvent.VK_DOWN:// 下⽅向键
moveDown();// 向下移动
break;
case KeyEvent.VK_LEFT:// 左⽅向键
moveLeft();// 向左移动
break;
case KeyEvent.VK_RIGHT:// 右⽅向键
moveRight();// 向右移动
break;
}
// 然后重新绘制界⾯
repaint();
// 在移动完成后可能已经通关,所以需要判断是否通关
if (isFinished()) {
// 禁⽤按键
acceptKey = false;
// 判断是否是最后⼀关,如果是则直接提⽰,如果不是则询问是否要进⼊下⼀关
if (grade == MapFactory.getCount()) {
JOptionPane.showMessageDialog(this, "恭喜通过最后⼀关!");
} else {
// 提⽰进⼊下⼀关
String msg = "恭喜通过第" + grade + "关\n是否要进⼊下⼀关?";
int choice = JOptionPane.showConfirmDialog(null, msg, "过关", JOptionPane.YES_NO_OPTION); if (choice == 1) {
System.exit(0);
} else if (choice == 0) {
// 进⼊下⼀关
acceptKey = true;
nextGrade();
}
}
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
// MouseEvent.BUTTON3表⽰⿏标右键
if (e.getButton() == MouseEvent.BUTTON3) {
undo();
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
/**
* 返回当前⼈的位置⽤getManX()⽅法和getManY()⽅法
*
* @return
*/
public int getManX() {
return row;
}
public int getManY() {
return column;
}
/**
* 返回当前关卡数
*
* @return
*/
public int getGrade() {
return grade;
}
/**
* 返回当前关卡的地图信息
*
* @return
*/
public byte[][] getMap() {
return MapFactory.getMap(grade);
}
/**
* 显⽰提⽰信息对话框
*
* @param str
*/
public void displayToast(String str) {
JOptionPane.showMessageDialog(null, str, "提⽰", JOptionPane.ERROR_MESSAGE);
}
/**
* 撤销移动操作
*/
public void undo() {
if (acceptKey) {
if (list.size() > 0) {
// 如果要撤销,必须要⾛过
// 考虑⽤栈更合适
Map priorMap = (Map) list.get(list.size() - 1);
this.map = priorMap.getMap();
this.row = priorMap.getManX();
this.column = priorMap.getManY();
repaint();// 重新画图
list.remove(list.size() - 1);
} else {
displayToast("不能再撤销了!");
}
} else {
displayToast("此关已完成,不能撤销!");
}
}
/**
* 实现下⼀关的初始化,并且调⽤repaint()⽅法显⽰游戏界⾯
*/
public void nextGrade() {
// 初始化下⼀关的数据
if (grade >= MapFactory.getCount()) {
displayToast("恭喜你完成所有关卡!");
acceptKey = false;
} else {
// 关卡数加1
grade++;
// 初始化下⼀关的地图数据
initMap();
// 重新绘制画⾯
repaint();
acceptKey = true;
}
}
/**
* 实现上⼀关初始化并且调⽤repaint()发显⽰游戏界⾯
*/
public void priorGrade() {
grade--;
acceptKey = true;
if (grade < 0) {
grade = 0;
}
initMap();
repaint();
}
}
声⾳播放类
public class SoundPlayerUtil {
public File file;
public AudioInputStream stream;
public AudioFormat format;
info;
Clip clip;
/**
* 加载声⾳⽂件,⽀持wav、mp3等声⾳⽂件
*
* @param filePath 声⾳⽂件的路径
*/
public void loadSound(String filePath) {
file = new File(filePath);
try {
stream = AudioSystem.getAudioInputStream(file);
} catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace();
}
format = stream.getFormat();
}
/**
* 播放⾳乐
*
* @param isLoop 表⽰是否循环播放⾳乐,如果传⼊的是true则表⽰循环播放
*/
public void playSound(boolean isLoop) {
info = new (Clip.class, format);
try {
clip = (Clip) AudioSystem.getLine(info);
clip.open(stream);
} catch (LineUnavailableException | IOException e) {
e.printStackTrace();
}
if (isLoop) {
clip.loop(Clip.LOOP_CONTINUOUSLY);// 添加该句代码可以循环播放
}
clip.start();
}
}
总结
通过此次的《推箱⼦》游戏实现,让我对swing的相关知识有了进⼀步的了解,对java这门语⾔也有了⽐以前更深刻的认识。

java的⼀些基本语法,⽐如数据类型、运算符、程序流程控制和数组等,理解更加透彻。

java最核⼼的核⼼就是⾯向对象思想,对于这⼀个概念,终于悟到了⼀些。

以上就是Java实现经典游戏推箱⼦的⽰例代码的详细内容,更多关于Java推箱⼦的资料请关注其它相关⽂章!。

相关文档
最新文档