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 }