| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446 |
- /******************************
- * This file holds game states.
- ******************************/
- /** [GAME STATE]
- *
- * .squareTwo. = gameName
- * .../...\...
- * ..a.....b.. = gameMode
- * ....\./....
- * .....|.....
- * ..equals... = gameOperation
- * .....|.....
- * .1,2,3,4,5. = gameDifficulty
- *
- * Character : kid
- * Theme : (not themed)
- * Concept : player select equivalent dividends for fractions with different divisors
- * Represent fractions as : subdivided rectangles
- *
- * Game modes can be :
- *
- * a : equivalence of fractions
- * top has more subdivisions
- * b : equivalence of fractions
- * bottom has more subdivisions
- *
- * Operations :
- *
- * equals : Player selects equivalent fractions of both blocks
- *
- * @namespace
- */
- const squareTwo = {
- ui: undefined,
- control: undefined,
- blocks: undefined,
- /**
- * Main code
- */
- create: function () {
- this.ui = {
- message: undefined,
- startChoice: {
- message: undefined,
- card: undefined,
- image: undefined,
- title: undefined,
- subtitle: undefined,
- accept: {
- button: undefined,
- text: undefined,
- },
- yes: {
- button: undefined,
- text: undefined,
- },
- no: {
- button: undefined,
- text: undefined,
- },
- },
- continue: {
- // modal: undefined,
- button: undefined,
- text: undefined,
- },
- explanation: {
- button: undefined,
- text: undefined,
- },
- };
- this.control = {
- blockWidth: 600,
- blockHeight: 75,
- isCorrect: false,
- showEndInfo: false,
- showExplanation: false,
- animationDelay: 0,
- startDelay: false,
- startEndAnimation: false,
- started: false,
- blockConfig: undefined,
- challengePreview: undefined,
- challengeAnsweredYes: undefined,
- };
- this.blocks = {
- top: {
- list: [], // List of block objects
- auxBlocks: [], // List of shadow under selection blocks
- fractions: [], // Fractions
- selectedAmount: 0,
- hasClicked: false, // Check if player clicked blocks from (a)
- animate: false, // Animate blocks from (a)
- warningText: undefined,
- label: undefined,
- },
- bottom: {
- list: [],
- auxBlocks: [],
- fractions: [],
- selectedAmount: 0,
- hasClicked: false,
- animate: false,
- warningText: undefined,
- label: undefined,
- },
- };
- renderBackground();
- // Calls function that loads navigation icons
- // FOR MOODLE
- if (moodle) {
- navigation.add.right(['audio']);
- } else {
- navigation.add.left(['back', 'menu'], 'customMenu');
- navigation.add.right(['audio']);
- }
- // Pre-generate the level configuration so the challenge modal can preview real fractions
- this.control.blockConfig = this.utils.generateBlockConfig();
- this.control.challengePreview = this.utils.generateChallengePreview(
- this.control.blockConfig
- );
- // Wait for user confirmation before starting the level
- this.utils.renderStartChoiceUI();
- game.event.add('click', this.events.onInputDown);
- game.event.add('mousemove', this.events.onInputOver);
- },
- /**
- * Game loop
- */
- update: function () {
- // Animate blocks
- if (self.blocks.top.animate || self.blocks.bottom.animate) {
- self.utils.moveBlocks();
- }
- // If (a) and (b) are already clicked
- if (
- !self.control.startDelay &&
- !self.control.startEndAnimation &&
- self.blocks.top.hasClicked &&
- self.blocks.bottom.hasClicked
- ) {
- self.control.startDelay = true;
- }
- if (self.control.startDelay && !self.control.startEndAnimation) {
- self.utils.startDelayHandler();
- }
- // Wait a bit and go to map state
- if (self.control.startEndAnimation) {
- self.utils.runEndAnimation();
- }
- game.render.all();
- },
- utils: {
- // RENDERS
- generateBlockConfig: function () {
- // Coordinates for (a) and (b)
- let xA, xB, yA, yB;
- if (gameMode != 'b') {
- // Has more subdivisions on (a)
- xA = context.canvas.width / 2 - self.control.blockWidth / 2;
- yA = getFrameInfo().y + 100;
- xB = xA;
- yB = yA + 3 * self.control.blockHeight + 30;
- } else {
- // Has more subdivisions on (b)
- xB = context.canvas.width / 2 - self.control.blockWidth / 2;
- yB = getFrameInfo().y + 100;
- xA = xB;
- yA = yB + 3 * self.control.blockHeight + 30;
- }
- // Possible subdivisionList for (a)
- const subdivisionList = [2, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20];
- const minIndex = (gameDifficulty - 1) * 2 + 1;
- const maxIndex = (gameDifficulty - 1) * 2 + 3;
- // Build a shuffled pool of all (A, B) pairs for this difficulty and
- // iterate through it so the same combination never repeats until all
- // possibilities have been exhausted, then reshuffle and start again.
- if (
- !squareTwo._configPool ||
- squareTwo._configPoolDifficulty !== gameDifficulty ||
- squareTwo._configIndex >= squareTwo._configPool.length
- ) {
- const allPairs = [];
- for (let i = minIndex; i <= maxIndex; i++) {
- const a = subdivisionList[i];
- for (let b = 2; b < a; b++) {
- if (a % b === 0) allPairs.push({ a, b });
- }
- }
- // Fisher-Yates shuffle
- for (let i = allPairs.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [allPairs[i], allPairs[j]] = [allPairs[j], allPairs[i]];
- }
- squareTwo._configPool = allPairs;
- squareTwo._configPoolDifficulty = gameDifficulty;
- squareTwo._configIndex = 0;
- }
- // Number of subdivisions of (a) and (b) (blocks)
- const { a: totalBlocksA, b: totalBlocksB } =
- squareTwo._configPool[squareTwo._configIndex++];
- const blockWidthA = self.control.blockWidth / totalBlocksA;
- const blockWidthB = self.control.blockWidth / totalBlocksB;
- if (isDebugMode) {
- console.log(
- '------------------------------' +
- '\nGame Map Position: ' +
- curMapPosition +
- '\n------------------------ setup' +
- '\narray: [2, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20]' +
- '\nMin index ((gameDifficulty - 1) * 2 + 1): ' +
- ((gameDifficulty - 1) * 2 + 1) +
- '\nMax index ((gameDifficulty - 1) * 2 + 3): ' +
- ((gameDifficulty - 1) * 2 + 3) +
- '\n------------------------ this' +
- '\ntotalBlocksA: ' +
- totalBlocksA +
- '\ntotalBlocksB: ' +
- totalBlocksB +
- '\npool index: ' + (squareTwo._configIndex - 1) + '/' + squareTwo._configPool.length +
- '\n------------------------------'
- );
- }
- return {
- xA,
- yA,
- xB,
- yB,
- totalBlocksA,
- totalBlocksB,
- blockWidthA,
- blockWidthB,
- };
- },
- generateChallengePreview: function (blockConfig) {
- if (!blockConfig) return undefined;
- const { totalBlocksA, totalBlocksB } = blockConfig;
- if (!totalBlocksA || !totalBlocksB) return undefined;
- const gcd = (a, b) => {
- let x = Math.abs(a);
- let y = Math.abs(b);
- while (y !== 0) {
- const t = x % y;
- x = y;
- y = t;
- }
- return x;
- };
- // Always produce an equivalent integer pair using the gcd
- const g = gcd(totalBlocksA, totalBlocksB);
- const leftUnit = totalBlocksA / g;
- const rightUnit = totalBlocksB / g;
- // Deterministic preview (prevents changing values between renders)
- const selectedA = leftUnit;
- const selectedB = rightUnit;
- // Show as left/right fractions in the modal (keep order consistent with blocks)
- return {
- left: { num: selectedA, den: totalBlocksA },
- right: { num: selectedB, den: totalBlocksB },
- };
- },
- renderStartChoiceUI: () => {
- const centerX = context.canvas.width / 2;
- const centerY = context.canvas.height / 2;
- const yesLabel = game.lang.yes;
- const noLabel = game.lang.no;
- const withNewlines = (s) => (s == null ? '' : String(s).replace(/\\n/g, '\n'));
- const title = withNewlines(game.lang.s2_challenge_title);
- const subtitle = withNewlines(game.lang.s2_challenge_subtitle);
- const question = withNewlines(game.lang.s2_challenge_question);
- // Layout reference (no white panel/card)
- const cardH = 560;
- const panelTop = centerY - cardH / 2;
- // Challenge image (ribbon + question mark)
- const imageScale = 0.7;
- const imageY = panelTop - 150;
- self.ui.startChoice.image = game.add.image(
- centerX,
- imageY,
- 'challenge',
- imageScale,
- 1
- );
- self.ui.startChoice.image.anchor(0.5, 0);
- // Title + subtitle
- self.ui.startChoice.title = game.add.text(
- centerX,
- imageY + 65,
- title,
- { ...textStyles.h2_, fill: colors.white, font: 'bold ' + textStyles.h2_.font }
- );
- self.ui.startChoice.title.anchor(0.5, 0.5);
- // Subtitle split in two lines (top emphasized)
- const subtitleLines = String(subtitle || '').split('\n');
- const subtitleTop = subtitleLines[0] || '';
- const subtitleBottom = subtitleLines.slice(1).join('\n');
- self.ui.startChoice.subtitle = {};
- self.ui.startChoice.subtitle.top = game.add.text(
- centerX,
- panelTop + 10,
- subtitleTop,
- {
- ...textStyles.h4_,
- font: 'bold ' + textStyles.h4_.font,
- fill: colors.blueDark,
- }
- );
- self.ui.startChoice.subtitle.top.anchor(0.5, 0.5);
- self.ui.startChoice.subtitle.bottom = game.add.text(
- centerX,
- panelTop + 55,
- subtitleBottom,
- { ...textStyles.h3_, fill: colors.blue }
- );
- self.ui.startChoice.subtitle.bottom.anchor(0.5, 0.5);
- // Fractions preview (left and right) - based on the real generated blockConfig
- const preview =
- self.control.challengePreview ||
- self.utils.generateChallengePreview(self.control.blockConfig);
- self.control.challengePreview = preview;
- if (preview && preview.left && preview.right) {
- const fracFont = { ...textStyles.fraction, fill: colors.blueDark, align: 'center', font: '76px monospace' };
- const fracY = panelTop + 255;
- const offsetX = 260;
- const leftX = centerX - offsetX;
- const rightX = centerX + offsetX;
- const leftText = game.add.text(
- leftX,
- fracY,
- preview.left.num + '\n' + preview.left.den,
- fracFont,
- 70
- );
- leftText.anchor(0.5, 0.5);
- const leftLine = game.add.geom.rect(
- leftX,
- fracY + 10,
- 120,
- 4,
- colors.blueDark,
- 1,
- colors.blueDark,
- 4
- );
- leftLine.anchor(0.5, 0);
- const rightText = game.add.text(
- rightX,
- fracY,
- preview.right.num + '\n' + preview.right.den,
- fracFont,
- 70
- );
- rightText.anchor(0.5, 0.5);
- const rightLine = game.add.geom.rect(
- rightX,
- fracY + 10,
- 120,
- 4,
- colors.blueDark,
- 1,
- colors.blueDark,
- 4
- );
- rightLine.anchor(0.5, 0);
- self.ui.startChoice.fractions = {
- leftText,
- leftLine,
- rightText,
- rightLine,
- };
- }
- // Prompt
- self.ui.startChoice.message = game.add.text(
- centerX,
- panelTop + 415,
- question,
- {
- ...textStyles.h3_,
- font: 'bold ' + textStyles.h3_.font,
- fill: colors.blueDark,
- }
- );
- self.ui.startChoice.message.anchor(0.5, 0.5);
- // Buttons
- const buttonW = 340;
- const buttonH = 90;
- const gap = 90;
- const buttonsY = panelTop + 600;
- const choiceTextStyle = { ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font };
- const borderColor = colors.blueMenuLine;
- const fillColor = colors.white;
- const fillAlpha = 0.9;
- const selectedFillColor = colors.blueLight;
- const createPillButton = (x, y, label) => {
- // Invisible hit target used for hover/click detection
- const hit = game.add.geom.rect(
- x,
- y,
- buttonW,
- buttonH,
- colors.white,
- 0,
- colors.white,
- 0
- );
- hit.anchor(0.5, 0.5);
- const outerW = buttonW;
- const outerH = buttonH;
- const innerInset = 10;
- const innerW = outerW - innerInset;
- const innerH = outerH - innerInset;
- const outerRect = game.add.geom.rect(
- x,
- y,
- outerW,
- outerH,
- borderColor,
- 1,
- borderColor,
- 0
- );
- outerRect.anchor(0.5, 0.5);
- outerRect.shadow = true;
- outerRect.shadowColor = colors.gray;
- outerRect.shadowBlur = 8;
- const innerRect = game.add.geom.rect(
- x,
- y,
- innerW,
- innerH,
- fillColor,
- fillAlpha,
- fillColor,
- 0
- );
- innerRect.anchor(0.5, 0.5);
- const text = game.add.text(x, y + 10, label, choiceTextStyle);
- return {
- hit,
- text,
- outerParts: [outerRect],
- innerParts: [innerRect],
- setSelected: (isSelected) => {
- const c = isSelected ? selectedFillColor : fillColor;
- [innerRect].forEach((p) => {
- p.fillColor = c;
- p.lineColor = c;
- });
- },
- };
- };
- const yesX = centerX - (buttonW / 2 + gap / 2);
- const noX = centerX + (buttonW / 2 + gap / 2);
- const yes = createPillButton(yesX, buttonsY, yesLabel);
- const no = createPillButton(noX, buttonsY, noLabel);
- // keep existing structure expected by event handlers
- self.ui.startChoice.yes.button = yes.hit;
- self.ui.startChoice.yes.text = yes.text;
- self.ui.startChoice.yes.visual = yes;
- self.ui.startChoice.no.button = no.hit;
- self.ui.startChoice.no.text = no.text;
- self.ui.startChoice.no.visual = no;
- game.render.all();
- },
- updateChallengeChoiceUI: () => {
- const selected = self.control.challengeAnsweredYes;
- const yesBtn = self.ui.startChoice?.yes?.button;
- const noBtn = self.ui.startChoice?.no?.button;
- const yesVisual = self.ui.startChoice?.yes?.visual;
- const noVisual = self.ui.startChoice?.no?.visual;
- if (yesBtn) {
- if (yesVisual && yesVisual.setSelected) {
- yesVisual.setSelected(selected === true);
- } else {
- yesBtn.fillColor = selected === true ? colors.blueLight : colors.white;
- }
- }
- if (noBtn) {
- if (noVisual && noVisual.setSelected) {
- noVisual.setSelected(selected === false);
- } else {
- noBtn.fillColor = selected === false ? colors.blueLight : colors.white;
- }
- }
- game.render.all();
- },
- startGame: () => {
- if (self.control.started) return;
- self.control.started = true;
- // Hide pre-start UI
- if (self.ui.startChoice && self.ui.startChoice.message) {
- self.ui.startChoice.message.alpha = 0;
- }
- if (self.ui.startChoice && self.ui.startChoice.title) {
- self.ui.startChoice.title.alpha = 0;
- }
- if (self.ui.startChoice && self.ui.startChoice.subtitle) {
- if (self.ui.startChoice.subtitle.alpha != null) {
- self.ui.startChoice.subtitle.alpha = 0;
- } else {
- const s = self.ui.startChoice.subtitle;
- if (s.top) s.top.alpha = 0;
- if (s.bottom) s.bottom.alpha = 0;
- }
- }
- if (self.ui.startChoice && self.ui.startChoice.image) {
- self.ui.startChoice.image.alpha = 0;
- }
- if (self.ui.startChoice && self.ui.startChoice.fractions) {
- Object.values(self.ui.startChoice.fractions).forEach((obj) => {
- if (obj) obj.alpha = 0;
- });
- }
- if (self.ui.startChoice && self.ui.startChoice.card) {
- self.ui.startChoice.card.alpha = 0;
- }
- if (self.ui.startChoice && self.ui.startChoice.yes) {
- if (self.ui.startChoice.yes.button) self.ui.startChoice.yes.button.alpha = 0;
- if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.alpha = 0;
- if (self.ui.startChoice.yes.visual) {
- const v = self.ui.startChoice.yes.visual;
- if (v.outerParts) v.outerParts.forEach((p) => (p.alpha = 0));
- if (v.innerParts) v.innerParts.forEach((p) => (p.alpha = 0));
- }
- }
- if (self.ui.startChoice && self.ui.startChoice.no) {
- if (self.ui.startChoice.no.button) self.ui.startChoice.no.button.alpha = 0;
- if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.alpha = 0;
- if (self.ui.startChoice.no.visual) {
- const v = self.ui.startChoice.no.visual;
- if (v.outerParts) v.outerParts.forEach((p) => (p.alpha = 0));
- if (v.innerParts) v.innerParts.forEach((p) => (p.alpha = 0));
- }
- }
- if (self.ui.startChoice && self.ui.startChoice.accept) {
- if (self.ui.startChoice.accept.button)
- self.ui.startChoice.accept.button.alpha = 0;
- if (self.ui.startChoice.accept.text)
- self.ui.startChoice.accept.text.alpha = 0;
- }
- document.body.style.cursor = 'auto';
- // Add kid + blocks + UI (original flow)
- self.utils.renderCharacters();
- self.utils.renderBlockSetup(self.control.blockConfig);
- self.utils.renderMainUI();
- game.timer.start(); // Set a timer for the current level (used in postScore)
- game.render.all();
- },
- renderBlockSetup: function (blockConfig) {
- const cfg = blockConfig || self.utils.generateBlockConfig();
- if (!cfg) return;
- const {
- xA,
- yA,
- xB,
- yB,
- totalBlocksA,
- totalBlocksB,
- blockWidthA,
- blockWidthB,
- } = cfg;
- // (a)
- self.utils.renderBlocks(
- self.blocks.top,
- 'top',
- totalBlocksA,
- blockWidthA,
- colors.blueDark,
- colors.blueLight,
- xA,
- yA
- );
- // (b)
- self.utils.renderBlocks(
- self.blocks.bottom,
- 'bottom',
- totalBlocksB,
- blockWidthB,
- colors.greenDark,
- colors.greenLight,
- xB,
- yB
- );
- },
- renderBlocks: function (
- blocks,
- blockType,
- totalBlocks,
- blockWidth,
- lineColor,
- fillColor,
- x0,
- y0
- ) {
- for (let i = 0; i < totalBlocks; i++) {
- // Blocks
- const curX = x0 + i * blockWidth;
- const curBlock = game.add.geom.rect(
- curX,
- y0,
- blockWidth,
- self.control.blockHeight,
- fillColor,
- 0.5,
- lineColor,
- 4
- );
- curBlock.position = blockType;
- curBlock.index = i;
- curBlock.finalX = x0;
- blocks.list.push(curBlock);
- // Auxiliar blocks (lower alpha)
- const alpha = 0.2;
- const curYAux = y0 + self.control.blockHeight + 10;
- const curAuxBlock = game.add.geom.rect(
- curX,
- curYAux,
- blockWidth,
- self.control.blockHeight,
- fillColor,
- alpha,
- lineColor,
- 1
- );
- blocks.auxBlocks.push(curAuxBlock);
- }
- // Label - number of blocks (on the right)
- let yLabel = y0 + self.control.blockHeight / 2 + 10;
- const xLabel = x0 + self.control.blockWidth + 35;
- const font = {
- ...textStyles.h4_,
- font: 'bold ' + textStyles.h4_.font,
- fill: lineColor,
- };
- blocks.label = game.add.text(xLabel, yLabel, blocks.list.length, font);
- blocks.label.alpha = showFractions ? 1 : 0;
- // 'selected blocks/fraction' label for (a) : at the bottom of (a)
- yLabel = y0 + self.control.blockHeight + 40;
- blocks.fractions[0] = game.add.text(xLabel, yLabel, '', font);
- blocks.fractions[1] = game.add.geom.line(
- xLabel,
- yLabel + 10,
- xLabel + 50,
- yLabel + 10,
- 2,
- lineColor
- );
- blocks.fractions[1].anchor(0.5, 0);
- blocks.fractions[0].alpha = 0;
- blocks.fractions[1].alpha = 0;
- // Invalid selection text
- blocks.warningText = game.add.text(
- context.canvas.width / 2,
- y0 - 20,
- game.lang.s2_error_msg,
- { ...font, font: textStyles.h4_.font }
- );
- blocks.warningText.alpha = 0;
- },
- renderCharacters: function () {
- self.kidAnimation = game.add.sprite(
- 100,
- context.canvas.height - 128 * 1.5,
- 'kid_standing',
- 0,
- 1.2
- );
- self.kidAnimation.anchor(0.5, 0.7);
- self.kidAnimation.curFrame = 3;
- },
- renderMainUI: () => {
- // Intro text
- const treatedMessage = game.lang.squareTwo_intro.split('\\n');
- const font = textStyles.h1_;
- self.ui.message = [];
- self.ui.message.push(
- game.add.text(
- context.canvas.width / 2,
- 170,
- treatedMessage[0] + '\n' + treatedMessage[1],
- font
- )
- );
- },
- renderOperationUI: () => {
- const uiList = [
- ...self.blocks.top.list,
- ...self.blocks.bottom.list,
- ...self.blocks.top.fractions,
- ...self.blocks.bottom.fractions,
- ];
- moveList(uiList, -400, 0);
- const font = textStyles.fraction;
- font.fill = colors.black;
- font.align = 'center';
- const nominators = [
- self.blocks.top.selectedAmount,
- self.blocks.bottom.selectedAmount,
- ];
- const denominators = [
- self.blocks.top.list.length,
- self.blocks.bottom.list.length,
- ];
- if (gameMode === 'b') {
- const leftNom = nominators[0];
- const leftDenom = denominators[0];
- nominators[0] = nominators[1];
- denominators[0] = denominators[1];
- nominators[1] = leftNom;
- denominators[1] = leftDenom;
- }
- const renderList = [];
- const padding = 100;
- const offsetX = 100;
- const cardHeight = 400;
- const x0 = padding + 400;
- const y0 = context.canvas.height / 2;
- let nextX = x0;
- const cardX = x0 - padding;
- const cardY = y0;
- // Card
- const card = game.add.geom.rect(
- cardX,
- cardY,
- 0,
- cardHeight,
- colors.blueLight,
- 0.5,
- colors.blueDark,
- 8
- );
- card.id = 'card';
- card.anchor(0, 0.5);
- renderList.push(card);
- renderList.push(
- game.add.text(
- nextX,
- y0,
- nominators[0] + '\n' + denominators[0],
- font,
- 70
- )
- );
- const topFractionLine = game.add.geom.rect(
- nextX,
- y0 + 10,
- 100,
- 4,
- colors.black,
- 4
- );
- topFractionLine.anchor(0.5, 0);
- renderList.push(topFractionLine);
- font.fill = self.control.isCorrect ? colors.green : colors.red;
- nextX += offsetX;
- renderList.push(
- game.add.text(nextX, y0 + 35, self.control.isCorrect ? '=' : '≠', font)
- );
- font.fill = colors.black;
- nextX += offsetX;
- renderList.push(
- game.add.text(
- nextX,
- y0,
- nominators[1] + '\n' + denominators[1],
- font,
- 70
- )
- );
- const bottomFractionLine = game.add.geom.rect(
- nextX,
- y0 + 10,
- 100,
- 4,
- colors.black,
- 4
- );
- bottomFractionLine.anchor(0.5, 0);
- renderList.push(bottomFractionLine);
- //let resultWidth = ''.length * widthOfChar;
- const cardWidth = nextX - x0 + padding * 2;
- card.width = cardWidth;
- const endSignX =
- (context.canvas.width - cardWidth) / 2 + cardWidth + 400 + 50;
- // Center Card
- moveList(renderList, (context.canvas.width - cardWidth) / 2, 0);
- self.fractionOperationUI = renderList;
- return endSignX;
- },
- renderExplanationUI: () => {
- const cx = context.canvas.width / 2;
- const cy = context.canvas.height / 2;
- const withNewlines = (s) =>
- s == null ? '' : String(s).replace(/\\n/g, '\n');
- // WRONG ANSWER: simple small card (no equation/bars)
- if (!self.control.isCorrect) {
- const errW = 680; const errH = 220;
- const errLeft = cx - errW / 2; const errTop = cy - errH / 2;
- const errBottom = errTop + errH;
- game.add.geom.rect(errLeft, errTop, errW, errH, colors.white, 0.97, colors.red, 4);
- game.add.text(cx, cy - 12,
- game.lang.s2_explain_body_wrong,
- { ...textStyles.h3_, fill: colors.red }
- );
- const btnW = 360; const btnH = 70; const btnCY = errBottom - 52;
- self.ui.explanation.button = game.add.geom.rect(
- cx - btnW / 2, btnCY - btnH / 2, btnW, btnH, colors.red
- );
- self.ui.explanation.text = game.add.text(cx, btnCY + 16, game.lang.retry, textStyles.btn);
- self.control.showExplanation = true;
- return;
- }
- // CORRECT ANSWER: full explanation card
- // Image is 1298×816 — all positions are relative to image coordinates
- const imgW = 1298; const imgH = 816;
- const cardLeft = cx - imgW / 2; // 311
- const cardTop = cy - imgH / 2; // 132
- const cardBottom = cardTop + imgH; // 948
- // 1. Background image + title text (image has the 🔍 icon, we add the text)
- game.add.image(cx, cy, gameMode === 'b' ? 'result-bg-multiply' : 'result-bg').anchor(0.5, 0.5);
- const titleText = withNewlines(game.lang.s2_explain_title);
- const titleY = cardTop + (titleText.includes('\n') ? 42 : 62);
- game.add.text(cx, titleY, titleText, {
- ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font,
- });
- // Fraction values
- let bigNum, bigDen, bigLineColor, bigFillColor;
- let smallNum, smallDen, smallLineColor, smallFillColor;
- if (gameMode !== 'b') {
- bigNum = self.blocks.top.selectedAmount; bigDen = self.blocks.top.list.length;
- bigLineColor = colors.blueDark; bigFillColor = colors.blue;
- smallNum = self.blocks.bottom.selectedAmount; smallDen = self.blocks.bottom.list.length;
- smallLineColor = colors.greenDark; smallFillColor = colors.green;
- } else {
- bigNum = self.blocks.bottom.selectedAmount; bigDen = self.blocks.bottom.list.length;
- bigLineColor = colors.greenDark; bigFillColor = colors.green;
- smallNum = self.blocks.top.selectedAmount; smallDen = self.blocks.top.list.length;
- smallLineColor = colors.blueDark; smallFillColor = colors.blue;
- }
- const factorRaw = (gameMode === 'b') ? smallDen / bigDen : bigDen / smallDen;
- const factor = Number.isInteger(factorRaw) ? factorRaw : parseFloat(factorRaw.toFixed(1));
- const fracStyle = { ...textStyles.fraction, fill: colors.blueDark, align: 'center' };
- // 2. Fraction numbers — placed over the image's fraction bar template
- // Image fraction bar LINE is at ~y=250 from image top; numY+32 should align with it
- // x centres measured from image: left≈24%, mid≈47%, right≈75%
- const numY = cardTop + 238; // numerator baseline
- const c1 = cardLeft + 315; // left fraction (≈ 626)
- const c3 = cardLeft + (gameMode === 'b' ? 635 : 610); // centre fraction (≈ 921)
- const c5 = cardLeft + 975; // right fraction (≈ 1286)
- game.add.text(c1, numY, bigNum + '\n' + bigDen, fracStyle, 65);
- game.add.text(c3, numY, factor + '\n' + factor, fracStyle, 65);
- game.add.text(c5, numY, smallNum + '\n' + smallDen, fracStyle, 65);
- // 3. Block bars + badge — centred around the image's star area
- // Star centre is ~49% of image width, ~51% of image height
- const badgeCx = cx; // star centre aligned to screen centre
- const barsTop = cardTop + 390; // ≈ 522
- const barH = 72; const barW = 270; const badgeW = 210; const barGap = 95;
- const leftBarLeft = badgeCx - badgeW / 2 - barGap - barW;
- const rightBarLeft = badgeCx + badgeW / 2 + barGap;
- const bwBig = barW / bigDen;
- for (let i = 0; i < bigDen; i++) {
- game.add.geom.rect(leftBarLeft + i * bwBig, barsTop, bwBig, barH,
- i < bigNum ? bigFillColor : colors.blueLight,
- i < bigNum ? 0.75 : 0.25, bigLineColor, 2);
- }
- const bwSmall = barW / smallDen;
- for (let i = 0; i < smallDen; i++) {
- game.add.geom.rect(rightBarLeft + i * bwSmall, barsTop, bwSmall, barH,
- i < smallNum ? smallFillColor : colors.greenLight,
- i < smallNum ? 0.75 : 0.25, smallLineColor, 2);
- }
- // Badge — image already draws the star, just overlay the text
- const badgeCenterY = barsTop + barH / 2;
- const badgeFontSize = Number.isInteger(factorRaw) ? 40 : 30;
- game.add.text(cx, badgeCenterY - 8, factor + '/' + factor + ' = 1', {
- ...textStyles.h4_, fill: colors.blueDark, font: `bold ${badgeFontSize}px Arial, sans-serif`,
- });
- // 4. Body text — below the star area, image body zone starts ~560px from image top
- const bodyKey = (gameMode === 'b') ? 's2_explain_body_correct_multiply' : 's2_explain_body_correct';
- const bodyParts = withNewlines(game.lang[bodyKey]).split('\n');
- const line1Raw = bodyParts[0];
- const wrapAt = 42;
- const wrapPos = line1Raw.lastIndexOf(' ', wrapAt);
- const line1 = (wrapPos > 0 && line1Raw.length > wrapAt)
- ? line1Raw.slice(0, wrapPos) + '\n' + line1Raw.slice(wrapPos + 1)
- : line1Raw;
- const line2 = (game.lang.s2_explain_body_factor_prefix) +
- ' ' + factor + '/' + factor + ' ' +
- (game.lang.s2_explain_body_factor_suffix);
- const line3 = bodyParts[1];
- const bodyStyle = { ...textStyles.p_, fill: colors.blueDark, font: 'bold 33px Arial, sans-serif' };
- const bodyStyleNormal = { ...textStyles.p_, fill: colors.blueDark, font: '33px Arial, sans-serif' };
- const bodyY = cardTop + 550;
- game.add.text(cx, bodyY, line1, bodyStyle, 38);
- game.add.text(cx, bodyY + 84, line2, bodyStyleNormal);
- game.add.text(cx, bodyY + 122, line3, bodyStyleNormal);
- // 5. Continue button
- const btnW = 400; const btnH = 75; const btnCenterY = cardBottom - 62;
- self.ui.explanation.button = game.add.geom.rect(
- cx - btnW / 2, btnCenterY - btnH / 2, btnW, btnH, colors.green
- );
- self.ui.explanation.text = game.add.text(cx, btnCenterY + 16, game.lang.continue, textStyles.btn);
- self.control.showExplanation = true;
- },
- renderEndUI: () => {
- let btnColor = colors.green;
- let btnText = game.lang.continue;
- if (!self.control.isCorrect) {
- btnColor = colors.red;
- btnText = game.lang.retry;
- }
- // continue button
- self.ui.continue.button = game.add.geom.rect(
- context.canvas.width / 2 + 400,
- context.canvas.height / 2 + 280,
- 450,
- 100,
- btnColor
- );
- self.ui.continue.button.anchor(0.5, 0.5);
- self.ui.continue.text = game.add.text(
- context.canvas.width / 2 + 400,
- context.canvas.height / 2 + 16 + 280,
- btnText,
- textStyles.btn
- );
- },
- startDelayHandler: () => {
- game.timer.stop();
- self.control.animationDelay++;
- if (self.control.animationDelay === 50) {
- self.ui.message[0].alpha = 0;
- self.utils.checkAnswer();
- self.control.animationDelay = 0;
- self.control.startEndAnimation = true;
- }
- },
- // UPDATE
- moveBlocks: function () {
- ['top', 'bottom'].forEach((cur) => {
- if (self.blocks[cur].animate) {
- // Lower selected blocks
- for (let i = 0; i < self.blocks[cur].selectedAmount; i++) {
- self.blocks[cur].list[i].y += 2;
- }
- // After fully lowering blocks, set fraction value
- if (self.blocks[cur].list[0].y >= self.blocks[cur].auxBlocks[0].y) {
- self.blocks[cur].fractions[0].name =
- self.blocks[cur].selectedAmount +
- '\n' +
- self.blocks[cur].list.length;
- self.blocks[cur].animate = false;
- }
- }
- });
- },
- checkAnswer: function () {
- self.control.isCorrect =
- self.blocks.top.selectedAmount / self.blocks.top.list.length ==
- self.blocks.bottom.selectedAmount / self.blocks.bottom.list.length;
- if (self.control.isCorrect) {
- // Correct: hide blocks to make room for explanation card
- ['top', 'bottom'].forEach((s) => {
- self.blocks[s].list.forEach((b) => (b.alpha = 0));
- self.blocks[s].auxBlocks.forEach((b) => (b.alpha = 0));
- self.blocks[s].fractions.forEach((b) => { if (b) b.alpha = 0; });
- if (self.blocks[s].label) self.blocks[s].label.alpha = 0;
- if (self.blocks[s].warningText) self.blocks[s].warningText.alpha = 0;
- });
- if (audioStatus) game.audio.okSound.play();
- completedLevels++;
- if (isDebugMode) console.log('Completed Levels: ' + completedLevels);
- } else {
- // Wrong: keep main blocks visible but hide aux blocks (they don't shift with renderOperationUI)
- ['top', 'bottom'].forEach((s) => {
- self.blocks[s].auxBlocks.forEach((b) => (b.alpha = 0));
- });
- if (audioStatus) game.audio.errorSound.play();
- }
- self.fetch.postScore();
- },
- runEndAnimation: function () {
- self.control.animationDelay++;
- if (self.control.animationDelay === 1) {
- if (self.control.isCorrect) {
- self.utils.renderExplanationUI();
- } else {
- const x = self.utils.renderOperationUI();
- game.add.image(x, context.canvas.height / 3, 'answer_wrong').anchor(0.5, 0.5);
- self.utils.renderEndUI();
- self.control.showEndInfo = true;
- }
- }
- },
- endLevel: function () {
- game.state.start('map');
- },
- // HANDLERS
- /**
- * Function called by self.onInputDown() when player clicked a valid rectangle.
- *
- * @param {object} curBlock clicked rectangle : can be self.blocks.top.list[i] or self.blocks.bottom.list[i]
- */
- clickSquareHandler: function (curBlock) {
- const curSet = curBlock.position;
- if (
- !self.blocks[curSet].hasClicked &&
- curBlock.index != self.blocks[curSet].list.length - 1
- ) {
- document.body.style.cursor = 'auto';
- // Turn auxiliar blocks invisible
- for (let i in self.blocks[curSet].list) {
- if (i > curBlock.index) self.blocks[curSet].auxBlocks[i].alpha = 0;
- }
- // Turn value label invisible
- self.blocks[curSet].label.alpha = 0;
- if (audioStatus) game.audio.popSound.play();
- // Save number of selected blocks
- self.blocks[curSet].selectedAmount = curBlock.index + 1;
- // Set fraction x position
- const newX =
- curBlock.finalX +
- self.blocks[curSet].selectedAmount *
- (self.control.blockWidth / self.blocks[curSet].list.length) +
- 40;
- self.blocks[curSet].fractions[0].x = newX;
- self.blocks[curSet].fractions[1].x = newX;
- self.blocks[curSet].fractions[0].name = `${curBlock.index + 1}\n${
- self.blocks[curSet].list.length
- }`;
- // End fraction line
- self.blocks[curSet].fractions[1].alpha = showFractions ? 1 : 0;
- self.blocks[curSet].hasClicked = true; // Inform player have clicked in current block set
- self.blocks[curSet].animate = true; // Let it initiate animation
- }
- game.render.all();
- },
- /**
- * Function called by self.onInputOver() when cursor is over a valid rectangle.
- *
- * @param {object} curBlock rectangle the cursor is over : can be self.blocks.top.list[i] or self.blocks.bottom.list[i]
- */
- overSquareHandler: function (curBlock) {
- const curSet = curBlock.position;
- if (!self.blocks[curSet].hasClicked) {
- // self.blocks.top.hasClicked || self.blocks.bottom.hasClicked
- // If over fraction 'n/n' shows warning message not allowing it
- if (curBlock.index == self.blocks[curSet].list.length - 1) {
- const otherSet = curSet == 'top' ? 'bottom' : 'top';
- self.blocks[curSet].warningText.alpha = 1;
- self.blocks[otherSet].warningText.alpha = 0;
- self.utils.outSquareHandler(curSet);
- } else {
- document.body.style.cursor = 'pointer';
- self.blocks.top.warningText.alpha = 0;
- self.blocks.bottom.warningText.alpha = 0;
- // Selected blocks become fully visible
- for (let i in self.blocks[curSet].list) {
- self.blocks[curSet].list[i].alpha = i <= curBlock.index ? 1 : 0.5;
- }
- self.blocks[curSet].fractions[0].name = curBlock.index + 1; // Nominator : selected blocks
- const newX =
- curBlock.finalX +
- (curBlock.index + 1) *
- (self.control.blockWidth / self.blocks[curSet].list.length) +
- 25;
- self.blocks[curSet].fractions[0].x = newX;
- self.blocks[curSet].fractions[1].x = newX;
- // End fraction nominator and denominator
- self.blocks[curSet].fractions[0].alpha = showFractions ? 1 : 0;
- }
- }
- },
- /**
- * Function called (by self.onInputOver() and self.utils.overSquareHandler()) when cursor is out of a valid rectangle.
- *
- * @param {object} curSet set of rectangles : can be top (self.blocks.top) or bottom (self.blocks.bottom)
- */
- outSquareHandler: function (curSet) {
- if (!self.blocks[curSet].hasClicked) {
- self.blocks[curSet].fractions[0].alpha = 0;
- self.blocks[curSet].fractions[1].alpha = 0;
- self.blocks[curSet].list.forEach((cur) => {
- cur.alpha = 0.5;
- });
- }
- },
- },
- events: {
- /**
- * Called by mouse click event
- *
- * @param {object} mouseEvent contains the mouse click coordinates
- */
- onInputDown: function (mouseEvent) {
- const x = game.math.getMouse(mouseEvent).x;
- const y = game.math.getMouse(mouseEvent).y;
- // Pre-start screen: only handle SIM/NÃO + navigation
- if (!self.control.started) {
- if (
- self.ui.startChoice &&
- self.ui.startChoice.yes &&
- self.ui.startChoice.yes.button &&
- game.math.isOverIcon(x, y, self.ui.startChoice.yes.button)
- ) {
- if (audioStatus) game.audio.popSound.play();
- self.control.challengeAnsweredYes = true;
- self.utils.updateChallengeChoiceUI();
- self.utils.startGame();
- return;
- }
- if (
- self.ui.startChoice &&
- self.ui.startChoice.no &&
- self.ui.startChoice.no.button &&
- game.math.isOverIcon(x, y, self.ui.startChoice.no.button)
- ) {
- if (audioStatus) game.audio.popSound.play();
- self.control.challengeAnsweredYes = false;
- self.utils.updateChallengeChoiceUI();
- self.utils.startGame();
- return;
- }
- navigation.onInputDown(x, y);
- game.render.all();
- return;
- }
- // Click block in (a)
- self.blocks.top.list.forEach((cur) => {
- if (game.math.isOverIcon(x, y, cur)) self.utils.clickSquareHandler(cur);
- });
- // Click block in (b)
- self.blocks.bottom.list.forEach((cur) => {
- if (game.math.isOverIcon(x, y, cur)) self.utils.clickSquareHandler(cur);
- });
- // Explanation screen continue/retry button
- if (self.control.showExplanation) {
- if (game.math.isOverIcon(x, y, self.ui.explanation.button)) {
- if (audioStatus) game.audio.popSound.play();
- canGoToNextMapPosition = self.control.isCorrect;
- self.utils.endLevel();
- }
- }
- // Continue button
- if (self.control.showEndInfo) {
- if (game.math.isOverIcon(x, y, self.ui.continue.button)) {
- if (audioStatus) game.audio.popSound.play();
- self.utils.endLevel();
- }
- }
- // Click navigation icons
- navigation.onInputDown(x, y);
- game.render.all();
- },
- /**
- * Called by mouse move event
- *
- * @param {object} mouseEvent contains the mouse move coordinates
- */
- onInputOver: function (mouseEvent) {
- const x = game.math.getMouse(mouseEvent).x;
- const y = game.math.getMouse(mouseEvent).y;
- // Pre-start screen hover: SIM/NÃO + navigation
- if (!self.control.started) {
- let isOverChoice = false;
- const choiceTextStyle = { ...textStyles.h3_, fill: colors.blueDark, font: 'bold ' + textStyles.h3_.font };
- const yesBtn = self.ui.startChoice && self.ui.startChoice.yes && self.ui.startChoice.yes.button;
- const noBtn = self.ui.startChoice && self.ui.startChoice.no && self.ui.startChoice.no.button;
- const yesVisual = self.ui.startChoice && self.ui.startChoice.yes && self.ui.startChoice.yes.visual;
- const noVisual = self.ui.startChoice && self.ui.startChoice.no && self.ui.startChoice.no.visual;
- if (yesBtn && game.math.isOverIcon(x, y, yesBtn)) {
- isOverChoice = true;
- document.body.style.cursor = 'pointer';
- if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.style = choiceTextStyle;
- if (yesVisual && yesVisual.outerParts && yesVisual.innerParts) {
- yesVisual.outerParts.forEach((p) => { p.shadowBlur = 10; });
- yesVisual.innerParts.forEach((p) => { p.fillColor = '#e8f0fc'; p.lineColor = '#e8f0fc'; });
- }
- } else if (yesBtn) {
- if (self.ui.startChoice.yes.text) self.ui.startChoice.yes.text.style = choiceTextStyle;
- if (yesVisual && yesVisual.outerParts && yesVisual.innerParts) {
- yesVisual.outerParts.forEach((p) => { p.shadowBlur = 8; });
- yesVisual.innerParts.forEach((p) => { p.fillColor = colors.white; p.lineColor = colors.white; });
- }
- }
- if (noBtn && game.math.isOverIcon(x, y, noBtn)) {
- isOverChoice = true;
- document.body.style.cursor = 'pointer';
- if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.style = choiceTextStyle;
- if (noVisual && noVisual.outerParts && noVisual.innerParts) {
- noVisual.outerParts.forEach((p) => { p.shadowBlur = 10; });
- noVisual.innerParts.forEach((p) => { p.fillColor = '#e8f0fc'; p.lineColor = '#e8f0fc'; });
- }
- } else if (noBtn) {
- if (self.ui.startChoice.no.text) self.ui.startChoice.no.text.style = choiceTextStyle;
- if (noVisual && noVisual.outerParts && noVisual.innerParts) {
- noVisual.outerParts.forEach((p) => { p.shadowBlur = 8; });
- noVisual.innerParts.forEach((p) => { p.fillColor = colors.white; p.lineColor = colors.white; });
- }
- }
- if (!isOverChoice) document.body.style.cursor = 'auto';
- navigation.onInputOver(x, y);
- game.render.all();
- return;
- }
- let flagA = false;
- let flagB = false;
- // Mouse over (a) : show fraction
- self.blocks.top.list.forEach((cur) => {
- if (game.math.isOverIcon(x, y, cur)) {
- flagA = true;
- self.utils.overSquareHandler(cur);
- }
- });
- if (!flagA) self.utils.outSquareHandler('top');
- // Mouse over (b) : show fraction
- self.blocks.bottom.list.forEach((cur) => {
- if (game.math.isOverIcon(x, y, cur)) {
- flagB = true;
- self.utils.overSquareHandler(cur);
- }
- });
- if (!flagB) self.utils.outSquareHandler('bottom');
- if (!flagA && !flagB) document.body.style.cursor = 'auto';
- // Explanation button hover
- if (self.control.showExplanation && self.ui.explanation.button) {
- if (game.math.isOverIcon(x, y, self.ui.explanation.button)) {
- document.body.style.cursor = 'pointer';
- self.ui.explanation.button.scale = self.ui.explanation.button.initialScale * 1.1;
- self.ui.explanation.text.style = textStyles.btnLg;
- } else {
- self.ui.explanation.button.scale = self.ui.explanation.button.initialScale * 1;
- self.ui.explanation.text.style = textStyles.btn;
- }
- }
- // Continue button
- if (self.control.showEndInfo) {
- if (game.math.isOverIcon(x, y, self.ui.continue.button)) {
- // If pointer is over icon
- document.body.style.cursor = 'pointer';
- self.ui.continue.button.scale =
- self.ui.continue.button.initialScale * 1.1;
- self.ui.continue.text.style = textStyles.btnLg;
- } else {
- // If pointer is not over icon
- document.body.style.cursor = 'auto';
- self.ui.continue.button.scale =
- self.ui.continue.button.initialScale * 1;
- self.ui.continue.text.style = textStyles.btn;
- }
- }
- // Mouse over navigation icons : show name
- navigation.onInputOver(x, y);
- game.render.all();
- },
- },
- fetch: {
- /**
- * Saves players data after level ends - to be sent to database. <br>
- *
- * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
- *
- * @see /php/save.php
- */
- postScore: function () {
- // Creates string that is going to be sent to db
- const data =
- '&line_game=' +
- gameShape +
- '&line_mode=' +
- gameMode +
- '&line_oper=equal' +
- '&line_leve=' +
- gameDifficulty +
- '&line_posi=' +
- curMapPosition +
- '&line_resu=' +
- self.control.isCorrect +
- '&line_time=' +
- game.timer.elapsed +
- '&line_deta=' +
- 'numBlocksA: ' +
- self.blocks.top.list.length +
- ', valueA: ' +
- self.blocks.top.selectedAmount +
- ', numBlocksB: ' +
- self.blocks.bottom.list.length +
- ', valueB: ' +
- self.blocks.bottom.selectedAmount +
- '&challenge_answered_yes=' +
- self.control.challengeAnsweredYes;
- // FOR MOODLE
- sendToDatabase(data);
- },
- },
- };
|