GameMachine.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // var Card = require('./Card.js');
  2. const SHA256 = require('crypto-js/sha256');
  3. // TODO test
  4. // Need to create an initialization move or something to get verified!
  5. // Players can have their names placed on state.players on init, showing turns and colors
  6. class GameMachine {
  7. constructor(setup) {
  8. this.state = {
  9. board: new Board(),
  10. players: [],
  11. hash: '0123456789',
  12. setup: setup,
  13. stacks: {
  14. moves: [],
  15. hashes: []
  16. }
  17. };
  18. this.setPlayers(setup.id);
  19. }
  20. getStack() {
  21. return {
  22. setup: this.state.setup,
  23. stack: {
  24. moves: this.state.stacks.moves.map((move) => move.export()),
  25. hashes: this.state.stacks.hashes
  26. }
  27. };
  28. }
  29. flush() {
  30. console.log(this.state);
  31. }
  32. setState(state) {
  33. this.state = { ...this.state, ...state};
  34. }
  35. setBoard(board) {
  36. this.state = { ...this.state, board};
  37. }
  38. save() {
  39. return {...this.state, board: this.state.board.save()};
  40. }
  41. load(state) {
  42. this.state.board.load(state.board);
  43. this.state = { ...state, board: this.state.board };
  44. }
  45. setPlayers(players) {
  46. this.state = { ...this.state, players};
  47. }
  48. getMyTeam(token) {
  49. return this.state.setup.id.indexOf(token);
  50. }
  51. getPlayerNumber(player) {
  52. return this.state.players[0] === player ? 0 : 1;
  53. // return this.state.players[0] === UserService.getUsername() ? 0 : 1;
  54. }
  55. getPositionTeam(x) {
  56. return this.state.players.indexOf(this.state.board.owners[x]);
  57. }
  58. hasFinished() {
  59. return this.state.stacks.hashes.length >= 10;
  60. }
  61. getWinner() {
  62. const score1 = this.state.board.getScore(this.state.players[0]);
  63. const score2 = this.state.board.getScore(this.state.players[1]) + 1;
  64. return score1 === score2 ? -1
  65. : (
  66. score1 > score2
  67. ? this.state.players[0]
  68. : this.state.players[1]
  69. );
  70. }
  71. isMyTurn(player) {
  72. let moves = this.state.stacks.hashes.length;
  73. return this.getPlayerNumber(player) === moves % 2 && moves < 10;
  74. }
  75. needFinalization() {
  76. return this.state.stacks.hashes.length === 9;
  77. }
  78. runMove(move) {
  79. const spray = SHA256(JSON.stringify(move)).toString();
  80. if (this.state.stacks.hashes.includes(spray)) {
  81. console.log('This move has been processed already');
  82. return;
  83. }
  84. try {
  85. move.verify(this.state);
  86. move.performMove(this.state.board);
  87. this.state.stacks.moves.push(move);
  88. this.state.stacks.hashes.push(spray);
  89. console.log(this.state.stacks);
  90. } catch (e) {
  91. throw e;
  92. }
  93. }
  94. ownerOf(x) {
  95. return this.state.board.owners[x];
  96. }
  97. runMatch(stack) {
  98. for (let i = 0; i < 9; i++) {
  99. this.runMove(stack[i]);
  100. }
  101. return this.getWinner();
  102. }
  103. }
  104. class Board {
  105. constructor() {
  106. this.data = [];
  107. this.owners = [];
  108. // These 2 to be updated after every move
  109. this.triggerPaths = []; // like attackVectors
  110. this.plusPaths = []; // like attackVectors
  111. for (let i=0; i < 9; i += 1) {
  112. this.triggerPaths[i] = [];
  113. this.plusPaths[i] = {
  114. winners: [],
  115. sums: []
  116. };
  117. }
  118. }
  119. getScore(player) {
  120. return this.owners.reduce((a,b) => b === player ? a + 1 : a, 0);
  121. }
  122. debug() {
  123. console.log('BOARD');
  124. console.log('---------------------------');
  125. console.log('Cards :');
  126. let s = '';
  127. for(let i=0,j=0;i<9;i+=1) {
  128. s += `P${this.owners[i]}:${(this.data[i] ? this.data[i].attack.reduce((a,b) => a + b + '|', '|') : 'Empty')}\t`;
  129. j += 1;
  130. if(j%3===0)s += '\n';
  131. }
  132. console.log(s);
  133. console.log('---------------------------');
  134. console.log('Triggers');
  135. console.log('---------------------------');
  136. s = '';
  137. for(let i=0,j=0;i<9;i+=1) {
  138. s += `[${this.triggerPaths[i]}]\t\t`;
  139. j += 1;
  140. if(j%3===0)s += '\n';
  141. }
  142. console.log(s);
  143. console.log('---------------------------');
  144. console.log('Pluspaths');
  145. console.log('---------------------------');
  146. s = '';
  147. for(let i=0,j=0;i<9;i+=1) {
  148. s += `[${(this.plusPaths[i].winners.reduce((a,b) => a + b + '|', '|'))}]\t\t`;
  149. j += 1;
  150. if(j%3===0)s += '\n';
  151. }
  152. console.log(s);
  153. console.log('---------------------------');
  154. }
  155. putCard(card, position, player) { // position = 0-9
  156. this.data[ position ] = card;
  157. this.owners[position] = player;
  158. this._calculatePlusAndTriggers(position);
  159. this._analyze(position);
  160. }
  161. _calculatePlusAndTriggers(position) {
  162. const attacker = this.data[position];
  163. for (let j = 0; j < 4; j += 1) {
  164. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  165. const dx = this._getDisplacement(j);
  166. const attackedCard = this.data[position + dx];
  167. const defendJ = ( j + 2 ) % 4;
  168. if (attackedCard) {
  169. const sum = attacker.attack[j] + attackedCard.attack[defendJ];
  170. this.plusPaths[ position ].sums[j] = sum;
  171. this.plusPaths[ position + dx].sums[defendJ] = sum;
  172. this.triggerPaths[ position ][j] = attacker.attack[j] - attackedCard.attack[defendJ];
  173. this.triggerPaths[position + dx][defendJ] = -this.triggerPaths[position][j]; // opposite to the above
  174. }
  175. }
  176. }
  177. _getDisplacement(j) {
  178. switch(j) {
  179. case 0: return 1; //return {x: 1, y:0};
  180. case 1: return -3; //return {x: 0, y:1};
  181. case 2: return -1; //return {x: -1, y:0};
  182. case 3: return 3; //return {x: 0, y:-1};
  183. default: throw Error ('Cannot _getDisplacement of this value: ' + j);
  184. }
  185. }
  186. _flipCard(position, combo, owner) {
  187. // change owner!
  188. let isFlipping = owner !== this.owners[position];
  189. this.owners[position] = owner;
  190. if(combo && isFlipping) this._analyze(position, combo);
  191. }
  192. _analyze(position, combo) {
  193. // check rules!
  194. if( this._checkSameRule(position) && !combo ) {
  195. // Apply Same Rule
  196. this._applySameRule(position);
  197. }
  198. if ( this._checkPlusRule(position) && !combo ) {
  199. // Apply Plus Rule
  200. this._applyPlusRule(position);
  201. }
  202. this._applyAttackRule(position, combo);
  203. }
  204. _applyAttackRule(position, combo = false) { //seems ok
  205. if ( !this.triggerPaths[position] ) return;
  206. for (let j = 0; j < 4; j += 1) {
  207. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  208. const dx = this._getDisplacement(j);
  209. if ( this.triggerPaths[position][j] > 0 ) {
  210. this._flipCard(position + dx, combo, this.owners[position]);
  211. }
  212. }
  213. }
  214. _applySameRule(position) {
  215. for (let j = 0; j < 4; j += 1) {
  216. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  217. const dx = this._getDisplacement(j);
  218. if ( this.triggerPaths[position][j] === 0 ) {
  219. this._flipCard(position + dx, true, this.owners[position]);
  220. }
  221. }
  222. }
  223. _applyPlusRule(position) {
  224. for (let j = 0; j < 4; j += 1) { // this for loop can be fixed seems obsolete & slow
  225. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  226. const dx = this._getDisplacement(j);
  227. if ( this.plusPaths[position].winners.includes(this.plusPaths[position].sums[j]) ) {
  228. this._flipCard(position + dx, true, this.owners[position]);
  229. }
  230. }
  231. }
  232. _checkSameRule(position) {
  233. let sames = 0;
  234. for (let j = 0; j < 4; j += 1) {
  235. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  236. if (this.triggerPaths[position] &&
  237. this.triggerPaths[position][j] === 0) {
  238. sames += 1;
  239. }
  240. }
  241. return sames > 1;
  242. }
  243. _checkPlusRule(position) { // refactor plz // need fix (problem double plus a.k.a. four side attack)
  244. let pluses = {};
  245. for (let j = 0; j < 4; j += 1) {
  246. if ( Board.ATTACK_VECTORS[position][j] === 0 ) continue;
  247. let sum = this.plusPaths[position].sums[j];
  248. if ( sum ) {
  249. pluses[sum] = (pluses[sum] || 0) + 1;
  250. }
  251. }
  252. for (let i in pluses) {
  253. if (pluses[i] > 1) {
  254. this.plusPaths[position].winners.push(parseInt(i, 10));
  255. }
  256. }
  257. return this.plusPaths[position].winners.length > 0;
  258. }
  259. isEmpty(position) {
  260. return !this.data[position];
  261. }
  262. save() {
  263. return {
  264. data: this.data,
  265. owners: this.owners
  266. };
  267. }
  268. load(state) {
  269. this.data = state.data;
  270. this.owners = state.owners;
  271. }
  272. }
  273. // START From top left goin row row
  274. Board.ATTACK_VECTORS = [
  275. // [R, U, L, D] // right up left down
  276. [1, 0, 0, 1],
  277. [1, 0, 1, 1],
  278. [0, 0, 1, 1],
  279. [1, 1, 0, 1],
  280. [1, 1, 1, 1],
  281. [0, 1, 1, 1],
  282. [1, 1, 0, 0],
  283. [1, 1, 1, 0],
  284. [0, 1, 1, 0]
  285. ];
  286. module.exports = GameMachine;