安卓实训项目:贪吃蛇V1.0
一、功能要求
1、玩家使用方向键或手势滑动操控一条长长的蛇不断吞苹果,同时蛇身随着吞下的苹果不断长,当蛇头撞到蛇身或障壁时游戏结束。
2、玩家可选择地图、设置背景音乐、选择蛇移动速度。
3、提供玩家排行榜,显示前三名分数(贪吃蛇吃掉苹果的数量)
项目图片素材【百度网盘下载网址 密码:gcpt】
二、运行效果
1、启动界面
2、游戏主控界面
3、选择地图
4、设置速度
5、选择背景音乐
6、开始游戏
7、游戏结束
8、查看排行榜
9、查看帮助信息
10、关于作者
三、涉及知识点
1、布局
2、常用控件
3、线程
4、消息处理器
5、自定义视图
6、图形图像绘制
7、媒体播放器
8、集合类
四、项目结构图
五、项目实现
1、创建安卓项目:贪吃蛇V1.0
2、修改包名
说明:net.hw.snake为根包,ui子包用于存放界面类。
3、创建子包entity、app与view
4、准备图片素材,拷贝到drawable-mdpi目录里
5、在res里创建raw文件夹,拷贝几首mp3音乐
6、在res里创建drawable目录,在里面创建按钮背景选择器
(1)开始游戏按钮背景选择器
(2)选择地图按钮背景选择器
(3)选择背景音乐按钮背景选择器
(4)设置速度按钮背景选择器
(5)排行榜按钮背景选择器
(6)关于作者按钮背景选择器
(7)帮助信息按钮背景选择器
(8)退出游戏按钮背景选择器
7、在app子包里创建应用程序常量接口AppConstants
/**
* 功能:应用程序常量接口
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.app;
import android.app.Activity;
public interface AppConstants {
/**
* 应用程序标记
*/
String TAG = "net.hw.snake";
/**
* 地图常量
*/
int O_SHAPE_MAP = 0;
int I_SHAPE_MAP = 1;
int X_SHAPE_MAP = 2;
int Z_SHAPE_MAP = 3;
int U_SHAPE_MAP = 4;
/**
* 速度常量
*/
int LOW_SPEED = 0;
int MEDIUM_SPEED = 1;
int HIGH_SPEED = 2;
/**
* 排行榜配置文件名
*/
String TOP_LIST_FILE_NAME = "toplist";
/**
* 文件访问模式
*/
int ACCESS_MODE = Activity.MODE_PRIVATE;
/**
* 游戏状态常量
*/
int PAUSE = 0;
int READY = 1;
int RUNNING = 2;
int LOSE = 3;
/**
* 移动方向常量
*/
int NORTH = 1;
int SOUTH = 2;
int EAST = 3;
int WEST = 4;
/**
* 图片常量
*/
int RED_STAR = 1;
int YELLOW_STAR = 2;
int GREEN_STAR = 3;
/**
* 蛇两次移动之间的间隔
*/
int SHORT_INTERVAL = 100;
int MEDIUM_INTERVAL = 350;
int LONG_INTERVAL = 600;
/**
* 前三名分数
*/
String SCORE_OF_FIRST = "score_of_first";
String SCORE_OF_SECOND = "score_of_second";
String SCORE_OF_THIRD = "score_of_third";
}
8、在app子包里创建贪吃蛇应用程序类SnakeApplicaiton
/**
* 功能:贪吃蛇应用程序类
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.app;
import android.app.Application;
import android.media.MediaPlayer;
public class SnakeApplication extends Application {
/**
* 地图索引
*/
private int mapIndex;
/**
* 背景音乐索引
*/
private int musicIndex;
/**
* 速度索引
*/
private int speedIndex;
/**
* 媒体播放器
*/
private MediaPlayer mediaPlayer;
@Override
public void onCreate() {
super.onCreate();
}
public int getMapIndex() {
return mapIndex;
}
public void setMapIndex(int mapIndex) {
this.mapIndex = mapIndex;
}
public int getMusicIndex() {
return musicIndex;
}
public void setMusicIndex(int musicIndex) {
this.musicIndex = musicIndex;
}
public int getSpeedIndex() {
return speedIndex;
}
public void setSpeedIndex(int speedIndex) {
this.speedIndex = speedIndex;
}
public MediaPlayer getMediaPlayer() {
return mediaPlayer;
}
public void setMediaPlayer(MediaPlayer mediaPlayer) {
this.mediaPlayer = mediaPlayer;
}
}
9、在entity子包里创建坐标实体类Coordinate
/**
* 功能:坐标实体
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.entity;
public class Coordinate {
/**
* 横坐标
*/
public int x;
/**
* 纵坐标
*/
public int y;
/**
* 构造方法
*
* @param x
* @param y
*/
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
/**
* 相等方法(判断两点是否重合)
*/
public boolean equals(Object obj) {
if (obj instanceof Coordinate) {
Coordinate other = (Coordinate) obj;
if (x == other.x && y == other.y) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
10、在view子包里创建方砖视图TileView
(1)在values目录创建属性文件attrs.xml
(2)方砖视图类TileView
/**
* 功能:方砖视图
* 作者:华卫
* 日期:2017年1月1日
*/
/**
* 控制方砖尺寸及其在视图里显示范围的参数,宽度与高度的单位是像素。
* 可绘制对象可以缩放来适合方砖尺寸,然后计算出横向与纵向的方砖数。
*/
package net.hw.snake.view;
import net.hw.snake.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
public class TileView extends View {
/**
* 方砖尺寸
*/
protected static int mTileSize;
/**
* 横向方砖数量
*/
protected static int mXTileCount;
/**
* 纵向方砖数量
*/
protected static int mYTileCount;
/**
* 横向偏移量
*/
private static int mXOffset;
/**
* 纵向偏移量
*/
private static int mYOffset;
/**
* 方砖数组
*/
private Bitmap[] mTileArray;
/**
* 方砖网格
*/
private int[][] mTileGrid;
/**
* 画笔
*/
private final Paint mPaint = new Paint();
/**
* 构造方法
*
* @param context
* @param attrs
* @param defStyle
*/
public TileView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
a.recycle();
}
/**
* 构造方法
*
* @param context
* @param attrs
*/
public TileView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
a.recycle();
}
/**
* 重置方砖
*
* @param tilecount
*/
public void resetTiles(int tileCount) {
mTileArray = new Bitmap[tileCount];
}
/**
* 屏幕尺寸发生变化时触发,重新计算横向与纵向方砖数以及横向与纵向的偏移量,设置方砖网格
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 横向方砖数
mXTileCount = (int) Math.floor(w / mTileSize);
// 纵向方砖数
mYTileCount = (int) Math.floor(h / mTileSize);
// 横向偏移量
mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
// 纵向偏移量
mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
// 方砖网格
mTileGrid = new int[mXTileCount][mYTileCount];
// 清除方砖
clearTiles();
}
/**
* 加载方砖
*
* @param key
* @param tile
*/
public void loadTile(int key, Drawable tile) {
Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
tile.setBounds(0, 0, mTileSize, mTileSize);
tile.draw(canvas);
mTileArray[key] = bitmap;
}
/**
* 清空方砖
*/
public void clearTiles() {
for (int x = 0; x < mXTileCount; x++) {
for (int y = 0; y < mYTileCount; y++) {
// 第一个参数为0,表明不画方砖
setTile(0, x, y);
}
}
}
/**
* 设置方砖
*
* @param tileIndex
* @param x
* @param y
*/
public void setTile(int tileIndex, int x, int y) {
// 在x行y列画指定的方砖
mTileGrid[x][y] = tileIndex;
}
/**
* 绘制方法
*/
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int x = 0; x < mXTileCount; x += 1) {
for (int y = 0; y < mYTileCount; y += 1) {
if (mTileGrid[x][y] > 0) {
canvas.drawBitmap(mTileArray[mTileGrid[x][y]],
mXOffset + x * mTileSize, mYOffset + y * mTileSize, mPaint);
}
}
}
}
}
11、在view子包里创建贪吃蛇视图SnakeView
/**
* 功能:贪吃蛇视图
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.view;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
import net.hw.snake.entity.Coordinate;
public class SnakeView extends TileView implements AppConstants {
/**
* 共享参数
*/
private SharedPreferences sp;
/**
* 编辑器
*/
private Editor editor;
/**
* 名次信息
*/
private String rankMsg;
/**
* 游戏状态变量
*/
public int mMode = READY;
/**
* 蛇当前移动方向
*/
public int mDirection = EAST;
/**
* 蛇下一个移动方向
*/
public int mNextDirection = EAST;
/**
* 分数:蛇吃掉的苹果数量
*/
private int mScore = 0;
/**
* 蛇移动的时间间隔
*/
private int mMoveDelay = 600;
/**
* 蛇上次移动的时间
*/
private long mLastMove;
/**
* 蛇身数组列表
*/
private ArrayList mSnakeTrail = new ArrayList();
/**
* 苹果数组列表
*/
private ArrayList mAppleList = new ArrayList();
/**
* 墙壁数组列表
*/
private ArrayList mWallList = new ArrayList();
/**
* 随机对象
*/
private static final Random RNG = new Random();
/**
* 贪吃蛇应用程序
*/
private SnakeApplication app;
/**
* 负责重绘的消息处理器
*/
private class RefreshHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 更新贪吃蛇视图
SnakeView.this.update();
// 重绘贪吃蛇视图
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
// 清空消息队列
this.removeMessages(0);
// 延迟发送消息
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}
/**
* 声明负责重绘的消息处理器
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
/**
* 贪吃蛇视图构造方法
*
* @param context
* @param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) ((Activity) context).getApplication();
// 初始化贪吃蛇视图
initSnakeView();
}
/**
* 构造贪吃蛇视图
*
* @param context
* @param attrs
* @param defStyle
*/
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) ((Activity) context).getApplication();
// 初始化贪吃蛇视图
initSnakeView();
}
/**
* 初始化贪吃蛇视图
*/
private void initSnakeView() {
// 设置为可获得焦点
setFocusable(true);
// 重置方砖(四种方砖)
resetTiles(4);
// 加载红星方砖
loadTile(RED_STAR, getContext().getResources().getDrawable(R.drawable.redstar));
// 加载黄星方砖
loadTile(YELLOW_STAR, getContext().getResources().getDrawable(R.drawable.yellowstar));
// 加载绿星方砖
loadTile(GREEN_STAR, getContext().getResources().getDrawable(R.drawable.greenstar));
// 加载音乐
switch (app.getMusicIndex()) {
case 0:
// 音乐:梦中的婚礼
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.dream));
break;
case 1:
// 音乐:秋日私语
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.autumn));
break;
case 2:
// 音乐:天空之城
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.sky));
break;
case 3:
// 音乐:致爱丽丝
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.alice));
break;
case 4:
// 歌曲:贝尔加湖畔
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.lake));
case 5:
// 歌曲:暗香
app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.fragrance));
}
// 设置为循环播放
app.getMediaPlayer().setLooping(true);
// 创建共享参数对象
sp = getContext().getSharedPreferences(TOP_LIST_FILE_NAME, ACCESS_MODE);
// 获得编辑器
editor = sp.edit();
}
/**
* 初始化新游戏
*/
public void initNewGame() {
// 清除蛇
mSnakeTrail.clear();
// 清除所有苹果
mAppleList.clear();
// 初始蛇,三节,朝东移动
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mNextDirection = EAST;
// 更新墙壁
updateWalls();
// 随机添加两个苹果
addRandomApple();
addRandomApple();
// 根据用户设置速度修改初始的蛇两次移动时间间隔初始值
switch (app.getSpeedIndex()) {
case LOW_SPEED:
mMoveDelay = LONG_INTERVAL;
break;
case MEDIUM_SPEED:
mMoveDelay = MEDIUM_INTERVAL;
break;
case HIGH_SPEED:
mMoveDelay = SHORT_INTERVAL;
break;
}
// 初始化游戏得分
mScore = 0;
}
/**
* 将数组列表转换成数组
*
* @param cvec
* 坐标对象的数组列表
* @return 包含坐标值的简单数组,如 [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* 保存游戏状态,那么在后台游戏进程被杀掉也不会丢失任何数据
*
* @return 保存视图状态的数据包
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Integer.valueOf(mMoveDelay));
map.putLong("mScore", Integer.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* 恢复游戏状态
*
* @param data
* 包含游戏状态的数据包
*/
public void restoreState(Bundle data) {
// 设置游戏模式为暂停
setMode(PAUSE);
mAppleList = coordArrayToArrayList(data.getIntArray("mAppleList"));
mDirection = data.getInt("mDirection");
mNextDirection = data.getInt("mNextDirection");
mMoveDelay = data.getInt("mMoveDelay");
mScore = data.getInt("mScore");
mSnakeTrail = coordArrayToArrayList(data.getIntArray("mSnakeTrail"));
}
/**
* 将坐标对构成的数组转换成坐标对象的数组列表
*
* @param rawArray
* : [x1,y1,x2,y2,...]
* @return 坐标对象的数组列表
*/
private ArrayList coordArrayToArrayList(int[] rawArray) {
ArrayList coordArrayList = new ArrayList();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/*
* 按键事件处理
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
// 按上方向键
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
// 开始新游戏
initNewGame();
// 设置为运行模式
setMode(RUNNING);
// 播放音乐
app.getMediaPlayer().start();
// 更新游戏界面
update();
return (true);
}
if (mMode == PAUSE) {
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
// 按下方向键
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
// 按左方向键
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
// 按右方向键
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
/**
* 设置游戏模式
*
* @param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
update();
app.getMediaPlayer().start();
return;
}
if (newMode == LOSE) {
app.getMediaPlayer().stop();
try {
app.getMediaPlayer().prepare();
app.getMediaPlayer().seekTo(0);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 随机添加苹果
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// 为苹果选择新位置
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
boolean collision = false;
// 判断新产生的苹果有没有与蛇或者墙碰撞
if (mSnakeTrail.contains(newCoord) || mWallList.contains(newCoord)) {
collision = true;
}
// 如果碰撞了,就表明没找到合适位置,还要继续找
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "新坐标为空!");
}
mAppleList.add(newCoord);
}
/**
* 更新方法:检查是否处于运行状态,决定是否该移动,更新蛇的位置
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* 更新墙壁
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
mWallList.add(new Coordinate(x, 0));
mWallList.add(new Coordinate(x, mYTileCount - 1));
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
mWallList.add(new Coordinate(0, y));
mWallList.add(new Coordinate(mXTileCount - 1, y));
}
switch (app.getMapIndex()) {
case I_SHAPE_MAP: // 工字型地图
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, 20);
mWallList.add(new Coordinate(x, 20));
}
for (int y = 20; y <= mYTileCount - 20; y++) {
setTile(GREEN_STAR, mXTileCount / 2, y);
mWallList.add(new Coordinate(mXTileCount / 2, y));
}
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, mYTileCount - 20);
mWallList.add(new Coordinate(x, mYTileCount - 20));
}
break;
case X_SHAPE_MAP: // X字型地图
for (int x = 10, y = 20; x < mXTileCount - 10 && y < mYTileCount - 20; x++, y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
for (int x = mXTileCount - 10, y = 20; x >= 10 && y < mYTileCount - 20; x--, y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
break;
case Z_SHAPE_MAP: // Z字型地图
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, 20);
mWallList.add(new Coordinate(x, 20));
}
for (int x = mXTileCount - 6, y = 20; y <= 35; y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, 35);
mWallList.add(new Coordinate(x, 35));
}
for (int x = 6, y = 35; y <= mYTileCount - 20; y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, mYTileCount - 20);
mWallList.add(new Coordinate(x, mYTileCount - 20));
}
break;
case U_SHAPE_MAP: // U字型地图
for (int x = 6, y = 20; y < mYTileCount - 20; y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
for (int x = mXTileCount - 7, y = 20; y < mYTileCount - 20; y++) {
setTile(GREEN_STAR, x, y);
mWallList.add(new Coordinate(x, y));
}
for (int x = 6; x < mXTileCount - 6; x++) {
setTile(GREEN_STAR, x, mYTileCount - 20);
mWallList.add(new Coordinate(x, mYTileCount - 20));
}
break;
}
}
/**
* 更新苹果
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* 更新蛇
*/
private void updateSnake() {
// 设置蛇生长状态
boolean growSnake = false;
// 通过蛇头来抓住蛇
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
// 根据蛇移动方向决定蛇头坐标
switch (mDirection) {
case EAST:
newHead = new Coordinate(head.x + 1, head.y);
break;
case WEST:
newHead = new Coordinate(head.x - 1, head.y);
break;
case NORTH:
newHead = new Coordinate(head.x, head.y - 1);
break;
case SOUTH:
newHead = new Coordinate(head.x, head.y + 1);
break;
}
// 判断是否撞墙
if (mWallList.contains(newHead)) {
// 游戏失败
setMode(LOSE);
// 更新排行榜
updateTopList();
// 停止音乐播放
app.getMediaPlayer().stop();
try {
app.getMediaPlayer().prepare();
app.getMediaPlayer().seekTo(0);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("游戏结束");
if (mScore > 0) {
builder.setMessage("呵呵,你吃掉了" + mScore + "个苹果!" + rankMsg);
} else {
builder.setMessage("遗憾,你一个苹果也没有吃掉!");
}
builder.setPositiveButton("返回主界面", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 设置游戏模式为准备就绪
setMode(READY);
// 关闭当前界面
((Activity) getContext()).finish();
}
});
builder.setNegativeButton("再来玩一次", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 初始化新游戏
initNewGame();
// 设置游戏模式为运行
setMode(RUNNING);
// 播放音乐
app.getMediaPlayer().start();
// 更新游戏
update();
}
});
builder.show();
}
// 判断是否撞到自己
if (mSnakeTrail.contains(newHead)) {
// 游戏失败
setMode(LOSE);
// 更新排行榜
updateTopList();
// 停止音乐播放
app.getMediaPlayer().stop();
try {
app.getMediaPlayer().prepare();
app.getMediaPlayer().seekTo(0);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("游戏结束");
if (mScore > 0) {
builder.setMessage("呵呵,你吃掉了" + mScore + "个苹果!" + rankMsg);
} else {
builder.setMessage("遗憾,你一个苹果也没有吃掉!");
}
builder.setPositiveButton("返回主界面", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setMode(READY);
((Activity) getContext()).finish();
}
});
builder.setNegativeButton("再来玩一次", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 初始化新游戏
initNewGame();
// 设置游戏模式为运行
setMode(RUNNING);
// 播放音乐
app.getMediaPlayer().start();
// 更新游戏
update();
}
});
builder.show();
}
// 检测是否碰撞了苹果
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
mAppleList.remove(c);
addRandomApple();
// 得一分
mScore++;
switch (app.getSpeedIndex()) {
case LOW_SPEED:
mMoveDelay *= 0.9;
break;
case MEDIUM_SPEED:
mMoveDelay *= 0.8;
break;
case HIGH_SPEED:
mMoveDelay *= 0.7;
}
// 蛇生长状态设置为真
growSnake = true;
}
}
// 在蛇头添加新元素
mSnakeTrail.add(0, newHead);
// 如果蛇不生长,那么去掉蛇尾一个元素
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
// 重新绘制蛇(蛇头设置为黄砖,蛇身设置为红砖)
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* 更新排行榜
*/
private void updateTopList() {
// 获取前三名分数
int scoreOfFirst = sp.getInt(SCORE_OF_FIRST, 0);
int scoreOfSecond = sp.getInt(SCORE_OF_SECOND, 0);
int scoreOfThird = sp.getInt(SCORE_OF_THIRD, 0);
if (mScore > scoreOfFirst) {
rankMsg = "恭喜你获得第一名!";
editor.putInt(SCORE_OF_FIRST, (int) mScore);
editor.putInt(SCORE_OF_SECOND, scoreOfSecond);
editor.putInt(SCORE_OF_THIRD, scoreOfThird);
} else if (mScore > scoreOfSecond) {
rankMsg = "恭喜你获得第二名!";
editor.putInt(SCORE_OF_SECOND, (int) mScore);
editor.putInt(SCORE_OF_THIRD, scoreOfSecond);
} else if (mScore > scoreOfThird) {
rankMsg = "恭喜你获得第三名!";
editor.putInt(SCORE_OF_THIRD, (int) mScore);
} else {
rankMsg = "加油啊,争取进入排行榜!";
}
// 提交数据
editor.commit();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMode == READY) {
initNewGame();
setMode(RUNNING);
app.getMediaPlayer().start();
}
if (mMode == LOSE) {
// 绘制背景图像
Paint paint = new Paint();
Bitmap background = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.lose);
canvas.drawBitmap(background, 0, 0, paint);
}
}
}
12、在ui子包里创建贪吃蛇界面类SnakeActivity
(1)贪吃蛇界面布局文件activity_snake.xml
(2)贪吃蛇界面类SnakeActivity
/**
* 功能:贪吃蛇界面
* 作者:华卫
* 日期:2017年1月1日
*/
/**
* 贪吃蛇: 人人都可以享受的简单游戏。
* 这是经典游戏“贪吃蛇”的一个实现。你控制一条在花园里游动着寻找苹果的蛇。
* 当心,蛇吃了苹果,不仅要变长,而且移动速度会更快,只要蛇撞墙就结束游戏。
*/
package net.hw.snake.ui;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.widget.TextView;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
import net.hw.snake.view.SnakeView;
public class SnakeActivity extends Activity implements OnGestureListener, AppConstants {
/**
* 贪吃蛇视图
*/
private SnakeView mSnakeView;
/**
* 手势侦测器
*/
private GestureDetector detector;
/**
* 贪吃蛇应用程序
*/
private SnakeApplication app;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_snake);
// 通过资源标识获得控件实例
mSnakeView = (SnakeView) findViewById(R.id.snake);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) getApplication();
// 初始化手势侦测器
detector = new GestureDetector(this, this);
// 贪吃蛇视图获得焦点
mSnakeView.requestFocus();
// 判断是否保存过游戏示例状态数据
if (savedInstanceState == null) {
// 刚刚启动———开启一次新游戏
mSnakeView.setMode(READY);
} else {
// 恢复贪吃蛇游戏状态数据
Bundle map = savedInstanceState.getBundle(TAG);
if (map != null) {
mSnakeView.restoreState(map);
} else {
// 设置贪吃蛇视图为暂停模式
mSnakeView.setMode(PAUSE);
}
}
}
/**
* 暂停回调方法
*/
@Override
protected void onPause() {
super.onPause();
// 随窗口暂停而将贪吃蛇游戏视图设置为暂停模式
mSnakeView.setMode(PAUSE);
// 暂停背景音乐
app.getMediaPlayer().pause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
// 存储游戏状态数据
outState.putBundle(TAG, mSnakeView.saveState());
}
/**
* 销毁回调方法
*/
@Override
protected void onDestroy() {
super.onDestroy();
// 停止背景音乐
app.getMediaPlayer().stop();
// 释放媒体播放器资源
app.setMediaPlayer(null);
}
// 将窗口的触碰事件交给手势侦测器来处理
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 手势向上滑动
if (e2.getY() < e1.getY() - 20) {
if (mSnakeView.mMode == READY | mSnakeView.mMode == LOSE) {
// 开始新游戏
mSnakeView.initNewGame();
// 设置为运行模式
mSnakeView.setMode(RUNNING);
// 播放音乐
app.getMediaPlayer().start();
// 更新游戏界面
mSnakeView.update();
}
if (mSnakeView.mMode == PAUSE) {
mSnakeView.setMode(RUNNING);
mSnakeView.update();
}
if (mSnakeView.mDirection != SOUTH) {
mSnakeView.mNextDirection = NORTH;
}
}
// 手势向下滑动
if (e2.getY() > e1.getY() + 20) {
if (mSnakeView.mDirection != NORTH) {
mSnakeView.mNextDirection = SOUTH;
}
}
// 手势向左滑动
if (e2.getX() < e1.getX() - 20) {
if (mSnakeView.mDirection != EAST) {
mSnakeView.mNextDirection = WEST;
}
}
// 手势向右滑动
if (e2.getX() > e1.getX() + 20) {
if (mSnakeView.mDirection != WEST) {
mSnakeView.mNextDirection = EAST;
}
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
}
13、在ui子包里创建选择地图界面类SelectMapActivity
(1)在values里的styles.xml里增加单选按钮和按钮的样式定义
(2)选择地图界面布局文件activity_select_map.xml
(3)选择地图界面类SelectMapActivity
/**
* 功能:选择地图
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioButton;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
public class SelectMapActivity extends Activity implements AppConstants {
/**
* 口字型地图的单选按钮
*/
private RadioButton rbOMap;
/**
* 工字型地图的单选按钮
*/
private RadioButton rbIMap;
/**
* X型地图的单选按钮
*/
private RadioButton rbXMap;
/**
* Z型地图的单选按钮
*/
private RadioButton rbZMap;
/**
* U型地图的单选按钮
*/
private RadioButton rbUMap;
/**
* 贪吃蛇应用程序
*/
private SnakeApplication app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_select_map);
// 通过资源标识获得界面控件实例
rbOMap = (RadioButton) findViewById(R.id.rb_o_map);
rbIMap = (RadioButton) findViewById(R.id.rb_i_map);
rbXMap = (RadioButton) findViewById(R.id.rb_x_map);
rbZMap = (RadioButton) findViewById(R.id.rb_z_map);
rbUMap = (RadioButton) findViewById(R.id.rb_u_map);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) getApplication();
// 设置地图单选按钮初始状态
switch (app.getMapIndex()) {
case O_SHAPE_MAP: // 口字型地图
rbOMap.setChecked(true);
break;
case I_SHAPE_MAP: // I字型地图
rbIMap.setChecked(true);
break;
case X_SHAPE_MAP: // X字型地图
rbXMap.setChecked(true);
break;
case Z_SHAPE_MAP: // Z字型地图
rbZMap.setChecked(true);
break;
case U_SHAPE_MAP: // U字型地图
rbUMap.setChecked(true);
break;
}
}
/**
* 确定按钮单击事件处理方法
*
* @param view
*/
public void doOK(View view) {
// 根据单选按钮选中状态设置地图索引
if (rbOMap.isChecked()) {
// 设置口字型地图
app.setMapIndex(O_SHAPE_MAP);
} else if (rbIMap.isChecked()) {
// 设置工字型地图
app.setMapIndex(I_SHAPE_MAP);
} else if (rbXMap.isChecked()) {
// 设置X字型地图
app.setMapIndex(X_SHAPE_MAP);
} else if (rbZMap.isChecked()) {
// 设置Z字型地图
app.setMapIndex(Z_SHAPE_MAP);
} else if (rbUMap.isChecked()) {
// 设置U字型地图
app.setMapIndex(U_SHAPE_MAP);
}
// 关闭当前界面
finish();
}
/**
* 取消按钮单击事件处理方法
*
* @param view
*/
public void doCancel(View view) {
// 关闭当前界面
finish();
}
}
14、在ui子包里创建设置速度界面类SetSpeedActivity
(1)设置速度界面布局文件activity_set_speed.xml
(2)设置速度界面类SetSpeedActivity
/**
* 功能:设置速度
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioButton;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
public class SetSpeedActivity extends Activity implements AppConstants{
/**
* 低速单选按钮
*/
private RadioButton rbLowSpeed;
/**
* 中速单选按钮
*/
private RadioButton rbMediumSpeed;
/**
* 高速单选按钮
*/
private RadioButton rbHighSpeed;
/**
* 贪吃蛇应用程序
*/
private SnakeApplication app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_set_speed);
// 通过资源索引获得界面控件实例
rbLowSpeed = (RadioButton) findViewById(R.id.rb_low_speed);
rbMediumSpeed = (RadioButton) findViewById(R.id.rb_medium_speed);
rbHighSpeed = (RadioButton) findViewById(R.id.rb_high_speed);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) getApplication();
// 设置速度单选按钮的初始状态
switch (app.getSpeedIndex()) {
case LOW_SPEED: // 低速
rbLowSpeed.setChecked(true);
break;
case MEDIUM_SPEED: // 中速
rbMediumSpeed.setChecked(true);
break;
case HIGH_SPEED: // 高速
rbHighSpeed.setChecked(true);
break;
}
}
/**
* 确定按钮单击事件处理方法
*
* @param view
*/
public void doOK(View view) {
// 设置速度索引
if (rbLowSpeed.isChecked()) {
// 设置低速
app.setSpeedIndex(LOW_SPEED);
} else if (rbMediumSpeed.isChecked()) {
// 设置中速
app.setSpeedIndex(MEDIUM_SPEED);
} else if (rbHighSpeed.isChecked()) {
// 设置高速
app.setSpeedIndex(HIGH_SPEED);
}
// 关闭当前界面
finish();
}
/**
* 取消按钮单击事件处理方法
*
* @param view
*/
public void doCancel(View view) {
// 关闭当前界面
finish();
}
}
15、在ui子包里创建选择背景音乐界面类SelectMusicActivity
(1)选择背景音乐界面布局文件activity_select_music.xml
(2)音乐列表项模板music_list_item.xml
(3)选择背景音乐界面类SelectMusicActivity
/**
* 功能:选择背景音乐
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import net.hw.snake.R;
import net.hw.snake.app.SnakeApplication;
public class SelectMusicActivity extends Activity {
/**
* 音乐数组
*/
private String[] musics;
/**
* 数组适配器
*/
private ArrayAdapter adapter;
/**
* 音乐列表控件
*/
private ListView lvMusic;
/**
* 贪吃蛇应用程序
*/
private SnakeApplication app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_select_music);
// 通过资源索引获得界面控件实例
lvMusic = (ListView) findViewById(R.id.lv_music);
// 获取贪吃蛇应用程序对象
app = (SnakeApplication) getApplication();
// 初始化音乐数组
musics = new String[] { "梦中的婚礼", "秋日的私语", "天空之城", "致爱丽丝", "贝尔加湖畔", "暗香" };
// 创建数组适配器
adapter = new ArrayAdapter(this, R.layout.music_list_item, musics);
// 给列表视图对象设置适配器
lvMusic.setAdapter(adapter);
// 给音乐列表控件注册监听器
lvMusic.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView adapter, View view, int position, long id) {
// 设置背景音乐索引
app.setMusicIndex(position);
// 弹出吐司提示用户
Toast.makeText(SelectMusicActivity.this, "您选择了【" + musics[position] + "】作为背景音乐!", Toast.LENGTH_LONG)
.show();
// 关闭当前界面
finish();
}
});
}
}
16、在ui子包里创建排行榜界面类TopListActivity
(1)排行榜界面布局文件activity_top_list.xml
(2)在字符串资源文件strings.xml里定义变量
(3)排行榜界面类TopListActivity
/**
* 功能:排行榜
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TopListActivity extends Activity implements AppConstants {
/**
* 第一名标签
*/
private TextView tvFirst;
/**
* 第二名标签
*/
private TextView tvSecond;
/**
* 第三名标签
*/
private TextView tvThird;
/**
* 共享参数对象
*/
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_top_list);
// 通过资源索引获得界面控件实例
tvFirst = (TextView) findViewById(R.id.tv_first);
tvSecond = (TextView) findViewById(R.id.tv_second);
tvThird = (TextView) findViewById(R.id.tv_third);
// 获取共享参数对象
sp = getSharedPreferences(TOP_LIST_FILE_NAME, ACCESS_MODE);
// 在标签上显示前三名分数
tvFirst.setText("第一名:" + sp.getInt(SCORE_OF_FIRST, 0));
tvSecond.setText("第二名:" + sp.getInt(SCORE_OF_SECOND, 0));
tvThird.setText("第三名:" + sp.getInt(SCORE_OF_THIRD, 0));
}
/**
* 返回按钮单击事件处理方法
*
* @param view
*/
public void doBack(View view) {
// 关闭当前界面
finish();
}
}
17、在ui子包里创建主界面类MainActivity
(1)主界面布局文件activity_main.xml
(2)字符串资源文件strings.xml里定义变量
贪吃蛇V1.0
Version1.0
口字型地图
工字型地图
X字型地图
U字型地图
Z字型地图
蛇移动速度:低速
蛇移动速度:中速
蛇移动速度:高速
确定
取消
第一名:
第二名:
第三名:
返回
单位:泸职院信息工程系\n作者:华卫\n电话:15892921170\n邮箱:maths007@163.com
贪吃蛇又名贪食蛇,是一款经典的小游戏。玩家使用方向键操控一条长长的蛇不断吞苹果,同时蛇身随着吞下的苹果不断长,当蛇头撞到蛇身或障壁时游戏结束。
设置
(4)主界面类MainActivity
/**
* 功能:贪吃蛇主控界面
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import net.hw.snake.R;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
}
/**
* 启动游戏
*
* @param view
*/
public void doStartGame(View view) {
startActivity(new Intent(MainActivity.this, SnakeActivity.class));
}
/**
* 选择地图
*
* @param view
*/
public void doSelectMap(View view) {
startActivity(new Intent(MainActivity.this, SelectMapActivity.class));
}
/**
* 设置速度
*
* @param view
*/
public void doSetSpeed(View view) {
startActivity(new Intent(MainActivity.this, SetSpeedActivity.class));
}
/**
* 选择背景音乐
*
* @param view
*/
public void doSelectMusic(View view) {
startActivity(new Intent(MainActivity.this, SelectMusicActivity.class));
}
/**
* 排行榜
*
* @param view
*/
public void doTopList(View view) {
startActivity(new Intent(MainActivity.this, TopListActivity.class));
}
/**
* 帮助信息
*
* @param view
*/
public void doHelp(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("帮助信息");
builder.setMessage(getResources().getString(R.string.game_help));
builder.setPositiveButton("确定", null);
builder.show();
}
/**
* 关于作者
*
* @param view
*/
public void doAboutAuthor(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("关于作者");
builder.setMessage(getResources().getString(R.string.author_info));
builder.setPositiveButton("确定", null);
builder.show();
}
/**
* 退出程序
*
* @param view
*/
public void doExit(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.drawable.snake);
builder.setTitle("温馨提示");
builder.setMessage("您是否要退出【贪吃蛇】游戏?");
builder.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
});
builder.setNegativeButton("否", null);
builder.show();
}
}
18、在ui子包里修改启动界面类SplashScreen
(1)启动界面布局文件activity_splash_screen.xml
(2)字符串资源文件strings.xml里定义变量
(3)启动界面类SplashScreenActivity
/**
* 功能:启动界面
* 作者:华卫
* 日期:2017年1月1日
*/
package net.hw.snake.ui;
import net.hw.snake.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class SplashScreenActivity extends Activity {
/**
* 启动画面延迟时间
*/
private final int DELAY_TIME = 3000;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_splash_screen);
// 利用消息处理器延迟处理
new Handler().postDelayed(new Runnable() {
public void run() {
// 跳转到主控界面
startActivity(new Intent(SplashScreenActivity.this,
MainActivity.class));
// 关闭启动界面
finish();
}
}, DELAY_TIME);
}
}
19、项目清单文件设置全部界面都是全屏显示
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >