I'm currently working on a web based Battleship game for one my Odin Project assignments. At some point I felt that the mediator pattern would be the perfect choice to deal with firing missiles for the player and the CPU. Now, my assignment encourages me to test the game thoroughly without console.log
but with Jest. I've been able to test some features of the game but the mediator pattern is confusing. Mocking functions or modules is likely to be the right direction to go for but to be honest I've read heaps of guides and I've not been able to implement them (understanding mocks has been hard as well). The function notifyAttack
inside EventManager
has already been tested the old way with console.log
.
Can anyone let me know what I'm doing wrong please?
Event Manager
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;
}
}
Game Manager
import {Player} from "./player";
export {GameManager}
const GameManager = {
turn: undefined,
player: undefined,
cpu: Player(),
}
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;
}
GameBoard
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;
}
Jest test
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}`));
})
})
So the flow goes in this way:
- A Player already set up in
GameManager
(Pablo) fires a missile by triggeringfire()
inside the Player object fire()
reportsEventManager
who fires the missile and its coordinatesEventManager
callsCPU
GameBoard
getAttack()
method that records Pablo's missing missile
You guys might wonder why I'm using an EventManager
instead of relying on GameManager
. Basically, GameManager
is in charge of changing turns, set up the game whereas EventManager
specifically deals with the battle to prevent coupling between Player
and CPU
I'd like to hear from you if you need more details for the question.
Cheers!
Aucun commentaire:
Enregistrer un commentaire