context.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. /**
  2. * See LICENSE file.
  3. *
  4. * Game model..
  5. */
  6. (function() {
  7. HN.GameModes= {
  8. classic: {
  9. fixed_table_size: true,
  10. rearrange_on_remove:true,
  11. rows_initial: 8,
  12. columns_initial: 8,
  13. rows_max: 8,
  14. columns_max: 8,
  15. time_policy: -500,
  16. minTurnTime: 12000,
  17. number_policy: [10,10,10,15,15,15,20,20,25,30,35,40,45,50],
  18. name: 'classic'
  19. },
  20. progressive : {
  21. fixed_table_size: false,
  22. rearrange_on_remove:true,
  23. rows_initial: 3,
  24. columns_initial: 3,
  25. rows_max: 8,
  26. columns_max: 8,
  27. time_policy: 0,
  28. number_policy: [10,10,10,10,10,15,15,15,15,20,25,30,35,40,45,50],
  29. name: 'progressive'
  30. },
  31. respawn : {
  32. fixed_table_size: true,
  33. rearrange_on_remove:true,
  34. respawn: true,
  35. respawn_time: 22000,
  36. rows_initial: 8,
  37. columns_initial: 8,
  38. rows_max: 8,
  39. columns_max: 8,
  40. time_policy: 500,
  41. minTurnTime: 8000,
  42. initial_map: [
  43. [0,0,0,0,0,0,0,0],
  44. [0,0,0,0,0,0,0,0],
  45. [0,0,0,0,0,0,0,0],
  46. [0,0,0,0,0,0,0,0],
  47. [0,0,0,1,1,0,0,0],
  48. [0,0,1,1,1,1,0,0],
  49. [0,1,1,1,1,1,1,0],
  50. [1,1,1,1,1,1,1,1]
  51. ],
  52. number_policy: [10,10,10,10,10,15,15,15,15,20,25,30,35,40,45,50],
  53. name: 'respawn'
  54. }
  55. }
  56. })();
  57. (function() {
  58. HN.Brick= function() {
  59. return this;
  60. };
  61. HN.Brick.prototype= {
  62. value: 0,
  63. color: 0,
  64. selected: false,
  65. removed: false,
  66. row: 0,
  67. column: 0,
  68. context: null,
  69. delegate: null,
  70. /**
  71. *
  72. * @param row
  73. * @param column
  74. * @param context the HN.Context instance
  75. */
  76. initialize : function(row, column, context, removed) {
  77. removed= removed || false;
  78. this.row= row;
  79. this.column= column;
  80. this.selected= false;
  81. this.removed= removed;
  82. this.color= (Math.random()*context.getNumberColors())>>0;
  83. this.context= context;
  84. this.respawn();
  85. },
  86. changeSelection : function() {
  87. // prevent brick selection while bricks are flying in.
  88. if ( this.context.status!==this.context.ST_RUNNNING ) {
  89. return;
  90. }
  91. this.selected= !this.selected;
  92. this.context.selectionChanged(this);
  93. },
  94. respawn : function() {
  95. this.selected= false;
  96. // favorecer los numeros 3..9
  97. if ( Math.random()>.3 ) {
  98. this.value= 4 + (Math.random()*6)>>0;
  99. } else {
  100. this.value= 1 + (Math.random()*3)>>0;
  101. }
  102. if ( this.value<1 ) {
  103. this.value=1;
  104. } else if ( this.value>9 ) {
  105. this.value=9;
  106. }
  107. if ( null!=this.delegate ) {
  108. this.delegate();
  109. }
  110. return this;
  111. }
  112. };
  113. })();
  114. (function() {
  115. HN.Context= function() {
  116. this.eventListener= [];
  117. return this;
  118. };
  119. HN.Context.prototype= {
  120. eventListener: null, // context listeners
  121. gameMode: null,
  122. rows: 0, // model size in
  123. columns: 0, // rows x columns
  124. numNumberColors:0,
  125. initialRows: 0,
  126. initialColumns: 0,
  127. currentRows: 0,
  128. currentColumns: 0,
  129. /**
  130. * Numero inicial de ladrillos activos en el nivel.
  131. * Se puede especificar un mapa de ladrillos activos a traves del gameMode.
  132. * Como no tiene porque coincidir con todos los ladrillos de initialRows*initialColumns,
  133. * necesito contarlos porque el juego no progresa de la animaci—n de entrada de ladrillos
  134. * volando hasta que todos llegan a su sitio.
  135. */
  136. initialBricks: 0,
  137. data: null, // context model. Bricks.
  138. guessNumber: 0, // number to sum up with bricks.
  139. time: 0, // maximum time to take to guess an adding number sequence.
  140. selectedList: null, // selected bricks.
  141. status: 0, // <-- control logic -->
  142. level: 0,
  143. score: 0, // game points.
  144. turnTime: 15000,
  145. turnTimes: [20000, 15000, 10000],
  146. difficulty: 0, // 0: easy, 1: hard, 2: hardcore.
  147. brickIncrementByDifficulty: [5,10],
  148. meters: 0,
  149. ST_STARTGAME: 5,
  150. ST_INITIALIZING: 0,
  151. ST_START_LEVEL: 2,
  152. ST_RUNNNING: 1,
  153. ST_LEVEL_RESULT: 3,
  154. ST_ENDGAME: 4,
  155. /**
  156. * Called once on game startup.
  157. *
  158. * @return nothing.
  159. */
  160. create : function( maxR, maxC, numNumberColors ) {
  161. this.rows= maxR;
  162. this.columns= maxC;
  163. this.numNumberColors= numNumberColors;
  164. this.data= [];
  165. var i,j;
  166. for( i=0; i<this.rows; i++ ) {
  167. this.data.push( [] );
  168. for( j=0; j<this.columns; j++ ) {
  169. this.data[i].push( new HN.Brick() );
  170. }
  171. }
  172. return this;
  173. },
  174. setGameMode : function( gameMode) {
  175. if ( gameMode!=this.gameMode ) {
  176. this.gameMode= gameMode;
  177. this.initialRows= gameMode.rows_initial;
  178. this.initialColumns= gameMode.columns_initial;
  179. }
  180. this.initialize();
  181. },
  182. getNumberColors : function() {
  183. return this.numNumberColors;
  184. },
  185. initialize : function() {
  186. this.setStatus( this.ST_STARTGAME );
  187. this.turnTime= this.turnTimes[this.difficulty];
  188. this.score=0;
  189. this.level=0;
  190. this.setAltitude(0);
  191. this.currentRows= this.initialRows;
  192. this.currentColumns= this.initialColumns;
  193. this.nextLevel();
  194. return this;
  195. },
  196. getLevelActiveBricks : function() {
  197. return this.initialBricks;
  198. },
  199. prepareBricks : function() {
  200. var i,j;
  201. for( i=0; i<this.rows; i++ ) {
  202. for( j=0; j<this.columns; j++ ) {
  203. this.data[i][j].initialize(i,j,this,true);
  204. }
  205. }
  206. if ( this.gameMode.initial_map ) {
  207. var im= this.gameMode.initial_map;
  208. this.initialBricks=0;
  209. for( i=0; i<this.currentRows; i++ ) {
  210. for( j=0; j<this.currentColumns; j++ ) {
  211. var removed= true;
  212. if ( im.length<i ) {
  213. removed= false;
  214. } else {
  215. if ( im[i].length<j ) {
  216. removed= false;
  217. } else {
  218. removed= im[i][j]==0;
  219. }
  220. }
  221. this.data[i][j].initialize(i,j,this,removed);
  222. if (!removed) {
  223. this.initialBricks++;
  224. }
  225. }
  226. }
  227. } else {
  228. this.initialBricks= this.currentRows*this.currentColumns;
  229. for( i=0; i<this.currentRows; i++ ) {
  230. for( j=0; j<this.currentColumns; j++ ) {
  231. this.data[i][j].initialize(i,j,this,false);
  232. }
  233. }
  234. }
  235. },
  236. nextLevel : function() {
  237. this.level++;
  238. this.fireEvent('context','levelchange',this.level);
  239. this.selectedList= [];
  240. // not fixed size.
  241. // add one column/row alternatively until reaching rows/columsn size.
  242. if ( !this.gameMode.fixed_table_size ) {
  243. if ( this.level>1 && (this.currentRows<this.rows || this.currentColumns<this.columns )) {
  244. if ( this.currentRows==this.currentColumns ) {
  245. this.currentColumns++;
  246. } else {
  247. this.currentRows++;
  248. }
  249. }
  250. }
  251. this.prepareBricks();
  252. this.setStatus( this.ST_INITIALIZING );
  253. if ( this.level>1 ) {
  254. // 1 seconds less each level.
  255. this.turnTime-= this.gameMode.time_policy;
  256. if ( this.gameMode.minTurnTime ) {
  257. if ( this.turnTime<this.gameMode.minTurnTime ) {
  258. this.turnTime= this.gameMode.minTurnTime;
  259. }
  260. }
  261. }
  262. return this;
  263. },
  264. /**
  265. * Notify listeners of a context event
  266. * @param sSource event source object
  267. * @param sEvent an string indicating the event type
  268. * @param params an object with event parameters. Each event type will have its own parameter set.
  269. */
  270. fireEvent : function( sSource, sEvent, params ) {
  271. var i;
  272. for( i=0; i<this.eventListener.length; i++ ) {
  273. this.eventListener[i].contextEvent( {
  274. source: sSource,
  275. event: sEvent,
  276. params: params
  277. });
  278. }
  279. },
  280. addContextListener : function( listener ) {
  281. this.eventListener.push(listener);
  282. return this;
  283. },
  284. getBrick : function( row, column ) {
  285. return this.data[row][column];
  286. },
  287. setStatus : function( status ) {
  288. this.status= status;
  289. this.fireEvent( 'context', 'status', this.status );
  290. if ( this.status==this.ST_RUNNNING ) {
  291. this.setGuessNumber();
  292. }
  293. },
  294. selectionChanged : function(brick) {
  295. // si ya estaba en la lista de seleccionados, quitarlo.
  296. var i,j;
  297. for( i=0; i<this.selectedList.length; i++ ) {
  298. // esta en la lista.
  299. // eliminar y salir del metodo
  300. if ( this.selectedList[i]==brick ) {
  301. this.selectedList.splice( i, 1 );
  302. this.fireEvent('brick','selection',brick);
  303. return;
  304. }
  305. }
  306. // chequear que la suma de los elementos seleccionados es igual al numero magico.
  307. var sum=0;
  308. for( i=0; i<this.selectedList.length; i++ ) {
  309. sum+= this.selectedList[i].value;
  310. }
  311. sum+= brick.value;
  312. var selected;
  313. if ( sum>this.guessNumber ) {
  314. brick.selected= false;
  315. selected= this.selectedList.slice(0);
  316. for( i=0; i<this.selectedList.length; i++ ) {
  317. this.selectedList[i].selected= false;
  318. }
  319. this.selectedList= [];
  320. // quitar marca de seleccion al ladrillo.
  321. this.fireEvent('brick','selectionoverflow', selected );
  322. } else if ( sum==this.guessNumber ) {
  323. this.selectedList.push(brick);
  324. selected= this.selectedList.slice(0);
  325. for( i=0; i<this.selectedList.length; i++ ) {
  326. this.selectedList[i].selected= false;
  327. this.selectedList[i].removed= true;
  328. }
  329. // rearrange bricks if needed
  330. if ( this.gameMode.rearrange_on_remove ) {
  331. for( i=0; i<this.selectedList.length; i++ ) {
  332. var r= this.selectedList[i].row;
  333. var c= this.selectedList[i].column;
  334. // bajar todos los elementos de columna una posicion.
  335. for( var row= r; row>0; row-- ) {
  336. var move= this.data[row-1][c];
  337. var to= this.data[row][c];
  338. var tmp= move;
  339. this.data[row-1][c]= this.data[row][c];
  340. this.data[row][c]= tmp;
  341. // cambiar row del brick. la columna es la misma
  342. tmp= move.row;
  343. move.row= to.row;
  344. to.row= tmp;
  345. this.fireEvent(
  346. 'brick',
  347. 'rearranged',
  348. {
  349. fromRow : move.row-1,
  350. toRow: move.row,
  351. column: c
  352. });
  353. }
  354. }
  355. }
  356. this.selectedList= [];
  357. this.fireEvent('brick','selection-cleared', selected );
  358. this.score+= this.multiplier * ((selected.length+1)*(this.difficulty==0?10:20))*selected.length;
  359. this.fireEvent('context','score',this);
  360. for( i=0; i<this.rows; i++ ) {
  361. for( j=0; j<this.columns; j++ ) {
  362. if ( !this.data[i][j].removed ) {
  363. this.setGuessNumber();
  364. return;
  365. }
  366. }
  367. }
  368. this.setStatus( this.ST_LEVEL_RESULT );
  369. } else {
  370. // todavia podemos sumar numeros.
  371. this.selectedList.push(brick);
  372. this.fireEvent('brick','selection',brick);
  373. this.setMultipliers();
  374. }
  375. },
  376. setGuessNumber : function() {
  377. // first get all available board numbers.
  378. var activeBricks= [];
  379. var i,j;
  380. for( i=0; i<this.rows; i++ ) {
  381. for( j=0; j<this.columns; j++ ) {
  382. if ( !this.data[i][j].removed ) {
  383. activeBricks.push(this.data[i][j]);
  384. }
  385. }
  386. }
  387. // scramble elements.
  388. if ( activeBricks.length>1 ) {
  389. for( i=0; i<activeBricks.length; i++ ) {
  390. var rpos0= (Math.random()*activeBricks.length)>>0;
  391. var tmp= activeBricks[i];
  392. activeBricks[i]= activeBricks[rpos0];
  393. activeBricks[rpos0]= tmp;
  394. }
  395. }
  396. /**
  397. * tenemos que estar seguros que el numero ofrecido al player debe estar entre:
  398. * 10-15 15-20 20-25 ... (facil)
  399. * 10-20 20-30 30-40 ... (dificil)
  400. */
  401. var sum=0;
  402. var diff= this.brickIncrementByDifficulty[this.difficulty];
  403. //var min= 10 + (this.level-1)*diff;
  404. var index__= this.level-1;
  405. if ( index__>=this.gameMode.number_policy.length ) {
  406. index__= this.gameMode.number_policy.length-1;
  407. }
  408. var min= this.gameMode.number_policy[index__];
  409. var max= min+diff;
  410. var brickCount=0;
  411. if ( activeBricks.length==1 ) {
  412. sum= activeBricks[0].value;
  413. } else if ( activeBricks.length==2 ) {
  414. sum= activeBricks[0].value+activeBricks[1].value;
  415. } else {
  416. for( i=0; i<activeBricks.length; i++ ) {
  417. if ( sum+activeBricks[i].value<=max ) {
  418. sum+= activeBricks[i].value;
  419. brickCount++;
  420. } else {
  421. if ( sum>min ) {
  422. break;
  423. }
  424. }
  425. }
  426. if ( brickCount==1 ) {
  427. sum= activeBricks[0].value+activeBricks[1].value;
  428. }
  429. }
  430. this.guessNumber= sum;
  431. this.fireEvent( 'context','guessnumber',this );
  432. this.setMultipliers();
  433. },
  434. timeUp : function() {
  435. this.setStatus( this.ST_ENDGAME );
  436. },
  437. respawn : function() {
  438. // comprobar que podemos meter nuevos elementos.
  439. var cabenMas= true;
  440. var i,j;
  441. for( i=0; i<this.currentColumns; i++ ) {
  442. // una columna est‡ llena. no seguir.
  443. if ( !this.data[0][i].removed ) {
  444. cabenMas= false;
  445. break;
  446. }
  447. }
  448. if (!cabenMas) {
  449. this.setStatus( this.ST_ENDGAME );
  450. return;
  451. }
  452. var respawnData= [];
  453. // meter una nueva fila de numeros.
  454. for( j=0; j<this.currentColumns; j++ ) {
  455. // buscar la fila donde cae el numero
  456. for( i=0; i<this.currentRows; i++ ) {
  457. if ( !this.data[i][j].removed ) {
  458. break;
  459. }
  460. }
  461. // i tiene la fila con el ultimo elemento valido
  462. i--;
  463. this.data[i][j].removed= false;
  464. this.data[i][j].selected= false;
  465. this.data[i][j].respawn();
  466. respawnData.push( {
  467. row: i,
  468. column: j
  469. } );
  470. }
  471. this.fireEvent('brick','respawn',respawnData);
  472. },
  473. /**
  474. * establece multiplicadores de puntos en funcion de:
  475. * + numero de ladridllos
  476. * + distancia total entre ladrillos
  477. */
  478. setMultipliers : function() {
  479. if ( this.selectedList && this.selectedList.length>0 ) {
  480. var x0= this.selectedList[0].column;
  481. var y0= this.selectedList[0].row;
  482. var d= 0;
  483. var i;
  484. for( i=1; i<this.selectedList.length; i++ ) {
  485. var x1= this.selectedList[i].column;
  486. var y1= this.selectedList[i].row;
  487. d+= Math.sqrt( (x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) );
  488. x0= x1;
  489. y0= y1;
  490. }
  491. d= d>>0;
  492. d= 1+ (d/10)>>0;
  493. if ( d<1 ) {
  494. d=1;
  495. } else if ( d>5 ) {
  496. d=5;
  497. }
  498. this.multiplier= d;
  499. } else {
  500. this.multiplier= 0;
  501. }
  502. this.fireEvent('context','multiplier',this);
  503. },
  504. incrementAltitude : function( increment ) {
  505. this.setAltitude( this.meters+increment );
  506. },
  507. setAltitude : function( altitude ) {
  508. this.meters= altitude;
  509. this.fireEvent( 'context','altitude',this.meters);
  510. }
  511. };
  512. })();