circleOne.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. /******************************
  2. * This file holds game states.
  3. ******************************/
  4. /** [GAME STATE]
  5. *
  6. * .....circleOne.... = gameName
  7. * ....../...\.......
  8. * .....a.....b...... = gameMode
  9. * .......\./........
  10. * ........|.........
  11. * ....../.|.\.......
  12. * .plus.minus.mixed. = gameOperation
  13. * ......\.|./.......
  14. * ........|.........
  15. * ....1,2,3,4,5..... = gameDifficulty
  16. *
  17. * Character : kid/balloon
  18. * Theme : flying in a balloon
  19. * Concept : 'How much the kid has to walk to get to the balloon?'
  20. * Represent fractions as : circles/arcs
  21. *
  22. * Game modes can be :
  23. *
  24. * a : Player can place balloon position
  25. * Place balloon in position (so the kid can get to it)
  26. * b : Player can select # of circles
  27. * Selects number of circles (that represent distance kid needs to walk to get to the balloon)
  28. *
  29. * Operations can be :
  30. *
  31. * plus : addition of fractions
  32. * Represented by : kid going to the right (floor positions 0..5)
  33. * minus : subtraction of fractions
  34. * Represented by: kid going to the left (floor positions 5..0)
  35. * mixed : Mix addition and subtraction of fractions in same
  36. * Represented by: kid going to the left (floor positions 0..5)
  37. *
  38. * @namespace
  39. */
  40. const circleOne = {
  41. control: undefined,
  42. animation: undefined,
  43. road: undefined,
  44. circles: undefined,
  45. kid: undefined,
  46. balloon: undefined,
  47. basket: undefined,
  48. walkedPath: undefined,
  49. help: undefined,
  50. message: undefined,
  51. continue: undefined,
  52. /**
  53. * Main code
  54. */
  55. create: function () {
  56. this.road = {
  57. x: 150,
  58. y: context.canvas.height - game.image['floor_grass'].width * 1.5,
  59. width: 1620,
  60. };
  61. this.continue = {
  62. modal: undefined,
  63. button: undefined,
  64. text: undefined,
  65. };
  66. this.walkedPath = [];
  67. const pointWidth = (game.sprite['map_place'].width / 2) * 0.45;
  68. const distanceBetweenPoints =
  69. (context.canvas.width - this.road.x * 2 - pointWidth) / 5; // Distance between road points
  70. const y0 = this.road.y + 20;
  71. const x0 =
  72. gameOperation === 'minus'
  73. ? this.road.x + 5 * distanceBetweenPoints - pointWidth / 2
  74. : this.road.x + pointWidth / 2; // Initial 'x' coordinate for the kid and the baloon
  75. const diameter =
  76. game.math.getRadiusFromCircunference(distanceBetweenPoints) * 2;
  77. this.circles = {
  78. diameter: diameter, // (Fixed) Circles Diameter
  79. cur: 0, // Current circle index
  80. list: [], // Circle objects
  81. };
  82. this.control = {
  83. correctX: x0, // Correct position (accumulative)
  84. nextX: undefined, // Next point position
  85. divisorsList: '', // used in postScore (Accumulative)
  86. hasClicked: false, // Checks if user has clicked
  87. checkAnswer: false, // Check kid inside ballon's basket
  88. isCorrect: false, // Informs answer is correct
  89. showEndInfo: false,
  90. endSignX: undefined,
  91. curWalkedPath: 0,
  92. // mode 'b' exclusive
  93. correctIndex: undefined,
  94. fractionIndex: -1, // Index of clicked circle (game (b))
  95. numberOfPlusFractions: undefined,
  96. };
  97. const walkOffsetX = 2;
  98. const walksPerDistanceBetweenPoints = distanceBetweenPoints / walkOffsetX;
  99. this.animation = {
  100. list: {
  101. left: undefined,
  102. right: undefined,
  103. },
  104. invertDirection: undefined,
  105. animateKid: false,
  106. animateBalloon: false,
  107. counter: undefined,
  108. walkOffsetX,
  109. angleOffset: 360 / walksPerDistanceBetweenPoints,
  110. };
  111. renderBackground('farmRoad');
  112. // Calls function that loads navigation icons
  113. // FOR MOODLE
  114. if (moodle) {
  115. navigation.add.right(['audio']);
  116. } else {
  117. navigation.add.left(['back', 'menu', 'show_answer'], 'customMenu');
  118. navigation.add.right(['audio']);
  119. }
  120. const validPath = { x0, y0, distanceBetweenPoints };
  121. this.utils.renderRoad(validPath);
  122. const [restart, balloonX] = this.utils.renderCircles(validPath);
  123. this.restart = restart;
  124. this.utils.renderCharacters(validPath, balloonX);
  125. this.utils.renderUI();
  126. if (!this.restart) {
  127. game.timer.start(); // Set a timer for the current level (used in postScore())
  128. game.event.add('click', this.events.onInputDown);
  129. game.event.add('mousemove', this.events.onInputOver);
  130. }
  131. },
  132. /**
  133. * Game loop
  134. */
  135. update: function () {
  136. // Starts kid animation
  137. if (self.animation.animateKid) {
  138. self.utils.animateKid();
  139. }
  140. // Check if kid is inside the basket
  141. if (self.control.checkAnswer) {
  142. self.utils.checkAnswer();
  143. }
  144. // Starts balloon flying animation
  145. if (self.animation.animateBalloon) {
  146. self.utils.animateBalloon();
  147. }
  148. game.render.all();
  149. },
  150. utils: {
  151. // RENDERS
  152. renderRoad: function (validPath) {
  153. const directionModifier = gameOperation === 'minus' ? -1 : 1;
  154. for (let i = 0; i <= 5; i++) {
  155. // place
  156. game.add
  157. .sprite(
  158. validPath.x0 +
  159. i * validPath.distanceBetweenPoints * directionModifier,
  160. validPath.y0,
  161. 'map_place',
  162. 0,
  163. 0.45
  164. )
  165. .anchor(0.5, 0.5);
  166. // circle behind number
  167. game.add.geom
  168. .circle(
  169. validPath.x0 +
  170. i * validPath.distanceBetweenPoints * directionModifier,
  171. validPath.y0 + 60,
  172. 60,
  173. undefined,
  174. 0,
  175. colors.white,
  176. 0.8
  177. )
  178. .anchor(0, 0.25);
  179. // number
  180. game.add.text(
  181. validPath.x0 +
  182. i * validPath.distanceBetweenPoints * directionModifier,
  183. validPath.y0 + 60,
  184. i * directionModifier,
  185. {
  186. ...textStyles.h2_,
  187. font: 'bold ' + textStyles.h2_.font,
  188. fill: directionModifier === 1 ? colors.green : colors.red,
  189. }
  190. );
  191. }
  192. self.utils.renderWalkedPath(
  193. validPath.x0 - 1,
  194. validPath.y0 - 5,
  195. gameOperation === 'minus' ? colors.red : colors.green
  196. );
  197. },
  198. renderWalkedPath: function (x, y, color) {
  199. const path = game.add.geom.rect(x, y, 1, 1, color, 4);
  200. //path.alpha = 0;
  201. self.walkedPath.push(path);
  202. return path;
  203. },
  204. renderCircles: function (validPath) {
  205. let restart = false;
  206. let hasBaseDifficulty = false;
  207. let balloonX = context.canvas.width / 2;
  208. const directionModifier = gameOperation === 'minus' ? -1 : 1;
  209. const fractionX =
  210. validPath.x0 - (self.circles.diameter - 10) * directionModifier;
  211. const font = {
  212. ...textStyles.h2_,
  213. font: 'bold ' + textStyles.h2_.font,
  214. };
  215. // Number of circles
  216. const max =
  217. gameOperation === 'mixed' || gameMode === 'b' ? 6 : curMapPosition + 1;
  218. const min =
  219. gameOperation === 'mixed' && curMapPosition < 2 ? 2 : curMapPosition; // Mixed level has at least 2 fractions
  220. const total = game.math.randomInRange(min, max); // Total number of circles
  221. // for mode 'b'
  222. self.control.numberOfPlusFractions = game.math.randomInRange(
  223. 1,
  224. total - 1
  225. );
  226. for (let i = 0; i < total; i++) {
  227. let curDirection = undefined;
  228. let curLineColor = undefined;
  229. let curFillColor = undefined;
  230. let curAngleDegree = undefined;
  231. let curIsCounterclockwise = undefined;
  232. let curFractionItems = undefined;
  233. let curCircle = undefined;
  234. const curCircleInfo = {
  235. direction: undefined,
  236. direc: undefined,
  237. distance: undefined,
  238. angle: undefined,
  239. fraction: {
  240. labels: [],
  241. nominator: undefined,
  242. denominator: undefined,
  243. },
  244. };
  245. const curDivisor = game.math.randomInRange(1, gameDifficulty); // Set fraction 'divisor' (depends on difficulty)
  246. if (curDivisor === gameDifficulty) hasBaseDifficulty = true; // True if after for ends has at least 1 '1/difficulty' fraction
  247. self.control.divisorsList += curDivisor + ','; // Add this divisor to the list of divisors (for postScore())
  248. // Set each circle direction
  249. switch (gameOperation) {
  250. case 'plus':
  251. curDirection = 'right';
  252. break;
  253. case 'minus':
  254. curDirection = 'left';
  255. break;
  256. case 'mixed':
  257. curDirection =
  258. i < self.control.numberOfPlusFractions ? 'right' : 'left';
  259. break;
  260. }
  261. curCircleInfo.direction = curDirection;
  262. // Set each circle visual info
  263. if (curDirection === 'right') {
  264. curIsCounterclockwise = true;
  265. curLineColor = colors.green;
  266. curFillColor = colors.greenLight;
  267. curCircleInfo.direc = 1;
  268. } else {
  269. curIsCounterclockwise = false;
  270. curLineColor = colors.red;
  271. curFillColor = colors.redLight;
  272. curCircleInfo.direc = -1;
  273. }
  274. font.fill = curLineColor;
  275. const curCircleY =
  276. validPath.y0 -
  277. 5 -
  278. self.circles.diameter / 2 -
  279. i * self.circles.diameter;
  280. // Draw circles
  281. if (curDivisor === 1) {
  282. curAngleDegree = 360;
  283. curCircle = game.add.geom.circle(
  284. validPath.x0,
  285. curCircleY,
  286. self.circles.diameter,
  287. curLineColor,
  288. 3,
  289. curFillColor,
  290. 1
  291. );
  292. curCircle.counterclockwise = curIsCounterclockwise;
  293. curCircleInfo.angleDegree = curAngleDegree;
  294. curFractionItems = [
  295. {
  296. x: fractionX,
  297. y: curCircleY + 10,
  298. text: '1',
  299. },
  300. {
  301. x: fractionX,
  302. y: curCircleY - 2,
  303. text: '',
  304. },
  305. {
  306. x: fractionX,
  307. y: curCircleY - 2,
  308. text: '',
  309. },
  310. {
  311. x: fractionX - 25,
  312. y: curCircleY + 10,
  313. text: curDirection === 'left' ? '-' : '',
  314. },
  315. ];
  316. } else {
  317. curAngleDegree = 360 / curDivisor;
  318. if (curDirection === 'right') curAngleDegree = 360 - curAngleDegree; // counterclockwise equivalent
  319. curCircle = game.add.geom.arc(
  320. validPath.x0,
  321. curCircleY,
  322. self.circles.diameter,
  323. 0,
  324. game.math.degreeToRad(curAngleDegree),
  325. curIsCounterclockwise,
  326. curLineColor,
  327. 3,
  328. curFillColor,
  329. 1
  330. );
  331. curCircleInfo.angleDegree = curAngleDegree;
  332. curFractionItems = [
  333. {
  334. x: fractionX,
  335. y: curCircleY + 34,
  336. text: curDivisor,
  337. },
  338. {
  339. x: fractionX,
  340. y: curCircleY - 2,
  341. text: '1',
  342. },
  343. {
  344. x: fractionX,
  345. y: curCircleY - 2,
  346. text: '__',
  347. },
  348. {
  349. x: fractionX - 35,
  350. y: curCircleY + 15,
  351. text: curDirection === 'left' ? '-' : '',
  352. },
  353. ];
  354. }
  355. if (showFractions) {
  356. for (let cur in curFractionItems) {
  357. curCircleInfo.fraction.labels.push(
  358. game.add.text(
  359. curFractionItems[cur].x,
  360. curFractionItems[cur].y,
  361. curFractionItems[cur].text,
  362. font
  363. )
  364. );
  365. }
  366. curCircleInfo.fraction.nominator = curCircleInfo.direc;
  367. curCircleInfo.fraction.denominator = curDivisor;
  368. }
  369. curCircle.rotate = 90;
  370. // If game is type (b) (select fractions)
  371. if (gameMode === 'b') {
  372. curCircle.index = i;
  373. }
  374. curCircleInfo.distance = Math.floor(
  375. validPath.distanceBetweenPoints / curDivisor
  376. );
  377. // Add to the list
  378. curCircle.info = curCircleInfo;
  379. self.circles.list.push(curCircle);
  380. self.control.correctX +=
  381. Math.floor(validPath.distanceBetweenPoints / curDivisor) *
  382. curCircle.info.direc;
  383. }
  384. // Restart if
  385. // Does not have base difficulty
  386. if (!hasBaseDifficulty) {
  387. restart = true;
  388. }
  389. // Calculate next circle
  390. self.control.nextX =
  391. validPath.x0 +
  392. self.circles.list[0].info.distance * self.circles.list[0].info.direc;
  393. // Restart if
  394. // Correct position is out of bounds
  395. let isBeforeMin, isAfterMax;
  396. if (gameOperation === 'minus') {
  397. isBeforeMin = self.control.correctX > validPath.x0;
  398. isAfterMax =
  399. self.control.correctX <
  400. validPath.x0 - 3 * validPath.distanceBetweenPoints;
  401. } else {
  402. isBeforeMin = self.control.correctX < validPath.x0;
  403. isAfterMax =
  404. self.control.correctX >
  405. validPath.x0 + 3 * validPath.distanceBetweenPoints;
  406. }
  407. if (isBeforeMin || isAfterMax) {
  408. restart = true;
  409. }
  410. // If game is type (b), selectiong a random balloon place
  411. if (gameMode === 'b') {
  412. balloonX = validPath.x0;
  413. self.control.correctIndex = game.math.randomInRange(
  414. self.control.numberOfPlusFractions,
  415. self.circles.list.length
  416. );
  417. for (let i = 0; i < self.control.correctIndex; i++) {
  418. balloonX +=
  419. self.circles.list[i].info.distance *
  420. self.circles.list[i].info.direc;
  421. }
  422. // Restart if
  423. // Balloon position is out of bounds
  424. if (gameOperation === 'minus') {
  425. isBeforeMin = balloonX > validPath.x0;
  426. isAfterMax =
  427. balloonX < validPath.x0 - 3 * validPath.distanceBetweenPoints;
  428. } else {
  429. isBeforeMin = balloonX < validPath.x0;
  430. isAfterMax = balloonX > validPath.x0 + self.road.width;
  431. }
  432. if (isBeforeMin || isAfterMax) {
  433. restart = true;
  434. }
  435. }
  436. return [restart, balloonX];
  437. },
  438. renderCharacters: function (validPath, balloonX) {
  439. // KID
  440. self.kid = game.add.sprite(
  441. validPath.x0,
  442. validPath.y0 - 32 - self.circles.list.length * self.circles.diameter,
  443. 'kid_walking',
  444. 0,
  445. 1.2
  446. );
  447. self.kid.anchor(0.5, 0.8);
  448. self.animation.list.right = [
  449. 'right',
  450. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
  451. 4,
  452. ];
  453. self.animation.list.left = [
  454. 'left',
  455. [23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12],
  456. 4,
  457. ];
  458. if (gameOperation === 'minus') {
  459. self.kid.animation = self.animation.list.left;
  460. self.kid.curFrame = 23;
  461. } else {
  462. self.kid.animation = self.animation.list.right;
  463. }
  464. // BALLOON
  465. self.balloon = game.add.image(
  466. balloonX,
  467. validPath.y0 - 295,
  468. 'balloon',
  469. 1.5,
  470. 0.5
  471. );
  472. self.balloon.alpha = 0.5;
  473. self.balloon.anchor(0.5, 0.5);
  474. self.basket = game.add.image(
  475. balloonX,
  476. validPath.y0 - 95,
  477. 'balloon_basket',
  478. 1.5
  479. );
  480. self.basket.alpha = 0.8;
  481. self.basket.anchor(0.5, 0.5);
  482. },
  483. renderUI: function () {
  484. // Help pointer
  485. self.help = game.add.image(0, 0, 'pointer', 2, 0);
  486. // Intro text
  487. const correctMessage =
  488. gameMode === 'a'
  489. ? game.lang.circleOne_intro_a
  490. : game.lang.circleOne_intro_b;
  491. const treatedMessage = correctMessage.split('\\n');
  492. self.message = [];
  493. self.message.push(
  494. game.add.text(
  495. context.canvas.width / 2,
  496. 170,
  497. treatedMessage[0],
  498. textStyles.h1_
  499. )
  500. );
  501. self.message.push(
  502. game.add.text(
  503. context.canvas.width / 2,
  504. 220,
  505. treatedMessage[1],
  506. textStyles.h1_
  507. )
  508. );
  509. // Modal
  510. self.continue.modal = game.add.geom.rect(
  511. 0,
  512. 0,
  513. context.canvas.width,
  514. context.canvas.height,
  515. undefined,
  516. 0,
  517. colors.white,
  518. 0
  519. );
  520. // Fraction operation
  521. self.utils.renderFractionCalculationUI();
  522. // continue button
  523. self.continue.button = game.add.geom.rect(
  524. context.canvas.width / 2,
  525. context.canvas.height / 2 + 200,
  526. 300,
  527. 100,
  528. undefined,
  529. 0,
  530. colors.green,
  531. 0
  532. );
  533. self.continue.button.anchor(0.5, 0.5);
  534. self.continue.text = game.add.text(
  535. context.canvas.width / 2,
  536. context.canvas.height / 2 + 16 + 200,
  537. game.lang.continue,
  538. textStyles.btn
  539. );
  540. self.continue.text.alpha = 0;
  541. },
  542. renderFractionCalculationUI: function () {
  543. let validCircles = self.circles.list;
  544. if (gameMode === 'b') {
  545. validCircles = [];
  546. for (let i = 0; i < self.control.correctIndex; i++) {
  547. validCircles.push(self.circles.list[i]);
  548. }
  549. }
  550. const font = textStyles.fraction;
  551. font.fill = colors.green;
  552. const nominators = [];
  553. const denominators = [];
  554. const renderList = [];
  555. const padding = 100;
  556. const offsetX = 100;
  557. const widthOfChar = 35;
  558. const x0 = padding;
  559. const y0 = context.canvas.height / 2;
  560. let nextX = x0;
  561. const cardHeight = 400;
  562. const cardX = x0 - padding;
  563. const cardY = y0; // + cardHeight / 4;
  564. // Card
  565. const card = game.add.geom.rect(
  566. cardX,
  567. cardY,
  568. 0,
  569. cardHeight,
  570. colors.blueDark,
  571. 8,
  572. colors.blueLight,
  573. 0.5
  574. );
  575. card.id = 'card';
  576. card.anchor(0, 0.5);
  577. renderList.push(card);
  578. // Fraction list
  579. for (let i in validCircles) {
  580. const curFraction = validCircles[i].info.fraction;
  581. let curFractionSign = '+';
  582. if (curFraction.labels[3].name === '-') {
  583. curFractionSign = '-';
  584. font.fill = colors.red;
  585. }
  586. renderList.push(
  587. game.add.text(x0 + i * offsetX, y0 + 35, curFractionSign, font)
  588. );
  589. renderList.push(
  590. game.add.text(x0 + i * offsetX + offsetX / 2, y0, '1', font)
  591. );
  592. renderList.push(
  593. game.add.text(x0 + offsetX / 2 + i * offsetX, y0, '_', font)
  594. );
  595. renderList.push(
  596. game.add.text(
  597. x0 + i * offsetX + offsetX / 2,
  598. y0 + 70,
  599. curFraction.labels[0].name,
  600. font
  601. )
  602. );
  603. nominators.push(curFraction.nominator);
  604. denominators.push(curFraction.denominator);
  605. }
  606. // Setup for fraction operation with least common multiple
  607. font.fill = colors.black;
  608. const updatedNominators = [];
  609. const mmc = game.math.mmcArray(denominators);
  610. let resultNominator = 0;
  611. for (let i in nominators) {
  612. const temp = nominators[i] * (mmc / denominators[i]);
  613. updatedNominators.push(temp);
  614. resultNominator += temp;
  615. }
  616. const resultNominatorUnsigned =
  617. resultNominator < 0 ? -resultNominator : resultNominator;
  618. const resultAux = resultNominator / mmc;
  619. const result =
  620. ('' + resultAux).length > 4 ? resultAux.toFixed(2) : resultAux;
  621. // let fracLine = '';
  622. // const updatedNominatorsString = updatedNominators
  623. // .map((n) => {
  624. // const len = ('' + n).length;
  625. // for (let i = 0; i < len; i++) fracLine += '_';
  626. // fracLine = n < 0 ? fracLine : fracLine + '_';
  627. // return n >= 0 ? '+' + n : n;
  628. // })
  629. // .join('');
  630. // const fractionMiddleX = widthOfChar * (fracLine.length / 2);
  631. // Fraction operation with least common multiple
  632. nextX = x0 + validCircles.length * offsetX + 20;
  633. // renderList.push(game.add.text(nextX, y0 + 35, '=', font));
  634. // nextX += offsetX / 2;
  635. // renderList.push(game.add.text(nextX, y0, updatedNominatorsString, font));
  636. // renderList.push(
  637. // game.add.text(nextX + fractionMiddleX, y0 + 70, mmc, font)
  638. // );
  639. // renderList.push(game.add.text(nextX, y0, fracLine, font));
  640. // Fraction result
  641. //nextX += fractionMiddleX * 2 + 50;
  642. renderList.push(game.add.text(nextX, y0 + 35, '=', font));
  643. font.align = 'center';
  644. nextX += offsetX + 40;
  645. renderList.push(
  646. game.add.text(nextX - 80, y0 + 35, result >= 0 ? '' : '-', font)
  647. );
  648. renderList.push(game.add.text(nextX, y0, resultNominatorUnsigned, font));
  649. renderList.push(game.add.text(nextX, y0 + 70, mmc, font));
  650. renderList.push(game.add.text(nextX, y0, '___', font));
  651. // Fraction result simplified setup
  652. const mdcAux = game.math.mdc(resultNominator, mmc);
  653. const mdc = mdcAux < 0 ? -mdcAux : mdcAux;
  654. if (mdc !== 1) {
  655. nextX += offsetX;
  656. renderList.push(game.add.text(nextX, y0 + 35, '=', font));
  657. nextX += offsetX;
  658. renderList.push(
  659. game.add.text(nextX - 50, y0 + 35, result > 0 ? '' : '-', font)
  660. );
  661. renderList.push(
  662. game.add.text(nextX, y0, resultNominatorUnsigned / mdc, font)
  663. );
  664. renderList.push(game.add.text(nextX, y0 + 70, mmc / mdc, font));
  665. renderList.push(game.add.text(nextX, y0, '__', font));
  666. }
  667. // Decimal result
  668. // nextX += offsetX;
  669. // renderList.push(game.add.text(nextX, y0 + 35, '=', font));
  670. // nextX += offsetX / 2;
  671. // renderList.push(game.add.text(nextX, y0 + 35, result, font));
  672. //let resultWidth = ('' + result).length * widthOfChar;
  673. let resultWidth = '__'.length * widthOfChar;
  674. const cardWidth = nextX - x0 + resultWidth + padding * 2;
  675. card.width = cardWidth;
  676. self.control.endSignX =
  677. (context.canvas.width - cardWidth) / 2 + cardWidth;
  678. // Center Card
  679. moveList(renderList, (context.canvas.width - cardWidth) / 2, 0);
  680. renderList.forEach((item) => {
  681. item.alpha = 0;
  682. });
  683. self.fractionOperationUI = renderList;
  684. },
  685. // UPDATE
  686. animateKid: function () {
  687. let lowerCircles = undefined;
  688. let curCircle = self.circles.list[self.circles.cur];
  689. let curDirec = curCircle.info.direc;
  690. // Move
  691. self.circles.list.forEach((circle) => {
  692. circle.x += self.animation.walkOffsetX * curDirec;
  693. });
  694. self.kid.x += self.animation.walkOffsetX * curDirec;
  695. self.walkedPath[self.control.curWalkedPath].width +=
  696. self.animation.walkOffsetX * curDirec;
  697. // Update arc
  698. curCircle.info.angleDegree += self.animation.angleOffset * curDirec;
  699. curCircle.angleEnd = game.math.degreeToRad(curCircle.info.angleDegree);
  700. // When finish current circle
  701. if (curCircle.info.direction === 'right') {
  702. lowerCircles = curCircle.x >= self.control.nextX;
  703. } else if (curCircle.info.direction === 'left') {
  704. lowerCircles = curCircle.x <= self.control.nextX;
  705. // If just changed from 'right' to 'left' inform to change direction of kid animation
  706. if (
  707. self.animation.invertDirection === undefined &&
  708. self.circles.cur > 0 &&
  709. self.circles.list[self.circles.cur - 1].info.direction === 'right'
  710. ) {
  711. self.animation.invertDirection = true;
  712. }
  713. }
  714. // Change direction of kid animation
  715. if (self.animation.invertDirection) {
  716. self.animation.invertDirection = false;
  717. game.animation.stop(self.kid.animation[0]);
  718. self.kid.animation = self.animation.list.left;
  719. self.kid.curFrame = 23;
  720. game.animation.play('left');
  721. self.control.curWalkedPath = 1;
  722. self.utils.renderWalkedPath(
  723. curCircle.x,
  724. self.walkedPath[0].y + 8,
  725. colors.red
  726. );
  727. }
  728. if (lowerCircles) {
  729. // Hide current circle
  730. curCircle.alpha = 0;
  731. // Lowers kid and other circles
  732. self.circles.list.forEach((circle) => {
  733. circle.y += self.circles.diameter;
  734. });
  735. self.kid.y += self.circles.diameter;
  736. self.circles.cur++; // Update index of current circle
  737. if (self.circles.list[self.circles.cur]) {
  738. curCircle = self.circles.list[self.circles.cur];
  739. curDirec = curCircle.info.direc;
  740. self.control.nextX += curCircle.info.distance * curDirec; // Update next position
  741. }
  742. }
  743. // When finish all circles (final position)
  744. if (
  745. self.circles.cur === self.circles.list.length ||
  746. curCircle.alpha === 0
  747. ) {
  748. self.animation.animateKid = false;
  749. self.control.checkAnswer = true;
  750. }
  751. },
  752. checkAnswer: function () {
  753. game.timer.stop();
  754. game.animation.stop(self.kid.animation[0]);
  755. self.fractionOperationUI.forEach((item) => {
  756. item.alpha = item.id === 'card' ? 0.5 : 1;
  757. });
  758. self.continue.modal.alpha = 0.3;
  759. if (game.math.isOverlap(self.basket, self.kid)) {
  760. self.control.isCorrect = true;
  761. self.kid.curFrame = self.kid.curFrame < 12 ? 24 : 25;
  762. if (audioStatus) game.audio.okSound.play();
  763. game.add
  764. .image(
  765. self.control.endSignX + 50, //context.canvas.width / 2,
  766. context.canvas.height / 2,
  767. 'answer_correct'
  768. )
  769. .anchor(0.5, 0.5);
  770. self.continue.text.name = game.lang.continue;
  771. completedLevels++;
  772. if (isDebugMode) console.log('Completed Levels: ' + completedLevels);
  773. } else {
  774. self.control.isCorrect = false; // Answer is incorrect
  775. if (audioStatus) game.audio.errorSound.play();
  776. game.add
  777. .image(
  778. self.control.endSignX, //context.canvas.width / 2,
  779. context.canvas.height / 2,
  780. 'answer_wrong'
  781. )
  782. .anchor(0.5, 0.5);
  783. self.continue.text.name = game.lang.retry;
  784. }
  785. self.fetch.postScore();
  786. self.control.checkAnswer = false;
  787. self.animation.counter = 0;
  788. self.animation.animateBalloon = true;
  789. },
  790. animateBalloon: function () {
  791. self.animation.counter++;
  792. self.balloon.y -= 2;
  793. self.basket.y -= 2;
  794. if (self.control.isCorrect) self.kid.y -= 2;
  795. if (self.animation.counter >= 100) {
  796. if (self.control.isCorrect) canGoToNextMapPosition = true;
  797. else canGoToNextMapPosition = false;
  798. self.control.showEndInfo = true;
  799. self.utils.showEndInfo();
  800. }
  801. },
  802. endLevel: function () {
  803. game.state.start('map');
  804. },
  805. // INFORMATION
  806. /**
  807. * Show correct answer
  808. */
  809. showAnswer: function () {
  810. if (!self.control.hasClicked) {
  811. // On gameMode (a)
  812. if (gameMode === 'a') {
  813. self.help.x = self.control.correctX - 20;
  814. self.help.y = self.road.y;
  815. // On gameMode (b)
  816. } else {
  817. self.help.x = self.circles.list[self.control.correctIndex - 1].x;
  818. self.help.y = self.circles.list[self.control.correctIndex - 1].y; // - self.circles.diameter / 2;
  819. }
  820. self.help.alpha = 0.7;
  821. }
  822. },
  823. showEndInfo: function () {
  824. let color;
  825. //let text;
  826. if (self.control.isCorrect) {
  827. color = colors.green;
  828. //text = game.lang.continue;
  829. } else {
  830. color = colors.red;
  831. //text = game.lang.retry;
  832. }
  833. // self.continue.text.name = text;
  834. self.continue.text.alpha = 1;
  835. self.continue.button.fillColor = color;
  836. self.continue.button.alpha = 1;
  837. },
  838. // HANDLERS
  839. /**
  840. * (in gameMode 'b') Function called when player clicked over a valid circle
  841. *
  842. * @param {number|object} cur clicked circle
  843. */
  844. clickCircleHandler: function (cur) {
  845. if (!self.control.hasClicked) {
  846. // On gameMode (a)
  847. if (gameMode === 'a') {
  848. self.balloon.x = cur;
  849. self.basket.x = cur;
  850. }
  851. // On gameMode (b)
  852. if (gameMode === 'b') {
  853. document.body.style.cursor = 'auto';
  854. for (let i in self.circles.list) {
  855. if (i <= cur.index) {
  856. self.circles.list[i].alpha = 1; // Keep selected circle
  857. self.control.fractionIndex = cur.index;
  858. } else {
  859. self.circles.list[i].alpha = 0; // Hide unselected circle
  860. self.kid.y += self.circles.diameter; // Lower kid to selected circle
  861. }
  862. }
  863. }
  864. if (audioStatus) game.audio.popSound.play();
  865. // Hide fractions
  866. if (showFractions) {
  867. self.circles.list.forEach((circle) => {
  868. circle.info.fraction.labels.forEach((labelPart) => {
  869. labelPart.alpha = 0;
  870. });
  871. });
  872. }
  873. // Hide solution pointer
  874. if (self.help != undefined) self.help.alpha = 0;
  875. self.message[0].alpha = 0;
  876. self.message[1].alpha = 0;
  877. self.balloon.alpha = 1;
  878. self.basket.alpha = 1;
  879. self.walkedPath[self.control.curWalkedPath].alpha = 1;
  880. self.control.hasClicked = true;
  881. self.animation.animateKid = true;
  882. game.animation.play(self.kid.animation[0]);
  883. }
  884. },
  885. /**
  886. * (in gameMode 'b') Function called when cursor is over a valid circle
  887. *
  888. * @param {object} cur circle the cursor is over
  889. */
  890. overCircleHandler: function (cur) {
  891. if (!self.control.hasClicked) {
  892. document.body.style.cursor = 'pointer';
  893. for (let i in self.circles.list) {
  894. const alpha = i <= cur.index ? 1 : 0.5;
  895. self.circles.list[i].alpha = alpha;
  896. self.circles.list[i].info.fraction.labels.forEach((lbl) => {
  897. lbl.alpha = alpha;
  898. });
  899. }
  900. }
  901. },
  902. /**
  903. * (in gameMode 'b') Function called when cursor leaves a valid circle
  904. */
  905. outCircleHandler: function () {
  906. if (!self.control.hasClicked) {
  907. document.body.style.cursor = 'auto';
  908. const alpha = 1;
  909. self.circles.list.forEach((circle) => {
  910. circle.alpha = alpha;
  911. circle.info.fraction.labels.forEach((lbl) => {
  912. lbl.alpha = alpha;
  913. });
  914. });
  915. }
  916. },
  917. },
  918. events: {
  919. /**
  920. * Called by mouse click event
  921. *
  922. * @param {object} mouseEvent contains the mouse click coordinates
  923. */
  924. onInputDown: function (mouseEvent) {
  925. const x = game.math.getMouse(mouseEvent).x;
  926. const y = game.math.getMouse(mouseEvent).y;
  927. // GAME MODE A : click road
  928. if (gameMode === 'a') {
  929. const isValid =
  930. y > 150 && x >= self.road.x && x <= self.road.x + self.road.width;
  931. if (isValid) self.utils.clickCircleHandler(x);
  932. }
  933. // GAME MODE B : click circle
  934. if (gameMode === 'b') {
  935. self.circles.list.forEach((circle) => {
  936. const isValid =
  937. game.math.distanceToPointer(
  938. x,
  939. circle.xWithAnchor,
  940. y,
  941. circle.yWithAnchor
  942. ) <=
  943. (circle.diameter / 2) * circle.scale;
  944. if (isValid) self.utils.clickCircleHandler(circle);
  945. });
  946. }
  947. // Continue button
  948. if (self.control.showEndInfo) {
  949. if (game.math.isOverIcon(x, y, self.continue.button)) {
  950. self.utils.endLevel();
  951. }
  952. }
  953. navigation.onInputDown(x, y);
  954. game.render.all();
  955. },
  956. /**
  957. * Called by mouse move event
  958. *
  959. * @param {object} mouseEvent contains the mouse move coordinates
  960. */
  961. onInputOver: function (mouseEvent) {
  962. const x = game.math.getMouse(mouseEvent).x;
  963. const y = game.math.getMouse(mouseEvent).y;
  964. let isOverCircle = false;
  965. // GAME MODE A : balloon follow mouse
  966. if (gameMode === 'a' && !self.control.hasClicked) {
  967. if (
  968. game.math.distanceToPointer(x, self.balloon.x, y, self.balloon.y) > 8
  969. ) {
  970. self.balloon.x = x;
  971. self.basket.x = x;
  972. }
  973. document.body.style.cursor = 'auto';
  974. }
  975. // GAME MODE B : hover circle
  976. if (gameMode === 'b' && !self.control.hasClicked) {
  977. self.circles.list.forEach((circle) => {
  978. const isValid =
  979. game.math.distanceToPointer(
  980. x,
  981. circle.xWithAnchor,
  982. y,
  983. circle.yWithAnchor
  984. ) <=
  985. (circle.diameter / 2) * circle.scale;
  986. if (isValid) {
  987. self.utils.overCircleHandler(circle);
  988. isOverCircle = true;
  989. }
  990. });
  991. if (!isOverCircle) self.utils.outCircleHandler();
  992. }
  993. // Continue button
  994. if (self.control.showEndInfo) {
  995. if (game.math.isOverIcon(x, y, self.continue.button)) {
  996. // If pointer is over icon
  997. document.body.style.cursor = 'pointer';
  998. self.continue.button.scale = self.continue.button.initialScale * 1.1;
  999. self.continue.text.style = textStyles.btnLg;
  1000. } else {
  1001. // If pointer is not over icon
  1002. document.body.style.cursor = 'auto';
  1003. self.continue.button.scale = self.continue.button.initialScale * 1;
  1004. self.continue.text.style = textStyles.btn;
  1005. }
  1006. }
  1007. navigation.onInputOver(x, y);
  1008. game.render.all();
  1009. },
  1010. },
  1011. fetch: {
  1012. /**
  1013. * Saves players data after level ends - to be sent to database <br>
  1014. *
  1015. * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
  1016. *
  1017. * @see /php/squareOne.js
  1018. */
  1019. postScore: function () {
  1020. // Creates string that is going to be sent to db
  1021. const data =
  1022. '&line_game=' +
  1023. gameShape +
  1024. '&line_mode=' +
  1025. gameMode +
  1026. '&line_oper=' +
  1027. gameOperation +
  1028. '&line_leve=' +
  1029. gameDifficulty +
  1030. '&line_posi=' +
  1031. curMapPosition +
  1032. '&line_resu=' +
  1033. self.control.isCorrect +
  1034. '&line_time=' +
  1035. game.timer.elapsed +
  1036. '&line_deta=' +
  1037. 'numCircles:' +
  1038. self.circles.list.length +
  1039. ', valCircles: ' +
  1040. self.control.divisorsList +
  1041. ' balloonX: ' +
  1042. self.basket.x +
  1043. ', selIndex: ' +
  1044. self.control.fractionIndex;
  1045. // FOR MOODLE
  1046. sendToDatabase(data);
  1047. },
  1048. },
  1049. };