squareTwo.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. /******************************
  2. * This file holds game states.
  3. ******************************/
  4. /** [GAME STATE]
  5. *
  6. * .squareTwo. = gameName
  7. * .../...\...
  8. * ..a.....b.. = gameMode
  9. * ....\./....
  10. * .....|.....
  11. * ..equals... = gameOperation
  12. * .....|.....
  13. * .1,2,3,4,5. = gameDifficulty
  14. *
  15. * Character : kid
  16. * Theme : (not themed)
  17. * Concept : player select equivalent dividends for fractions with different divisors
  18. * Represent fractions as : subdivided rectangles
  19. *
  20. * Game modes can be :
  21. *
  22. * a : equivalence of fractions
  23. * top has more subdivisions
  24. * b : equivalence of fractions
  25. * bottom has more subdivisions
  26. *
  27. * Operations :
  28. *
  29. * equals : Player selects equivalent fractions of both blocks
  30. *
  31. * @namespace
  32. */
  33. const squareTwo = {
  34. ui: undefined,
  35. control: undefined,
  36. blocks: undefined,
  37. /**
  38. * Main code
  39. */
  40. create: function () {
  41. this.ui = {
  42. message: undefined,
  43. startChoice: {
  44. message: undefined,
  45. card: undefined,
  46. image: undefined,
  47. title: undefined,
  48. subtitle: undefined,
  49. accept: {
  50. button: undefined,
  51. text: undefined,
  52. },
  53. yes: {
  54. button: undefined,
  55. text: undefined,
  56. },
  57. no: {
  58. button: undefined,
  59. text: undefined,
  60. },
  61. },
  62. continue: {
  63. // modal: undefined,
  64. button: undefined,
  65. text: undefined,
  66. },
  67. explanation: {
  68. button: undefined,
  69. text: undefined,
  70. },
  71. };
  72. this.control = {
  73. blockWidth: 600,
  74. blockHeight: 75,
  75. isCorrect: false,
  76. showEndInfo: false,
  77. showExplanation: false,
  78. animationDelay: 0,
  79. startDelay: false,
  80. startEndAnimation: false,
  81. started: false,
  82. blockConfig: undefined,
  83. challengePreview: undefined,
  84. challengeAnsweredYes: undefined,
  85. };
  86. this.blocks = {
  87. top: {
  88. list: [], // List of block objects
  89. auxBlocks: [], // List of shadow under selection blocks
  90. fractions: [], // Fractions
  91. selectedAmount: 0,
  92. hasClicked: false, // Check if player clicked blocks from (a)
  93. animate: false, // Animate blocks from (a)
  94. warningText: undefined,
  95. label: undefined,
  96. },
  97. bottom: {
  98. list: [],
  99. auxBlocks: [],
  100. fractions: [],
  101. selectedAmount: 0,
  102. hasClicked: false,
  103. animate: false,
  104. warningText: undefined,
  105. label: undefined,
  106. },
  107. };
  108. renderBackground();
  109. // Calls function that loads navigation icons
  110. // FOR MOODLE
  111. if (moodle) {
  112. navigation.add.right(['audio']);
  113. } else {
  114. navigation.add.left(['back', 'menu'], 'customMenu');
  115. navigation.add.right(['audio']);
  116. }
  117. // Pre-generate the level configuration so the challenge modal can preview real fractions
  118. this.control.blockConfig = this.utils.generateBlockConfig();
  119. this.control.challengePreview = this.utils.generateChallengePreview(
  120. this.control.blockConfig
  121. );
  122. // Wait for user confirmation before starting the level
  123. this.utils.renderStartChoiceUI();
  124. game.event.add('click', this.events.onInputDown);
  125. game.event.add('mousemove', this.events.onInputOver);
  126. },
  127. /**
  128. * Game loop
  129. */
  130. update: function () {
  131. // Animate blocks
  132. if (self.blocks.top.animate || self.blocks.bottom.animate) {
  133. self.utils.moveBlocks();
  134. }
  135. // If (a) and (b) are already clicked
  136. if (
  137. !self.control.startDelay &&
  138. !self.control.startEndAnimation &&
  139. self.blocks.top.hasClicked &&
  140. self.blocks.bottom.hasClicked
  141. ) {
  142. self.control.startDelay = true;
  143. }
  144. if (self.control.startDelay && !self.control.startEndAnimation) {
  145. self.utils.startDelayHandler();
  146. }
  147. // Wait a bit and go to map state
  148. if (self.control.startEndAnimation) {
  149. self.utils.runEndAnimation();
  150. }
  151. game.render.all();
  152. },
  153. utils: {
  154. // RENDERS
  155. generateBlockConfig: function () {
  156. // Coordinates for (a) and (b)
  157. let xA, xB, yA, yB;
  158. if (gameMode != 'b') {
  159. // Has more subdivisions on (a)
  160. xA = context.canvas.width / 2 - self.control.blockWidth / 2;
  161. yA = getFrameInfo().y + 100;
  162. xB = xA;
  163. yB = yA + 3 * self.control.blockHeight + 30;
  164. } else {
  165. // Has more subdivisions on (b)
  166. xB = context.canvas.width / 2 - self.control.blockWidth / 2;
  167. yB = getFrameInfo().y + 100;
  168. xA = xB;
  169. yA = yB + 3 * self.control.blockHeight + 30;
  170. }
  171. // Possible subdivisionList for (a)
  172. const subdivisionList = [2, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20];
  173. // Random index for 'subdivision'
  174. const randomIndex = game.math.randomInRange(
  175. (gameDifficulty - 1) * 2 + 1,
  176. (gameDifficulty - 1) * 2 + 3
  177. );
  178. // Number of subdivisions of (a) and (b) (blocks)
  179. const totalBlocksA = subdivisionList[randomIndex];
  180. const totalBlocksB = game.math.randomDivisor(totalBlocksA);
  181. const blockWidthA = self.control.blockWidth / totalBlocksA;
  182. const blockWidthB = self.control.blockWidth / totalBlocksB;
  183. if (isDebugMode) {
  184. console.log(
  185. '------------------------------' +
  186. '\nGame Map Position: ' +
  187. curMapPosition +
  188. '\n------------------------ setup' +
  189. '\narray: [2, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20]' +
  190. '\nMin index ((gameDifficulty - 1) * 2 + 1): ' +
  191. ((gameDifficulty - 1) * 2 + 1) +
  192. '\nMax index ((gameDifficulty - 1) * 2 + 3): ' +
  193. ((gameDifficulty - 1) * 2 + 3) +
  194. '\n------------------------ this' +
  195. '\nget random min max for A: array[' +
  196. randomIndex +
  197. '] = ' +
  198. totalBlocksA +
  199. '\nget random divisor for B: ' +
  200. totalBlocksB +
  201. '\n------------------------------'
  202. );
  203. }
  204. return {
  205. xA,
  206. yA,
  207. xB,
  208. yB,
  209. totalBlocksA,
  210. totalBlocksB,
  211. blockWidthA,
  212. blockWidthB,
  213. };
  214. },
  215. generateChallengePreview: function (blockConfig) {
  216. if (!blockConfig) return undefined;
  217. const { totalBlocksA, totalBlocksB } = blockConfig;
  218. if (!totalBlocksA || !totalBlocksB) return undefined;
  219. const gcd = (a, b) => {
  220. let x = Math.abs(a);
  221. let y = Math.abs(b);
  222. while (y !== 0) {
  223. const t = x % y;
  224. x = y;
  225. y = t;
  226. }
  227. return x;
  228. };
  229. // Always produce an equivalent integer pair using the gcd
  230. const g = gcd(totalBlocksA, totalBlocksB);
  231. const leftUnit = totalBlocksA / g;
  232. const rightUnit = totalBlocksB / g;
  233. // Deterministic preview (prevents changing values between renders)
  234. const selectedA = leftUnit;
  235. const selectedB = rightUnit;
  236. // Show as left/right fractions in the modal (keep order consistent with blocks)
  237. return {
  238. left: { num: selectedA, den: totalBlocksA },
  239. right: { num: selectedB, den: totalBlocksB },
  240. };
  241. },
  242. renderStartChoiceUI: () => {
  243. const centerX = context.canvas.width / 2;
  244. const centerY = context.canvas.height / 2;
  245. const yesLabel = game.lang.yes;
  246. const noLabel = game.lang.no;
  247. const withNewlines = (s) => (s == null ? '' : String(s).replace(/\\n/g, '\n'));
  248. const title = withNewlines(game.lang.s2_challenge_title);
  249. const subtitle = withNewlines(game.lang.s2_challenge_subtitle);
  250. const question = withNewlines(game.lang.s2_challenge_question);
  251. // Layout reference (no white panel/card)
  252. const cardH = 560;
  253. const panelTop = centerY - cardH / 2;
  254. // Challenge image (ribbon + question mark)
  255. const imageScale = 0.7;
  256. const imageY = panelTop - 150;
  257. self.ui.startChoice.image = game.add.image(
  258. centerX,
  259. imageY,
  260. 'challenge',
  261. imageScale,
  262. 1
  263. );
  264. self.ui.startChoice.image.anchor(0.5, 0);
  265. // Title + subtitle
  266. self.ui.startChoice.title = game.add.text(
  267. centerX,
  268. imageY + 65,
  269. title,
  270. { ...textStyles.h2_, fill: colors.white, font: 'bold ' + textStyles.h2_.font }
  271. );
  272. self.ui.startChoice.title.anchor(0.5, 0.5);
  273. // Subtitle split in two lines (top emphasized)
  274. const subtitleLines = String(subtitle || '').split('\n');
  275. const subtitleTop = subtitleLines[0] || '';
  276. const subtitleBottom = subtitleLines.slice(1).join('\n');
  277. self.ui.startChoice.subtitle = {};
  278. self.ui.startChoice.subtitle.top = game.add.text(
  279. centerX,
  280. panelTop + 10,
  281. subtitleTop,
  282. {
  283. ...textStyles.h4_,
  284. font: 'bold ' + textStyles.h4_.font,
  285. fill: colors.blueDark,
  286. }
  287. );
  288. self.ui.startChoice.subtitle.top.anchor(0.5, 0.5);
  289. self.ui.startChoice.subtitle.bottom = game.add.text(
  290. centerX,
  291. panelTop + 55,
  292. subtitleBottom,
  293. { ...textStyles.h3_, fill: colors.blue }
  294. );
  295. self.ui.startChoice.subtitle.bottom.anchor(0.5, 0.5);
  296. // Fractions preview (left and right) - based on the real generated blockConfig
  297. const preview =
  298. self.control.challengePreview ||
  299. self.utils.generateChallengePreview(self.control.blockConfig);
  300. self.control.challengePreview = preview;
  301. if (preview && preview.left && preview.right) {
  302. const fracFont = { ...textStyles.fraction, fill: colors.blueDark, align: 'center', font: '76px monospace' };
  303. const fracY = panelTop + 255;
  304. const offsetX = 260;
  305. const leftX = centerX - offsetX;
  306. const rightX = centerX + offsetX;
  307. const leftText = game.add.text(
  308. leftX,
  309. fracY,
  310. preview.left.num + '\n' + preview.left.den,
  311. fracFont,
  312. 70
  313. );
  314. leftText.anchor(0.5, 0.5);
  315. const leftLine = game.add.geom.rect(
  316. leftX,
  317. fracY + 10,
  318. 120,
  319. 4,
  320. colors.blueDark,
  321. 1,
  322. colors.blueDark,
  323. 4
  324. );
  325. leftLine.anchor(0.5, 0);
  326. const rightText = game.add.text(
  327. rightX,
  328. fracY,
  329. preview.right.num + '\n' + preview.right.den,
  330. fracFont,
  331. 70
  332. );
  333. rightText.anchor(0.5, 0.5);
  334. const rightLine = game.add.geom.rect(
  335. rightX,
  336. fracY + 10,
  337. 120,
  338. 4,
  339. colors.blueDark,
  340. 1,
  341. colors.blueDark,
  342. 4
  343. );
  344. rightLine.anchor(0.5, 0);
  345. self.ui.startChoice.fractions = {
  346. leftText,
  347. leftLine,
  348. rightText,
  349. rightLine,
  350. };
  351. }
  352. // Prompt
  353. self.ui.startChoice.message = game.add.text(
  354. centerX,
  355. panelTop + 415,
  356. question,
  357. {
  358. ...textStyles.h3_,
  359. font: 'bold ' + textStyles.h3_.font,
  360. fill: colors.blueDark,
  361. }
  362. );
  363. self.ui.startChoice.message.anchor(0.5, 0.5);
  364. // Buttons
  365. const buttonW = 340;
  366. const buttonH = 90;
  367. const gap = 90;
  368. const buttonsY = panelTop + 600;
  369. const choiceTextStyle = { ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font };
  370. const borderColor = colors.blueMenuLine;
  371. const fillColor = colors.white;
  372. const fillAlpha = 0.9;
  373. const selectedFillColor = colors.blueLight;
  374. const createPillButton = (x, y, label) => {
  375. // Invisible hit target used for hover/click detection
  376. const hit = game.add.geom.rect(
  377. x,
  378. y,
  379. buttonW,
  380. buttonH,
  381. colors.white,
  382. 0,
  383. colors.white,
  384. 0
  385. );
  386. hit.anchor(0.5, 0.5);
  387. const outerW = buttonW;
  388. const outerH = buttonH;
  389. const innerInset = 10;
  390. const innerW = outerW - innerInset;
  391. const innerH = outerH - innerInset;
  392. const outerRect = game.add.geom.rect(
  393. x,
  394. y,
  395. outerW,
  396. outerH,
  397. borderColor,
  398. 1,
  399. borderColor,
  400. 0
  401. );
  402. outerRect.anchor(0.5, 0.5);
  403. outerRect.shadow = true;
  404. outerRect.shadowColor = colors.gray;
  405. outerRect.shadowBlur = 8;
  406. const innerRect = game.add.geom.rect(
  407. x,
  408. y,
  409. innerW,
  410. innerH,
  411. fillColor,
  412. fillAlpha,
  413. fillColor,
  414. 0
  415. );
  416. innerRect.anchor(0.5, 0.5);
  417. const text = game.add.text(x, y + 10, label, choiceTextStyle);
  418. return {
  419. hit,
  420. text,
  421. outerParts: [outerRect],
  422. innerParts: [innerRect],
  423. setSelected: (isSelected) => {
  424. const c = isSelected ? selectedFillColor : fillColor;
  425. [innerRect].forEach((p) => {
  426. p.fillColor = c;
  427. p.lineColor = c;
  428. });
  429. },
  430. };
  431. };
  432. const yesX = centerX - (buttonW / 2 + gap / 2);
  433. const noX = centerX + (buttonW / 2 + gap / 2);
  434. const yes = createPillButton(yesX, buttonsY, yesLabel);
  435. const no = createPillButton(noX, buttonsY, noLabel);
  436. // keep existing structure expected by event handlers
  437. self.ui.startChoice.yes.button = yes.hit;
  438. self.ui.startChoice.yes.text = yes.text;
  439. self.ui.startChoice.yes.visual = yes;
  440. self.ui.startChoice.no.button = no.hit;
  441. self.ui.startChoice.no.text = no.text;
  442. self.ui.startChoice.no.visual = no;
  443. game.render.all();
  444. },
  445. updateChallengeChoiceUI: () => {
  446. const selected = self.control.challengeAnsweredYes;
  447. const yesBtn = self.ui.startChoice?.yes?.button;
  448. const noBtn = self.ui.startChoice?.no?.button;
  449. const yesVisual = self.ui.startChoice?.yes?.visual;
  450. const noVisual = self.ui.startChoice?.no?.visual;
  451. if (yesBtn) {
  452. if (yesVisual && yesVisual.setSelected) {
  453. yesVisual.setSelected(selected === true);
  454. } else {
  455. yesBtn.fillColor = selected === true ? colors.blueLight : colors.white;
  456. }
  457. }
  458. if (noBtn) {
  459. if (noVisual && noVisual.setSelected) {
  460. noVisual.setSelected(selected === false);
  461. } else {
  462. noBtn.fillColor = selected === false ? colors.blueLight : colors.white;
  463. }
  464. }
  465. game.render.all();
  466. },
  467. startGame: () => {
  468. if (self.control.started) return;
  469. self.control.started = true;
  470. // Hide pre-start UI
  471. if (self.ui.startChoice && self.ui.startChoice.message) {
  472. self.ui.startChoice.message.alpha = 0;
  473. }
  474. if (self.ui.startChoice && self.ui.startChoice.title) {
  475. self.ui.startChoice.title.alpha = 0;
  476. }
  477. if (self.ui.startChoice && self.ui.startChoice.subtitle) {
  478. if (self.ui.startChoice.subtitle.alpha != null) {
  479. self.ui.startChoice.subtitle.alpha = 0;
  480. } else {
  481. const s = self.ui.startChoice.subtitle;
  482. if (s.top) s.top.alpha = 0;
  483. if (s.bottom) s.bottom.alpha = 0;
  484. }
  485. }
  486. if (self.ui.startChoice && self.ui.startChoice.image) {
  487. self.ui.startChoice.image.alpha = 0;
  488. }
  489. if (self.ui.startChoice && self.ui.startChoice.fractions) {
  490. Object.values(self.ui.startChoice.fractions).forEach((obj) => {
  491. if (obj) obj.alpha = 0;
  492. });
  493. }
  494. if (self.ui.startChoice && self.ui.startChoice.card) {
  495. self.ui.startChoice.card.alpha = 0;
  496. }
  497. if (self.ui.startChoice && self.ui.startChoice.yes) {
  498. if (self.ui.startChoice.yes.button) self.ui.startChoice.yes.button.alpha = 0;
  499. if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.alpha = 0;
  500. if (self.ui.startChoice.yes.visual) {
  501. const v = self.ui.startChoice.yes.visual;
  502. if (v.outerParts) v.outerParts.forEach((p) => (p.alpha = 0));
  503. if (v.innerParts) v.innerParts.forEach((p) => (p.alpha = 0));
  504. }
  505. }
  506. if (self.ui.startChoice && self.ui.startChoice.no) {
  507. if (self.ui.startChoice.no.button) self.ui.startChoice.no.button.alpha = 0;
  508. if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.alpha = 0;
  509. if (self.ui.startChoice.no.visual) {
  510. const v = self.ui.startChoice.no.visual;
  511. if (v.outerParts) v.outerParts.forEach((p) => (p.alpha = 0));
  512. if (v.innerParts) v.innerParts.forEach((p) => (p.alpha = 0));
  513. }
  514. }
  515. if (self.ui.startChoice && self.ui.startChoice.accept) {
  516. if (self.ui.startChoice.accept.button)
  517. self.ui.startChoice.accept.button.alpha = 0;
  518. if (self.ui.startChoice.accept.text)
  519. self.ui.startChoice.accept.text.alpha = 0;
  520. }
  521. document.body.style.cursor = 'auto';
  522. // Add kid + blocks + UI (original flow)
  523. self.utils.renderCharacters();
  524. self.utils.renderBlockSetup(self.control.blockConfig);
  525. self.utils.renderMainUI();
  526. game.timer.start(); // Set a timer for the current level (used in postScore)
  527. game.render.all();
  528. },
  529. renderBlockSetup: function (blockConfig) {
  530. const cfg = blockConfig || self.utils.generateBlockConfig();
  531. if (!cfg) return;
  532. const {
  533. xA,
  534. yA,
  535. xB,
  536. yB,
  537. totalBlocksA,
  538. totalBlocksB,
  539. blockWidthA,
  540. blockWidthB,
  541. } = cfg;
  542. // (a)
  543. self.utils.renderBlocks(
  544. self.blocks.top,
  545. 'top',
  546. totalBlocksA,
  547. blockWidthA,
  548. colors.blueDark,
  549. colors.blueLight,
  550. xA,
  551. yA
  552. );
  553. // (b)
  554. self.utils.renderBlocks(
  555. self.blocks.bottom,
  556. 'bottom',
  557. totalBlocksB,
  558. blockWidthB,
  559. colors.greenDark,
  560. colors.greenLight,
  561. xB,
  562. yB
  563. );
  564. },
  565. renderBlocks: function (
  566. blocks,
  567. blockType,
  568. totalBlocks,
  569. blockWidth,
  570. lineColor,
  571. fillColor,
  572. x0,
  573. y0
  574. ) {
  575. for (let i = 0; i < totalBlocks; i++) {
  576. // Blocks
  577. const curX = x0 + i * blockWidth;
  578. const curBlock = game.add.geom.rect(
  579. curX,
  580. y0,
  581. blockWidth,
  582. self.control.blockHeight,
  583. fillColor,
  584. 0.5,
  585. lineColor,
  586. 4
  587. );
  588. curBlock.position = blockType;
  589. curBlock.index = i;
  590. curBlock.finalX = x0;
  591. blocks.list.push(curBlock);
  592. // Auxiliar blocks (lower alpha)
  593. const alpha = 0.2;
  594. const curYAux = y0 + self.control.blockHeight + 10;
  595. const curAuxBlock = game.add.geom.rect(
  596. curX,
  597. curYAux,
  598. blockWidth,
  599. self.control.blockHeight,
  600. fillColor,
  601. alpha,
  602. lineColor,
  603. 1
  604. );
  605. blocks.auxBlocks.push(curAuxBlock);
  606. }
  607. // Label - number of blocks (on the right)
  608. let yLabel = y0 + self.control.blockHeight / 2 + 10;
  609. const xLabel = x0 + self.control.blockWidth + 35;
  610. const font = {
  611. ...textStyles.h4_,
  612. font: 'bold ' + textStyles.h4_.font,
  613. fill: lineColor,
  614. };
  615. blocks.label = game.add.text(xLabel, yLabel, blocks.list.length, font);
  616. blocks.label.alpha = showFractions ? 1 : 0;
  617. // 'selected blocks/fraction' label for (a) : at the bottom of (a)
  618. yLabel = y0 + self.control.blockHeight + 40;
  619. blocks.fractions[0] = game.add.text(xLabel, yLabel, '', font);
  620. blocks.fractions[1] = game.add.geom.line(
  621. xLabel,
  622. yLabel + 10,
  623. xLabel + 50,
  624. yLabel + 10,
  625. 2,
  626. lineColor
  627. );
  628. blocks.fractions[1].anchor(0.5, 0);
  629. blocks.fractions[0].alpha = 0;
  630. blocks.fractions[1].alpha = 0;
  631. // Invalid selection text
  632. blocks.warningText = game.add.text(
  633. context.canvas.width / 2,
  634. y0 - 20,
  635. game.lang.s2_error_msg,
  636. { ...font, font: textStyles.h4_.font }
  637. );
  638. blocks.warningText.alpha = 0;
  639. },
  640. renderCharacters: function () {
  641. self.kidAnimation = game.add.sprite(
  642. 100,
  643. context.canvas.height - 128 * 1.5,
  644. 'kid_standing',
  645. 0,
  646. 1.2
  647. );
  648. self.kidAnimation.anchor(0.5, 0.7);
  649. self.kidAnimation.curFrame = 3;
  650. },
  651. renderMainUI: () => {
  652. // Intro text
  653. const treatedMessage = game.lang.squareTwo_intro.split('\\n');
  654. const font = textStyles.h1_;
  655. self.ui.message = [];
  656. self.ui.message.push(
  657. game.add.text(
  658. context.canvas.width / 2,
  659. 170,
  660. treatedMessage[0] + '\n' + treatedMessage[1],
  661. font
  662. )
  663. );
  664. },
  665. renderOperationUI: () => {
  666. const uiList = [
  667. ...self.blocks.top.list,
  668. ...self.blocks.bottom.list,
  669. ...self.blocks.top.fractions,
  670. ...self.blocks.bottom.fractions,
  671. ];
  672. moveList(uiList, -400, 0);
  673. const font = textStyles.fraction;
  674. font.fill = colors.black;
  675. font.align = 'center';
  676. const nominators = [
  677. self.blocks.top.selectedAmount,
  678. self.blocks.bottom.selectedAmount,
  679. ];
  680. const denominators = [
  681. self.blocks.top.list.length,
  682. self.blocks.bottom.list.length,
  683. ];
  684. if (gameMode === 'b') {
  685. const leftNom = nominators[0];
  686. const leftDenom = denominators[0];
  687. nominators[0] = nominators[1];
  688. denominators[0] = denominators[1];
  689. nominators[1] = leftNom;
  690. denominators[1] = leftDenom;
  691. }
  692. const renderList = [];
  693. const padding = 100;
  694. const offsetX = 100;
  695. const cardHeight = 400;
  696. const x0 = padding + 400;
  697. const y0 = context.canvas.height / 2;
  698. let nextX = x0;
  699. const cardX = x0 - padding;
  700. const cardY = y0;
  701. // Card
  702. const card = game.add.geom.rect(
  703. cardX,
  704. cardY,
  705. 0,
  706. cardHeight,
  707. colors.blueLight,
  708. 0.5,
  709. colors.blueDark,
  710. 8
  711. );
  712. card.id = 'card';
  713. card.anchor(0, 0.5);
  714. renderList.push(card);
  715. renderList.push(
  716. game.add.text(
  717. nextX,
  718. y0,
  719. nominators[0] + '\n' + denominators[0],
  720. font,
  721. 70
  722. )
  723. );
  724. const topFractionLine = game.add.geom.rect(
  725. nextX,
  726. y0 + 10,
  727. 100,
  728. 4,
  729. colors.black,
  730. 4
  731. );
  732. topFractionLine.anchor(0.5, 0);
  733. renderList.push(topFractionLine);
  734. font.fill = self.control.isCorrect ? colors.green : colors.red;
  735. nextX += offsetX;
  736. renderList.push(
  737. game.add.text(nextX, y0 + 35, self.control.isCorrect ? '=' : '≠', font)
  738. );
  739. font.fill = colors.black;
  740. nextX += offsetX;
  741. renderList.push(
  742. game.add.text(
  743. nextX,
  744. y0,
  745. nominators[1] + '\n' + denominators[1],
  746. font,
  747. 70
  748. )
  749. );
  750. const bottomFractionLine = game.add.geom.rect(
  751. nextX,
  752. y0 + 10,
  753. 100,
  754. 4,
  755. colors.black,
  756. 4
  757. );
  758. bottomFractionLine.anchor(0.5, 0);
  759. renderList.push(bottomFractionLine);
  760. //let resultWidth = ''.length * widthOfChar;
  761. const cardWidth = nextX - x0 + padding * 2;
  762. card.width = cardWidth;
  763. const endSignX =
  764. (context.canvas.width - cardWidth) / 2 + cardWidth + 400 + 50;
  765. // Center Card
  766. moveList(renderList, (context.canvas.width - cardWidth) / 2, 0);
  767. self.fractionOperationUI = renderList;
  768. return endSignX;
  769. },
  770. renderExplanationUI: () => {
  771. const cx = context.canvas.width / 2;
  772. const cy = context.canvas.height / 2;
  773. const withNewlines = (s) =>
  774. s == null ? '' : String(s).replace(/\\n/g, '\n');
  775. // WRONG ANSWER: simple small card (no equation/bars)
  776. if (!self.control.isCorrect) {
  777. const errW = 680; const errH = 220;
  778. const errLeft = cx - errW / 2; const errTop = cy - errH / 2;
  779. const errBottom = errTop + errH;
  780. game.add.geom.rect(errLeft, errTop, errW, errH, colors.white, 0.97, colors.red, 4);
  781. game.add.text(cx, cy - 12,
  782. game.lang.s2_explain_body_wrong,
  783. { ...textStyles.h3_, fill: colors.red }
  784. );
  785. const btnW = 360; const btnH = 70; const btnCY = errBottom - 52;
  786. self.ui.explanation.button = game.add.geom.rect(
  787. cx - btnW / 2, btnCY - btnH / 2, btnW, btnH, colors.red
  788. );
  789. self.ui.explanation.text = game.add.text(cx, btnCY + 16, game.lang.retry, textStyles.btn);
  790. self.control.showExplanation = true;
  791. return;
  792. }
  793. // CORRECT ANSWER: full explanation card
  794. // Image is 1298×816 — all positions are relative to image coordinates
  795. const imgW = 1298; const imgH = 816;
  796. const cardLeft = cx - imgW / 2; // 311
  797. const cardTop = cy - imgH / 2; // 132
  798. const cardBottom = cardTop + imgH; // 948
  799. // 1. Background image + title text (image has the 🔍 icon, we add the text)
  800. game.add.image(cx, cy, 'result-bg').anchor(0.5, 0.5);
  801. const titleText = withNewlines(game.lang.s2_explain_title);
  802. const titleY = cardTop + (titleText.includes('\n') ? 42 : 62);
  803. game.add.text(cx, titleY, titleText, {
  804. ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font,
  805. });
  806. // Fraction values
  807. let bigNum, bigDen, bigLineColor, bigFillColor;
  808. let smallNum, smallDen, smallLineColor, smallFillColor;
  809. if (gameMode !== 'b') {
  810. bigNum = self.blocks.top.selectedAmount; bigDen = self.blocks.top.list.length;
  811. bigLineColor = colors.blueDark; bigFillColor = colors.blue;
  812. smallNum = self.blocks.bottom.selectedAmount; smallDen = self.blocks.bottom.list.length;
  813. smallLineColor = colors.greenDark; smallFillColor = colors.green;
  814. } else {
  815. bigNum = self.blocks.bottom.selectedAmount; bigDen = self.blocks.bottom.list.length;
  816. bigLineColor = colors.greenDark; bigFillColor = colors.green;
  817. smallNum = self.blocks.top.selectedAmount; smallDen = self.blocks.top.list.length;
  818. smallLineColor = colors.blueDark; smallFillColor = colors.blue;
  819. }
  820. const factor = bigDen / smallDen;
  821. const fracStyle = { ...textStyles.fraction, fill: colors.blueDark, align: 'center' };
  822. // 2. Fraction numbers — placed over the image's fraction bar template
  823. // Image fraction bar LINE is at ~y=250 from image top; numY+32 should align with it
  824. // x centres measured from image: left≈24%, mid≈47%, right≈75%
  825. const numY = cardTop + 238; // numerator baseline
  826. const c1 = cardLeft + 315; // left fraction (≈ 626)
  827. const c3 = cardLeft + 610; // centre fraction (≈ 921)
  828. const c5 = cardLeft + 975; // right fraction (≈ 1286)
  829. game.add.text(c1, numY, bigNum + '\n' + bigDen, fracStyle, 65);
  830. game.add.text(c3, numY, factor + '\n' + factor, fracStyle, 65);
  831. game.add.text(c5, numY, smallNum + '\n' + smallDen, fracStyle, 65);
  832. // 3. Block bars + badge — centred around the image's star area
  833. // Star centre is ~49% of image width, ~51% of image height
  834. const badgeCx = cx; // star centre aligned to screen centre
  835. const barsTop = cardTop + 390; // ≈ 522
  836. const barH = 72; const barW = 270; const badgeW = 210; const barGap = 95;
  837. const leftBarLeft = badgeCx - badgeW / 2 - barGap - barW;
  838. const rightBarLeft = badgeCx + badgeW / 2 + barGap;
  839. const bwBig = barW / bigDen;
  840. for (let i = 0; i < bigDen; i++) {
  841. game.add.geom.rect(leftBarLeft + i * bwBig, barsTop, bwBig, barH,
  842. i < bigNum ? bigFillColor : colors.blueLight,
  843. i < bigNum ? 0.75 : 0.25, bigLineColor, 2);
  844. }
  845. const bwSmall = barW / smallDen;
  846. for (let i = 0; i < smallDen; i++) {
  847. game.add.geom.rect(rightBarLeft + i * bwSmall, barsTop, bwSmall, barH,
  848. i < smallNum ? smallFillColor : colors.greenLight,
  849. i < smallNum ? 0.75 : 0.25, smallLineColor, 2);
  850. }
  851. // Badge — image already draws the star, just overlay the text
  852. const badgeCenterY = barsTop + barH / 2;
  853. game.add.text(cx, badgeCenterY - 8, factor + '/' + factor + ' = 1', {
  854. ...textStyles.h4_, fill: colors.blueDark, font: 'bold 40px Arial, sans-serif',
  855. });
  856. // 4. Body text — below the star area, image body zone starts ~560px from image top
  857. const bodyParts = withNewlines(game.lang.s2_explain_body_correct).split('\n');
  858. const line1Raw = bodyParts[0];
  859. const wrapAt = 42;
  860. const wrapPos = line1Raw.lastIndexOf(' ', wrapAt);
  861. const line1 = (wrapPos > 0 && line1Raw.length > wrapAt)
  862. ? line1Raw.slice(0, wrapPos) + '\n' + line1Raw.slice(wrapPos + 1)
  863. : line1Raw;
  864. const line2 = (game.lang.s2_explain_body_factor_prefix) +
  865. ' ' + factor + '/' + factor + ' ' +
  866. (game.lang.s2_explain_body_factor_suffix);
  867. const line3 = bodyParts[1];
  868. const bodyStyle = { ...textStyles.p_, fill: colors.blueDark, font: 'bold 33px Arial, sans-serif' };
  869. const bodyStyleNormal = { ...textStyles.p_, fill: colors.blueDark, font: '33px Arial, sans-serif' };
  870. const bodyY = cardTop + 550;
  871. game.add.text(cx, bodyY, line1, bodyStyle, 38);
  872. game.add.text(cx, bodyY + 84, line2, bodyStyleNormal);
  873. game.add.text(cx, bodyY + 122, line3, bodyStyleNormal);
  874. // 5. Continue button
  875. const btnW = 400; const btnH = 75; const btnCenterY = cardBottom - 62;
  876. self.ui.explanation.button = game.add.geom.rect(
  877. cx - btnW / 2, btnCenterY - btnH / 2, btnW, btnH, colors.green
  878. );
  879. self.ui.explanation.text = game.add.text(cx, btnCenterY + 16, game.lang.continue, textStyles.btn);
  880. self.control.showExplanation = true;
  881. },
  882. renderEndUI: () => {
  883. let btnColor = colors.green;
  884. let btnText = game.lang.continue;
  885. if (!self.control.isCorrect) {
  886. btnColor = colors.red;
  887. btnText = game.lang.retry;
  888. }
  889. // continue button
  890. self.ui.continue.button = game.add.geom.rect(
  891. context.canvas.width / 2 + 400,
  892. context.canvas.height / 2 + 280,
  893. 450,
  894. 100,
  895. btnColor
  896. );
  897. self.ui.continue.button.anchor(0.5, 0.5);
  898. self.ui.continue.text = game.add.text(
  899. context.canvas.width / 2 + 400,
  900. context.canvas.height / 2 + 16 + 280,
  901. btnText,
  902. textStyles.btn
  903. );
  904. },
  905. startDelayHandler: () => {
  906. game.timer.stop();
  907. self.control.animationDelay++;
  908. if (self.control.animationDelay === 50) {
  909. self.ui.message[0].alpha = 0;
  910. self.utils.checkAnswer();
  911. self.control.animationDelay = 0;
  912. self.control.startEndAnimation = true;
  913. }
  914. },
  915. // UPDATE
  916. moveBlocks: function () {
  917. ['top', 'bottom'].forEach((cur) => {
  918. if (self.blocks[cur].animate) {
  919. // Lower selected blocks
  920. for (let i = 0; i < self.blocks[cur].selectedAmount; i++) {
  921. self.blocks[cur].list[i].y += 2;
  922. }
  923. // After fully lowering blocks, set fraction value
  924. if (self.blocks[cur].list[0].y >= self.blocks[cur].auxBlocks[0].y) {
  925. self.blocks[cur].fractions[0].name =
  926. self.blocks[cur].selectedAmount +
  927. '\n' +
  928. self.blocks[cur].list.length;
  929. self.blocks[cur].animate = false;
  930. }
  931. }
  932. });
  933. },
  934. checkAnswer: function () {
  935. self.control.isCorrect =
  936. self.blocks.top.selectedAmount / self.blocks.top.list.length ==
  937. self.blocks.bottom.selectedAmount / self.blocks.bottom.list.length;
  938. if (self.control.isCorrect) {
  939. // Correct: hide blocks to make room for explanation card
  940. ['top', 'bottom'].forEach((s) => {
  941. self.blocks[s].list.forEach((b) => (b.alpha = 0));
  942. self.blocks[s].auxBlocks.forEach((b) => (b.alpha = 0));
  943. self.blocks[s].fractions.forEach((b) => { if (b) b.alpha = 0; });
  944. if (self.blocks[s].label) self.blocks[s].label.alpha = 0;
  945. if (self.blocks[s].warningText) self.blocks[s].warningText.alpha = 0;
  946. });
  947. if (audioStatus) game.audio.okSound.play();
  948. completedLevels++;
  949. if (isDebugMode) console.log('Completed Levels: ' + completedLevels);
  950. } else {
  951. // Wrong: keep main blocks visible but hide aux blocks (they don't shift with renderOperationUI)
  952. ['top', 'bottom'].forEach((s) => {
  953. self.blocks[s].auxBlocks.forEach((b) => (b.alpha = 0));
  954. });
  955. if (audioStatus) game.audio.errorSound.play();
  956. }
  957. self.fetch.postScore();
  958. },
  959. runEndAnimation: function () {
  960. self.control.animationDelay++;
  961. if (self.control.animationDelay === 100) {
  962. if (self.control.isCorrect) {
  963. self.utils.renderExplanationUI();
  964. } else {
  965. const x = self.utils.renderOperationUI();
  966. game.add.image(x, context.canvas.height / 3, 'answer_wrong').anchor(0.5, 0.5);
  967. self.utils.renderEndUI();
  968. self.control.showEndInfo = true;
  969. }
  970. }
  971. },
  972. endLevel: function () {
  973. game.state.start('map');
  974. },
  975. // HANDLERS
  976. /**
  977. * Function called by self.onInputDown() when player clicked a valid rectangle.
  978. *
  979. * @param {object} curBlock clicked rectangle : can be self.blocks.top.list[i] or self.blocks.bottom.list[i]
  980. */
  981. clickSquareHandler: function (curBlock) {
  982. const curSet = curBlock.position;
  983. if (
  984. !self.blocks[curSet].hasClicked &&
  985. curBlock.index != self.blocks[curSet].list.length - 1
  986. ) {
  987. document.body.style.cursor = 'auto';
  988. // Turn auxiliar blocks invisible
  989. for (let i in self.blocks[curSet].list) {
  990. if (i > curBlock.index) self.blocks[curSet].auxBlocks[i].alpha = 0;
  991. }
  992. // Turn value label invisible
  993. self.blocks[curSet].label.alpha = 0;
  994. if (audioStatus) game.audio.popSound.play();
  995. // Save number of selected blocks
  996. self.blocks[curSet].selectedAmount = curBlock.index + 1;
  997. // Set fraction x position
  998. const newX =
  999. curBlock.finalX +
  1000. self.blocks[curSet].selectedAmount *
  1001. (self.control.blockWidth / self.blocks[curSet].list.length) +
  1002. 40;
  1003. self.blocks[curSet].fractions[0].x = newX;
  1004. self.blocks[curSet].fractions[1].x = newX;
  1005. self.blocks[curSet].fractions[0].name = `${curBlock.index + 1}\n${
  1006. self.blocks[curSet].list.length
  1007. }`;
  1008. // End fraction line
  1009. self.blocks[curSet].fractions[1].alpha = showFractions ? 1 : 0;
  1010. self.blocks[curSet].hasClicked = true; // Inform player have clicked in current block set
  1011. self.blocks[curSet].animate = true; // Let it initiate animation
  1012. }
  1013. game.render.all();
  1014. },
  1015. /**
  1016. * Function called by self.onInputOver() when cursor is over a valid rectangle.
  1017. *
  1018. * @param {object} curBlock rectangle the cursor is over : can be self.blocks.top.list[i] or self.blocks.bottom.list[i]
  1019. */
  1020. overSquareHandler: function (curBlock) {
  1021. const curSet = curBlock.position;
  1022. if (!self.blocks[curSet].hasClicked) {
  1023. // self.blocks.top.hasClicked || self.blocks.bottom.hasClicked
  1024. // If over fraction 'n/n' shows warning message not allowing it
  1025. if (curBlock.index == self.blocks[curSet].list.length - 1) {
  1026. const otherSet = curSet == 'top' ? 'bottom' : 'top';
  1027. self.blocks[curSet].warningText.alpha = 1;
  1028. self.blocks[otherSet].warningText.alpha = 0;
  1029. self.utils.outSquareHandler(curSet);
  1030. } else {
  1031. document.body.style.cursor = 'pointer';
  1032. self.blocks.top.warningText.alpha = 0;
  1033. self.blocks.bottom.warningText.alpha = 0;
  1034. // Selected blocks become fully visible
  1035. for (let i in self.blocks[curSet].list) {
  1036. self.blocks[curSet].list[i].alpha = i <= curBlock.index ? 1 : 0.5;
  1037. }
  1038. self.blocks[curSet].fractions[0].name = curBlock.index + 1; // Nominator : selected blocks
  1039. const newX =
  1040. curBlock.finalX +
  1041. (curBlock.index + 1) *
  1042. (self.control.blockWidth / self.blocks[curSet].list.length) +
  1043. 25;
  1044. self.blocks[curSet].fractions[0].x = newX;
  1045. self.blocks[curSet].fractions[1].x = newX;
  1046. // End fraction nominator and denominator
  1047. self.blocks[curSet].fractions[0].alpha = showFractions ? 1 : 0;
  1048. }
  1049. }
  1050. },
  1051. /**
  1052. * Function called (by self.onInputOver() and self.utils.overSquareHandler()) when cursor is out of a valid rectangle.
  1053. *
  1054. * @param {object} curSet set of rectangles : can be top (self.blocks.top) or bottom (self.blocks.bottom)
  1055. */
  1056. outSquareHandler: function (curSet) {
  1057. if (!self.blocks[curSet].hasClicked) {
  1058. self.blocks[curSet].fractions[0].alpha = 0;
  1059. self.blocks[curSet].fractions[1].alpha = 0;
  1060. self.blocks[curSet].list.forEach((cur) => {
  1061. cur.alpha = 0.5;
  1062. });
  1063. }
  1064. },
  1065. },
  1066. events: {
  1067. /**
  1068. * Called by mouse click event
  1069. *
  1070. * @param {object} mouseEvent contains the mouse click coordinates
  1071. */
  1072. onInputDown: function (mouseEvent) {
  1073. const x = game.math.getMouse(mouseEvent).x;
  1074. const y = game.math.getMouse(mouseEvent).y;
  1075. // Pre-start screen: only handle SIM/NÃO + navigation
  1076. if (!self.control.started) {
  1077. if (
  1078. self.ui.startChoice &&
  1079. self.ui.startChoice.yes &&
  1080. self.ui.startChoice.yes.button &&
  1081. game.math.isOverIcon(x, y, self.ui.startChoice.yes.button)
  1082. ) {
  1083. if (audioStatus) game.audio.popSound.play();
  1084. self.control.challengeAnsweredYes = true;
  1085. self.utils.updateChallengeChoiceUI();
  1086. self.utils.startGame();
  1087. return;
  1088. }
  1089. if (
  1090. self.ui.startChoice &&
  1091. self.ui.startChoice.no &&
  1092. self.ui.startChoice.no.button &&
  1093. game.math.isOverIcon(x, y, self.ui.startChoice.no.button)
  1094. ) {
  1095. if (audioStatus) game.audio.popSound.play();
  1096. self.control.challengeAnsweredYes = false;
  1097. self.utils.updateChallengeChoiceUI();
  1098. self.utils.startGame();
  1099. return;
  1100. }
  1101. navigation.onInputDown(x, y);
  1102. game.render.all();
  1103. return;
  1104. }
  1105. // Click block in (a)
  1106. self.blocks.top.list.forEach((cur) => {
  1107. if (game.math.isOverIcon(x, y, cur)) self.utils.clickSquareHandler(cur);
  1108. });
  1109. // Click block in (b)
  1110. self.blocks.bottom.list.forEach((cur) => {
  1111. if (game.math.isOverIcon(x, y, cur)) self.utils.clickSquareHandler(cur);
  1112. });
  1113. // Explanation screen continue/retry button
  1114. if (self.control.showExplanation) {
  1115. if (game.math.isOverIcon(x, y, self.ui.explanation.button)) {
  1116. if (audioStatus) game.audio.popSound.play();
  1117. canGoToNextMapPosition = self.control.isCorrect;
  1118. self.utils.endLevel();
  1119. }
  1120. }
  1121. // Continue button
  1122. if (self.control.showEndInfo) {
  1123. if (game.math.isOverIcon(x, y, self.ui.continue.button)) {
  1124. if (audioStatus) game.audio.popSound.play();
  1125. self.utils.endLevel();
  1126. }
  1127. }
  1128. // Click navigation icons
  1129. navigation.onInputDown(x, y);
  1130. game.render.all();
  1131. },
  1132. /**
  1133. * Called by mouse move event
  1134. *
  1135. * @param {object} mouseEvent contains the mouse move coordinates
  1136. */
  1137. onInputOver: function (mouseEvent) {
  1138. const x = game.math.getMouse(mouseEvent).x;
  1139. const y = game.math.getMouse(mouseEvent).y;
  1140. // Pre-start screen hover: SIM/NÃO + navigation
  1141. if (!self.control.started) {
  1142. let isOverChoice = false;
  1143. const choiceTextStyle = { ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font };
  1144. const yesBtn = self.ui.startChoice && self.ui.startChoice.yes && self.ui.startChoice.yes.button;
  1145. const noBtn = self.ui.startChoice && self.ui.startChoice.no && self.ui.startChoice.no.button;
  1146. const yesVisual = self.ui.startChoice && self.ui.startChoice.yes && self.ui.startChoice.yes.visual;
  1147. const noVisual = self.ui.startChoice && self.ui.startChoice.no && self.ui.startChoice.no.visual;
  1148. if (yesBtn && game.math.isOverIcon(x, y, yesBtn)) {
  1149. isOverChoice = true;
  1150. document.body.style.cursor = 'pointer';
  1151. if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.style = choiceTextStyle;
  1152. if (yesVisual && yesVisual.outerParts && yesVisual.innerParts) {
  1153. yesVisual.outerParts.forEach((p) => { p.shadowBlur = 10; });
  1154. yesVisual.innerParts.forEach((p) => { p.fillColor = '#e8f0fc'; p.lineColor = '#e8f0fc'; });
  1155. }
  1156. } else if (yesBtn) {
  1157. if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.style = choiceTextStyle;
  1158. if (yesVisual && yesVisual.outerParts && yesVisual.innerParts) {
  1159. yesVisual.outerParts.forEach((p) => { p.shadowBlur = 8; });
  1160. yesVisual.innerParts.forEach((p) => { p.fillColor = colors.white; p.lineColor = colors.white; });
  1161. }
  1162. }
  1163. if (noBtn && game.math.isOverIcon(x, y, noBtn)) {
  1164. isOverChoice = true;
  1165. document.body.style.cursor = 'pointer';
  1166. if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.style = choiceTextStyle;
  1167. if (noVisual && noVisual.outerParts && noVisual.innerParts) {
  1168. noVisual.outerParts.forEach((p) => { p.shadowBlur = 10; });
  1169. noVisual.innerParts.forEach((p) => { p.fillColor = '#e8f0fc'; p.lineColor = '#e8f0fc'; });
  1170. }
  1171. } else if (noBtn) {
  1172. if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.style = choiceTextStyle;
  1173. if (noVisual && noVisual.outerParts && noVisual.innerParts) {
  1174. noVisual.outerParts.forEach((p) => { p.shadowBlur = 8; });
  1175. noVisual.innerParts.forEach((p) => { p.fillColor = colors.white; p.lineColor = colors.white; });
  1176. }
  1177. }
  1178. if (!isOverChoice) document.body.style.cursor = 'auto';
  1179. navigation.onInputOver(x, y);
  1180. game.render.all();
  1181. return;
  1182. }
  1183. let flagA = false;
  1184. let flagB = false;
  1185. // Mouse over (a) : show fraction
  1186. self.blocks.top.list.forEach((cur) => {
  1187. if (game.math.isOverIcon(x, y, cur)) {
  1188. flagA = true;
  1189. self.utils.overSquareHandler(cur);
  1190. }
  1191. });
  1192. if (!flagA) self.utils.outSquareHandler('top');
  1193. // Mouse over (b) : show fraction
  1194. self.blocks.bottom.list.forEach((cur) => {
  1195. if (game.math.isOverIcon(x, y, cur)) {
  1196. flagB = true;
  1197. self.utils.overSquareHandler(cur);
  1198. }
  1199. });
  1200. if (!flagB) self.utils.outSquareHandler('bottom');
  1201. if (!flagA && !flagB) document.body.style.cursor = 'auto';
  1202. // Explanation button hover
  1203. if (self.control.showExplanation && self.ui.explanation.button) {
  1204. if (game.math.isOverIcon(x, y, self.ui.explanation.button)) {
  1205. document.body.style.cursor = 'pointer';
  1206. self.ui.explanation.button.scale = self.ui.explanation.button.initialScale * 1.1;
  1207. self.ui.explanation.text.style = textStyles.btnLg;
  1208. } else {
  1209. self.ui.explanation.button.scale = self.ui.explanation.button.initialScale * 1;
  1210. self.ui.explanation.text.style = textStyles.btn;
  1211. }
  1212. }
  1213. // Continue button
  1214. if (self.control.showEndInfo) {
  1215. if (game.math.isOverIcon(x, y, self.ui.continue.button)) {
  1216. // If pointer is over icon
  1217. document.body.style.cursor = 'pointer';
  1218. self.ui.continue.button.scale =
  1219. self.ui.continue.button.initialScale * 1.1;
  1220. self.ui.continue.text.style = textStyles.btnLg;
  1221. } else {
  1222. // If pointer is not over icon
  1223. document.body.style.cursor = 'auto';
  1224. self.ui.continue.button.scale =
  1225. self.ui.continue.button.initialScale * 1;
  1226. self.ui.continue.text.style = textStyles.btn;
  1227. }
  1228. }
  1229. // Mouse over navigation icons : show name
  1230. navigation.onInputOver(x, y);
  1231. game.render.all();
  1232. },
  1233. },
  1234. fetch: {
  1235. /**
  1236. * Saves players data after level ends - to be sent to database. <br>
  1237. *
  1238. * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
  1239. *
  1240. * @see /php/save.php
  1241. */
  1242. postScore: function () {
  1243. // Creates string that is going to be sent to db
  1244. const data =
  1245. '&line_game=' +
  1246. gameShape +
  1247. '&line_mode=' +
  1248. gameMode +
  1249. '&line_oper=equal' +
  1250. '&line_leve=' +
  1251. gameDifficulty +
  1252. '&line_posi=' +
  1253. curMapPosition +
  1254. '&line_resu=' +
  1255. self.control.isCorrect +
  1256. '&line_time=' +
  1257. game.timer.elapsed +
  1258. '&line_deta=' +
  1259. 'numBlocksA: ' +
  1260. self.blocks.top.list.length +
  1261. ', valueA: ' +
  1262. self.blocks.top.selectedAmount +
  1263. ', numBlocksB: ' +
  1264. self.blocks.bottom.list.length +
  1265. ', valueB: ' +
  1266. self.blocks.bottom.selectedAmount +
  1267. '&challenge_answered_yes=' +
  1268. self.control.challengeAnsweredYes;
  1269. // FOR MOODLE
  1270. sendToDatabase(data);
  1271. },
  1272. },
  1273. };