|
|
@@ -43,20 +43,48 @@ const squareTwo = {
|
|
|
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: {
|
|
|
@@ -93,12 +121,15 @@ const squareTwo = {
|
|
|
navigation.add.right(['audio']);
|
|
|
}
|
|
|
|
|
|
- // Add kid
|
|
|
- this.utils.renderCharacters();
|
|
|
- this.utils.renderBlockSetup();
|
|
|
- this.utils.renderMainUI();
|
|
|
+ // 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.timer.start(); // Set a timer for the current level (used in postScore)
|
|
|
game.event.add('click', this.events.onInputDown);
|
|
|
game.event.add('mousemove', this.events.onInputOver);
|
|
|
},
|
|
|
@@ -136,18 +167,18 @@ const squareTwo = {
|
|
|
|
|
|
utils: {
|
|
|
// RENDERS
|
|
|
- renderBlockSetup: function () {
|
|
|
+ generateBlockConfig: function () {
|
|
|
// Coordinates for (a) and (b)
|
|
|
let xA, xB, yA, yB;
|
|
|
|
|
|
if (gameMode != 'b') {
|
|
|
- // Has more subdivisions on (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 (a)
|
|
|
+ // Has more subdivisions on (b)
|
|
|
xB = context.canvas.width / 2 - self.control.blockWidth / 2;
|
|
|
yB = getFrameInfo().y + 100;
|
|
|
xA = xB;
|
|
|
@@ -192,6 +223,387 @@ const squareTwo = {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ 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,
|
|
|
@@ -443,6 +855,127 @@ const squareTwo = {
|
|
|
|
|
|
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, '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 factor = bigDen / smallDen;
|
|
|
+ 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 + 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;
|
|
|
+ game.add.text(cx, badgeCenterY - 8, factor + '/' + factor + ' = 1', {
|
|
|
+ ...textStyles.h4_, fill: colors.blueDark, font: 'bold 40px Arial, sans-serif',
|
|
|
+ });
|
|
|
+
|
|
|
+ // 4. Body text — below the star area, image body zone starts ~560px from image top
|
|
|
+ const bodyParts = withNewlines(game.lang.s2_explain_body_correct).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;
|
|
|
@@ -502,33 +1035,28 @@ const squareTwo = {
|
|
|
});
|
|
|
},
|
|
|
checkAnswer: function () {
|
|
|
- for (let block of self.blocks.top.auxBlocks) {
|
|
|
- block.alpha = 0;
|
|
|
- }
|
|
|
- for (let block of self.blocks.bottom.auxBlocks) {
|
|
|
- block.alpha = 0;
|
|
|
- }
|
|
|
-
|
|
|
- // After delay is over, check result
|
|
|
- //if (self.control.animationDelay > 50) {
|
|
|
self.control.isCorrect =
|
|
|
self.blocks.top.selectedAmount / self.blocks.top.list.length ==
|
|
|
self.blocks.bottom.selectedAmount / self.blocks.bottom.list.length;
|
|
|
|
|
|
- const x = self.utils.renderOperationUI();
|
|
|
-
|
|
|
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();
|
|
|
- game.add
|
|
|
- .image(x + 50, context.canvas.height / 2, 'answer_correct')
|
|
|
- .anchor(0.5, 0.5);
|
|
|
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();
|
|
|
- game.add
|
|
|
- .image(x, context.canvas.height / 2, 'answer_wrong')
|
|
|
- .anchor(0.5, 0.5);
|
|
|
}
|
|
|
|
|
|
self.fetch.postScore();
|
|
|
@@ -537,11 +1065,14 @@ const squareTwo = {
|
|
|
self.control.animationDelay++;
|
|
|
|
|
|
if (self.control.animationDelay === 100) {
|
|
|
- self.utils.renderEndUI();
|
|
|
- self.control.showEndInfo = true;
|
|
|
-
|
|
|
- if (self.control.isCorrect) canGoToNextMapPosition = true;
|
|
|
- else canGoToNextMapPosition = false;
|
|
|
+ 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 () {
|
|
|
@@ -669,6 +1200,38 @@ const squareTwo = {
|
|
|
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);
|
|
|
@@ -679,6 +1242,15 @@ const squareTwo = {
|
|
|
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)) {
|
|
|
@@ -701,6 +1273,55 @@ const squareTwo = {
|
|
|
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;
|
|
|
|
|
|
@@ -724,6 +1345,18 @@ const squareTwo = {
|
|
|
|
|
|
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)) {
|
|
|
@@ -779,7 +1412,9 @@ const squareTwo = {
|
|
|
', numBlocksB: ' +
|
|
|
self.blocks.bottom.list.length +
|
|
|
', valueB: ' +
|
|
|
- self.blocks.bottom.selectedAmount;
|
|
|
+ self.blocks.bottom.selectedAmount +
|
|
|
+ '&challenge_answered_yes=' +
|
|
|
+ self.control.challengeAnsweredYes;
|
|
|
|
|
|
// FOR MOODLE
|
|
|
sendToDatabase(data);
|