pacman-core.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. /*
  2. Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
  3. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  4. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  5. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6. */
  7. var Game = {
  8. id : null,
  9. type : "offline",
  10. player : 1,
  11. PACMAN_START_X : 14 * TILE_SIZE,
  12. PACMAN_START_Y : 24 * TILE_SIZE,
  13. GHOST_STATE_CHASE : 1,
  14. GHOST_STATE_SCATTER : 2,
  15. GHOST_STATE_FRIGHTENED : 3,
  16. GHOST_STATE_IN_JAIL : 4,
  17. GHOST_STATE_EATEN : 5,
  18. GHOST_STATE_FRIGHTENED_BLINK : 6,
  19. GHOST_EVENT_CHASE : "ghost_event_chase",
  20. GHOST_EVENT_SCATTER : "ghost_event_scatter",
  21. GHOST_EVENT_DOT_EATEN : "ghost_event_dot_eaten",
  22. DOT_POINTS : 10,
  23. BIG_DOT_POINTS : 50,
  24. totalDots : 0,
  25. dots : {},
  26. timer : null,
  27. frightTimer : null,
  28. bonusTimer : null,
  29. level : -1,
  30. levelData : null,
  31. step : 0,
  32. score : 0,
  33. eatenDots : 0,
  34. lives : 3,
  35. running : false,
  36. mode : 2, // Game.GHOST_STATE_SCATTER
  37. frightMode : false,
  38. eaten: 0,
  39. pacman : null,
  40. miss : null,
  41. hero : null,
  42. blinky : null,
  43. pinky : null,
  44. inky : null,
  45. clyde : null,
  46. ghosts : new Array(),
  47. actors : {},
  48. heroes : new Array(),
  49. maze : MAZE,
  50. init : function() {
  51. GUI.updateMessage("READY");
  52. $(".dot.hiddenDot").each( function(incr, elt) {
  53. Game.dots[elt.id] = "dot";
  54. });
  55. $(".dot.hiddenDot").removeClass("hiddenDot");
  56. $(".bigDot.hiddenDot").each( function(incr, elt) {
  57. Game.dots[elt.id] = "bigDot";
  58. });
  59. $(".bigDot.hiddenDot").removeClass("hiddenDot")
  60. Game.totalDots = $(".dot").length + $(".bigDot").length;
  61. SCOREBOARD.init();
  62. SCOREBOARD.set_score( Game.score );
  63. Game.level++;
  64. Game.step = 0;
  65. Game.eatenDots = 0;
  66. GUI.updateLevelNumber( Game.level + 1 );
  67. Game.build(LEVELS[Math.min(Game.level, LEVELS.length)]);
  68. },
  69. build : function(data) {
  70. Game.levelData = data;
  71. Game.addPacman();
  72. Game.addGhosts();
  73. Sound.play("opening");
  74. setTimeout("Game.start();", 4500);
  75. },
  76. start : function() {
  77. //if( $.browser.webkit )
  78. $(document).keydown( function( event ) {
  79. if( event.which > 36 && event.which < 41 )
  80. return false;
  81. } );
  82. // $(document).keypress(scrollPreventFct );
  83. GUI.updateMessage("");
  84. Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
  85. Game.running = true;
  86. },
  87. levelComplete : function() {
  88. Game.running = false;
  89. Game.timer.stop();
  90. Game.timer = null;
  91. setTimeout("Game.init();", 3000);
  92. },
  93. eat : function(type) {
  94. Game.eatenDots++;
  95. if( type === "bigDot" ) {
  96. Game.score += Game.BIG_DOT_POINTS;
  97. // console.log( "Eating big dot " + Game.score );
  98. SCOREBOARD.add( Game.BIG_DOT_POINTS );
  99. } else {
  100. Game.score += Game.DOT_POINTS;
  101. // console.log( "Eating dot " + Game.score );
  102. SCOREBOARD.add( Game.DOT_POINTS );
  103. }
  104. if( Game.eatenDots == 70 || Game.eatenDots == 170 ) {
  105. Game.bonusTimer = setTimeout("Game.hideBonus();", ( 9 + Math.random() ) * 1000 );
  106. $("#" + Game.maze.bonus_target).addClass( Game.levelData.bonus.type);
  107. }
  108. if( Game.eatenDots === Game.totalDots )
  109. Game.levelComplete();
  110. },
  111. eatGhost : function(ghost) {
  112. Sound.play("ghost");
  113. Game.eaten++;
  114. var points = Game.eaten * 200;
  115. Game.score += points;
  116. // console.log(new Date() + " Eating " + ghost.id + " " + (Game.eaten * 200) + " "+ Game.score );
  117. SCOREBOARD.add( points );
  118. },
  119. hideBonus : function() {
  120. $("#" + Game.maze.bonus_target).removeClass( Game.levelData.bonus.type);
  121. Game.bonusTimer = null;
  122. },
  123. die : function() {
  124. Game.running = false;
  125. $.each( Game.actors, function(index, actor) {
  126. actor.speed = 0;
  127. })
  128. Game.pacman.die();
  129. Game.timer.stop();
  130. Game.step = 0;
  131. Game.timer = null;
  132. $("#life" + Game.lives).effect( "pulsate", {times:3, mode:"hide"}, 500 );
  133. Game.lives--;
  134. if( Game.lives > 0 )
  135. setTimeout( "Game.startAfterDie();", 3000);
  136. else {
  137. GUI.drawText( $("#message"), "GAME OVER", true );
  138. Game.show_game_over();
  139. }
  140. },
  141. show_game_over: function() {
  142. },
  143. startAfterDie : function() {
  144. var dotsCounters = new Array();
  145. $.each(Game.ghosts, function(index, ghost ) {
  146. dotsCounters[index] = ghost.dotsCounter;
  147. });
  148. Game.addGhosts();
  149. Game.addPacman();
  150. //Game.addMissPacman();
  151. $.each(Game.ghosts, function(index, ghost ) {
  152. ghost.dotsCounter = dotsCounters[index];
  153. if( ghost.dotsCounter >= ghost.dotsLimits[Math.min(Game.level, ghost.dotsLimits.length - 1)] ) {
  154. ghost.speed = ghost.initialSpeed;
  155. ghost.state_to(Game.GHOST_STATE_SCATTER);
  156. }
  157. });
  158. Game.running = true;
  159. Game.step = 0;
  160. Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
  161. },
  162. timerManager : function() {
  163. Game.step++;
  164. if( Game.step % 2 == 1 ) {
  165. $(".actor").trigger(Game.GHOST_EVENT_CHASE);
  166. Game.mode = Game.GHOST_STATE_CHASE;
  167. } else {
  168. $(".actor").trigger(Game.GHOST_EVENT_SCATTER);
  169. Game.mode = Game.GHOST_STATE_SCATTER;
  170. }
  171. if( Game.step < Game.levelData.mode.length - 1 && Game.levelData.mode[Game.step] != INFINITY )
  172. Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
  173. },
  174. addPacman : function() {
  175. if( $("#pacman").length == 0) {
  176. Game.pacman = new Pacman();
  177. $("#actors").addSprite("pacman", {animation: Game.pacman.animations["right"], posx:Game.pacman.x, posy: Game.pacman.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  178. Game.pacman.node = $("#pacman");
  179. Game.pacman.node.addClass( "actor" );
  180. Game.actors[ "pacman" ] = Game.pacman;
  181. Game.heroes[ "pacman" ] = Game.pacman;
  182. Game.hero = Game.pacman;
  183. }
  184. Game.pacman.init();
  185. Game.pacman.speed = Game.levelData.pacman.speed;
  186. Game.pacman.left();
  187. },
  188. addMissPacman : function() {
  189. if( $("#miss_pacman").length == 0) {
  190. Game.miss = new Pacman();
  191. Game.miss.animations["right"] = new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsety: 272, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL });
  192. Game.miss.animations["up"] = new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsetx: 96, offsety: 272, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL });
  193. $("#actors").addSprite("miss_pacman", {animation: Game.miss.animations["right"], posx:Game.miss.x, posy: Game.miss.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  194. Game.miss.node = $("#miss_pacman");
  195. Game.miss.node.addClass( "actor" );
  196. Game.actors[ "miss_pacman" ] = Game.miss;
  197. Game.heroes[ "miss_pacman" ] = Game.miss;
  198. }
  199. Game.miss.init();
  200. Game.miss.x = Game.MISS_PACMAN_START_X;
  201. Game.miss.y = Game.MISS_PACMAN_START_Y;
  202. Game.miss.speed = Game.levelData.pacman.speed;
  203. Game.miss.right(true);
  204. Game.miss.left(true);
  205. Game.miss.node.x(Game.miss.x);
  206. Game.miss.node.y(Game.miss.y);
  207. Game.miss.right();
  208. },
  209. addGhosts : function() {
  210. Game.addBlinky();
  211. Game.addPinky();
  212. Game.addInky();
  213. Game.addClyde();
  214. },
  215. addBlinky : function() {
  216. if( $("#blinky").length == 0 ) {
  217. Game.blinky = new Ghost("blinky", 0, {x: 14 * TILE_SIZE, y: 14 * TILE_SIZE}, {x: 25, y: 0 }, function() {
  218. var prey = Game.actors[ "blinky" ].prey;
  219. return {x: prey.getTileX(), y: prey.getTileY()};
  220. }, [0,0,0], Game.GHOST_STATE_SCATTER);
  221. Game.blinky.center();
  222. $("#actors").addSprite("blinky", {animation: Game.blinky.animations["right"], posx:Game.blinky.x, posy: Game.blinky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  223. Game.blinky.node = $("#blinky");
  224. Game.blinky.node.addClass( "actor" );
  225. Game.actors[ "blinky" ] = Game.blinky;
  226. Game.blinky.loadBindings();
  227. Game.blinky.originalTarget = Game.blinky.target;
  228. Game.blinky.target = function() {
  229. var remainingDots = Game.totalDots - Game.eatenDots;
  230. var elroySpecs = Game.levelData.ghost;
  231. if( ( Game.blinky.state == Game.GHOST_STATE_SCATTER || Game.blinky.state == Game.GHOST_STATE_CHASE ) && remainingDots <= elroySpecs.elroy1Dots ) {
  232. if( remainingDots <= elroySpecs.elroy2Dots ) {
  233. Game.blinky.speed = elroySpecs.elroy2Speed;
  234. }
  235. else {
  236. Game.blinky.speed = elroySpecs.elroy1Speed;
  237. }
  238. return Game.blinky.personnalTarget();
  239. }
  240. return Game.blinky.originalTarget();
  241. };
  242. Game.ghosts.push( Game.blinky );
  243. } else {
  244. Game.blinky.init();
  245. }
  246. Game.blinky.state = Game.GHOST_STATE_SCATTER;
  247. Game.blinky.left();
  248. Game.blinky.initialSpeed = Game.levelData.ghost.speed;
  249. Game.blinky.speed = Game.blinky.initialSpeed;
  250. },
  251. addPinky : function() {
  252. if( $("#pinky").length == 0 ) {
  253. Game.pinky = new Ghost("pinky", 1, {x: 14 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 2, y: 0 }, function() {
  254. var prey = Game.actors[ "pinky" ].prey;
  255. var direction = this.prey.direction;
  256. if( direction % 2 == 0 )
  257. return {x: prey.getTileX() + (direction == LEFT ? -4 : 4), y: prey.getTileY()};
  258. else
  259. return {x: prey.getTileX(), y: prey.getTileY() + (direction == UP ? -4 : 4) };
  260. }, [0,0,0], Game.GHOST_STATE_IN_JAIL);
  261. Game.pinky.center();
  262. $("#actors").addSprite("pinky", {animation: Game.pinky.animations["right"], posx: Game.pinky.x, posy: Game.pinky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  263. Game.pinky.node = $("#pinky");
  264. Game.pinky.node.addClass( "actor" );
  265. Game.actors[ "pinky" ] = Game.pinky;
  266. Game.pinky.loadBindings();
  267. Game.ghosts.push( Game.pinky );
  268. } else {
  269. Game.pinky.init();
  270. }
  271. Game.pinky.initialSpeed = Game.levelData.ghost.speed;
  272. Game.pinky.left();
  273. },
  274. addInky : function() {
  275. if( $("#inky").length == 0 ) {
  276. Game.inky = new Ghost("inky", 2, {x: 12 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 27, y: 34 }, function() {
  277. var prey = Game.actors[ "inky" ].prey;
  278. var direction = prey.direction;
  279. if( direction % 2 == 0 )
  280. direction = {x: prey.getTileX() + (direction == LEFT ? -2 : 2) - Game.blinky.getTileX(), y: prey.getTileY() - Game.blinky.getTileY()};
  281. else
  282. direction = {x: prey.getTileX() - Game.blinky.getTileX(), y: prey.getTileY() + (direction == UP ? -2 : 2) - Game.blinky.getTileY()};
  283. return {x: direction.x * 2, y: direction.y * 2};
  284. }, [30,0,0], Game.GHOST_STATE_IN_JAIL);
  285. Game.inky.center();
  286. $("#actors").addSprite("inky", {animation: Game.inky.animations["right"], posx:Game.inky.x, posy: Game.inky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  287. Game.inky.node = $("#inky");
  288. Game.inky.node.addClass( "actor" );
  289. Game.actors[ "inky" ] = Game.inky;
  290. Game.inky.loadBindings();
  291. Game.ghosts.push( Game.inky );
  292. } else {
  293. Game.inky.init();
  294. }
  295. Game.inky.initialSpeed = Game.levelData.ghost.speed;
  296. Game.inky.right();
  297. },
  298. addClyde : function() {
  299. if( $("#clyde").length == 0 ) {
  300. Game.clyde = new Ghost("clyde", 3, {x: 16 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 0, y: 34 }, function() {
  301. var prey = Game.actors[ "clyde" ].prey;
  302. return distance( {x: this.getTileX(), y: this.getTileY()} , {x: prey.getTileX(), y: prey.getTileY()}) < 8 ?
  303. this.scatterTarget : {x: prey.getTileX(), y: prey.getTileY()};
  304. }, [60,50,0], Game.GHOST_STATE_IN_JAIL);
  305. Game.clyde.center();
  306. $("#actors").addSprite("clyde", {animation: Game.clyde.animations["right"], posx:Game.clyde.x, posy: Game.clyde.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
  307. Game.clyde.node = $("#clyde");
  308. Game.clyde.node.addClass( "actor" );
  309. Game.actors[ "clyde" ] = Game.clyde;
  310. Game.clyde.loadBindings();
  311. Game.ghosts.push( Game.clyde );
  312. } else {
  313. Game.clyde.init();
  314. }
  315. Game.clyde.initialSpeed = Game.levelData.ghost.speed;
  316. Game.clyde.left();
  317. },
  318. moveGhosts : function() {
  319. $.each(Game.ghosts, function(index, ghost ) {
  320. ghost.move();
  321. });
  322. },
  323. nearEndFright : function() {
  324. $.each(Game.ghosts, function(index, ghost ) {
  325. if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
  326. ghost.state_to(Game.GHOST_STATE_FRIGHTENED_BLINK);
  327. });
  328. setTimeout( 'Game.endFright();', 160 * 4 * Game.levelData.frightFlashesCount);
  329. },
  330. endFright : function() {
  331. if( Game.timer )
  332. Game.timer.resume();
  333. Game.frightTimer = null;
  334. Game.eaten = 0;
  335. $('.actor').trigger( Game.mode == Game.GHOST_STATE_CHASE ? Game.GHOST_EVENT_CHASE : Game.GHOST_EVENT_SCATTER );
  336. }
  337. }
  338. function distance(currentTile, target) {
  339. return Math.sqrt( (target.x - currentTile.x) * (target.x - currentTile.x) + (target.y - currentTile.y)*(target.y - currentTile.y));
  340. };
  341. //Game objects:
  342. function Actor(){}
  343. Actor.prototype = {
  344. node : null,
  345. animations : null,
  346. x : null,
  347. y : null,
  348. speed : null,
  349. direction : null, // 1: up, 2: left, 3:down, 4: right
  350. directionX : 0,
  351. directionY : 0,
  352. getX : function() {
  353. return x;
  354. },
  355. getY : function() {
  356. return y;
  357. },
  358. getTileX : function() {
  359. return Math.floor(this.x / TILE_SIZE);
  360. },
  361. getTileY : function() {
  362. return Math.floor(this.y / TILE_SIZE);
  363. },
  364. getTile : function() {
  365. return this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT;
  366. },
  367. getInsideTileX : function() {
  368. return this.x % TILE_SIZE;
  369. },
  370. getInsideTileY : function() {
  371. return this.y % TILE_SIZE;
  372. },
  373. move : function() {
  374. if( !Game.running )
  375. return;
  376. this.x += this.directionX * this.speed * ACTOR_SPEED;
  377. this.y += this.directionY * this.speed * ACTOR_SPEED;
  378. this.node.x(this.x );
  379. this.node.y(this.y );
  380. },
  381. up : function( force ) {
  382. if( force || this.direction != UP ) {
  383. this.directionX = 0;
  384. this.directionY = -1;
  385. this.direction = UP;
  386. this.node.setAnimation(this.animations["up"]);
  387. this.node.flipv(false);
  388. this.node.fliph(false);
  389. this.center();
  390. }
  391. },
  392. down : function( force ) {
  393. if( force || this.direction != DOWN ) {
  394. this.directionX = 0;
  395. this.directionY = 1;
  396. this.direction = DOWN;
  397. if( this.animations["down"] ) {
  398. this.node.setAnimation(this.animations["down"]);
  399. this.node.fliph( false );
  400. } else {
  401. this.node.setAnimation(this.animations["up"]);
  402. this.node.flipv( true );
  403. this.node.fliph( false );
  404. }
  405. this.center();
  406. }
  407. },
  408. left : function( force ) {
  409. if( force || this.direction != LEFT ) {
  410. this.directionX = -1;
  411. this.directionY = 0;
  412. this.direction = LEFT;
  413. this.node.flipv( false );
  414. if( this.animations["left"] ) {
  415. this.node.setAnimation(this.animations["left"]);
  416. } else {
  417. this.node.setAnimation(this.animations["right"]);
  418. this.node.fliph( true );
  419. }
  420. this.center();
  421. }
  422. },
  423. right : function( force ) {
  424. if( force || this.direction != RIGHT ) {
  425. this.directionX = 1;
  426. this.directionY = 0;
  427. this.direction = RIGHT;
  428. this.node.setAnimation(this.animations["right"]);
  429. this.node.fliph( false );
  430. this.node.flipv( false );
  431. this.center();
  432. }
  433. },
  434. canLeft : function() {
  435. return Game.maze.structure[this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT - 1] <= 0;
  436. },
  437. canRight : function() {
  438. return Game.maze.structure[this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT + 1] <= 0;
  439. },
  440. canUp : function() {
  441. return Game.maze.structure[this.getTileX() + (this.getTileY() - 1 ) * WIDTH_TILE_COUNT ] <= 0;
  442. },
  443. canDown : function() {
  444. return Game.maze.structure[this.getTileX() + (this.getTileY() + 1 ) * WIDTH_TILE_COUNT ] <= 0;
  445. },
  446. isNearMiddleTile : function() {
  447. return Math.abs( HALF_TILE_SIZE - this.getInsideTileX() ) < 4 && Math.abs( HALF_TILE_SIZE - this.getInsideTileY() ) < 4;
  448. },
  449. center : function() {
  450. this.x = this.getTileX() * TILE_SIZE + HALF_TILE_SIZE;
  451. this.y = this.getTileY() * TILE_SIZE + HALF_TILE_SIZE;
  452. },
  453. isInTunnel : function() {
  454. var tile = this.getTile();
  455. return $.inArray(tile, Game.maze.tunnel) > -1;
  456. }
  457. };
  458. /*********************************************/
  459. /****************** PACMAN *******************/
  460. /*********************************************/
  461. function Pacman() {
  462. this.animations = {
  463. "right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsety: 16, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  464. "up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsetx: 64, offsety: 16, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  465. "die": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 7, offsety: 208, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL | $.gameQuery.ANIMATION_ONCE | $.gameQuery.ANIMATION_CALLBACK }),
  466. "die2": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 4, offsety: 240, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL | $.gameQuery.ANIMATION_ONCE })
  467. }
  468. };
  469. Pacman.prototype = {
  470. x : Game.PACMAN_START_X,
  471. y : Game.PACMAN_START_Y,
  472. speed : null,
  473. directionX : 0,
  474. directionY : 0,
  475. lastEatenGhost : null,
  476. stop : false,
  477. previousTile : null,
  478. init : function() {
  479. this.x = Game.PACMAN_START_X;
  480. this.y = Game.PACMAN_START_Y;
  481. this.speed = Game.levelData.pacman.speed;
  482. this.right(true);
  483. this.left(true);
  484. this.node.x(this.x);
  485. this.node.y(this.y);
  486. },
  487. left : function() {
  488. if( this.direction != LEFT && this.canLeft() ) {
  489. this.stop = false;
  490. this._super("left", arguments);
  491. }
  492. },
  493. right : function() {
  494. if( this.direction != RIGHT && this.canRight() ) {
  495. this.stop = false;
  496. this._super("right", arguments);
  497. }
  498. },
  499. up : function() {
  500. if( this.direction != UP && this.canUp() ) {
  501. this.stop = false;
  502. this._super("up", arguments);
  503. }
  504. },
  505. down : function() {
  506. if( this.direction != DOWN && this.canDown() ) {
  507. this.stop = false;
  508. this._super("down", arguments);
  509. }
  510. },
  511. move : function() {
  512. if( !this.stop ) {
  513. this.previousTile = {x: this.getTileX(), y: this.getTileY()};
  514. this._super("move", arguments);
  515. var currentTile = {x: this.getTileX(), y: this.getTileY()};
  516. if( this.previousTile.x !== currentTile.x || this.previousTile.y !== currentTile.y ) {
  517. var id = this.getTile();
  518. if( Game.dots[ id ] )
  519. this.eatDot( id );
  520. if( id == Game.maze.bonus_target )
  521. this.eatBonus();
  522. this.eatGhosts();
  523. }
  524. var inTunnel = this.isInTunnel();
  525. if( this.x < 0 )
  526. this.x += PLAYGROUND_WIDTH;
  527. if( this.x > PLAYGROUND_WIDTH )
  528. this.x -= PLAYGROUND_WIDTH;
  529. switch( this.direction ) {
  530. case LEFT :
  531. if( !inTunnel && !this.canLeft() )
  532. this.stop = true;
  533. break;
  534. case RIGHT :
  535. if( !inTunnel && !this.canRight() )
  536. this.stop = true;
  537. break;
  538. case UP :
  539. if( !this.canUp() )
  540. this.stop = true;
  541. break;
  542. case DOWN :
  543. if( !this.canDown() )
  544. this.stop = true;
  545. break;
  546. }
  547. }
  548. },
  549. eatDot : function(id) {
  550. Game.eat(Game.dots[id]);
  551. $('.actor').trigger(Game.GHOST_EVENT_DOT_EATEN);
  552. if( Game.dots[id] === "bigDot" ) {
  553. $.each(Game.ghosts, function(index, ghost ) {
  554. if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
  555. ghost.state_to(Game.GHOST_STATE_FRIGHTENED)
  556. });
  557. Game.timer.pause();
  558. if( Game.frightTimer )
  559. clearTimeout( Game.frightTimer );
  560. Game.frightTimer = setTimeout( 'Game.nearEndFright();', Game.levelData.frightTime * 1000 - 160 * 4 * Game.levelData.frightFlashesCount);
  561. }
  562. Game.dots[id] = null;
  563. $("#" + id ).addClass("hiddenDot");
  564. },
  565. eatGhosts : function() {
  566. var tile = this.getTile();
  567. $.each(Game.ghosts, function(index, ghost ) {
  568. if( tile == ghost.getTile() ) {
  569. Game.pacman.eatGhost( ghost );
  570. }
  571. });
  572. },
  573. eatGhost : function( ghost ) {
  574. if( ghost.state == Game.GHOST_STATE_EATEN ) {
  575. // console.log( ghost.id + " already eaten" );
  576. return;
  577. }
  578. if( ghost.state != Game.GHOST_STATE_FRIGHTENED && ghost.state != Game.GHOST_STATE_FRIGHTENED_BLINK ) {
  579. Game.die();
  580. } else if( Game.pacman.lastEatenGhost !== ghost.id ){
  581. ghost.state_to(Game.GHOST_STATE_EATEN);
  582. // console.log( "Eating " + ghost.id + " " + ghost.state );
  583. Game.eatGhost(ghost);
  584. }
  585. },
  586. eatBonus : function() {
  587. if( !$("#" + Game.maze.bonus_target).hasClass( Game.levelData.bonus.type) && Game.bonusTimer == null )
  588. return;
  589. Sound.play("fruit");
  590. eatenBonus.push(Game.levelData.bonus.type);
  591. Game.score += Game.levelData.bonus.points;
  592. // console.log( "Eating bonus " + Game.levelData.bonus.points + " " + Game.score );
  593. SCOREBOARD.add( Game.levelData.bonus.points );
  594. Game.hideBonus();
  595. },
  596. die : function() {
  597. Sound.play("dies");
  598. this.node.setAnimation(this.animations["die"], function(node) {
  599. Game.pacman.node.setAnimation(Game.pacman.animations["die2"]);
  600. });
  601. }
  602. };
  603. // Overriding Actor.methods() method
  604. heriter(Pacman.prototype, Actor.prototype);
  605. function Ghost(id, ghostIndex, start, scatterTarget, personnalTarget, dotsLimits, state ) {
  606. this.animations = {
  607. "normal_up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  608. "normal_right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 128, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  609. "normal_down": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 64, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  610. "frightened": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 0, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  611. "frightened_blink": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 4, offsetx: 0, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  612. "eaten_up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 128, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  613. "eaten_down": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 160, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
  614. "eaten_right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 192, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL })
  615. }
  616. this.animations["up"] = this.animations["normal_up"];
  617. this.animations["down"] = this.animations["normal_down"];
  618. this.animations["right"] = this.animations["normal_right"];
  619. this.id = id;
  620. this.scatterTarget = scatterTarget;
  621. this.personnalTarget = personnalTarget;
  622. this.x = start.x;
  623. this.y = start.y;
  624. this.startingTileX = start.x;
  625. this.startingTileY = start.y;
  626. this.state = state;
  627. this.dotsLimits = dotsLimits;
  628. this.prey = Game.pacman;
  629. };
  630. Ghost.prototype = {
  631. id : null,
  632. startingTileX : 0,
  633. startingTileY : 0,
  634. initialSpeed : 0,
  635. speed : 0,
  636. directionX : 0,
  637. directionY : 0,
  638. state: null,
  639. scatterTarget : null,
  640. lastDirectionTile : null,
  641. prey : null,
  642. dotsCounter : 0,
  643. dotsLimits : [],
  644. init : function() {
  645. this.dotsCounter = 0;
  646. this.speed = 0;
  647. this.x = this.startingTileX;
  648. this.y = this.startingTileY;
  649. this.right(true);
  650. this.left(true);
  651. this.state = Game.GHOST_STATE_IN_JAIL;
  652. this.node.x(this.x);
  653. this.node.y(this.y);
  654. },
  655. target : function() {
  656. switch( this.state ) {
  657. case Game.GHOST_STATE_CHASE :
  658. return this.personnalTarget();
  659. case Game.GHOST_STATE_SCATTER :
  660. return this.scatterTarget;
  661. case Game.GHOST_STATE_FRIGHTENED :
  662. var currentTile = {x: this.getTileX(), y: this.getTileY()};
  663. var targets = new Array();
  664. if( this.canUp() && this.direction != DOWN )
  665. targets.push( {x:currentTile.x, y:currentTile.y - 1} );
  666. if( this.canDown() && this.direction != UP )
  667. targets.push( {x:currentTile.x, y:currentTile.y + 1} );
  668. if( this.canLeft() && this.direction != RIGHT )
  669. targets.push( {x:currentTile.x - 1, y:currentTile.y} );
  670. if( this.canRight() && this.direction != LEFT )
  671. targets.push( {x:currentTile.x + 1, y:currentTile.y} );
  672. return targets[ parseInt(Math.random() * targets.length ) ];
  673. case Game.GHOST_STATE_IN_JAIL :
  674. case Game.GHOST_STATE_EATEN :
  675. return {x: 13, y: 14};
  676. }
  677. },
  678. loadBindings : function() {
  679. this.node.bind(Game.GHOST_EVENT_CHASE, {ghost: this}, function(evt) {
  680. var ghost = evt.data.ghost;
  681. if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
  682. ghost.state_to(Game.GHOST_STATE_CHASE);
  683. });
  684. this.node.bind(Game.GHOST_EVENT_SCATTER, {ghost: this}, function(evt) {
  685. var ghost = evt.data.ghost;
  686. if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
  687. ghost.state_to(Game.GHOST_STATE_SCATTER);
  688. });
  689. this.node.bind(Game.GHOST_EVENT_DOT_EATEN, {ghost: this}, function(evt) {
  690. var ghost = evt.data.ghost;
  691. if( ghost.state == Game.GHOST_STATE_IN_JAIL && ghost.dotsCounter++ >= ghost.dotsLimits[Math.min(Game.level, ghost.dotsLimits.length - 1)] ) {
  692. ghost.speed = ghost.initialSpeed;
  693. ghost.state_to(Game.mode);
  694. }
  695. });
  696. },
  697. personnalTarget : function() {
  698. },
  699. state_to : function( state ) {
  700. var up;
  701. var down;
  702. var right;
  703. var reverse = this.state != Game.GHOST_STATE_FRIGHTENED && this.state != Game.GHOST_STATE_IN_JAIL; // previous state
  704. this.state = state;
  705. switch( state ) {
  706. case Game.GHOST_STATE_CHASE :
  707. this.speed = Game.levelData.ghost.speed;
  708. case Game.GHOST_STATE_SCATTER :
  709. this.speed = Game.levelData.ghost.speed;
  710. case Game.GHOST_STATE_IN_JAIL :
  711. up = this.animations["normal_up"];
  712. down = this.animations["normal_down"];
  713. right = this.animations["normal_right"];
  714. break;
  715. case Game.GHOST_STATE_FRIGHTENED :
  716. up = down = right = this.animations["frightened"];
  717. this.speed = Game.levelData.ghost.frightSpeed;
  718. break;
  719. case Game.GHOST_STATE_FRIGHTENED_BLINK :
  720. up = down = right = this.animations["frightened_blink"];
  721. this.state = Game.GHOST_STATE_FRIGHTENED;
  722. break;
  723. case Game.GHOST_STATE_EATEN :
  724. up = this.animations["eaten_up"];
  725. down = this.animations["eaten_down"];
  726. right = this.animations["eaten_right"];
  727. this.speed = 1;
  728. break;
  729. }
  730. this.animations["up"] = up;
  731. this.animations["down"] = down;
  732. this.animations["right"] = right;
  733. if( reverse )
  734. switch( this.direction ) {
  735. case UP:
  736. this.direction = DOWN;
  737. break;
  738. case LEFT:
  739. this.direction = RIGHT;
  740. break;
  741. case DOWN:
  742. this.direction = UP;
  743. break;
  744. case RIGHT:
  745. this.direction = LEFT;
  746. break;
  747. }
  748. var inTunnel = this.isInTunnel();
  749. var distances = [
  750. {direction: UP, distance: this.canUp() && this.direction != DOWN ? 1 : INFINITY},
  751. {direction: LEFT, distance: (inTunnel && this.direction == LEFT ) || (this.canLeft() && this.direction != RIGHT) ? 1 : INFINITY},
  752. {direction: DOWN, distance: this.canDown() && this.direction != UP ? 1 : INFINITY},
  753. {direction: RIGHT, distance: (inTunnel && this.direction == RIGHT ) || (this.canRight() && this.direction != LEFT) ? 1 : INFINITY},
  754. ];
  755. distances.sort( function(a, b) {
  756. if( a.distance == b.distance )
  757. return a.direction - b.direction;
  758. return a.distance - b.distance;
  759. })
  760. var selected = distances[0];
  761. switch( selected.direction ) {
  762. case UP:
  763. this.up(true);
  764. break;
  765. case LEFT:
  766. this.left(true);
  767. break;
  768. case DOWN:
  769. this.down(true);
  770. break;
  771. case RIGHT:
  772. this.right(true);
  773. break;
  774. }
  775. },
  776. canUp : function() {
  777. switch( this.getTile() ) {
  778. case 404:
  779. case 407:
  780. case 684:
  781. case 687:
  782. return false;
  783. case 461:
  784. case 462:
  785. return true;
  786. default:
  787. return Game.maze.structure[ this.getTileX() + (this.getTileY() - 1 ) * WIDTH_TILE_COUNT ] <= 0;
  788. }
  789. },
  790. canDown : function() {
  791. switch( this.getTile() ) {
  792. case 405:
  793. case 406:
  794. return false;
  795. default:
  796. return Game.maze.structure[ this.getTileX() + (this.getTileY() + 1 ) * WIDTH_TILE_COUNT ] <= 0;
  797. }
  798. },
  799. move : function() {
  800. this._super("move", arguments);
  801. var currentTile = {x: this.getTileX(), y: this.getTileY()};
  802. var id = this.getTile();;
  803. if( this.lastDirectionTile != id && this.isNearMiddleTile()) {
  804. this.lastDirectionTile = id;
  805. this.eaten();
  806. var distances = null;
  807. var target = this.target();
  808. if( this.state == Game.GHOST_STATE_EATEN && id == Game.maze.ghost_frightened_target ) {
  809. this.state_to(Game.mode);
  810. }
  811. var inTunnel = this.isInTunnel();
  812. if( inTunnel )
  813. this.speed = Game.levelData.ghost.tunnelSpeed;
  814. else if( this.state != Game.GHOST_STATE_IN_JAIL )
  815. this.speed = this.state == Game.GHOST_STATE_FRIGHTENED ? Game.levelData.ghost.frightSpeed : Game.levelData.ghost.speed;
  816. if( this.x < 0 )
  817. this.x += PLAYGROUND_WIDTH;
  818. if( this.x > PLAYGROUND_WIDTH )
  819. this.x -= PLAYGROUND_WIDTH;
  820. if( Game.maze.choice_tiles.indexOf( id ) != -1 ) {
  821. distances = [
  822. {direction: UP, distance: this.canUp() && this.direction != DOWN ? distance({x:currentTile.x, y:currentTile.y - 1}, target ) : INFINITY},
  823. {direction: LEFT, distance: this.canLeft() && this.direction != RIGHT ? distance( {x:currentTile.x - 1, y:currentTile.y }, target ) : INFINITY},
  824. {direction: DOWN, distance: this.canDown() && this.direction != UP ? distance({x:currentTile.x, y:currentTile.y + 1}, target ) : INFINITY},
  825. {direction: RIGHT, distance: this.canRight() && this.direction != LEFT ? distance({x:currentTile.x + 1, y:currentTile.y}, target ) : INFINITY},
  826. ];
  827. } else {
  828. distances = [
  829. {direction: UP, distance: this.canUp() && this.direction != DOWN ? 1 : INFINITY},
  830. {direction: LEFT, distance: (inTunnel && this.direction == LEFT ) || (this.canLeft() && this.direction != RIGHT) ? 1 : INFINITY},
  831. {direction: DOWN, distance: this.canDown() && this.direction != UP ? 1 : INFINITY},
  832. {direction: RIGHT, distance: (inTunnel && this.direction == RIGHT ) || (this.canRight() && this.direction != LEFT) ? 1 : INFINITY},
  833. ];
  834. }
  835. distances.sort( function(a, b) {
  836. if( a.distance == b.distance )
  837. return a.direction - b.direction;
  838. return a.distance - b.distance;
  839. })
  840. var selected = distances[0];
  841. switch( selected.direction ) {
  842. case LEFT :
  843. if( this.direction != LEFT )
  844. this.left();
  845. break;
  846. case RIGHT :
  847. if( this.direction != RIGHT )
  848. this.right();
  849. break;
  850. case UP :
  851. if( this.direction != UP )
  852. this.up();
  853. break;
  854. case DOWN :
  855. if( this.direction != DOWN )
  856. this.down();
  857. break;
  858. }
  859. }
  860. var inTunnel = this.isInTunnel();
  861. if( this.x < 0 )
  862. this.x += PLAYGROUND_WIDTH;
  863. if( this.x > PLAYGROUND_WIDTH )
  864. this.x -= PLAYGROUND_WIDTH;
  865. },
  866. eaten : function(target) {
  867. if( typeof target === "undefined" )
  868. target = this;
  869. if( target.getTile() == Game.pacman.getTile() ) {
  870. // console.log(" Eaten from ghost" );
  871. Game.pacman.eatGhost(target);
  872. // if( target.state != Game.GHOST_STATE_FRIGHTENED && target.state != Game.GHOST_STATE_EATEN ) {
  873. // Game.die();
  874. // } else {
  875. // target.state_to(Game.GHOST_STATE_EATEN);
  876. // Game.eatGhost(this);
  877. // }
  878. }
  879. }
  880. };
  881. heriter(Ghost.prototype, Actor.prototype);