// var Card = require('./Card.js'); const SHA256 = require('crypto-js/sha256'); // TODO test // Need to create an initialization move or something to get verified! // Players can have their names placed on state.players on init, showing turns and colors class GameMachine { constructor(setup) { this.state = { board: new Board(), players: [], hash: '0123456789', setup: setup, stacks: { moves: [], hashes: [] } }; this.setPlayers(setup.id); } getStack() { return { setup: this.state.setup, stack: { moves: this.state.stacks.moves.map((move) => move.export()), hashes: this.state.stacks.hashes } }; } flush() { console.log(this.state); } setState(state) { this.state = { ...this.state, ...state}; } setBoard(board) { this.state = { ...this.state, board}; } save() { return {...this.state, board: this.state.board.save()}; } load(state) { this.state.board.load(state.board); this.state = { ...state, board: this.state.board }; } setPlayers(players) { this.state = { ...this.state, players}; } getMyTeam(token) { return this.state.setup.id.indexOf(token); } getPlayerNumber(player) { return this.state.players[0] === player ? 0 : 1; // return this.state.players[0] === UserService.getUsername() ? 0 : 1; } getPositionTeam(x) { return this.state.players.indexOf(this.state.board.owners[x]); } hasFinished() { return this.state.stacks.hashes.length >= 10; } getWinner() { const score1 = this.state.board.getScore(this.state.players[0]); const score2 = this.state.board.getScore(this.state.players[1]) + 1; return score1 === score2 ? -1 : ( score1 > score2 ? this.state.players[0] : this.state.players[1] ); } isMyTurn(player) { let moves = this.state.stacks.hashes.length; return this.getPlayerNumber(player) === moves % 2 && moves < 10; } needFinalization() { return this.state.stacks.hashes.length === 9; } runMove(move) { const spray = SHA256(JSON.stringify(move)).toString(); if (this.state.stacks.hashes.includes(spray)) { console.log('This move has been processed already'); return; } try { move.verify(this.state); move.performMove(this.state.board); this.state.stacks.moves.push(move); this.state.stacks.hashes.push(spray); console.log(this.state.stacks); } catch (e) { throw e; } } ownerOf(x) { return this.state.board.owners[x]; } runMatch(stack) { for (let i = 0; i < 9; i++) { this.runMove(stack[i]); } return this.getWinner(); } } class Board { constructor() { this.data = []; this.owners = []; // These 2 to be updated after every move this.triggerPaths = []; // like attackVectors this.plusPaths = []; // like attackVectors for (let i=0; i < 9; i += 1) { this.triggerPaths[i] = []; this.plusPaths[i] = { winners: [], sums: [] }; } } getScore(player) { return this.owners.reduce((a,b) => b === player ? a + 1 : a, 0); } debug() { console.log('BOARD'); console.log('---------------------------'); console.log('Cards :'); let s = ''; for(let i=0,j=0;i<9;i+=1) { s += `P${this.owners[i]}:${(this.data[i] ? this.data[i].attack.reduce((a,b) => a + b + '|', '|') : 'Empty')}\t`; j += 1; if(j%3===0)s += '\n'; } console.log(s); console.log('---------------------------'); console.log('Triggers'); console.log('---------------------------'); s = ''; for(let i=0,j=0;i<9;i+=1) { s += `[${this.triggerPaths[i]}]\t\t`; j += 1; if(j%3===0)s += '\n'; } console.log(s); console.log('---------------------------'); console.log('Pluspaths'); console.log('---------------------------'); s = ''; for(let i=0,j=0;i<9;i+=1) { s += `[${(this.plusPaths[i].winners.reduce((a,b) => a + b + '|', '|'))}]\t\t`; j += 1; if(j%3===0)s += '\n'; } console.log(s); console.log('---------------------------'); } putCard(card, position, player) { // position = 0-9 this.data[ position ] = card; this.owners[position] = player; this._calculatePlusAndTriggers(position); this._analyze(position); } _calculatePlusAndTriggers(position) { const attacker = this.data[position]; for (let j = 0; j < 4; j += 1) { if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; const dx = this._getDisplacement(j); const attackedCard = this.data[position + dx]; const defendJ = ( j + 2 ) % 4; if (attackedCard) { const sum = attacker.attack[j] + attackedCard.attack[defendJ]; this.plusPaths[ position ].sums[j] = sum; this.plusPaths[ position + dx].sums[defendJ] = sum; this.triggerPaths[ position ][j] = attacker.attack[j] - attackedCard.attack[defendJ]; this.triggerPaths[position + dx][defendJ] = -this.triggerPaths[position][j]; // opposite to the above } } } _getDisplacement(j) { switch(j) { case 0: return 1; //return {x: 1, y:0}; case 1: return -3; //return {x: 0, y:1}; case 2: return -1; //return {x: -1, y:0}; case 3: return 3; //return {x: 0, y:-1}; default: throw Error ('Cannot _getDisplacement of this value: ' + j); } } _flipCard(position, combo, owner) { // change owner! let isFlipping = owner !== this.owners[position]; this.owners[position] = owner; if(combo && isFlipping) this._analyze(position, combo); } _analyze(position, combo) { // check rules! if( this._checkSameRule(position) && !combo ) { // Apply Same Rule this._applySameRule(position); } if ( this._checkPlusRule(position) && !combo ) { // Apply Plus Rule this._applyPlusRule(position); } this._applyAttackRule(position, combo); } _applyAttackRule(position, combo = false) { //seems ok if ( !this.triggerPaths[position] ) return; for (let j = 0; j < 4; j += 1) { if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; const dx = this._getDisplacement(j); if ( this.triggerPaths[position][j] > 0 ) { this._flipCard(position + dx, combo, this.owners[position]); } } } _applySameRule(position) { for (let j = 0; j < 4; j += 1) { if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; const dx = this._getDisplacement(j); if ( this.triggerPaths[position][j] === 0 ) { this._flipCard(position + dx, true, this.owners[position]); } } } _applyPlusRule(position) { for (let j = 0; j < 4; j += 1) { // this for loop can be fixed seems obsolete & slow if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; const dx = this._getDisplacement(j); if ( this.plusPaths[position].winners.includes(this.plusPaths[position].sums[j]) ) { this._flipCard(position + dx, true, this.owners[position]); } } } _checkSameRule(position) { let sames = 0; for (let j = 0; j < 4; j += 1) { if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; if (this.triggerPaths[position] && this.triggerPaths[position][j] === 0) { sames += 1; } } return sames > 1; } _checkPlusRule(position) { // refactor plz // need fix (problem double plus a.k.a. four side attack) let pluses = {}; for (let j = 0; j < 4; j += 1) { if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue; let sum = this.plusPaths[position].sums[j]; if ( sum ) { pluses[sum] = (pluses[sum] || 0) + 1; } } for (let i in pluses) { if (pluses[i] > 1) { this.plusPaths[position].winners.push(parseInt(i, 10)); } } return this.plusPaths[position].winners.length > 0; } isEmpty(position) { return !this.data[position]; } save() { return { data: this.data, owners: this.owners }; } load(state) { this.data = state.data; this.owners = state.owners; } } // START From top left goin row row Board.ATTACK_VECTORS = [ // [R, U, L, D] // right up left down [1, 0, 0, 1], [1, 0, 1, 1], [0, 0, 1, 1], [1, 1, 0, 1], [1, 1, 1, 1], [0, 1, 1, 1], [1, 1, 0, 0], [1, 1, 1, 0], [0, 1, 1, 0] ]; module.exports = GameMachine;