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 }