File indexing completed on 2024-05-05 15:53:20
0001 /* gcompris - sudoku.js 0002 0003 Copyright (C) 0004 2003, 2014: Bruno Coudoin: initial version 0005 2014: Johnny Jazeix: Qt port 0006 0007 SPDX-License-Identifier: GPL-3.0-or-later 0008 */ 0009 0010 .pragma library 0011 .import QtQuick 2.12 as Quick 0012 .import "qrc:/gcompris/src/core/core.js" as Core 0013 0014 var numberOfLevel 0015 var items 0016 var symbols 0017 var url = "qrc:/gcompris/src/activities/sudoku/resource/" 0018 0019 function start(items_) { 0020 items = items_ 0021 items.score.currentSubLevel = 0 0022 numberOfLevel = items.levels.length 0023 items.currentLevel = Core.getInitialLevel(numberOfLevel); 0024 0025 // Shuffle all levels 0026 for(var nb = 0 ; nb < items.levels.length ; ++ nb) { 0027 Core.shuffle(items.levels[nb]["data"]); 0028 } 0029 0030 initLevel() 0031 } 0032 0033 function stop() { 0034 } 0035 0036 function initLevel() { 0037 items.score.numberOfSubLevels = items.levels[items.currentLevel]["data"].length 0038 symbols = items.levels[items.currentLevel]["symbols"] 0039 0040 for(var i = items.availablePiecesModel.model.count-1 ; i >= 0 ; -- i) { 0041 items.availablePiecesModel.model.remove(i); 0042 } 0043 items.sudokuModel.clear(); 0044 0045 // Copy current sudoku in local variable 0046 var initialSudoku = items.levels[items.currentLevel]["data"][items.score.currentSubLevel]; 0047 0048 items.columns = initialSudoku.length 0049 items.rows = items.columns 0050 0051 // Compute number of regions 0052 var nbLines = Math.floor(Math.sqrt(items.columns)); 0053 items.background.nbRegions = (nbLines*nbLines == items.columns) ? nbLines : 1; 0054 0055 // Create grid 0056 for(var i = 0 ; i < initialSudoku.length ; ++ i) { 0057 var line = []; 0058 for(var j = 0 ; j < initialSudoku[i].length ; ++ j) { 0059 items.sudokuModel.append({ 0060 'textValue': initialSudoku[i][j], 0061 'initial': initialSudoku[i][j] != ".", 0062 'mState': initialSudoku[i][j] != "." ? "initial" : "default", 0063 }) 0064 } 0065 } 0066 0067 for(var i = 0 ; i < symbols.length ; ++ i) { 0068 for(var line = 0 ; line < items.sudokuModel.count ; ++ line) { 0069 if(items.sudokuModel.get(line).textValue == symbols[i].text) { 0070 items.availablePiecesModel.model.append(symbols[i]); 0071 break; // break to pass to the next symbol 0072 } 0073 } 0074 } 0075 items.buttonsBlocked = false 0076 } 0077 0078 function nextLevel() { 0079 items.score.stopWinAnimation() 0080 items.score.currentSubLevel = 0 0081 items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel); 0082 initLevel(); 0083 } 0084 0085 function previousLevel() { 0086 items.score.stopWinAnimation() 0087 items.score.currentSubLevel = 0 0088 items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel); 0089 initLevel(); 0090 } 0091 0092 /* 0093 Code that increments the sublevel and level 0094 And bail out if no more levels are available 0095 */ 0096 function incrementLevel() { 0097 if(items.score.currentSubLevel >= items.score.numberOfSubLevels) { 0098 items.bonus.good("flower") 0099 } 0100 else { 0101 initLevel() 0102 } 0103 } 0104 0105 function clickOn(caseX, caseY) { 0106 var initialSudoku = items.levels[items.currentLevel]["data"][items.score.currentSubLevel]; 0107 0108 var currentCase = caseX + caseY * initialSudoku.length; 0109 0110 if(initialSudoku[caseY][caseX] == '.') { // Don't update fixed cases. 0111 var currentSymbol = items.availablePiecesModel.model.get(items.availablePiecesModel.view.currentIndex); 0112 var isGood = isLegal(caseX, caseY, currentSymbol.text); 0113 /* 0114 If current case is empty, we look if it is legal and put the symbol. 0115 Else, we colorize the existing cases in conflict with the one pressed 0116 */ 0117 if(items.sudokuModel.get(currentCase).textValue == '.') { 0118 if(isGood) { 0119 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/win.wav') 0120 items.sudokuModel.get(currentCase).textValue = currentSymbol.text 0121 } else { 0122 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') 0123 } 0124 } 0125 else { 0126 // Already a symbol in this case, we remove it 0127 items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/darken.wav') 0128 items.sudokuModel.get(currentCase).textValue = '.' 0129 } 0130 } 0131 0132 if(isSolved()) { 0133 items.buttonsBlocked = true 0134 items.score.currentSubLevel += 1 0135 items.score.playWinAnimation() 0136 items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav") 0137 } 0138 } 0139 0140 // return true or false if the given number is possible 0141 function isLegal(posX, posY, value) { 0142 0143 var possible = true 0144 0145 // Check this number is not already in a row 0146 var firstX = posY * items.columns; 0147 var lastX = firstX + items.columns-1; 0148 0149 var clickedCase = posX + posY * items.columns; 0150 0151 for (var x = firstX ; x <= lastX ; ++ x) { 0152 if (x == clickedCase) 0153 continue 0154 0155 var rowValue = items.sudokuModel.get(x) 0156 0157 if(value == rowValue.textValue) { 0158 items.sudokuModel.get(x).mState = "error"; 0159 possible = false 0160 } 0161 } 0162 0163 var firstY = posX; 0164 var lastY = items.sudokuModel.count - items.columns + firstY; 0165 0166 // Check this number is not already in a column 0167 for (var y = firstY ; y <= lastY ; y += items.columns) { 0168 0169 if (y == clickedCase) 0170 continue 0171 0172 var colValue = items.sudokuModel.get(y) 0173 0174 if(value == colValue.textValue) { 0175 items.sudokuModel.get(y).mState = "error"; 0176 possible = false 0177 } 0178 } 0179 0180 // Check this number is in a region 0181 if(items.background.nbRegions > 1) { 0182 // First, find the top-left square of the region 0183 var firstSquareX = Math.floor(posX/items.background.nbRegions)*items.background.nbRegions; 0184 var firstSquareY = Math.floor(posY/items.background.nbRegions)*items.background.nbRegions; 0185 0186 for(var x = firstSquareX ; x < firstSquareX +items.background.nbRegions ; x ++) { 0187 for(var y = firstSquareY ; y < firstSquareY + items.background.nbRegions ; y ++) { 0188 if(x == posX && y == posY) { 0189 // Do not check the current square 0190 continue; 0191 } 0192 0193 var checkedCase = x + y * items.columns; 0194 var checkedCaseValue = items.sudokuModel.get(checkedCase) 0195 0196 if(value == checkedCaseValue.textValue) { 0197 items.sudokuModel.get(checkedCase).mState = "error"; 0198 possible = false 0199 } 0200 } 0201 } 0202 } 0203 0204 return possible 0205 } 0206 0207 /* 0208 Return true or false if the given sudoku is solved 0209 We don't really check it's solved, only that all squares 0210 have a value. This works because only valid numbers can 0211 be entered up front. 0212 */ 0213 function isSolved() { 0214 for(var i = 0 ; i < items.sudokuModel.count ; ++ i) { 0215 var value = items.sudokuModel.get(i).textValue 0216 if(value == '.') 0217 return false 0218 } 0219 return true 0220 } 0221 0222 function restoreState(mCase) { 0223 items.sudokuModel.get(mCase.gridIndex).mState = mCase.isInitial ? "initial" : "default" 0224 } 0225 0226 function dataToImageSource(data) { 0227 var imageName = ""; 0228 0229 for(var i = 0 ; i < symbols.length ; ++ i) { 0230 if(symbols[i].text == data) { 0231 imageName = url + symbols[i].imgName; 0232 break; 0233 } 0234 } 0235 0236 return imageName; 0237 } 0238 0239 function onKeyPressed(event) { 0240 var keyValue = -1; 0241 switch(event.key) 0242 { 0243 case Qt.Key_1: 0244 keyValue = 0; 0245 break; 0246 case Qt.Key_2: 0247 keyValue = 1; 0248 break; 0249 case Qt.Key_3: 0250 keyValue = 2; 0251 break; 0252 case Qt.Key_4: 0253 keyValue = 3; 0254 break; 0255 case Qt.Key_5: 0256 keyValue = 4; 0257 break; 0258 case Qt.Key_6: 0259 keyValue = 5; 0260 break; 0261 case Qt.Key_7: 0262 keyValue = 6; 0263 break; 0264 case Qt.Key_8: 0265 keyValue = 7; 0266 break; 0267 case Qt.Key_9: 0268 keyValue = 8; 0269 break; 0270 } 0271 if(keyValue >= 0 && keyValue < items.availablePiecesModel.model.count) { 0272 items.availablePiecesModel.view.currentIndex = keyValue; 0273 event.accepted=true; 0274 } 0275 }