File indexing completed on 2024-04-21 14:43:45

0001 /* GCompris - traffic.js
0002  *
0003  * SPDX-FileCopyrightText: 2014 Holger Kaelberer
0004  *
0005  * Authors:
0006  *   Bruno Coudoin <bruno.coudoin@gcompris.net> (GTK+ version)
0007  *   Holger Kaelberer <holger.k@elberer.de> (Qt Quick port)
0008  *
0009  *   SPDX-License-Identifier: GPL-3.0-or-later
0010  */
0011 
0012 .pragma library
0013 .import QtQuick 2.12 as Quick
0014 .import QtQml 2.12 as Qml
0015 .import "qrc:/gcompris/src/core/core.js" as Core
0016 
0017 /* The format and the dataset for the traffic game in gcompris
0018  * is taken from
0019  * http://www.javascript-games.org/puzzle/rushhour/
0020  *
0021  * [LevelX]
0022  * CardY=string describing card 1
0023  * CardY=string describing card 2
0024  * ...
0025  * Where X is the Gcompris Level (in the control bar)
0026  * Where Y is the sublevel.
0027  *
0028  * This is followed by a comma separated list defining the cars on the
0029  * grid. So the string looks as follows:
0030  *
0031  * 'ID''X''Y'
0032  *
0033  * - 'ID' is one char in the range A-R and X
0034  *   A-K Specify a different car color of size 2
0035  *   O-R Specify a different car color of size 3
0036  *   X   Always Red, the goal car of size 2
0037  *
0038  * - 'X' xpos numbers between
0039  *    1 to 6 for Vertical car
0040  *    A to F for Horizontal car
0041  *
0042  * - 'Y' ypos numbers between
0043  *    1 to 6 for Horizontal car
0044  *    A to F for Vertical car
0045  *
0046  */
0047 var dataList = [
0048   /* [Level1] */
0049   "XD3,O2F",
0050   "XD3,A4C,O2F,PD5",
0051   "XC3,A2E,BE5,O2F,P4B",
0052   "XB3,AB1,B2E,C2F,D4C,OD1,PD4",
0053   "XC3,A3F,BE6,O2E,PD5,Q2B",
0054   /* [Level2] */
0055   "XB3,AE5,O2F,P4D",
0056   "XB3,A2F,B4C,OD4,PB6,QA1,RD1",
0057   "XC3,AC2,B3B,C4C,O2F,PD5",
0058   "XD3,AB2,B4D,CE5,O2F",
0059   "XC3,A1F,B2E,CD4,DE6,E5D,O3F",
0060   /* [Level3] */
0061   "XC3,A2E,BB4,C5B,OA1,PD1,QC5",
0062   "XC3,AB2,B3B,C4D,DE5,O1E",
0063   "XD3,AE2,BE6,C5B,O3F,P4D",
0064   "XD3,AD2,BE5,C4D,OA1,PD1,Q2F",
0065   "XC3,AB4,CE6,D5B,OC5,P1E",
0066   /* [Level4] */
0067   "XC3,AA1,BC2,CE5,D2B,E4C,O1E",
0068   "XA3,AB1,B2C,CD4,E3F,OD1,F4C,QD5",
0069   "XA3,AE4,BC6,CE6,D2C,E2F,F4C,G4D,OD1,P4A",
0070   "XC3,AB2,BD2,CC4,D5B,E3E,FC6,GE6,O2F,PD5",
0071   "XD3,A2C,BD2,C2F,DE4,E4D,F5B,OA1,PD1,QC6",
0072   /* [Level5] */
0073   "XC3,A1C,B2F,C4C,D4D,EE4,FC6,GE6,OD1,P4B",
0074   "XD3,A2B,B4C,CE6,O2F,P4D,Q1A,R4A",
0075   "XD3,A1D,BC5,CC6,DE6,E4E,F5B,GE1,O2F,PB4",
0076   "XB3,AD2,BB4,C3E,DE5,EE6,O2F,P3D",
0077   "XB3,AD2,B3E,CB4,DE5,EE6,OA1,PD1,Q2F,R3D",
0078   /* [Level6] */
0079   "XC3,AC2,BE2,C3E,D3F,EB4,FE5,GE6,HA6,O4D,P3A,QD1",
0080   "XB3,AA1,B5A,CE5,O1F,P2A,Q2D,RC6",
0081   "XA3,A1A,B2D,C3E,D5C,EE5,FA6,GD6,OD1,P2F,QA4",
0082   "XB3,AB4,B5B,CC6,O3D,P4F",
0083   "XB3,A4C,B5F,O1A,P1D,QD4,RC6",
0084   /* [Level7] */
0085   "XB3,AA1,B1D,CA2,DA4,E4C,F5A,O2E,P2F,Q3D,RD6",
0086   "XB3,A1B,BC1,C1E,D1F,E2D,F3F,IC4,H5D",
0087   "XA3,AD1,BC2,C2E,D3C,E3D,FA4,GE4,HA5,I5C,KA6,O1F,PD5,QD6",
0088   "XA3,A1B,BC1,CE1,D2D,EE2,F3F,G5C,H5F,O3E,P4A,QB4",
0089   "XB3,AA1,B1C,CE1,DA2,E5D,FE5,GA6,HE6,O2F,P3A,QB4",
0090   /* [Level8] */
0091   "XB3,AB1,B4C,E5F,O1A,P1D,QD4,RC6",
0092   "XA3,A1A,BB1,C5E,O1F,P2C,QD4,RA6",
0093   "XD3,AA1,BC1,C1E,D2C,E3B,FD4,G5D,HE5,IB6,KE6,O2F,P4A",
0094   "XC3,AA1,B1C,CE2,D3A,E3B,F3E,G3F,HC4,I5C,JE5,KA6",
0095   "XC3,AB1,BD1,CA2,DC2,E4C,F4D,GE5,HB6,ID6,O2E,P2F,Q3A,R3B",
0096   /* [Level9] */
0097   "XD3,AA1,BC1,C1E,D2A,EC2,F3B,GA6,O1F,P3C,QD4",
0098   "XA3,A1A,BC2,CE2,D3C,EA4,F5E,G5F,OB1,P4D,QA5,RA6",
0099   "XB3,AA1,B1C,CA2,DB5,O1D,P3A,QB4,RA6",
0100   "XC3,A1C,BD1,D3B,EC4,F4E,J2E,OB5",
0101   "XA3,A1A,BB2,C2D,D3C,E5C,FD5,OD1,P3F,QD6",
0102   /* [Level10] */
0103   "XB3,AA1,B1C,O1D,P2A,QB4,RD6",
0104   "XB3,A1C,B2A,CE2,D4B,EE4,F5A,GC5,H5F,OD1,P2D,QB6",
0105   "XD3,A2C,BD2,C4C,D4D,EE4,FE5,OC1,P1F,QC6",
0106   "XC3,A1C,BD1,C2B,D3A,E3E,FB4,G5E,HA6,OA5",
0107   "XB3,AA1,B1C,CE1,DA2,E3E,F5B,G5D,HE5,IE6,O2F,P3A,QB4",
0108   /* [Level11] */
0109   "XB3,A1B,B2A,C2D,D3F,E4A,F5C,G5F,HD6,OD1,P2E,RB4",
0110   "XA3,A1A,BB1,CB2,D3C,ED4,F5C,O1D,P3F,RD6",
0111   "XA3,A1D,BE2,C4A,D4B,ED4,FA6,GC6,OA1,P2C,Q4F,RC5",
0112   "XA3,A2C,B3F,C4A,DB4,ED4,FB5,G5D,H5F,OA1,P1E,RA6",
0113   "XB3,A1C,B2D,CA4,DC4,EA6,FC6,O1A,PD1,Q4F",
0114   /* [Level12] */
0115   "XB3,AA1,B2D,CE2,D3A,ED4,FA5,OD1,P3F,Q4C,RD6",
0116   "XA3,AA1,B1D,CE1,D4A,EB4,FD4,HA6,K5D,O1C,P4F",
0117   "XA3,A1B,BE1,DB4,ED4,FB5,G5D,H5E,I4A,P4F,QA6,R1C", 
0118   "XA3,A1A,B2D,C3E,D4D,E5C,FE5,HD6,IA6,P2F,QA4,RD1",
0119   "XA3,AD1,B2D,DB5,E5D,F5E,GA6,K4A,O1C,P1F,QB4",
0120   /* [Level13] */
0121   "XC3,AE1,B2B,CC2,D4D,E5C,FE5,GA6,O1A,PB1,Q2F,RA4",
0122   "XB3,AA1,B1C,CE1,DA2,E5D,FE5,GA6,HE6,O2E,P2F,Q3A,RB4",
0123   "XA3,A1A,BB2,C2D,D3C,ED4,F5C,GD5,OD1,QD6,R3F",
0124   "XA3,A1C,B2D,C3C,DA4,ED4,F5A,G5B,HC5,IC6,OD1,R3F",
0125   "XD3,AB1,B1E,C2B,D2C,E4D,F5C,GE5,HA6,ID6,O1A,P2F,QA4"
0126 ];
0127 
0128 var colorMap = {
0129     'X': "#DA3131",
0130     'A': "#E9CA39",
0131     'B': "#467EEA",
0132     'C': "#4BD754",
0133     'D': "#D8813C",
0134     'E': "#BF36C8",
0135     'F': "#4B59D7",
0136     'G': "#36C8C5",
0137     'H': "#C8B336",
0138     'I': "#D9D9D9",
0139     'J': "#66D83C",
0140     'K': "#6D36C8",
0141     'O': "#3A8FC2",
0142     'P': "#CE9161",
0143     'Q': "#CDCDCD",
0144     'R': "#7BCE61"
0145 };
0146 
0147 var baseUrl="qrc:/gcompris/src/activities/traffic/resource/";
0148 
0149 var carList = [
0150   baseUrl + "car1.svg",
0151   baseUrl + "car2.svg",
0152   baseUrl + "car3.svg",
0153   baseUrl + "car4.svg",
0154   baseUrl + "car5.svg",
0155   baseUrl + "car6.svg",
0156   baseUrl + "car7.svg",
0157   baseUrl + "car8.svg",
0158   baseUrl + "car9.svg",
0159   baseUrl + "car10.svg",
0160   baseUrl + "car11.svg",
0161   baseUrl + "car12.svg"
0162 ];
0163 
0164 var truckList = [
0165   baseUrl + "truck1.svg",
0166   baseUrl + "truck2.svg",
0167   baseUrl + "truck3.svg",
0168   baseUrl + "truck4.svg"
0169 ];
0170 
0171 var level = null;
0172 var maxLevel = 13;
0173 var maxSubLevel = 5;
0174 var items;
0175 var cars;
0176 var numCars;
0177 var carComponent = null;
0178 var activeCars = new Array();
0179 var haveWon = false;
0180 var mode = null;
0181 var truckIndex = 0;
0182 var carIndex = 0;
0183 var isMoving = false;
0184 
0185 function start(items_, mode_) {
0186     items = items_;
0187     mode = mode_;
0188     items.currentLevel = Core.getInitialLevel(maxLevel);
0189     items.score.currentSubLevel = 0;
0190     initLevel();
0191 }
0192 
0193 function stop() {
0194     cleanupActiveCars();
0195 }
0196 
0197 function findYBounds(car)
0198 {
0199     if (car.yBounds !== undefined)
0200         return;
0201     var bounds = { "lower": 0, "upper": items.jamGrid.height };
0202 
0203     for (var i = 0; i < activeCars.length; i++) {
0204         if (activeCars[i] != car &&
0205             ((activeCars[i].xPos == car.xPos)
0206              || (activeCars[i].isHorizontal && activeCars[i].xPos < car.xPos 
0207                  && activeCars[i].xPos+activeCars[i].size > car.xPos)))
0208         {
0209             // y intersects
0210             if (activeCars[i].effY < car.effY &&
0211                 activeCars[i].effY + activeCars[i].effHeight > bounds.lower) {
0212                 bounds.lower = activeCars[i].effY + activeCars[i].effHeight;
0213             }
0214             else if (activeCars[i].effY > car.effY && activeCars[i].effY < bounds.upper)
0215                 bounds.upper = activeCars[i].effY;
0216         }
0217     }
0218     car.yBounds = bounds;
0219 }
0220 
0221 function findXBounds(car)
0222 {
0223     if (car.xBounds !== undefined)
0224         return;
0225     var bounds = { "lower": 0, "upper": items.jamGrid.width + (car.goal ? car.blockSize : 0)};
0226     for (var i = 0; i < activeCars.length; i++) {
0227         if (activeCars[i] != car &&
0228             ((activeCars[i].yPos == car.yPos)
0229              || (!activeCars[i].isHorizontal && activeCars[i].yPos < car.yPos 
0230                  && activeCars[i].yPos+activeCars[i].size > car.yPos)))
0231         {
0232             // y intersects
0233             if (activeCars[i].effX < car.effX &&
0234                 activeCars[i].effX + activeCars[i].effWidth > bounds.lower) {
0235                 bounds.lower = activeCars[i].effX + activeCars[i].effWidth;
0236             }
0237             else if (activeCars[i].effX > car.effX && activeCars[i].effX < bounds.upper)
0238                 bounds.upper = activeCars[i].effX;
0239         }
0240     }
0241     car.xBounds = bounds;
0242 }
0243 
0244 function updateCarPosition(car, newX, newY)
0245 {
0246     if (car.isHorizontal) {
0247         findXBounds(car);
0248         var deltaX = Math.min(car.xBounds.upper - car.effWidth, Math.max(car.xBounds.lower, newX)) - car.effX;
0249         car.x += deltaX;
0250         car.effX += deltaX;
0251         // check for reached goal:
0252         if (car.goal && car.effX + car.width >= items.jamGrid.width) {
0253             haveWon = true;
0254             items.score.currentSubLevel += 1;
0255             items.score.playWinAnimation();
0256             items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav");
0257             return;
0258         }
0259     } else {
0260         findYBounds(car)
0261         var deltaY = Math.min(car.yBounds.upper - car.effHeight, Math.max(car.yBounds.lower, newY)) - car.effY;
0262         car.y += deltaY;
0263         car.effY += deltaY;
0264     }
0265 }
0266 
0267 function snapCarToGrid(car)
0268 {
0269     if (car.isHorizontal) {
0270         car.xPos = Math.min(5, Math.max(0, Math.round(car.x / car.blockSize)));
0271         car.x = car.effX = Qt.binding(function() { return car.xPos * car.blockSize; });
0272     } else {
0273         car.yPos = Math.min(5, Math.max(0, Math.round(car.y / car.blockSize)));
0274         car.y = car.effY = Qt.binding(function() { return car.yPos * car.blockSize; });
0275     }
0276     car.xBounds = car.yBounds = undefined;
0277 }
0278 
0279 function drawCar(car)
0280 {
0281     if (!carComponent) {
0282         carComponent = Qt.createComponent("qrc:/gcompris/src/activities/traffic/Car.qml");
0283         if (carComponent.status != Qml.Component.Ready) {
0284             console.error("Error creating Rectangle component (" 
0285                     + carComponent.status + "): " + carComponent.errorString());
0286             carComponent = null;
0287             return;
0288         }
0289     }
0290     
0291     var id = car[0];
0292     var x = car[1];
0293     var y = car[2];
0294     var xPos;
0295     var yPos;
0296     xPos = 0;
0297     yPos = y.charCodeAt(0) - '1'.charCodeAt(0);
0298     var isHorizontal = true;
0299     var size = 0;
0300     var source;
0301     
0302     if (id == 'O' || id == 'P' || id == 'Q' || id == 'R') { 
0303         size = 3;
0304         source = truckList[Math.floor(truckIndex++ % truckList.length)];
0305     } else {
0306         size = 2;
0307         source = carList[Math.floor(carIndex++ % (carList.length-1)) + 1];
0308     }
0309     
0310     if (x == 'A') xPos = 0;
0311     else if (x == 'B') xPos = 1;
0312     else if (x == 'C') xPos = 2;
0313     else if (x == 'D') xPos = 3;
0314     else if (x == 'E') xPos = 4;
0315     else if (x == 'F') xPos = 5;
0316     else { // vertical
0317         yPos = x.charCodeAt(0) - '1'.charCodeAt(0); //vertical
0318         isHorizontal = false;
0319 
0320         if (y == 'A') xPos = 0;
0321         else if (y == 'B') xPos = 1;
0322         else if (y == 'C') xPos = 2;
0323         else if (y == 'D') xPos = 3;
0324         else if (y == 'E') xPos = 4;
0325         else if (y == 'F') xPos = 5;
0326     }
0327     
0328     var color = colorMap[id];
0329     var goal;
0330     if (id == 'X') {
0331         goal = 1;
0332         source = carList[0];
0333     }
0334 
0335     var carObject = carComponent.createObject( items.jamGrid, {
0336         "xPos": xPos,
0337         "yPos": yPos,
0338         "size": size,
0339         "goal": goal,
0340         "color": color,
0341         "source": source,
0342         "isHorizontal": isHorizontal,
0343         "audioEffects": items.audioEffects
0344 
0345     });
0346     if (carObject == null)
0347         console.error("traffic: Error creating Car object!");
0348     else
0349         activeCars.push(carObject);
0350 }
0351 
0352 function drawJam()
0353 {
0354     for (var i = 0; i < cars.length; i++)
0355         drawCar(cars[i]);
0356 }
0357 
0358 function cleanupActiveCars()
0359 {
0360     while (activeCars.length > 0)
0361         activeCars.pop().destroy();
0362 }
0363 
0364 function initLevel() {
0365     // destroy old cars
0366     isMoving = false;
0367     cleanupActiveCars();
0368     truckIndex = 0;
0369     carIndex = 0;
0370     if (items.score.currentSubLevel == 0) {
0371         // initialize level
0372         items.score.numberOfSubLevels = maxSubLevel;
0373     }
0374     // initialize sublevel
0375     haveWon = false;
0376     cars = dataList[(items.currentLevel * maxSubLevel) + items.score.currentSubLevel].split(",");
0377     drawJam();
0378 }
0379 
0380 function nextLevel() {
0381     items.score.stopWinAnimation();
0382     items.currentLevel = Core.getNextLevel(items.currentLevel, maxLevel);
0383     items.score.currentSubLevel = 0;
0384     initLevel();
0385 }
0386 
0387 function previousLevel() {
0388     items.score.stopWinAnimation();
0389     items.currentLevel = Core.getPreviousLevel(items.currentLevel, maxLevel);
0390     items.score.currentSubLevel = 0;
0391     initLevel();
0392 }
0393 
0394 function nextSubLevel() {
0395     if (items.score.currentSubLevel >= maxSubLevel) {
0396         items.bonus.good("smiley");
0397     } else
0398         initLevel();
0399 }