/server/class/GameValidator.class.php

https://github.com/russx2/tap-trap · PHP · 444 lines · 245 code · 144 blank · 55 comment · 79 complexity · 5567ac33eb2da9ec403326afccdc0d08 MD5 · raw file

  1. <?php
  2. require_once DIR_CLASS . '/MathRandom.class.php';
  3. class GameValidator {
  4. private $m_matrix;
  5. private $m_isValid;
  6. private $m_score;
  7. private $m_gotBonus;
  8. function __construct( $seed) {
  9. $this->m_isValid = false;
  10. $this->m_score = 0;
  11. $this->m_gotBonus = false;
  12. $this->initialise( $seed);
  13. }
  14. public function initialise( $seed) {
  15. $this->m_matrix = array();
  16. // initialise random number generator with game seed
  17. sjMathRandom::setSeed( $seed);
  18. $typeCount = 0;
  19. // initialise board matrix, evenly distributing tiles
  20. for( $x = 0; $x < BOARD_WIDTH; $x++) {
  21. for( $y = 0; $y < BOARD_HEIGHT; $y++) {
  22. if( $y == 0) {
  23. $this->m_matrix[$x] = array();
  24. }
  25. $typeCount = ( $typeCount + 1) % BOARD_NUM_TILES;
  26. $this->m_matrix[$x][$y] = $typeCount;
  27. }
  28. }
  29. // randomise tiles based on seed
  30. for( $x = 0; $x < BOARD_WIDTH; $x++) {
  31. for( $y = 0; $y < BOARD_HEIGHT; $y++) {
  32. $newX = sjMathRandom::getRandom( BOARD_WIDTH) - 1;
  33. $newY = sjMathRandom::getRandom( BOARD_HEIGHT) - 1;
  34. // swop tiles
  35. $typeA = $this->m_matrix[$x][$y];
  36. $typeB = $this->m_matrix[$newX][$newY];
  37. $this->m_matrix[$x][$y] = $typeB;
  38. $this->m_matrix[$newX][$newY] = $typeA;
  39. }
  40. }
  41. }
  42. public function processMoves( $moves) {
  43. // extract click data from string
  44. $movesIndividual = explode( '|', $moves);
  45. // process each move
  46. foreach( $movesIndividual AS $move) {
  47. $coords = explode( ',', $move);
  48. $clickX = (int)$coords[0];
  49. $clickY = (int)$coords[1];
  50. // check boundaries on coordinates
  51. if( $clickX < 0 || $clickX > BOARD_WIDTH - 1 ||
  52. $clickY < 0 || $clickY > BOARD_HEIGHT - 1) {
  53. // immediate failure - we're being fed some duff data here
  54. $this->m_isValid = false;
  55. return false;
  56. }
  57. // retrieve all tiles selected on this click
  58. $tileSelection = $this->getLinkedTiles( $clickX, $clickY);
  59. // add on score for this selection
  60. $this->m_score += $this->selectionScore( $tileSelection);
  61. // remove selection from board
  62. $this->selectionDestroy( $tileSelection);
  63. // alter the game board to reflect destroying selection
  64. $this->moveTiles( $tileSelection);
  65. }
  66. // all moves are processed so we're now checking that the game has actually
  67. // ended at this point
  68. if( $this->hasFurtherMoves()) {
  69. $this->m_isValid = false;
  70. return false;
  71. }
  72. // we have a valid game, now just check if they got the empty board bonus
  73. if( $this->isEmpty()) {
  74. $this->m_gotBonus = true;
  75. $this->m_score += BOARD_CLEAR_BONUS;
  76. }
  77. // game is valid
  78. $this->m_isValid = true;
  79. }
  80. private function selectionScore( $currentSelection) {
  81. // no selection? no score
  82. if( count( $currentSelection) === 0) {
  83. return 0;
  84. }
  85. $numTiles = count( $currentSelection);
  86. // no score for 2 tiles or less
  87. if( $numTiles < 3) {
  88. return 0;
  89. }
  90. return ($numTiles - 2) * ($numTiles - 2);
  91. }
  92. public function isValid() {
  93. return $this->m_isValid;
  94. }
  95. public function getScore() {
  96. return $this->m_score;
  97. }
  98. public function gotBonus() {
  99. return $this->m_gotBonus;
  100. }
  101. private function getLinkedTiles( $x, $y) {
  102. $linkedTiles = array();
  103. $typeID = $this->m_matrix[$x][$y];
  104. $this->__getLinkedTiles( $typeID, $x, $y, $linkedTiles);
  105. return $linkedTiles;
  106. }
  107. private function __getLinkedTiles( $typeID, $x, $y, &$results) {
  108. // check if we've visited this node before - if we have,
  109. // return immediately
  110. for( $i = 0; $i < count( $results); $i++) {
  111. if( $results[$i]['x'] == $x && $results[$i]['y'] == $y) {
  112. return;
  113. }
  114. }
  115. // is this tile the correct type? -- REQUIRED?
  116. if( $this->m_matrix[$x][$y] != $typeID) {
  117. return;
  118. }
  119. // now make sure we now add this tile as a match (store reference)
  120. $res = array();
  121. $res['x'] = $x;
  122. $res['y'] = $y;
  123. $results[] = $res;
  124. // check above
  125. if( ( $y > 0) &&
  126. ( $this->m_matrix[$x][$y-1] !== null) &&
  127. ( $this->m_matrix[$x][$y-1] == $typeID)) {
  128. $this->__getLinkedTiles( $typeID, $x, $y - 1, $results);
  129. }
  130. // check below
  131. if( ( $y < (BOARD_HEIGHT - 1)) &&
  132. ( $this->m_matrix[$x][$y+1] !== null) &&
  133. ( $this->m_matrix[$x][$y+1] == $typeID)) {
  134. $this->__getLinkedTiles( $typeID, $x, $y + 1, $results);
  135. }
  136. // check left
  137. if( ( $x > 0) &&
  138. ( $this->m_matrix[$x-1][$y] !== null) &&
  139. ( $this->m_matrix[$x-1][$y] == $typeID)) {
  140. $this->__getLinkedTiles( $typeID, $x - 1, $y, $results);
  141. }
  142. // check right
  143. if( ( $x < (BOARD_WIDTH - 1)) &&
  144. ( $this->m_matrix[$x+1][$y] !== null) &&
  145. ( $this->m_matrix[$x+1][$y] == $typeID)) {
  146. $this->__getLinkedTiles( $typeID, $x + 1, $y, $results);
  147. }
  148. }
  149. private function selectionDestroy( $currentSelection) {
  150. // if there is no current selection, ignore
  151. if( count( $currentSelection) == 0) {
  152. return;
  153. }
  154. // remove all tiles in the selection
  155. for( $i = 0; $i < count( $currentSelection); $i++) {
  156. $x = $currentSelection[$i]['x'];
  157. $y = $currentSelection[$i]['y'];
  158. $this->m_matrix[$x][$y] = null;
  159. }
  160. }
  161. private function moveTiles( $currentSelection) {
  162. // if there is no current selection, ignore
  163. if( count( $currentSelection) < 2) {
  164. return;
  165. }
  166. // shift all tiles that are now hovering above an empty cell
  167. //foreach( $currentSelection AS $i2) {
  168. for( $i = 0; $i < count( $currentSelection); $i++) {
  169. $x = $currentSelection[$i]['x'];
  170. $y = $currentSelection[$i]['y'];
  171. // not top row
  172. if( $y > 0) {
  173. // move items above down a row
  174. $this->shiftColumnDown( $x, $y - 1, 1);
  175. }
  176. // the tricky bit - now we need to make sure that any selected
  177. // tiles (that are below this one) are referenced correctly.
  178. // fix all references to this column in selected tiles
  179. foreach( $currentSelection AS $key => $coord) {
  180. // 'fix' any stored references to tiles (the y may be wrong now)
  181. if( $coord['x'] == $x &&
  182. $coord['y'] <= $y &&
  183. $coord['y'] != BOARD_HEIGHT - 1) {
  184. $currentSelection[$key]['y']++;
  185. }
  186. }
  187. }
  188. // now check for blank columns - and shift later columns left
  189. $y = BOARD_HEIGHT - 1;
  190. for( $x = BOARD_WIDTH - 1; $x > 0; $x--) {
  191. while( $this->m_matrix[$x - 1][$y] === null && $this->m_matrix[$x][$y] !== null) {
  192. $this->shiftColumnLeft( $x);
  193. }
  194. }
  195. }
  196. private function shiftColumnDown( $x, $startingAtY, $numMoves) {
  197. // make sure we're not trying to shift out of the array!
  198. if( $startingAtY > ( BOARD_HEIGHT - 1 - $numMoves)) {
  199. return;
  200. }
  201. // sequentially shift each tile down by the number of moves requested
  202. for( $y = $startingAtY; $y >= 0; $y--) {
  203. $tile = $this->m_matrix[$x][$y];
  204. // move tile down
  205. $this->m_matrix[$x][$y + $numMoves] = $tile;
  206. $this->m_matrix[$x][$y] = null;
  207. }
  208. }
  209. private function shiftColumnLeft( $startingAtX) {
  210. // can't shift off the board!
  211. if( $startingAtX === 0) {
  212. return;
  213. }
  214. for( $x = $startingAtX; $x < BOARD_WIDTH; $x++) {
  215. for( $y = 0; $y < BOARD_HEIGHT; $y++) {
  216. $tile = $this->m_matrix[$x][$y];
  217. // move tile left
  218. $this->m_matrix[$x - 1][$y] = $tile;
  219. $this->m_matrix[$x][$y] = null;
  220. }
  221. }
  222. }
  223. private function isEmpty() {
  224. // check bottom left tile - if this is empty, the board must be empty
  225. if( $this->m_matrix[0][BOARD_HEIGHT - 1] === null) {
  226. return true;
  227. }
  228. return false;
  229. }
  230. private function hasFurtherMoves() {
  231. // if it's an empty board, can't be any further moves
  232. if( $this->isEmpty()) {
  233. return false;
  234. }
  235. // check horizontal pairs (starting at bottom left for efficiency since we're most likely to
  236. // find a match starting here due to how the tiles fall and slide left)
  237. for( $y = BOARD_HEIGHT - 1; $y >= 0; $y--) {
  238. for( $x = 0; $x < BOARD_WIDTH - 1; $x++) {
  239. $tileA = $this->m_matrix[$x][$y];
  240. $tileB = $this->m_matrix[$x+1][$y];
  241. // if this or the next tile is null, skip
  242. if( $tileA === null || $tileB === null) {
  243. continue;
  244. }
  245. // if the two tiles are the same type, we can return true immediately
  246. if( $tileA === $tileB) {
  247. return true;
  248. }
  249. }
  250. }
  251. // check vertical pairs (starting bottom left)
  252. for( $x = 0; $x < BOARD_WIDTH; $x++) {
  253. for( $y = BOARD_HEIGHT - 1; $y > 0; $y--) {
  254. $tileA = $this->m_matrix[$x][$y];
  255. $tileB = $this->m_matrix[$x][$y-1];
  256. // if this or the next tile is null, we can break out of this loop since tiles
  257. // can't hover!
  258. if( $tileA === null || $tileB === null) {
  259. break;
  260. }
  261. // if the two tiles are the same type, we can return true immediately
  262. if( $tileA === $tileB) {
  263. return true;
  264. }
  265. }
  266. }
  267. // nothing found - board is frozen!
  268. return false;
  269. }
  270. public function __debugDrawBoard() {
  271. // debug
  272. echo '<br /><table border="1">';
  273. for( $y = 0; $y < BOARD_HEIGHT; $y++) {
  274. for( $x = 0; $x < BOARD_WIDTH; $x++) {
  275. if( $x == 0) {
  276. echo '<tr>';
  277. }
  278. $num = $this->m_matrix[$x][$y];
  279. if( $num == 0) {
  280. $colour = 'blue';
  281. }
  282. else if( $num == 1) {
  283. $colour = 'red';
  284. }
  285. else if( $num == 2){
  286. $colour = 'green';
  287. }
  288. else if( $num == null){
  289. $colour = 'white';
  290. }
  291. echo '<td bgcolor="'.$colour.'"><div style="width: 5px; height: 5px;"></div></td>';
  292. if( $x == (BOARD_WIDTH - 1)) {
  293. echo '</tr>';
  294. }
  295. }
  296. }
  297. echo '</table>';
  298. }
  299. }
  300. ?>