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 }