File indexing completed on 2024-05-12 03:42:47

0001 /* GCompris - frieze.js
0002  *
0003  * SPDX-FileCopyrightText: 2023 Bruno ANSELME <be.root@free.fr>
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  *
0006  * Token encoding rules for levels'data
0007  *  3 letters for shape, color and size (shapes, colors and sizes below)
0008  *    "sGb" = square, green and big
0009  *    "tRs" = triangle, red and small
0010  */
0011 .pragma library
0012 .import GCompris 1.0 as GCompris // for ApplicationInfo
0013 .import "qrc:/gcompris/src/core/core.js" as Core
0014 
0015 const svgUrl = "qrc:/gcompris/src/activities/frieze/resource/svg/"
0016 
0017 var numberOfLevel
0018 var items
0019 var friezeSize = 10
0020 var innerColor = "burlywood"
0021 var outerColor = "brown"
0022 var svgHeader = "data:image/svg+xml;utf8,"
0023 var started = false
0024 
0025 var emptyToken = "eBb"  // Empty shape, any color, any size
0026 var tokenSvg = {
0027     "e": "empty.svg",
0028     "s": "square.svg",
0029     "t": "triangle.svg",
0030     "c": "circle.svg",
0031     "f": "star.svg"
0032 }
0033 
0034 var shapes = {}
0035 var smallShapes = {}
0036 
0037 var palette = {
0038         "R": "#da4343",   // red
0039         "G": "#52d460",   // green
0040         "B": "#48cbdf",   // blue
0041         "Y": "#f1c43c"    // yellow
0042     }
0043 var darkPalette = {
0044         "R": "#8a2828",
0045         "G": "#3c7942",
0046         "B": "#34747e",
0047         "Y": "#9b7c17"
0048     }
0049 var colorChars = {
0050         "R": "R.svg",
0051         "G": "G.svg",
0052         "B": "B.svg",
0053         "Y": "Y.svg"
0054     }
0055 
0056 var tokenSizes = [ "b", "s"]  // Big, small
0057 
0058 var tokenPatterns = {}
0059 var noDupPatterns = {}      // Non-duplicate pattern (never use the same symbol twice consecutively)
0060 
0061 function start(items_) {
0062     items = items_;
0063     numberOfLevel = items.levels.length;
0064     items.currentLevel = Core.getInitialLevel(numberOfLevel);
0065     for(var symbol in tokenSvg) {   // Load svg as source code
0066         shapes[symbol] = items.file.read(svgUrl + tokenSvg[symbol]);
0067         if (symbol === "e")
0068             smallShapes[symbol] = items.file.read(svgUrl + tokenSvg[symbol]);
0069         else
0070             smallShapes[symbol] = items.file.read(svgUrl + tokenSvg[symbol].replace(".svg", "-small.svg"));
0071     }
0072     createPatterns();
0073     started = true;
0074     initLevel();
0075 }
0076 
0077 function stop() {
0078 }
0079 
0080 function addPattern(patterns, str, limit, depth) {
0081     var len = str.length;
0082     if (len === depth) {
0083         patterns.push(str);
0084         return;
0085     }
0086     var newStr = str + "A";
0087     for (var j = 0; j <= limit; j++) {
0088         addPattern(patterns, newStr, j, depth);
0089         if (j === 0)
0090             limit++;
0091         var charCode = newStr.charCodeAt(len);
0092         newStr = newStr.slice(0, len);
0093         newStr += String.fromCharCode(charCode + 1);
0094     }
0095 }
0096 
0097 function removeDuplicate(patterns) {    // Remove patterns with duplicate symbols consecutively
0098     var noDup = [];
0099     for (var i = 0; i < patterns.length; i++) {
0100         var last = "";
0101         var first = patterns[i][0];
0102         var ok = true;
0103         for (var j = 0; j < patterns[i].length; j++) {
0104             if (patterns[i][j] === last) {
0105                 ok = false;
0106             } else {
0107                 last = patterns[i][j];
0108             }
0109         }
0110         ok &= (patterns[i][patterns[i].length - 1] !== first);   // when last equal first, it's a duplicated symbol
0111         if (ok) noDup.push(patterns[i]);
0112     }
0113     return noDup;
0114 }
0115 
0116 function createPatterns() {
0117     for (var depth = 2; depth < 9; depth++) {
0118         var patterns = [];
0119         addPattern(patterns, "A", 0, depth);
0120         patterns.shift();        // remove first pattern (AAAAA)
0121         tokenPatterns[depth] = patterns;
0122         noDupPatterns[depth] = removeDuplicate(patterns);
0123     }
0124 }
0125 
0126 function createFrieze() {
0127     items.solutionModel.clear();
0128     items.answerModel.clear();
0129     items.tokensModel.clear();
0130 
0131     // Build random arrays of shapes and colors
0132     var randShapes = Object.keys(shapes);
0133     randShapes.shift();
0134     Core.shuffle(randShapes);
0135     var randColors = Object.keys(palette);
0136     Core.shuffle(randColors);
0137     // Build random tokens from data description
0138     var theTokens = [];
0139     var level = items.levels[items.currentLevel].subLevels[items.currentSubLevel];
0140     friezeSize = level.count;
0141     for (var i = 0; i < level.tokens.length; i++) {
0142         theTokens[i] = randShapes[level.tokens[i][0] - 1] + randColors[level.tokens[i][1] - 1] + tokenSizes[level.tokens[i][2] - 1];
0143     }
0144     // Insert theTokens into tokensModel
0145     for (i = 0; i < theTokens.length; i++) {
0146         items.tokensModel.append({ "content_": theTokens[i],
0147                                    "clickable_": true,
0148                                    "shown_": true,
0149                                    "animated_": false});
0150     }
0151     // Select a random pattern with patLength size or tokensModel's
0152     var pattern =  (items.levels[items.currentLevel].duplicate)
0153             ? tokenPatterns[level.patLength][Math.floor(Math.random() * tokenPatterns[level.patLength].length)]
0154             : noDupPatterns[level.patLength][Math.floor(Math.random() * noDupPatterns[level.patLength].length)];
0155 
0156     while (items.solutionModel.count < friezeSize) {  // Repeat pattern to fill model
0157         for (var j = 0; j < pattern.length; j++) {
0158             var tok = JSON.parse(JSON.stringify(items.tokensModel.get(pattern.charCodeAt(j) - 'A'.charCodeAt(0))));
0159             tok.shown_ = (items.solutionModel.count < items.levels[items.currentLevel].shown);
0160             tok.clickable_ = false;
0161             items.solutionModel.append(tok);
0162         }
0163     }
0164     while (items.solutionModel.count > friezeSize) {
0165         items.solutionModel.remove(friezeSize, 1);     // Remove last element while too long
0166     }
0167     items.tokensModel.shuffleModel()    // Shuffle tokens when solution is built
0168     for (i = 0; i < friezeSize; i++)
0169         items.answerModel.append({ "content_": emptyToken,
0170                                    "clickable_": false,
0171                                    "shown_": true,
0172                                    "animated_": false });
0173     items.currentAnswer = 0;
0174     items.currentToken = 0;
0175     items.solution.visible = true;
0176     items.tokens.visible = !items.levels[items.currentLevel].hidden;
0177     items.readyButton.enabled = items.levels[items.currentLevel].hidden;
0178 }
0179 
0180 function initShape(tokenItem) {
0181     var content = tokenItem.content;
0182     var svgText = (content[2] === "b") ? shapes[content[0]] : smallShapes[content[0]];
0183     tokenItem.image.source = svgHeader + svgText.replace(innerColor, palette[content[1]]).replace(outerColor, darkPalette[content[1]]);
0184     tokenItem.colorChar.source = (content[0] === "e") ? svgUrl + "empty.svg" : svgUrl + colorChars[content[1]];
0185 }
0186 
0187 function tokenClicked(idx, content) {
0188     if (items.currentAnswer === items.answerModel.count)
0189         return;
0190     items.currentToken = idx;
0191     items.animationToken.x = items.tokens.currentItem.x;    // Start position for animationToken
0192     items.animationToken.y = items.tokens.currentItem.y;
0193     items.animationToken.content = content;
0194     items.tokens.currentItem.opacity = 0.0;
0195     items.animationToken.state = "moveto";
0196     items.buttonsBlocked = true;
0197 }
0198 
0199 function cancelDrop() {
0200     if (items.currentAnswer > 0 && !items.buttonsBlocked) {
0201         items.currentAnswer--;
0202         items.answer.children[items.currentAnswer].state = "fade";
0203         items.buttonsBlocked = true;
0204     }
0205 }
0206 
0207 function checkResult() {
0208     items.buttonsBlocked = true;
0209 
0210     var ok = true;
0211     for (var i = 0 ; i < friezeSize; i++) {
0212         var sol = items.solutionModel.get(i);
0213         var ans = items.answerModel.get(i);
0214         if (sol.content_ !== ans.content_)
0215             ok = false;
0216     }
0217     if (ok) {
0218         items.solution.visible = true;
0219         items.currentSubLevel++;
0220         for (var j= 0; j < friezeSize; j++) {
0221             items.solutionModel.setProperty(j, "shown_", true);
0222         }
0223         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0224         items.score.playWinAnimation();
0225     } else {
0226         items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav");
0227         items.errorRectangle.startAnimation();
0228         items.buttonsBlocked = false;
0229     }
0230 }
0231 
0232 function initLevel() {
0233     items.buttonsBlocked = false;
0234     numberOfLevel = items.levels.length;
0235     items.instruction.text = items.levels[items.currentLevel].title;
0236     items.subLevelCount = items.levels[items.currentLevel].subLevels.length;
0237     if (items.levels[items.currentLevel].shuffle)
0238         Core.shuffle(items.levels[items.currentLevel].subLevels);
0239     createFrieze();
0240 }
0241 
0242 function nextLevel() {
0243     items.score.stopWinAnimation();
0244     items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel);
0245     items.currentSubLevel = 0;
0246     initLevel();
0247 }
0248 
0249 function previousLevel() {
0250     items.score.stopWinAnimation();
0251     items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel);
0252     items.currentSubLevel = 0;
0253     initLevel();
0254 }
0255 
0256 function nextSubLevel() {
0257     items.errorRectangle.resetState();
0258     items.buttonsBlocked = false;
0259     if(items.currentSubLevel >= items.subLevelCount)
0260         items.bonus.good("smiley");
0261     else
0262         createFrieze();
0263 }
0264 
0265 function handleKeys(event) {
0266     if (items.buttonsBlocked)
0267         return
0268     switch (event.key) {
0269     case Qt.Key_Left:
0270         if (!items.readyButton.enabled)
0271             items.tokens.moveCurrentIndexLeft();
0272         break
0273     case Qt.Key_Right:
0274         if (!items.readyButton.enabled)
0275             items.tokens.moveCurrentIndexRight();
0276         break
0277     case Qt.Key_Up:
0278         if (!items.readyButton.enabled)
0279             items.tokens.moveCurrentIndexUp();
0280         break
0281     case Qt.Key_Down:
0282         if (!items.readyButton.enabled)
0283             items.tokens.moveCurrentIndexDown();
0284         break
0285     case Qt.Key_Space:
0286         if (!items.readyButton.enabled)
0287             tokenClicked(items.currentToken, items.tokens.currentItem.content);
0288         break
0289     case Qt.Key_Delete:
0290     case Qt.Key_Backspace:
0291         if (!items.readyButton.enabled)
0292             cancelDrop();
0293         break
0294     case Qt.Key_Enter:
0295     case Qt.Key_Return:
0296         if (items.currentAnswer === items.answerModel.count)
0297             checkResult();
0298         break
0299     case Qt.Key_Tab:
0300         if (items.levels[items.currentLevel].hidden)
0301             items.toggleReady();
0302         break
0303     }
0304 }