命令模式:将一个请求封装成一个对象,从而允许你使用不同的请求,队列或者是日志讲客户端参数化,同时支持请求操作的撤销和恢复:
咋一听上去真的不好理解,光说不练假把式,还是动手写东西来的快,既然是比较适用于支持撤销操作的话,那这里改进了个游戏华容道,进行命令模式的演示:cocos creator 2.2.2版本演示
华容道相信大家都玩过,他的命令也不多,就一个移动命令,说到这里就说道了命令模式的一个缺点就是会导致类膨胀,假如有成百上千个命令,那么继承的代码就会很多,这样确实是不好处理。
好了废话不多说,首先讲一讲最重要的一个类就是Command了,对所有的命令都继承这个抽象类,其他的子类只需要实现它的方法就ok了,
import { Action } from "./Action";
import Handler from "./Handler";
export default abstract class Command {
constructor() {
}
// 执行命令
public abstract exec();
// 撤销操作
public abstract undo();
// 重做指令
public abstract redo();
}
这里最重要的方法就是exec方法和undo方法,分别对应着执行命令和撤销命令操作,有了这些东西,它需要一个真正干活的家伙,有请MoveCommand闪灵登场:
import Command from "./Command";
import { Action } from "./Action";
import Handler from "./Handler";
import Hero from "../Hero";
enum Direction {
TOP,
BOTTOM,
LEFT,
RIGHT
}
export default class MoveCommand implements Command {
private moveNode: cc.Node;
private map: number[][];
private dir: number;
private pointMap: cc.Vec2[][];
constructor(moveNode: cc.Node = null,map: number[][] = null,dir: number = -1,pointMap: cc.Vec2[][]) {
this.moveNode = moveNode;
this.map = map;
this.dir = dir;
this.pointMap = pointMap;
}
// 执行
public exec() {
if(this.dir === Direction.TOP) {
this.move2Top();
}
if(this.dir === Direction.BOTTOM) {
this.move2Bottom();
}
if(this.dir === Direction.LEFT) {
this.move2Left();
}
if(this.dir === Direction.RIGHT) {
this.move2Right();
}
}
// 撤销操作
public undo() {
if(this.dir === Direction.TOP) {
this.move2Bottom();
}
if(this.dir === Direction.BOTTOM) {
this.move2Top();
}
if(this.dir === Direction.LEFT) {
this.move2Right();
}
if(this.dir === Direction.RIGHT) {
this.move2Left();
}
}
// 重做
public redo() {
}
private computePosition(nodeCom: Hero,colArr: number[],rowArr: number[]): void {
colArr = nodeCom.getColArr();
// nodeCom.setColArr[]
let sumX: number = 0;
let sumY: number = 0;
for(let m = 0; m < colArr.length; m++) {
for(let n = 0; n < rowArr.length; n++) {
let positionItem: cc.Vec2 = this.pointMap[rowArr[n]][colArr[m]];
if(positionItem) {
sumX += positionItem.x;
sumY += positionItem.y;
}
}
}
let x = sumX / (rowArr.length * colArr.length);
let y = sumY / (rowArr.length * colArr.length);
this.moveNode.x = x;
this.moveNode.y = y;
// this.moveNode = null;
}
// 向上走
private move2Top(): void {
// 改变地图的模型数据
let nodeCom: Hero = <Hero>this.moveNode.getComponent("Hero");
let rowArr: number[] = nodeCom.getRowArr();
let colArr: number[] = nodeCom.getColArr();
let maxRow: number = Math.max(...rowArr);
let minRow: number = Math.min(...rowArr);
for(let i = 0; i < colArr.length; i++) {
this.map[maxRow + 1][colArr[i]] = 1;
this.map[minRow][colArr[i]] = 0;
}
let targetRowArr: number[] = rowArr.map((value) => {
return value + 1;
});
nodeCom.setRowArr(targetRowArr);
this.computePosition(nodeCom,colArr,targetRowArr);
}
// 向下走
private move2Bottom(): void {
let nodeCom: Hero = <Hero>this.moveNode.getComponent("Hero");
let rowArr: number[] = nodeCom.getRowArr();
let colArr: number[] = nodeCom.getColArr();
let maxRow: number = Math.max(...rowArr);
let minRow: number = Math.min(...rowArr);
for(let i = 0; i < colArr.length; i++) {
this.map[maxRow][colArr[i]] = 0;
this.map[minRow - 1][colArr[i]] = 1;
}
let targetRowArr: number[] = rowArr.map((value) => {
return value - 1;
});
nodeCom.setRowArr(targetRowArr);
this.computePosition(nodeCom,colArr,targetRowArr);
}
// 向左走
private move2Left(): void {
// 改变地图的模型数据
let nodeCom: Hero = <Hero>this.moveNode.getComponent("Hero");
let rowArr: number[] = nodeCom.getRowArr();
let colArr: number[] = nodeCom.getColArr();
for(let i = 0; i < rowArr.length; i++) {
for(let j = 0; j < colArr.length; j++) {
this.map[rowArr[i]][colArr[j]] = 0;
this.map[rowArr[i]][colArr[j] - 1] = 1;
}
}
let targetcolArr: number[] = colArr.map((value) => {
return value - 1;
});
nodeCom.setColArr(targetcolArr);
this.computePosition(nodeCom,colArr,rowArr);
}
// 向右走
private move2Right(): void {
// 改变地图的模型数据
let nodeCom: Hero = <Hero>this.moveNode.getComponent("Hero");
let rowArr: number[] = nodeCom.getRowArr();
let colArr: number[] = nodeCom.getColArr();
for(let i = 0; i < rowArr.length; i++) {
for(let j = 0; j < colArr.length; j++) {
this.map[rowArr[i]][colArr[j]] = 0;
this.map[rowArr[i]][colArr[j] + 1] = 1;
}
}
let targetcolArr: number[] = colArr.map((value) => {
return value + 1;
});
nodeCom.setColArr(targetcolArr);
this.computePosition(nodeCom,colArr,rowArr);
}
}
这个类保存了是那个节点执行了哪个方法(对应它的dir属性)他是直接改变了游戏中的map对象,这样在撤销操作的时候只需要执行它相反的方法就行了,
问题来了,这个到底怎么封装这个命令出来呢?这里思考几分钟不要看下面的答案哈哈
答案就是:命令处理类 CommandHandler
它的内部应用这两个栈,undo栈,redo栈
我们每执行一个移动命令都实例化一个对象出来,放到undo栈中,等撤销的时候从这个undo栈中进行取命令执行完事,代码如下:
/**
*
*
* 命令模式处理器
*
*/
import { Action } from "./Action";
import Command from "./Command";
export default class CommandHandler {
private undoStack: Command[];
private redoStack: Command[];
private action: Action;
constructor() {
this.undoStack = [];
this.redoStack = [];
}
// 处理
public handle(action: Action,command?: Command) {
// 执行
if(action === Action.EXEC) {
command.exec();
this.undoStack.push(command);
this.redoStack = [];
}
// 撤销
if(action === Action.UNDO && this.undoStack.length > 0) {
let command: Command = this.undoStack.pop();
command.undo();
this.redoStack.push(command);
} else {
console.log("栈里面没有命令了");
}
// 重做
if(action === Action.REDO && this.redoStack.length > 0) {
let command: Command = this.redoStack.pop();
command.redo();
this.undoStack.push(command);
}
}
}
在游戏的类game.ts文件中初始化这个处理类:
private commandHandler: CommandHandler = null;
async start () {
this.commandHandler = new CommandHandler();
// 设置有限状态机状态
this.curState = GameState.INIT;
this.nodeDirection = Direction.RIGHT;
await this.init();
}
当按钮点击撤销的时候执行撤销操作:
private btnEvent(e: cc.Event,data: any): void {
switch(data) {
case "next":
break;
case "pre":
break;
case "restart":
this.restart();
break;
case "undo":
// 执行悔棋逻辑
this.commandHandler.handle(Action.UNDO);
break;
}
}
游戏主逻辑记录了当前的移动方向,然后就可以发送命令开始执行了:
set curDir(dir: Direction) {
if(this.currentMoveNode) {
switch(dir) {
case Direction.TOP:
this.nodeDirection = Direction.TOP;
this.commandHandler.handle(Action.EXEC,new MoveCommand(this.currentMoveNode,this.map,Direction.TOP,this.pointMap));
break;
case Direction.BOTTOM:
this.nodeDirection = Direction.BOTTOM;
this.commandHandler.handle(Action.EXEC,new MoveCommand(this.currentMoveNode,this.map,Direction.BOTTOM,this.pointMap));
break;
case Direction.LEFT:
this.nodeDirection = Direction.LEFT;
this.commandHandler.handle(Action.EXEC,new MoveCommand(this.currentMoveNode,this.map,Direction.LEFT,this.pointMap));
break;
case Direction.RIGHT:
this.nodeDirection = Direction.RIGHT;
this.commandHandler.handle(Action.EXEC,new MoveCommand(this.currentMoveNode,this.map,Direction.RIGHT,this.pointMap));
break;
}
}
}
大功告成,打完收工