JavaEDU614 团队第三周项目总结
2016-07-12 09:20 614Java学习团队 阅读(195) 评论(0) 编辑 收藏 举报JavaEDU614 团队第三周项目总结
本周,根据项目计划完成模块的设计代码
本项目主要是完成俄罗斯方块的基本操作。用户可以自己练习和娱乐。需要满足以下几点要求。
(1)界面控制游戏开始、暂停和结束。
(2)利用↑方向键来改 变下落方块的形状。
(3)方向键↓加速方块下落。
(4)方向键←和→横向移动方块。
(5)从最底部开始消去满行。
本系统主要是完成俄罗斯方块游戏的基本操作,所以在本游戏中应该实现以下功能。
- 响应键盘
玩家可以从电脑键盘来控制游戏。 - 绘制游戏图形界面
玩家开始游戏后,在电脑屏幕上绘制出状态栏和下方方格。方块能够在界面上随机下落,并由键盘控制形状和移动。 - 记录玩家分数
当游戏结束后,显示玩家在游戏中所获得的分数。
具体设计
- GameController类设计
/**
* 游戏控制器
* 控制游戏的开始,结束,暂停,继续
* 控制游戏中形状的移动,变形,自动下落,障碍物的满行,计分,级别变更等
* 在游戏状态变化时向注册的监听器发出游戏事件
* 在形状,障碍物发生变化时向注册的监听器发出游戏显示事件
*/
public class GameController extends KeyAdapter
implements ConfigListener, ScoringListener {
/**
* 游戏监听器
*/
private GameListener[] gameListeners;
/**
* 游戏显示监听器
*/
private GameViewListener[] gameViewListeners;
/**
* 预览监听器
*/
private PreviewListener[] previewListeners;
/**
* 计分管理器
*/
private ScoringController scorer;
/**
* 地形
*/
private Ground ground;
/**
* 形状
*/
private Shape shape;
/**
* 形状创建的时间
* 用于屏蔽形状刚创建时的操作事件
* 有时用户对一个形状持续执行某一操作, 直至该形状触底变成障碍物,
* 而用户可能仍未停止操作, 导致该操作直接作用于下一形状, <br>
* 但这一操作对于下一形状可能并不合适<br>
* 以形状创建的时间加上一个配置的偏移时间来屏蔽形状刚创建时的部分操作事件
* 以减少这种情况<br>
*/
private long shapeCreateTime;
/**
* 下一次的形状, 用于提供预览
*/
private Shape nextShape;
/**
* 形状自动下落的控制器
*/
private Thread shapeDropDriver;
/**
* 游戏是否正在运行
*/
private boolean playing;
/**
* 游戏是否已暂停
*/
private boolean pause;
public GameController() {
// 将监听器初始化为0长度数组, 减少后续事件广播时的空值判断
gameListeners = new GameListener[0];
gameViewListeners = new GameViewListener[0];
previewListeners = new PreviewListener[0];
// 计分器
scorer = new ScoringController();
// 计分器增加监听器
scorer.addScoringListener(this);
// 增加声音监听器
SoundController sound = new SoundController();
addGameListener(sound); // 游戏状态变化触发的声音
addGameViewListener(sound); // 游戏显示变化触发的声音(形状下落到位)
scorer.addScoringListener(sound); // 计分变化触发的声音
}
/**
* 游戏是否是暂停状态
* @return
*/
public boolean isPause() {
return pause;
}
/**
* 游戏是否正在运行
* @return
*/
public boolean isPlaying() {
return playing;
}
/**
* 按键处理
* 1. 开始/结束
* 2. 暂停/继续
* 3. 形状向左移动
* 4. 形状向右移动
* 5. 形状变形
* 6. 形状向下移动
* 7. 形状一落到底
* 形状的按键处理首先判断形状移动或变形后是否与地形发生碰撞
* 如果不发生碰撞则执行移动或变形操作
* 当形状不能再向下移动时, 将该形状变成障碍物, 然后进行满行,计分等后续处理
* 形状在移动或变形前和后都将发出游戏显示事件, 以便通知显示组件更新显示
* 形状向下移动的处理过程可能需要较长时间, 是另启线程运行的
*/
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == Config.CURRENT.getStartKey()) {
// 开始/结束的按键处理
if(playing) {
gameStop();
} else {
// 耗时操作, 另启线程执行
new Thread() {
public void run() {
gameCreate();
}
}.start();
}
} else if(keyCode == Config.CURRENT.getPauseKey()) {
// 暂停/继续的按键处理
if(!playing) return;
if(pause)
gameContinue();
else
gamePause();
} else { // 针对形状的按键事件
// 如果游戏未开始或已暂停, 抛弃之
if(!playing || pause)
return;
// 形状仍未被创建或按键的时间早于创建时间, 抛弃之
if(shape == null|| e.getWhen() <= shapeCreateTime)
return;
if(keyCode == Config.CURRENT.getLeftKey()) {
// 形状向左移动的按键处理
Shape shape = (Shape)this.shape.clone();
shape.moveLeft();
if(!ground.collisional(shape)) {
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeWillMoved(this.shape);
this.shape.moveLeft();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeMoved(this.shape);
}
} else if(keyCode == Config.CURRENT.getRightKey()) {
// 形状向右移动的按键处理
Shape shape = (Shape)this.shape.clone();
shape.moveRight();
if(!ground.collisional(shape)) {
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeWillMoved(this.shape);
this.shape.moveRight();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeMoved(this.shape);
}
} else if(keyCode == Config.CURRENT.getRotateKey()) {
// 形状变形的按键处理
Shape shape = (Shape)this.shape.clone();
shape.rotate();
if(!ground.collisional(shape)) {
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeWillMoved(this.shape);
this.shape.rotate();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeMoved(this.shape);
}
} else if(keyCode == Config.CURRENT.getDownKey()) {
// 形状向下移动的按键处理
// 该处理过程可能需要较长时间, 故另启线程运行
new Thread() {
public void run() {
shapeDrop(false);
}
}.start();
} else if(keyCode == Config.CURRENT.getSwiftKey()) {
// 形状一落到底的按键处理
// 关掉目前的形状自动下落驱动器
// 另外开启一个无间歇的形状自动下落驱动器
((ShapeDropDriver)this.shapeDropDriver).kill();
shapeDropDriver = new ShapeDropDriver(true);
shapeDropDriver.start();
}
}
}
/**
* 形状向下移动的处理
* 首先判断形状向下移动后是否与地形发生碰撞, 如果不发生碰撞则执行移动操作
* 当形状不能再向下移动时, 将该形状变成障碍
* 并进行后续处理:
* 1. 判断是否存在满行
* 2. 删除满行
* 删除满行前发出游戏显示事件, 以便通知显示组件显示一些效果
* 3. 计分
* 计分有可能导致级别变化
* 4. 创建新形状
* @param swift 是否直落到底
*/
private synchronized void shapeDrop(boolean swift) {
// 判断形状向下移动后是否与地形发生碰撞
// 复制形状, 令该形状下移, 判断下移后是否与地形发生碰撞
Shape cloneShape = (Shape)shape.clone();
cloneShape.moveDown();
if(!ground.collisional(cloneShape)) {
// 未与地形发生碰撞, 向下移动
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeWillMoved(shape);
shape.moveDown();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeMoved(shape);
} else {
// 形状变成障碍物
ground.fill(shape);
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeDroped(swift);
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundFilledShape(ground, shape);
// 销毁当前的形状
shape = null;
((ShapeDropDriver)shapeDropDriver).kill();
shapeDropDriver = null;
// 检查满行
int[] fullLine = ground.checkFullLine();
if(fullLine.length > 0) {
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundWillDeleteLine(ground, fullLine);
// 删除满行
ground.deleteLine(fullLine);
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundDeletedLine(ground);
// 计分
scorer.score(fullLine.length);
}
// 创建新形状
shapeCreate();
}
}
/**
* 创建新形状
* 将预览形状置为当前形状, 再创建一个新的预览形状
* 没有位置创建新形状的时候, 判定为游戏结束
*/
private void shapeCreate() {
if(!playing)
return;
// 初始位置
int x = (ground.getWidth() - nextShape.getWidth()) / 2;
int y = 1 - nextShape.getHeight();
nextShape.moveTo(x, y);
// 没有位置创建新形状了, 判定为游戏结束
if(ground.collisional(nextShape)) {
playing = false;
shape = null;
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameOver();
} else {
// 将预览形状置为当前形状
shape = nextShape;
shapeCreateTime = System.currentTimeMillis();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].shapeCreated(shape);
shapeDropDriver = new ShapeDropDriver();
shapeDropDriver.start();
nextShape = null;
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCleared();
// 创建一个新的预览形状
int complexity = scorer.getCurrentLevel().getComplexity();
nextShape = ShapeFactory.getRandomShape(complexity);
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCreated(nextShape);
}
}
/**
* 创建新游戏
*/
public void gameCreate() {
synchronized(this) {
if(playing) return;
playing = true;
}
if(pause) {
pause = false;
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameContinue();
}
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameStart();
// 初始化游戏环境
if(ground == null) {
int width = Config.CURRENT.getGroundWidth();
int height = Config.CURRENT.getGroundHeight();
ground = new Ground(width, height);
} else {
ground.clear();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundCleared();
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCleared();
}
// 初始化计分器
scorer.init();
if(playing) {
// 创建预览形状
int complexity = scorer.getCurrentLevel().getComplexity();
nextShape = ShapeFactory.getRandomShape(complexity);
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCreated(nextShape);
// 创建新形状
shapeCreate();
}
}
/**
* 停止当前游戏
*/
public void gameStop() {
if(!playing) return;
// 停止游戏确认
boolean confirm = true;
for(int i = 0; i < gameListeners.length; i++) {
if(!gameListeners[i].gameWillStop()) {
confirm = false;
}
}
// 可以停止游戏
if(confirm) {
playing = false;
shape = null;
if(this.shapeDropDriver != null) {
((ShapeDropDriver)this.shapeDropDriver).kill();
this.shapeDropDriver = null;
}
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameOver();
}
}
/**
* 暂停游戏
*/
public void gamePause() {
if(!playing || pause) return;
pause = true;
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gamePause();
}
/**
* 继续游戏
*/
public void gameContinue() {
if(!playing || !pause) return;
pause = false;
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameContinue();
}
/**
* 有关级别的配置项改变时的处理
*/
public void levelConfigChanged() {
if(!playing) return;
// 停止当前游戏
playing = false;
shape = null;
if(this.shapeDropDriver != null) {
((ShapeDropDriver)this.shapeDropDriver).kill();
this.shapeDropDriver = null;
}
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameOver();
new Thread() {
public void run() {
gameCreate();
}
}.start();
}
public void hotkeyConfigChanged() {}
public void viewConfigChanged() {}
/**
* 级别改变时的处理
* 1. 将预览形状清空
* 2. 将障碍物清空
* 清空前发出游戏显示事件, 以便通知显示组件显示一些效果
* 3. 根据新的级别要求创建一个新的预览形状
* 4. 如果新的级别要求填充一些随机障碍物, 填充之
* 填充后发出游戏显示事件, 以便通知显示组件显示一些效果
*/
public void levelChanged(Level level) {
// 1. 将预览形状清空
nextShape = null;
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCleared();
// 2. 将障碍物清空
// 清空前发出游戏显示事件, 以便通知显示组件显示一些效果
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundWillClear(ground);
if(!playing) return;
ground.clear();
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundCleared();
// 3. 根据新的级别要求创建一个新的预览形状
nextShape = ShapeFactory.getRandomShape(
scorer.getCurrentLevel().getComplexity());
for(int i = 0; i < previewListeners.length; i++)
previewListeners[i].shapePreviewCreated(nextShape);
// 4. 如果新的级别要求填充一些随机障碍物, 填充之
if(level.getFraiseLine() > 0) {
ground.randomFill(scorer.getCurrentLevel().getFraiseLine(),
scorer.getCurrentLevel().getFraiseFillRate());
// 填充后发出游戏显示事件, 以便通知显示组件显示一些效果
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundFilledRandom(ground);
}
}
/**
* 计分初始化时的处理
* 如果初始级别要求填充一些随机障碍物, 填充之
*/
public void scoringInit(int scoring, int speed, Level level) {
// 如果新的级别要求填充一些随机障碍物, 填充之
if(level.getFraiseLine() > 0) {
ground.randomFill(scorer.getCurrentLevel().getFraiseLine(),
scorer.getCurrentLevel().getFraiseFillRate());
// 填充后发出游戏显示事件, 以便通知显示组件显示一些效果
for(int i = 0; i < gameViewListeners.length; i++)
gameViewListeners[i].groundFilledRandom(ground);
}
}
public void shapeDroped(boolean swift) {}
public void scoringChanged(int scoring, boolean levelChanged) {}
public void speedChanged(int speed) {}
/**
* 超过最高级别, 游戏结束
*/
public void winning(int scoring, int speed, Level level) {
playing = false;
shape = null;
for(int i = 0; i < gameListeners.length; i++)
gameListeners[i].gameOver();
}
/**
* 增加游戏监听器
* @param listener GameListener 游戏监听器
*/
public void addGameListener(GameListener listener) {
gameListeners = (GameListener[])Utilities.arrayAddItem(
gameListeners, listener);
}
/**
* 移除游戏监听器
* @param listener GameListener 游戏监听器
*/
public void removeGameListener(GameListener listener) {
gameListeners = (GameListener[])Utilities.arrayRemoveItem(
gameListeners, listener);
}
/**
* 增加游戏显示监听器
* @param listener GameViewListener 游戏显示监听器
*/
public void addGameViewListener(GameViewListener listener) {
gameViewListeners = (GameViewListener[])Utilities.arrayAddItem(
gameViewListeners, listener);
}
/**
* 移除游戏显示监听器
* @param listener GameViewListener 游戏显示监听器
*/
public void removeGameViewListener(GameViewListener listener) {
gameViewListeners = (GameViewListener[])Utilities.arrayRemoveItem(
gameViewListeners, listener);
}
/**
* 增加预览监听器
* @param listener PreviewListener 预览监听器
*/
public void addPreviewListener(PreviewListener listener) {
previewListeners = (PreviewListener[])Utilities.arrayAddItem(
previewListeners, listener);
}
/**
* 移除预览监听器
* @param listener PreviewListener 预览监听器
*/
public void removePreviewListener(PreviewListener listener) {
previewListeners = (PreviewListener[])Utilities.arrayRemoveItem(
previewListeners, listener);
}
/**
* 增加计分监听器
* @param listener ScoringListener 计分监听器
*/
public void addScoringListener(ScoringListener listener) {
scorer.addScoringListener(listener);
}
/**
* 增加计分监听器
* @param listener ScoringListener 计分监听器
* @param first boolean 是否增加至首位
*/
public void addScoringListener(ScoringListener listener, boolean first) {
scorer.addScoringListener(listener, first);
}
/**
* 移除计分监听器
* @param listener ScoringListener 计分监听器
*/
public void removeScoringListener(ScoringListener listener) {
scorer.removeScoringListener(listener);
}
/**
* 形状自动下落驱动器
* @author zhaohuihua
*/
private class ShapeDropDriver extends Thread {
/**
* 该驱动器是否运行的标志<br>
* 如为false, 则结束运行<br>
*/
private boolean run;
/**
* 是否直落到底的标志<br>
* 如为false, 每次下落前间歇一定周期<br>
* 如为true, 无间歇直落到底<br>
*/
private boolean swift;
/**
* 形状自动下落驱动器
*/
public ShapeDropDriver() {
this.run = true;
}
/**
* 形状自动下落驱动器
* @param swift boolean 是否直落到底
*/
public ShapeDropDriver(boolean swift) {
this.run = true;
this.swift = swift;
this.setDaemon(true);
}
/**
* 销毁驱动器
*/
public void kill() {
run = false;
}
/**
* 休眠一定周期后形状自动向下移动一格
*/
public void run() {
while(playing && run) {
try {
// 如果不是直落到底, 休眠一个周期
if(!swift)
Thread.sleep(scorer.getCurrentSpeed());
} catch (InterruptedException e) {
}
if(playing && !pause && shape != null) {
// 向下移动
shapeDrop(swift);
}
}
}
}
}
- ScoringController类设计
package zhh.game.tetris.controller;
import zhh.game.tetris.entity.Level;
import zhh.game.tetris.entity.LevelSet;
import zhh.game.tetris.entity.LevelSetFactory;
import zhh.game.tetris.global.Config;
import zhh.game.tetris.global.Utilities;
import zhh.game.tetris.listener.ScoringListener;
/**
* 计分控制器<br>
* 负责计分,级别的管理<br>
* 维护当前环境共有多少级别, 以及根据得分进行级别的升级调整<br>
* 在得分,级别变化时向注册的监听器发出计分事件<br>
* @author zhaohh
*/
public class ScoringController {
/**
* 计分规则
*/
public static final int[] SCORING_RULE = {0, 100, 220, 360, 520};
/**
* 计分监听器
*/
private ScoringListener[] scoringListeners;
/**
* 当前级别集
*/
private LevelSet levelSet;
/**
* 当前级别
*/
private int level;
/**
* 初始级别
*/
private int initLevel;
/**
* 当前得分
*/
private int scoring;
/**
* 当前速度
*/
private int speed;
/**
* 计分控制器<br>
* 默认的<br>
*/
public ScoringController() {
this.scoring = 0;
this.level = Config.CURRENT.getInitLevel();
this.initLevel = Config.CURRENT.getInitLevel();
this.levelSet = LevelSetFactory.getLevelSet(
Config.CURRENT.getCurrentLevelSet());
this.speed = getCurrentLevel().getInitSpeed();
}
/**
* 初始化内部参数
*/
public void init() {
this.scoring = 0;
this.level = Config.CURRENT.getInitLevel();
this.initLevel = Config.CURRENT.getInitLevel();
this.levelSet = LevelSetFactory.getLevelSet(
Config.CURRENT.getCurrentLevelSet());
this.speed = getCurrentLevel().getInitSpeed();
int length = scoringListeners == null ? 0 : scoringListeners.length;
for(int i = 0; i < length; i++)
scoringListeners[i].scoringInit(scoring, speed, getCurrentLevel());
}
/**
* 计分
* @param line int 得分的行数
*/
public void score(int line) {
// 根据得分的行数获取应得分数
if(line > SCORING_RULE.length)
line = SCORING_RULE.length;
// 计分
this.scoring += SCORING_RULE[line];
// 计算级别
int newLevel = scoring / levelSet.getInterval() + initLevel;
int length = scoringListeners == null ? 0 : scoringListeners.length;
for(int i = 0; i < length; i++)
scoringListeners[i].scoringChanged(scoring, level != newLevel);
if(level < newLevel) {
if(newLevel < levelSet.getLevelCount()) {
// 级别变化了
level = newLevel;
int newSpeed = getCurrentLevel().getInitSpeed();
// 速度变化了
if(speed != newSpeed) {
speed = newSpeed;
for(int i = 0; i < length; i++)
scoringListeners[i].speedChanged(speed);
}
for(int i = 0; i < length; i++)
scoringListeners[i].levelChanged(getCurrentLevel());
} else { // 过了最后一关
level = -1;
for(int i = 0; i < length; i++)
scoringListeners[i].winning(scoring, speed,
levelSet.getLevels()[levelSet.getLevelCount() - 1]);
}
} else if(getCurrentLevel().getInterval() > 0) { // 同级别内可能变速
// 计算速度
Level currentLevel = getCurrentLevel();
// 当前级别的得分
int currentLevelScoring = scoring - (level - initLevel)
* levelSet.getInterval();
// 新的速度
int newSpeed = currentLevel.getInitSpeed() -
(currentLevelScoring / currentLevel.getInterval()) *
currentLevel.getIncrement();
if(newSpeed < currentLevel.getMinSpeed())
newSpeed = currentLevel.getMinSpeed();
// 速度变化了
if(speed != newSpeed) {
speed = newSpeed;
for(int i = 0; i < length; i++)
scoringListeners[i].speedChanged(speed);
}
}
}
/**
* 获取当前得分
* @return int 当前得分
*/
public int getCurrentScoring() {
return scoring;
}
/**
* 获取当前速度
* @return
*/
public int getCurrentSpeed() {
return speed;
}
/**
* 获取当前级别
* @return Level 当前级别<br>
* 如果返回的级别为null, 表示级别已超过当前环境中的最高级别<br>
*/
public Level getCurrentLevel() {
return level == -1 ? null : levelSet.getLevel(level);
}
/**
* 判断游戏是否已经过了最后一关
* @return
*/
public boolean isWinning() {
return level == -1 || level >= levelSet.getLevelCount();
}
/**
* 增加计分监听器
* @param listener ScoringListener 计分监听器
*/
public void addScoringListener(ScoringListener listener) {
addScoringListener(listener, false);
}
/**
* 增加计分监听器
* @param listener ScoringListener 计分监听器
* @param first boolean 是否增加至首位
*/
public void addScoringListener(ScoringListener listener, boolean first) {
if(scoringListeners == null) scoringListeners = new ScoringListener[]{};
scoringListeners = (ScoringListener[])Utilities.arrayAddItem(
scoringListeners, listener, first);
}
/**
* 移除计分监听器
* @param listener ScoringListener 计分监听器
*/
public void removeScoringListener(ScoringListener listener) {
scoringListeners = (ScoringListener[])Utilities.arrayRemoveItem(
scoringListeners, listener);
}
}
总结
本周按项目计划,完成了部分模块的代码设计。实现游戏的开始和结束,实现方块的下落、变换和移动,实现计分模块。还没有完全实现最终程序,仍需进项代码实现和修改。另外,还需进行代码的测试。会好好利用时间,完成最终得游戏。