circleOne.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /*
  2. GAME LEVELS - CIRCLE I & II: balloon level
  3. Name of game state : 'CircleOne'
  4. Shape : circle
  5. Character : kid/balloon
  6. Theme : flying a balloon
  7. Concept : 'How much the kid has to walk to get to the balloon?'
  8. Represent fractions as : circles
  9. # of different difficulties for each level : 5
  10. Levels can be : 'A' or 'B' (in variable 'levelType')
  11. A : Player can place balloon position
  12. Place balloon in position (so the kid can get to it)
  13. B : Player can select # of circles
  14. Selects number of circles (that represent distance kid needs to walk to get to the balloon)
  15. Sublevels can be : 'Plus', 'Minus' or 'Mixed' (in variable 'sublevelType')
  16. Plus : addition of fractions
  17. Represented by : kid going to the right (floor positions 0..5)
  18. Minus : subtraction of fractions
  19. Represented by: kid going to the left (floor positions 5..0)
  20. Mixed : Mix addition and subtraction of fractions in same
  21. Represented by: kid going to the left (floor positions 0..5)
  22. */
  23. const circleOne = {
  24. preload: function () {
  25. document.body.style.cursor = "auto";
  26. game.loop.stop();
  27. game.event.clear();
  28. game.animation.clear();
  29. self = this;
  30. // NOTHING TO LOAD HERE
  31. circleOne.create();
  32. },
  33. create: function () {
  34. game.render.clear();
  35. // CONTROL VARIABLES
  36. this.availableAnimations = [];
  37. this.changeAnimationFrames = undefined;
  38. this.checkAnswer = false; //Check kid inside ballon's basket
  39. this.animate = false; //Start move animation
  40. this.animateEnding = false; //Start ballon fly animation
  41. this.hasClicked = false; //Air ballon positioned
  42. this.result = false; //Game is correct
  43. this.count = 0;
  44. this.divisorsList = ""; // used in func_postScore
  45. let hasBaseDifficulty = false; // Will validate that level isnt too easy (has at least one '1/difficulty' fraction)
  46. const startX = (sublevelType == 'Minus') ? 66 + 5 * 156 : 66; // Initial 'x' coordinate for the kid and the baloon
  47. this.correctX = startX; //Ending position, accumulative
  48. // BACKGROUND
  49. // Add background image
  50. game.add.image(0, 0, 'bgimage');
  51. // Add clouds
  52. game.add.image(300, 100, 'cloud');
  53. game.add.image(660, 80, 'cloud');
  54. game.add.image(110, 85, 'cloud', 0.8);
  55. // Add floor of grass
  56. for (let i = 0; i < 9; i++) { game.add.image(i * 100, 501, 'floor'); }
  57. // Road
  58. this.road = game.add.image(47, 515, 'road', 1.01, 0.94);
  59. // Road points
  60. const distanceBetweenPoints = 156; // Distance between road points
  61. for (let i = 0; i <= 5; i++) {
  62. game.add.image(66 + i * distanceBetweenPoints, 526, 'place_off', 0.3).anchor(0.5, 0.5);
  63. game.add.text(66 + i * distanceBetweenPoints, 560, i, textStyles.valueLabelBlue1);
  64. }
  65. this.trace = game.add.graphic.rect(startX - 1, 526, 1, 1, undefined, 1);
  66. this.trace.alpha = 0;
  67. // Calls function that loads navigation icons
  68. navigationIcons.func_addIcons(
  69. true, true, true, // left buttons
  70. true, false, // right buttons
  71. difficultyScreen, this.func_viewHelp
  72. );
  73. // CIRCLES AND FRACTIONS
  74. this.circles = {
  75. all: [], // Circles objects of current level
  76. label: [], // Fractions labels
  77. diameter: 60, // (Fixed) diameter for circles
  78. cur: 0, // Current circle index
  79. direction: [], // Circle direction : 'Right' (plus), 'Left' (minus)
  80. distance: [], // Fraction of distance between circles (used in walking animation)
  81. angle: [], // Angle in degrees : 90 / 180 / 270 / 360
  82. lineColor: [], // Circle line colors (also used for tracing on floor)
  83. direc: [], // Can be : 1 or -1 : will be multiplied to values to easily change object direction when needed
  84. };
  85. this.balloonPlace = defaultWidth / 2; // Balloon place
  86. // Number of circles
  87. const max = (sublevelType == 'Mixed' || levelType == 'B') ? 6 : mapPosition + 1;
  88. const min = (sublevelType == 'Mixed' && mapPosition < 2) ? 2 : mapPosition; // Mixed level has at least 2 fractions
  89. const total = game.math.randomInRange(min, max); // Total number of circles
  90. // levelType 'B' exclusive variables
  91. this.fractionIndex = -1; // Index of clicked circle (game B)
  92. this.numberOfPlusFractions = game.math.randomInRange(1, total - 1);
  93. // CIRCLES
  94. const levelDirection = (sublevelType == 'Minus') ? -1 : 1;
  95. const x = startX + 65 * levelDirection;
  96. for (let i = 0; i < total; i++) {
  97. const divisor = game.math.randomInRange(1, gameDifficulty); // Set fraction 'divisor' (depends on difficulty)
  98. if (divisor == gameDifficulty) hasBaseDifficulty = true; // true if after for ends has at least 1 '1/difficulty' fraction
  99. this.divisorsList += divisor + ","; // Add this divisor to the list of divisors (for func_postScore)
  100. // Set each circle direction
  101. let direction;
  102. switch (sublevelType) {
  103. case 'Plus': direction = 'Right'; break;
  104. case 'Minus': direction = 'Left'; break;
  105. case 'Mixed':
  106. if (i < this.numberOfPlusFractions) direction = 'Right';
  107. else direction = 'Left';
  108. break;
  109. }
  110. this.circles.direction[i] = direction;
  111. // set each circle color
  112. let lineColor, anticlockwise;
  113. if (direction == 'Right') {
  114. lineColor = colors.darkBlue;
  115. this.circles.direc[i] = 1;
  116. anticlockwise = true;
  117. } else {
  118. lineColor = colors.red;
  119. this.circles.direc[i] = -1;
  120. anticlockwise = false;
  121. }
  122. this.circles.lineColor[i] = lineColor;
  123. // Draw circles
  124. let circle, label = [];
  125. if (divisor == 1) {
  126. circle = game.add.graphic.circle(startX, 490 - i * this.circles.diameter, this.circles.diameter,
  127. lineColor, 2, colors.lightBlue, 1);
  128. circle.anticlockwise = anticlockwise;
  129. this.circles.angle.push(360);
  130. if (fractionLabel) {
  131. label[0] = game.add.text(x, 490 - i * this.circles.diameter, divisor, textStyles.valueLabelBlue1);
  132. this.circles.label.push(label);
  133. }
  134. } else {
  135. let degree = 360 / divisor;
  136. if (direction == 'Right') degree = 360 - degree; // anticlockwise equivalent
  137. circle = game.add.graphic.arc(startX, 490 - i * this.circles.diameter, this.circles.diameter,
  138. 0, game.math.degreeToRad(degree), anticlockwise,
  139. lineColor, 2, colors.lightBlue, 1);
  140. this.circles.angle.push(degree);
  141. if (fractionLabel) {
  142. label[0] = game.add.text(x, 480 - i * this.circles.diameter + 32, divisor, textStyles.valueLabelBlue2);
  143. label[1] = game.add.text(x, 488 - i * this.circles.diameter, '1', textStyles.valueLabelBlue2);
  144. label[2] = game.add.text(x, 488 - i * this.circles.diameter, '___', textStyles.valueLabelBlue2);
  145. this.circles.label.push(label);
  146. }
  147. }
  148. circle.rotate = 90;
  149. //If game is type B (select fractions)
  150. if (levelType == 'B') {
  151. circle.alpha = 0.5;
  152. circle.index = i;
  153. }
  154. this.circles.distance.push(Math.floor(distanceBetweenPoints / divisor));
  155. this.circles.all.push(circle);
  156. this.correctX += Math.floor(distanceBetweenPoints / divisor) * this.circles.direc[i];
  157. }
  158. //Calculate next circle
  159. this.nextX = startX + this.circles.distance[0] * this.circles.direc[0];
  160. // check if need to restart
  161. let restart = false;
  162. //If top circle position is out of bounds (when on the ground) or game doesnt have base difficulty, restart
  163. if (this.correctX < 66 || this.correctX > 66 + 3 * 260 || !hasBaseDifficulty) {
  164. restart = true;
  165. }
  166. //If game is type B, selectiong a random balloon place
  167. if (levelType == 'B') {
  168. this.balloonPlace = startX;
  169. this.endIndex = game.math.randomInRange(this.numberOfPlusFractions, this.circles.all.length);
  170. for (let i = 0; i < this.endIndex; i++) {
  171. this.balloonPlace += this.circles.distance[i] * this.circles.direc[i];
  172. }
  173. //If balloon position is out of bounds, restart
  174. if (this.balloonPlace < 66 || this.balloonPlace > 66 + 5 * distanceBetweenPoints) {
  175. restart = true;
  176. }
  177. }
  178. // KID
  179. this.availableAnimations['Right'] = ['Right', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4];
  180. this.availableAnimations['Left'] = ['Left', [23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12], 4];
  181. this.kid = game.add.sprite(startX, 495 - this.circles.all.length * this.circles.diameter, 'kid_walk', 0, 0.8);
  182. this.kid.anchor(0.5, 0.8);
  183. if (sublevelType == 'Minus') {
  184. this.kid.animation = this.availableAnimations['Left'];
  185. this.kid.curFrame = 23;
  186. } else {
  187. this.kid.animation = this.availableAnimations['Right'];
  188. }
  189. // BALLOON
  190. this.balloon = game.add.image(this.balloonPlace, 350, 'balloon', 1, 1, 0.5);
  191. this.balloon.alpha = 0.5;
  192. this.balloon.anchor(0.5, 0.5);
  193. this.basket = game.add.image(this.balloonPlace, 472, 'balloon_basket');
  194. this.basket.anchor(0.5, 0.5);
  195. // help pointer
  196. this.help = game.add.image(0, 0, 'help_pointer', 0.5);
  197. this.help.anchor(0.5, 0);
  198. this.help.alpha = 0;
  199. if (restart) {
  200. circleOne.preload();
  201. } else {
  202. game.render.all();
  203. game.timer.start(); // Set a timer for the current level (used in func_postScore)
  204. game.event.add('click', circleOne.func_onInputDown);
  205. game.event.add('mousemove', circleOne.func_onInputOver);
  206. game.loop.start(this);
  207. }
  208. },
  209. update: function () {
  210. self.count++;
  211. //Start animation
  212. if (self.animate) {
  213. let cur = self.circles.cur;
  214. let DIREC = self.circles.direc[cur];
  215. if (self.count % 2 == 0) { // lowers animation
  216. // Move kid
  217. self.kid.x += 2 * DIREC;
  218. // Move circles
  219. for (let i in self.circles.all) {
  220. self.circles.all[i].x += 2 * DIREC;
  221. }
  222. // Manage line on the floor
  223. self.trace.width += 2 * DIREC;
  224. self.trace.lineColor = self.circles.all[cur].lineColor;
  225. // Change angle of current arc
  226. self.circles.angle[cur] += 4.6 * DIREC;
  227. self.circles.all[cur].angleEnd = game.math.degreeToRad(self.circles.angle[cur]);
  228. // When finish current circle
  229. let lowerCircles;
  230. if (self.circles.direction[cur] == 'Right') {
  231. lowerCircles = self.circles.all[cur].x >= self.nextX;
  232. } else if (self.circles.direction[cur] == 'Left') {
  233. lowerCircles = self.circles.all[cur].x <= self.nextX;
  234. // if just changed from 'right' to 'left' inform to change direction of kid animation
  235. if (self.changeAnimationFrames == undefined && cur > 0 && self.circles.direction[cur - 1] == 'Right') {
  236. self.changeAnimationFrames = true;
  237. }
  238. }
  239. // Change direction of kid animation
  240. if (self.changeAnimationFrames) {
  241. self.changeAnimationFrames = false;
  242. game.animation.stop(self.kid.animation[0]);
  243. self.kid.animation = self.availableAnimations['Left'];
  244. self.kid.curFrame = 23;
  245. game.animation.play(self.kid.animation[0]);
  246. }
  247. if (lowerCircles) {
  248. self.circles.all[cur].alpha = 0; // Cicle disappear
  249. self.circles.all.forEach(cur => {
  250. cur.y += self.circles.diameter; // Lower circles
  251. });
  252. self.kid.y += self.circles.diameter; // Lower kid
  253. self.circles.cur++; // update current circle
  254. cur = self.circles.cur;
  255. DIREC = self.circles.direc[cur];
  256. self.nextX += self.circles.distance[cur] * DIREC; // Update next position
  257. }
  258. // When finish all circles (final position)
  259. if (cur == self.circles.all.length || self.circles.all[cur].alpha == 0) {
  260. self.animate = false;
  261. self.checkAnswer = true;
  262. }
  263. }
  264. }
  265. //Check if kid is inside the basket
  266. if (self.checkAnswer) {
  267. game.timer.stop();
  268. game.animation.stop(self.kid.animation[0]);
  269. if (self.func_checkOverlap(self.basket, self.kid)) {
  270. self.result = true; // answer is correct
  271. self.kid.curFrame = (self.kid.curFrame < 12) ? 24 : 25;
  272. if (audioStatus) game.audio.okSound.play();
  273. game.add.image(defaultWidth / 2, defaultHeight / 2, 'ok').anchor(0.5, 0.5);
  274. completedLevels++;
  275. if (debugMode) console.log("completedLevels = " + completedLevels);
  276. } else {
  277. self.result = false; // answer is incorrect
  278. if (audioStatus) game.audio.errorSound.play();
  279. game.add.image(defaultWidth / 2, defaultHeight / 2, 'error').anchor(0.5, 0.5);
  280. }
  281. self.func_postScore();
  282. self.animateEnding = true;
  283. self.checkAnswer = false;
  284. self.count = 0;
  285. }
  286. // balloon flying animation
  287. if (self.animateEnding) {
  288. self.balloon.y -= 2;
  289. self.basket.y -= 2;
  290. if (self.result) self.kid.y -= 2;
  291. if (self.count >= 140) {
  292. if (self.result) mapMove = true;
  293. else mapMove = false;
  294. mapScreen.preload();
  295. }
  296. }
  297. game.render.all();
  298. },
  299. /* EVENT HANDLER */
  300. func_onInputDown: function (mouseEvent) {
  301. const x = mouseEvent.offsetX;
  302. const y = mouseEvent.offsetY;
  303. // LEVEL A : click road
  304. if (levelType == 'A') {
  305. const cur = self.road;
  306. const valid = y > 60 && (x >= cur.xWithAnchor && x <= (cur.xWithAnchor + cur.width * cur.scaleWidth));
  307. if (valid) self.func_clicked(x);
  308. }
  309. // LEVEL B : click circle
  310. if (levelType == 'B') {
  311. self.circles.all.forEach(cur => {
  312. const distance = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor);
  313. const valid = distance <= cur.diameter / 2;
  314. if (valid) self.func_clicked(cur);
  315. });
  316. }
  317. navigationIcons.func_onInputDown(x, y);
  318. game.render.all();
  319. },
  320. func_onInputOver: function (mouseEvent) {
  321. const x = mouseEvent.offsetX;
  322. const y = mouseEvent.offsetY;
  323. let flag = false;
  324. // LEVEL A : balloon follow mouse
  325. if (levelType == 'A' && !self.hasClicked) {
  326. if (game.math.distanceToPointer(x, self.balloon.x, y, self.balloon.y) > 8) {
  327. self.balloon.x = x;
  328. self.basket.x = x;
  329. }
  330. }
  331. // LEVEL B : hover circle
  332. if (levelType == 'B' && !self.hasClicked) {
  333. self.circles.all.forEach(cur => {
  334. const distance = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor);
  335. const valid = distance <= cur.diameter / 2;
  336. if (valid) {
  337. self.func_overCircle(cur);
  338. flag = true;
  339. }
  340. });
  341. if (!flag) self.func_outCircle();
  342. }
  343. game.render.all();
  344. },
  345. /* CALLED BY EVENT HANDLER */
  346. // in levelType 'B'
  347. func_overCircle: function (cur) {
  348. if (!self.hasClicked) {
  349. document.body.style.cursor = "pointer";
  350. for (let i in self.circles.all) {
  351. self.circles.all[i].alpha = (i <= cur.index) ? 1 : 0.5;
  352. }
  353. }
  354. },
  355. // in levelType 'B'
  356. func_outCircle: function () {
  357. if (!self.hasClicked) {
  358. document.body.style.cursor = "auto";
  359. self.circles.all.forEach(cur => {
  360. cur.alpha = 0.5;
  361. });
  362. }
  363. },
  364. // in levelType 'B'
  365. func_clicked: function (cur) {
  366. if (!self.hasClicked) {
  367. // On levelType A
  368. if (levelType == 'A') {
  369. self.balloon.x = cur;
  370. self.basket.x = cur;
  371. // On levelType B
  372. } else if (levelType == 'B') {
  373. document.body.style.cursor = "auto";
  374. for (let i in self.circles.all) {
  375. if (i <= cur.index) {
  376. self.circles.all[i].alpha = 1; // Keep selected circle
  377. self.fractionIndex = cur.index;
  378. } else {
  379. self.circles.all[i].alpha = 0; // Hide unselected circle
  380. self.kid.y += self.circles.diameter; // Lower kid to selected circle
  381. }
  382. }
  383. }
  384. if (audioStatus) game.audio.beepSound.play();
  385. // hide fractions
  386. if (fractionLabel) {
  387. self.circles.label.forEach(cur => {
  388. cur.forEach(cur => {
  389. cur.alpha = 0;
  390. });
  391. });
  392. }
  393. // Hide solution pointer
  394. if (self.help != undefined) self.help.alpha = 0;
  395. self.balloon.alpha = 1;
  396. self.trace.alpha = 1;
  397. self.hasClicked = true;
  398. self.animate = true;
  399. game.animation.play(this.kid.animation[0]);
  400. }
  401. },
  402. /* GAME FUNCTIONS */
  403. func_checkOverlap: function (spriteA, spriteB) {
  404. const xA = spriteA.x;
  405. const xB = spriteB.x;
  406. // consider it comming from both sides
  407. if (Math.abs(xA - xB) > 14) return false;
  408. else return true;
  409. },
  410. func_viewHelp: function () {
  411. if (!self.hasClicked) {
  412. // On levelType A
  413. if (levelType == 'A') {
  414. self.help.x = self.correctX;
  415. self.help.y = 490;
  416. // On levelType B
  417. } else {
  418. self.help.x = self.circles.all[self.endIndex - 1].x;
  419. self.help.y = self.circles.all[self.endIndex - 1].y - self.circles.diameter / 2;
  420. }
  421. self.help.alpha = 0.7;
  422. }
  423. },
  424. /* METADATA FOR GAME */
  425. func_postScore: function () {
  426. // Create some variables we need to send to our PHP file
  427. const data = "&s_game=" + gameShape
  428. + "&s_mode=" + levelType
  429. + "&s_oper=" + sublevelType
  430. + "&s_leve=" + gameDifficulty
  431. + "&s_posi=" + mapPosition
  432. + "&s_resu=" + self.result
  433. + "&s_time=" + game.timer.elapsed
  434. + "&s_deta="
  435. + "numCircles:" + self.circles.all.length
  436. + ", valCircles: " + self.divisorsList
  437. + " balloonX: " + self.basket.x
  438. + ", selIndex: " + self.fractionIndex;
  439. postScore(data);
  440. }
  441. };