diff --git a/packages/server/services/Game.v2.js b/packages/server/services/Game.v2.js index 2a75db2..9fbb717 100644 --- a/packages/server/services/Game.v2.js +++ b/packages/server/services/Game.v2.js @@ -95,8 +95,9 @@ const getNeighbors = boardSize => (point, i, boardState) => { return point; } -const initBoard = ({ boardSize, handicap }) => { +const initBoard = (game) => { const boardState = {}; + const { boardSize, handicap } = game; for (let i = 0; i < Math.pow(boardSize, 2); i++) { const point = Point({ x: Math.floor(i / boardSize) + 1, @@ -107,8 +108,9 @@ const initBoard = ({ boardSize, handicap }) => { } if (handicap) { HANDI_PLACE[boardSize][handicap].forEach(pt => { - boardState[pt].stone = 1; + boardState[pt].makeMove(game); }); + game.turn *= -1; } const boardStateWithNeighbors = pipeMap(getNeighbors(boardSize))(boardState) return boardStateWithNeighbors; @@ -135,9 +137,8 @@ const Game = ({gameData = {}, gameRecord = []} = {}) => ({ initGame: function() { this.winner = null; this.pass = 0; - this.turn = this.handicap ? -1 : 1; - this.boardState = initBoard({ boardSize: this.boardSize, handicap: this.handicap}) - // return this.boardState + this.turn = 1; + this.boardState = initBoard(this) return { ...this, legalMoves: getBoardState(this)}; }, @@ -152,12 +153,18 @@ const Game = ({gameData = {}, gameRecord = []} = {}) => ({ || ( this.turn === -1 && player === 'white' ); if (isTurn) { if (point.legal) { - point.makeMove(this.turn); + point.makeMove(this); this.turn *= -1; success = true; } } return {...this, legalMoves: getBoardState(this), success }; + }, + + initGroup: function(point) { + const newSymbol = Symbol(`${point.pos.x}-${point.pos.y}`); + this.groups[newSymbol] = new Set(); + return newSymbol; } }); @@ -167,7 +174,7 @@ const Point = ({x, y, boardSize = 19}) => ({ legal: true, territory: 0, capturing: [], - groupMembers: [ this ], + group: null, neighbors: { top: x > 1 ? `${ x - 1 }-${ y }` : null, btm: x < boardSize ? `${ x + 1 }-${ y }` : null, @@ -175,9 +182,30 @@ const Point = ({x, y, boardSize = 19}) => ({ lft: y > 1 ? `${ x }-${ y - 1 }` : null }, - makeMove: function(turn) { - this.stone = turn; + makeMove: function(game) { + this.stone = game.turn; this.legal = false; + return this.joinGroup({ point: this, game }); + }, + + joinGroup: function({ point, game }) { + if (point.group !== this.group || !point.group) { + // if point has no group set current group to new Symbol in game object + if (!point.group) { + point.group = game.initGroup(point); + } + + // add current point to global group + game.groups[point.group].add(this); + this.group = point.group; + for (let neighbor of Object.values(this.neighbors)) { + if (neighbor.stone === this.stone + && neighbor.group !== this.group) { + neighbor.joinGroup({ point: this, game }); + } + } + } + } }); diff --git a/packages/server/test/Game.v2.spec.js b/packages/server/test/Game.v2.spec.js index 28e4b13..47ea6c1 100644 --- a/packages/server/test/Game.v2.spec.js +++ b/packages/server/test/Game.v2.spec.js @@ -32,14 +32,14 @@ describe('Game', () => { describe('Game().initGame() returns boardState', () => { it('initGame() returns default 19x19', done => { - Game().initGame().legalMoves - .should.eql(emptyBoard); + Game().initGame() + .legalMoves.should.eql(emptyBoard); done(); }); it('initGame() with 2 handicap returns legalMoves with stones', done => { - Game({gameData: { handicap: 2 }}).initGame().legalMoves - .should.eql({...emptyBoard, '4-16': 1, '16-4': 1}); + Game({gameData: { handicap: 2 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '4-16': 1, '16-4': 1}); done(); }); @@ -65,87 +65,135 @@ describe('Game().initGame() returns boardState', () => { }); it('initGame( 19x19 ) with all levels of handicap returns legalMoves with stones', done => { - Game({gameData: { boardSize: 19, handicap: 2 }}).initGame().legalMoves - .should.eql({...emptyBoard, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 3 }}).initGame().legalMoves - .should.eql({...emptyBoard, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 4 }}).initGame().legalMoves - .should.eql({...emptyBoard, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 5 }}).initGame().legalMoves - .should.eql({...emptyBoard, '10-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 6 }}).initGame().legalMoves - .should.eql({...emptyBoard, '10-4': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 7 }}).initGame().legalMoves - .should.eql({...emptyBoard, '10-10': 1, '10-4': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 8 }}).initGame().legalMoves - .should.eql({...emptyBoard, '16-10': 1, '10-4': 1, '10-16': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); - Game({gameData: { boardSize: 19, handicap: 9 }}).initGame().legalMoves - .should.eql({...emptyBoard, '10-10': 1, '16-10': 1, '10-4': 1, '10-16': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 2 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 3 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 4 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 5 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '10-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 6 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '10-4': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 7 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '10-10': 1, '10-4': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 8 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '16-10': 1, '10-4': 1, '10-16': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); + Game({gameData: { boardSize: 19, handicap: 9 }}).initGame() + .legalMoves.should.eql({...emptyBoard, '10-10': 1, '16-10': 1, '10-4': 1, '10-16': 1, '4-10': 1, '4-4': 1, '16-16': 1, '4-16': 1, '16-4': 1 }); done(); }) it('initGame( 13x13) returns legalMoves', done => { - Game({gameData: { boardSize: 13 }}).initGame().legalMoves - .should.eql(emptyBoard13); + Game({gameData: { boardSize: 13 }}).initGame() + .legalMoves.should.eql(emptyBoard13); done(); }); it('initGame( 13x13 ) with all levels of handicap returns legalMoves with stones', done => { - Game({gameData: { boardSize: 13, handicap: 2 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 3 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 4 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 5 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '7-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 6 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '7-4': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 7 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '7-7': 1, '7-4': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 8 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '10-7': 1, '7-4': 1, '7-10': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); - Game({gameData: { boardSize: 13, handicap: 9 }}).initGame().legalMoves - .should.eql({...emptyBoard13, '7-7': 1, '10-7': 1, '7-4': 1, '7-10': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 2 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 3 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 4 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 5 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '7-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 6 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '7-4': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 7 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '7-7': 1, '7-4': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 8 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '10-7': 1, '7-4': 1, '7-10': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); + Game({gameData: { boardSize: 13, handicap: 9 }}).initGame() + .legalMoves.should.eql({...emptyBoard13, '7-7': 1, '10-7': 1, '7-4': 1, '7-10': 1, '4-7': 1, '4-4': 1, '10-10': 1, '4-10': 1, '10-4': 1 }); done(); }); it('initGame( 9x9 ) returns legalMoves', done => { - Game({gameData: { boardSize: 9 }}).initGame().legalMoves - .should.eql(emptyBoard9); + Game({gameData: { boardSize: 9 }}).initGame() + .legalMoves.should.eql(emptyBoard9); done(); }); it('initGame( 9x9 ) with all levels of handicap returns legalMoves with stones', done => { - Game({gameData: { boardSize: 9, handicap: 2 }}).initGame().legalMoves - .should.eql({...emptyBoard9, '3-7': 1, '7-3': 1 }); - Game({gameData: { boardSize: 9, handicap: 3 }}).initGame().legalMoves - .should.eql({...emptyBoard9, '7-7': 1, '3-7': 1, '7-3': 1 }); - Game({gameData: { boardSize: 9, handicap: 4 }}).initGame().legalMoves - .should.eql({...emptyBoard9, '3-3': 1, '7-7': 1, '3-7': 1, '7-3': 1 }); + Game({gameData: { boardSize: 9, handicap: 2 }}).initGame() + .legalMoves.should.eql({...emptyBoard9, '3-7': 1, '7-3': 1 }); + Game({gameData: { boardSize: 9, handicap: 3 }}).initGame() + .legalMoves.should.eql({...emptyBoard9, '7-7': 1, '3-7': 1, '7-3': 1 }); + Game({gameData: { boardSize: 9, handicap: 4 }}).initGame() + .legalMoves.should.eql({...emptyBoard9, '3-3': 1, '7-7': 1, '3-7': 1, '7-3': 1 }); done(); }); }); describe('Game.makeMove({ player: str, pos: { x: int, y: int } })', () => { - it('place move returns game object with proper board', done => { - Game().initGame().makeMove({ player: 'black', pos: { x: 4, y: 4 } }).legalMoves - .should.eql({ ...emptyBoard, '4-4': 1 }); - Game({ gameData: { handicap: 2 } }).initGame().makeMove({ player: 'white', pos: { x: 4, y: 4 } }).legalMoves - .should.eql({ ...emptyBoard, '4-16': 1, '16-4': 1, '4-4': -1 }); + it('makeMove returns game object with proper board', done => { + Game().initGame().makeMove({ player: 'black', pos: { x: 4, y: 4 } }) + .legalMoves.should.eql({ ...emptyBoard, '4-4': 1 }); + Game({ gameData: { handicap: 2 } }).initGame().makeMove({ player: 'white', pos: { x: 4, y: 4 } }) + .legalMoves.should.eql({ ...emptyBoard, '4-16': 1, '16-4': 1, '4-4': -1 }); done(); }); - it('make move returns success: false with move out of turn', done => { - Game().initGame().makeMove({ player: 'white', pos: { x: 4, y: 4 } }).success - .should.eql(false); - Game({ gameData: { handicap: 2 } }).initGame().makeMove({ player: 'black', pos: { x: 4, y: 4 } }).success - .should.eql(false); + it('makeMove returns success: false with move out of turn', done => { + Game().initGame().makeMove({ player: 'white', pos: { x: 4, y: 4 } }) + .success.should.eql(false); + Game({ gameData: { handicap: 2 } }).initGame().makeMove({ player: 'black', pos: { x: 4, y: 4 } }) + .success.should.eql(false); done(); }); -}); -// describe('Point.joinGroup() ') + it('makeMove returns success: false when move is at occupied point', done => { + Game({ gameData: { handicap: 2 } }).initGame().makeMove({ player: 'white', pos: { x: 4, y: 16 } }) + .success.should.eql(false); + done(); + }); + + it('makeMove next to adjacent stone of the same color joins stones as a group', done => { + const game = Game({ gameData: { handicap: 2 } }).initGame() // 4 3 4 + .makeMove({ player: 'white', pos: { x: 4, y: 4 } }) // 14 1 4 -1 -1 + .makeMove({ player: 'black', pos: { x: 4, y: 15 }}) // 15 1 5 -1 + .makeMove({ player: 'white', pos: { x: 3, y: 4 } }) // 16 1h + .makeMove({ player: 'black', pos: { x: 4, y: 14 }}) + .makeMove({ player: 'white', pos: { x: 4, y: 5 }}) + + const blackGroup = game.boardState['4-14'].group; + game.groups[blackGroup].has(game.boardState['4-14']).should.eql(true); + game.groups[blackGroup].has(game.boardState['4-15']).should.eql(true); + game.groups[blackGroup].has(game.boardState['4-16']).should.eql(true); + const whiteGroup = game.boardState['4-4'].group; + game.groups[whiteGroup].has(game.boardState['4-4']).should.eql(true); + game.groups[whiteGroup].has(game.boardState['3-4']).should.eql(true); + game.groups[whiteGroup].has(game.boardState['4-5']).should.eql(true); + done(); + }); + + it('makeMove next to adjacent stone of different color does not join stones as a group', done => { + const game = Game({ gameData: { handicap: 2 } }).initGame() // 3 4 + .makeMove({ player: 'white', pos: { x: 4, y: 15 } }) // 14 1 + .makeMove({ player: 'black', pos: { x: 4, y: 14 }}) // 15 1 -1 no groups + .makeMove({ player: 'white', pos: { x: 3, y: 16 } }) // 16 -1 1h + .makeMove({ player: 'black', pos: { x: 3, y: 15 }}) + + const hoshiGroup = game.boardState['4-16'].group; + game.groups[hoshiGroup].has(game.boardState['4-16']).should.eql(true); + game.groups[hoshiGroup].has(game.boardState['4-15']).should.eql(false); + game.groups[hoshiGroup].has(game.boardState['3-14']).should.eql(false); + game.groups[hoshiGroup].has(game.boardState['3-15']).should.eql(false); + done(); + }) + + // it('makeMove returns success: false when move is made in point with no liberties', done => { + // Game({ gameData: { handicap: 2 } }).initGame() + // .makeMove({ player: 'white', pos: { x: 4, y: 4 } }).makeMove({ player: 'black', pos: { x: 6, y: 16 } }) + // .makeMove({ player: 'white', pos: { x: 16, y: 16 }}).makeMove({ player: 'black', pos: { x: 5, y: 15 } }) + // .makeMove({ player: 'white', pos: { x: 16, y: 10 }}).makeMove({ player: 'black', pos: { x: 5, y: 17 } }) + // .makeMove({ player: 'white', pos: { x: 5, y: 16 }}) + // .success.should.eql(false); + // done(); + // }) +}); const initialMeta = { winner: null,