我目前正在为我的奥丁项目任务开发一款基于网络的战舰游戏。在某种程度上,我觉得调解模式将是处理玩家和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()
reportsEventManager
who fires the missile and its coordinatesEventManager
callsCPU
GameBoard
getAttack()
method that records Pablo's missing missile
你们可能会想,为什么我使用EventManager
而不是依赖GameManager
。基本上,GameManager
负责改变回合,设置游戏,而EventManager
专门处理防止Player
和CPU
之间耦合的战斗
如果你需要这个问题的更多细节,我想听听你的意见。
干杯