我目前正在为我的奥丁项目任务开发一款基于网络的战舰游戏。在某种程度上,我觉得调解模式将是处理玩家和CPU发射导弹的完美选择。现在,我的任务鼓励我不用console.log而是用Jest彻底测试游戏。我已经能够测试游戏的一些功能,但调解模式令人困惑。模拟函数或模块可能是正确的方向,但老实说,我已经阅读了大量指南,但我还无法实现它们(理解模拟也很困难)。EventManager中的函数notifyAttack已经用console.log的旧方法进行了测试。
有人能告诉我我做错了什么吗?
事件管理器
export {EventManager}
const EventManager = {
gameManager: GameManager,
notifyAttack(who, coordinate){
if(!who)
throw new Error(`Unknown player`);
else
who === `CPU` ? GameManager.player.board.getAttack(coordinate) : GameManager.cpu.board.getAttack(coordinate);
GameManager.turn = who;
}
}
游戏管理器
import {Player} from "./player";
export {GameManager}
const GameManager = {
turn: undefined,
player: undefined,
cpu: Player(),
}
游戏者
import {coordinate, GameBoard} from './gameboard';
import { EventManager } from './eventmanager';
export {Player}
const playerActions = {
eventManager: EventManager,
fire(coordinate){
this.eventManager.notifyAttack(this.name, coordinate);
}
}
function Player(name){
const player = Object.create(playerActions);
player.board = GameBoard();
name === undefined ? player.name = `CPU`: player.name = name;
return player;
}
游戏板
import { Ship } from "./ship"
export {GameBoard, coordinate, shipOrientation, tile}
function coordinate(x,y){
const boardSize = 10;
if(x > boardSize || x < 1)
throw new Error(`X coordinate is out of boundaries`);
if(y > boardSize || y < 1)
throw new Error(`Y coordinate is out of boundaries`);
return{x:x, y:y}
}
function tile(coordinate, id){
return{coordinate: coordinate, id: id}
}
const shipOrientation = {
HORIZONTAL: Symbol(`horizontal`),
VERTICAL: Symbol(`vertical`),
}
const gameboardActions = {
placeShips(shipType, orientation, inputCoordinate){
const ship = Ship(shipType);
ship.ID = `${inputCoordinate.x},${inputCoordinate.y}`;
this.tiles.forEach(tile=>{
if(tile.coordinate.x === inputCoordinate.x && tile.coordinate.y === inputCoordinate.y)
throw new Error(`There's already an object on that input coordinate`);
})
if(orientation === shipOrientation.HORIZONTAL){
if(inputCoordinate.x + ship.length > this.size)
throw new Error(`Part of ship is out of board X boundary`);
for(let i = 0; i<ship.length; ++i)
this.tiles.push(tile(coordinate(inputCoordinate.x+i, inputCoordinate.y), `${ship.ID}`));
}else if(orientation === shipOrientation.VERTICAL){
if(inputCoordinate.y + ship.length > this.size)
throw new Error(`Part of ship is out of board Y boundary`);
for(let i = 0; i<ship.length; ++i)
this.tiles.push(tile(coordinate(inputCoordinate.x, inputCoordinate.y+i), `${ship.ID}`));
}else
throw new Error(`Undefined ship orientation`);
this.shipsLog.set(`${ship.ID}`,ship);
},
getAttack(inputCoordinate){
let isShip, ID;
this.tiles.forEach(tile=>{
if(tile.coordinate.y===inputCoordinate.y&&tile.coordinate.x===inputCoordinate.x&&tile.id){
ID = tile.id;
return isShip = true;
}
})
if(isShip){
this.shipsLog.get(ID).hit()
if(this.shipsLog.get(ID).isSunk){
this.removeShip(ID);
this.checkSunkFleet();
}
}else
this.tiles.push(tile(inputCoordinate, undefined));
},
removeShip(ID){
this.shipsLog.delete(ID);
for(let i = 0; i<this.tiles.length; ++i)
if(this.tiles[i].id===ID)
this.tiles.splice(i,1);
},
checkSunkFleet(){
this.shipsLog.size === 0 ? this.sunkFleet=true:this.sunkFleet=false;
}
}
function GameBoard (){
const gameboard = Object.create(gameboardActions);
gameboard.shipsLog = new Map();
gameboard.tiles= [];
gameboard.size= 10;
gameboard.sunkFleet = false;
return gameboard;
}
杰斯特试验
import {GameBoard, coordinate} from "./gameboard";
import {GameManager} from './gamemanager';
import {Player} from "./player";
import {EventManager} from "./eventmanager";
GameManager.player = Player(`Pablo`);
describe(`Player set up`, ()=>{
test(`Player's name is Pablo`,()=>{
expect(GameManager.player.name).toMatch(/^[A-Z]+$/i);
});
test(`Player has a board to play with`, ()=>{
expect(GameManager.player.board).toMatchObject(GameBoard());
});
})
describe(`Player's actions`,()=>{
test(`Pablo fires a missile, he misses ship target though`, ()=>{
const myCoordinate = coordinate(5,5);
const spy = jest.spyOn(EventManager, 'notifyAttack')
GameManager.player.fire(myCoordinate);
expect(spy).toBeCalled();
expect(GameManager.cpu.getAttack).toBeCalledWith(myCoordinate);
expect(GameManager.cpu.shipsLog.has(`${myCoordinate.x}, ${myCoordinate.y}`));
})
})
所以流程是这样的:
- A 游戏者 already set up in
GameManager(Pablo) fires a missile by triggeringfire()inside the 游戏者 object fire()reportsEventManagerwho fires the missile and its coordinatesEventManagercallsCPUGameBoardgetAttack()method that records Pablo's missing missile
你们可能会想,为什么我使用EventManager而不是依赖GameManager。基本上,GameManager负责改变回合,设置游戏,而EventManager专门处理防止Player和CPU之间耦合的战斗
如果你需要这个问题的更多细节,我想听听你的意见。
干杯