File indexing completed on 2024-05-05 11:57:12
0001 /* GCompris - analog_electricity.js 0002 * 0003 * SPDX-FileCopyrightText: 2020 Aiswarya Kaitheri Kandoth <aiswaryakk29@gmail.com> 0004 * 0005 * Authors: 0006 * Bruno Coudoin (GTK+ version) 0007 * Aiswarya Kaitheri Kandoth <aiswaryakk29@gmail.com> (Qt Quick port) 0008 * Timothée Giet <animtim@gmail.com> (Help for the Qt Quick port) 0009 * 0010 * SPDX-License-Identifier: GPL-3.0-or-later 0011 */ 0012 .pragma library 0013 .import QtQuick 2.12 as Quick 0014 .import "cktsim.js" as Engine 0015 .import "qrc:/gcompris/src/core/core.js" as Core 0016 0017 var url = "qrc:/gcompris/src/activities/analog_electricity/resource/"; 0018 var urlDigital = "qrc:/gcompris/src/activities/digital_electricity/resource/"; 0019 0020 var numberOfLevel; 0021 var items; 0022 var view; 0023 var toolDelete; 0024 var animationInProgress; 0025 var selectedIndex; 0026 var selectedTerminal; 0027 var components = []; 0028 var determiningComponents = []; 0029 var connectionCount = 0; 0030 var levelProperties; 0031 var invalidCircuit = false; 0032 var processingAnswer = false; 0033 var answerKeys = []; 0034 0035 var uniqueID = 0; 0036 var netlistComponents = []; 0037 var vSourcesList = []; 0038 var netlist = []; 0039 0040 var wireColors = [ 0041 "#72559b", 0042 "#094386", 0043 "#97509e", 0044 "#2890d4", 0045 "#407a52", 0046 "#fee348", 0047 "#a3ca4b", 0048 "#ea6712", 0049 "#f4819a", 0050 "#b82c58", 0051 "#6b4a36", 0052 "#d42e2e", 0053 "#e9dfc9" 0054 ] 0055 var colorIndex = 0; 0056 0057 var currentZoom; 0058 var maxZoom = 0.375; 0059 var minZoom = 0.125; 0060 var defaultZoom = 0.25; 0061 var zoomStep = 0.0625; 0062 //bool to avoid createNetList when deleting wires on stop function... 0063 var isStopped = false; 0064 0065 var direction = { 0066 LEFT: -1, 0067 RIGHT: 1, 0068 UP: -2, 0069 DOWN: 2 0070 }; 0071 0072 var viewPort = { 0073 leftExtreme: 0, 0074 rightExtreme: 1, 0075 topExtreme: 0, 0076 bottomExtreme: 1, 0077 leftEdge: 0, 0078 topEdge: 0 0079 }; 0080 0081 function start(items_) { 0082 items = items_; 0083 numberOfLevel = items.tutorialDataset.tutorialLevels.length; 0084 items.currentLevel = Core.getInitialLevel(numberOfLevel); 0085 initLevel(); 0086 } 0087 0088 function reset() { 0089 stop(); 0090 initLevel(); 0091 } 0092 0093 function stop() { 0094 isStopped = true; 0095 var nbOfComponents = components.length; 0096 for(var i = 0 ; i < nbOfComponents ; ++i) { 0097 removeComponent(0); 0098 } 0099 } 0100 0101 function initLevel() { 0102 connectionCount = 0; 0103 netlistComponents = []; 0104 netlist = []; 0105 components = []; 0106 items.availablePieces.model.clear(); 0107 items.availablePieces.view.currentDisplayedGroup = 0; 0108 items.availablePieces.view.previousNavigation = 1; 0109 items.availablePieces.view.nextNavigation = 1; 0110 determiningComponents = []; 0111 colorIndex = 0; 0112 animationInProgress = false; 0113 disableToolDelete(); 0114 deselect(); 0115 items.availablePieces.hideToolbar(); 0116 0117 currentZoom = defaultZoom; 0118 viewPort.leftEdge = 0; 0119 viewPort.topEdge = 0; 0120 items.playArea.x = items.mousePan.drag.maximumX; 0121 items.playArea.y = items.mousePan.drag.maximumY; 0122 isStopped = false; 0123 0124 0125 if (!items.isTutorialMode) { 0126 items.tutorialInstruction.index = -1; 0127 processingAnswer = false; 0128 loadFreeMode(); 0129 } else { 0130 processingAnswer = false; 0131 levelProperties = items.tutorialDataset.tutorialLevels[items.currentLevel]; 0132 0133 for (var i = 0; i < levelProperties.inputComponentList.length; i++) { 0134 var currentInputComponent = levelProperties.inputComponentList[i]; 0135 items.availablePieces.model.append( { 0136 "imgName": currentInputComponent.imageName, 0137 "componentSrc": currentInputComponent.componentSource, 0138 "imgWidth": currentInputComponent.width, 0139 "imgHeight": currentInputComponent.height, 0140 "toolTipText": currentInputComponent.toolTipText 0141 }); 0142 } 0143 0144 for (var i = 0; i < levelProperties.playAreaComponentList.length; i++) { 0145 var index = components.length; 0146 0147 var currentPlayAreaComponent = levelProperties.playAreaComponentList[i]; 0148 var staticElectricalComponent = Qt.createComponent("qrc:/gcompris/src/activities/analog_electricity/components/" + currentPlayAreaComponent.componentSource); 0149 0150 components[index] = staticElectricalComponent.createObject( 0151 items.playArea, { 0152 "componentIndex": index, 0153 "posX": levelProperties.playAreaComponentPositionX[i] * currentZoom, 0154 "posY": levelProperties.playAreaComponentPositionY[i] * currentZoom, 0155 "imgWidth": currentPlayAreaComponent.width * currentZoom, 0156 "imgHeight": currentPlayAreaComponent.height * currentZoom, 0157 "destructible": false 0158 }); 0159 ++uniqueID; 0160 components[index].componentName = components[index].componentName + uniqueID.toString(); 0161 components[index].initConnections(); 0162 } 0163 deselect(); 0164 0165 var _determiningComponentsIndex = levelProperties.determiningComponentsIndex 0166 for (var i = 0; i < _determiningComponentsIndex.length; i++) { 0167 determiningComponents[i] = components[_determiningComponentsIndex[i]]; 0168 } 0169 0170 //create wires 0171 for (i = 0; i < levelProperties.wires.length; i++) { 0172 var terminalNumber = levelProperties.wires[i][1]; 0173 var connectionPoint = components[levelProperties.wires[i][0]].connectionPoints.itemAt(terminalNumber); 0174 terminalPointSelected(connectionPoint, false); 0175 0176 terminalNumber = levelProperties.wires[i][3]; 0177 var terminalToConnect = components[levelProperties.wires[i][2]].connectionPoints.itemAt(terminalNumber); 0178 terminalPointSelected(terminalToConnect, false); 0179 } 0180 0181 if (levelProperties.introMessage.length != 0) { 0182 items.tutorialInstruction.index = 0; 0183 items.tutorialInstruction.intro = levelProperties.introMessage; 0184 } else { 0185 items.tutorialInstruction.index = -1; 0186 } 0187 } 0188 } 0189 0190 function loadFreeMode() { 0191 var componentList = items.tutorialDataset.componentList; 0192 0193 for (var i = 0; i < componentList.length; i++) { 0194 items.availablePieces.model.append( { 0195 "imgName": componentList[i].imageName, 0196 "componentSrc": componentList[i].componentSource, 0197 "imgWidth": componentList[i].width, 0198 "imgHeight": componentList[i].height, 0199 "toolTipText": componentList[i].toolTipText, 0200 }); 0201 } 0202 } 0203 0204 function checkAnswer() { 0205 answerKeys = []; 0206 0207 if(invalidCircuit){ 0208 if(items.currentLevel === 1) { 0209 //special case for level 2 to teach voltage source loop 0210 items.bonus.good('gnu'); 0211 processingAnswer = false; 0212 return; 0213 } else { 0214 //default case for all other levels 0215 items.bonus.bad('gnu', items.bonus.checkAnswer); 0216 processingAnswer = false; 0217 return; 0218 } 0219 } 0220 0221 for(var i = 0; i < determiningComponents.length; ++i) { 0222 answerKeys[i] = determiningComponents[i].checkComponentAnswer(); 0223 processingAnswer = true; 0224 } 0225 0226 for(var i in answerKeys) { 0227 if(levelProperties.answerKey[i] === answerKeys[i] && processingAnswer) { 0228 items.bonus.good('gnu'); 0229 } else { 0230 items.bonus.bad('gnu', items.bonus.checkAnswer); 0231 processingAnswer = false; 0232 } 0233 } 0234 } 0235 0236 function zoomIn() { 0237 var previousZoom = currentZoom; 0238 currentZoom += zoomStep; 0239 if (currentZoom > maxZoom) 0240 currentZoom = maxZoom; 0241 var zoomRatio = currentZoom / previousZoom; 0242 updateComponentDimension(zoomRatio); 0243 0244 if (currentZoom == maxZoom) { 0245 items.availablePieces.zoomInBtn.state = "cannotZoomIn"; 0246 } else { 0247 items.availablePieces.zoomInBtn.state = "canZoomIn"; 0248 } 0249 items.availablePieces.zoomOutBtn.state = "canZoomOut"; 0250 0251 if (items.zoomLvl < 0.5) { 0252 items.zoomLvl += 0.125; 0253 } 0254 } 0255 0256 function zoomOut() { 0257 var previousZoom = currentZoom; 0258 currentZoom -= zoomStep; 0259 if (currentZoom < minZoom) 0260 currentZoom = minZoom; 0261 var zoomRatio = currentZoom / previousZoom; 0262 updateComponentDimension(zoomRatio); 0263 0264 if (currentZoom == minZoom) { 0265 items.availablePieces.zoomOutBtn.state = "cannotZoomOut"; 0266 } else { 0267 items.availablePieces.zoomOutBtn.state = "canZoomOut"; 0268 } 0269 items.availablePieces.zoomInBtn.state = "canZoomIn"; 0270 0271 if (items.zoomLvl > 0) { 0272 items.zoomLvl -= 0.125; 0273 } 0274 } 0275 0276 function updateComponentDimension(zoomRatio) { 0277 for (var i = 0; i < components.length; i++) { 0278 components[i].posX *= zoomRatio; 0279 components[i].posY *= zoomRatio; 0280 components[i].imgWidth *= zoomRatio; 0281 components[i].imgHeight *= zoomRatio; 0282 updateWires(i); 0283 } 0284 } 0285 0286 function nextLevel() { 0287 items.currentLevel = Core.getNextLevel(items.currentLevel, numberOfLevel); 0288 reset(); 0289 } 0290 0291 function previousLevel() { 0292 items.currentLevel = Core.getPreviousLevel(items.currentLevel, numberOfLevel); 0293 reset(); 0294 } 0295 0296 function createComponent(x, y, componentIndex) { 0297 x = x / items.playArea.width; 0298 y = y / items.playArea.height; 0299 0300 var index = components.length; 0301 0302 var component = items.availablePieces.repeater.itemAt(componentIndex); 0303 var electricComponent = Qt.createComponent("qrc:/gcompris/src/activities/analog_electricity/components/" + component.source); 0304 0305 components[index] = electricComponent.createObject( 0306 items.playArea, { 0307 "componentIndex": index, 0308 "posX": x, 0309 "posY": y, 0310 "imgWidth": component.imageWidth * currentZoom, 0311 "imgHeight": component.imageHeight * currentZoom, 0312 "destructible": true 0313 }); 0314 ++uniqueID; 0315 components[index].componentName = components[index].componentName + uniqueID.toString(); 0316 components[index].initConnections(); 0317 deselect(); 0318 } 0319 0320 /* Creates wire between two points. 0321 */ 0322 function terminalPointSelected(terminal, destructible) { 0323 if(destructible == undefined) 0324 destructible = true; 0325 if(selectedTerminal == -1 || selectedTerminal == terminal) 0326 selectedTerminal = terminal; 0327 else if(selectedTerminal.parent != terminal.parent) { 0328 var connectionPoint = terminal; 0329 createWire(connectionPoint, destructible); 0330 deselect(); 0331 } 0332 else { 0333 deselect(); 0334 selectedTerminal = terminal; 0335 terminal.selected = true; 0336 } 0337 disableToolDelete(); 0338 } 0339 0340 function disableToolDelete() { 0341 if(toolDelete == true) { 0342 toolDelete = false; 0343 items.availablePieces.toolDelete.state = "notSelected"; 0344 } 0345 } 0346 0347 function nextColorIndex() { 0348 if(colorIndex < wireColors.length - 1) 0349 ++colorIndex; 0350 else 0351 colorIndex = 0; 0352 } 0353 0354 function createWire(connectionPoint, destructible) { 0355 var wireComponent = Qt.createComponent("qrc:/gcompris/src/activities/analog_electricity/Wire.qml"); 0356 if(connectionPoint.wires.length === 0 && selectedTerminal.wires.length === 0) { 0357 connectionPoint.updateNetlistIndex(selectedTerminal.netlistIndex); 0358 selectedTerminal.updateNetlistIndex(selectedTerminal.netlistIndex); 0359 connectionPoint.colorIndex = colorIndex; 0360 selectedTerminal.colorIndex = colorIndex; 0361 nextColorIndex(); 0362 } else { 0363 if(connectionPoint.wires.length > 0) { 0364 selectedTerminal.updateNetlistIndex(connectionPoint.netlistIndex, connectionPoint.colorIndex); 0365 } 0366 if(selectedTerminal.wires.length > 0) { 0367 connectionPoint.updateNetlistIndex(selectedTerminal.netlistIndex, selectedTerminal.colorIndex); 0368 } 0369 } 0370 var wire = wireComponent.createObject( 0371 items.playArea, { 0372 "node1": selectedTerminal, 0373 "node2": connectionPoint, 0374 "destructible": destructible, 0375 }); 0376 connectionPoint.wires.push(wire); 0377 selectedTerminal.wires.push(wire); 0378 updateWires(connectionPoint.parent.componentIndex); 0379 updateWires(selectedTerminal.parent.componentIndex); 0380 connectionPoint.parent.checkConnections(); 0381 selectedTerminal.parent.checkConnections(); 0382 restartTimer(); 0383 } 0384 0385 function updateWires(index) { 0386 var component = components[index]; 0387 if(component == undefined || component.noOfConnectionPoints == undefined) 0388 return; 0389 0390 var rotatedAngle = component.initialAngle * Math.PI / 180; 0391 var rotatedAngleSin = Math.sin(rotatedAngle); 0392 var rotatedAngleCos = Math.cos(rotatedAngle); 0393 for(var i = 0 ; i < component.noOfConnectionPoints ; ++i) { 0394 var terminal = component.connectionPoints.itemAt(i); 0395 for(var j = 0 ; j < terminal.wires.length ; ++j) { 0396 var wire = terminal.wires[j]; 0397 if(wire.node1 != terminal) { 0398 var otherAngle = wire.node1.parent.initialAngle * Math.PI / 180; 0399 var otherAngleCos = Math.cos(otherAngle); 0400 var otherAngleSin = Math.sin(otherAngle); 0401 var x = wire.node1.xCenterFromComponent; 0402 var y = wire.node1.yCenterFromComponent; 0403 var x1 = wire.node1.xCenter - x + x * otherAngleCos - y * otherAngleSin; 0404 var y1 = wire.node1.yCenter - y + x * otherAngleSin + y * otherAngleCos; 0405 0406 x = terminal.xCenterFromComponent; 0407 y = terminal.yCenterFromComponent; 0408 var x2 = terminal.xCenter - x + x * rotatedAngleCos - y * rotatedAngleSin; 0409 var y2 = terminal.yCenter - y + x * rotatedAngleSin + y * rotatedAngleCos; 0410 0411 var width = Math.pow((Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)),0.5) + 2; 0412 var angle = (180/Math.PI)*Math.atan((y2-y1)/(x2-x1)); 0413 if(x2 - x1 < 0) 0414 angle = angle - 180; 0415 wire.x = x1; 0416 wire.y = y1 - wire.height / 2; 0417 wire.width = width; 0418 wire.rotation = angle; 0419 } 0420 } 0421 } 0422 for(var i = 0 ; i < component.noOfConnectionPoints ; ++i) { 0423 var terminal = component.connectionPoints.itemAt(i); 0424 for(var j = 0 ; j < terminal.wires.length ; ++j) { 0425 var x = terminal.xCenterFromComponent; 0426 var y = terminal.yCenterFromComponent; 0427 var x1 = terminal.xCenter - x + x * rotatedAngleCos - y * rotatedAngleSin; 0428 var y1 = terminal.yCenter - y + x * rotatedAngleSin + y * rotatedAngleCos; 0429 0430 var wire = terminal.wires[j]; 0431 if(wire.node2 != terminal) { 0432 var otherAngle = wire.node2.parent.initialAngle * Math.PI / 180; 0433 var otherAngleCos = Math.cos(otherAngle); 0434 var otherAngleSin = Math.sin(otherAngle); 0435 x = wire.node2.xCenterFromComponent; 0436 y = wire.node2.yCenterFromComponent; 0437 var x2 = wire.node2.xCenter - x + x * otherAngleCos - y * otherAngleSin; 0438 var y2 = wire.node2.yCenter - y + x * otherAngleSin + y * otherAngleCos; 0439 0440 var width = Math.pow((Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)),0.5) + 2; 0441 var angle = (180/Math.PI)*Math.atan((y2-y1)/(x2-x1)); 0442 if(x2 - x1 < 0) 0443 angle = angle - 180; 0444 wire.x = x1; 0445 wire.y = y1; 0446 wire.width = width; 0447 wire.rotation = angle; 0448 } 0449 } 0450 } 0451 } 0452 0453 function updateWiresOnResize() { 0454 for(var i = 0; i < components.length; ++i) { 0455 updateWires(i); 0456 } 0457 } 0458 0459 function deselect() { 0460 items.availablePieces.rotateLeft.state = "canNotBeSelected"; 0461 items.availablePieces.rotateRight.state = "canNotBeSelected"; 0462 items.availablePieces.info.state = "canNotBeSelected"; 0463 items.infoTxt.visible = false 0464 selectedIndex = -1; 0465 selectedTerminal = -1; 0466 for(var i = 0 ; i < components.length ; ++i) { 0467 var component = components[i]; 0468 for(var j = 0 ; j < component.noOfConnectionPoints ; ++j) 0469 component.connectionPoints.itemAt(j).selected = false; 0470 } 0471 } 0472 0473 function removeComponent(index) { 0474 var component = components[index]; 0475 for(var i = 0 ; i < component.noOfConnectionPoints ; ++i) { 0476 var terminal = component.connectionPoints.itemAt(i); 0477 if(terminal.wires.length != 0) { 0478 var wiresLength = terminal.wires.length; 0479 for(var j = 0; j < wiresLength; ++j) { 0480 removeWire(terminal.wires[0]); 0481 } 0482 } 0483 } 0484 0485 components[index].destroy(); 0486 components.splice(index, 1); 0487 0488 for(var i = index; i < components.length; ++i) { 0489 --components[i].componentIndex; 0490 } 0491 deselect(); 0492 } 0493 0494 function removeWire(wire) { 0495 var connectionPoint1 = wire.node1; 0496 var connectionPoint2 = wire.node2; 0497 0498 var removeIndex = connectionPoint1.wires.indexOf(wire); 0499 connectionPoint1.wires.splice(removeIndex, 1); 0500 removeIndex = connectionPoint2.wires.indexOf(wire); 0501 connectionPoint2.wires.splice(removeIndex, 1); 0502 0503 wire.destroy(); 0504 deselect(); 0505 0506 if(connectionPoint1.wires.length === 0) { 0507 connectionPoint1.resetIndex(); 0508 } else { 0509 ++connectionCount; 0510 connectionPoint1.updateNetlistIndex(connectionCount, colorIndex); 0511 nextColorIndex(); 0512 } 0513 if(connectionPoint2.wires.length === 0) { 0514 connectionPoint2.resetIndex(); 0515 } else { 0516 ++connectionCount; 0517 connectionPoint2.updateNetlistIndex(connectionCount, colorIndex); 0518 nextColorIndex(); 0519 } 0520 connectionPoint1.parent.checkConnections(); 0521 connectionPoint2.parent.checkConnections(); 0522 0523 if(!isStopped) 0524 restartTimer(); 0525 } 0526 0527 function componentSelected(index) { 0528 selectedIndex = index; 0529 items.availablePieces.rotateLeft.state = "canBeSelected"; 0530 items.availablePieces.rotateRight.state = "canBeSelected"; 0531 items.availablePieces.info.state = "canBeSelected"; 0532 } 0533 0534 function rotateLeft() { 0535 components[selectedIndex].rotationAngle = -2; 0536 components[selectedIndex].rotateComponent.start(); 0537 } 0538 0539 function rotateRight() { 0540 components[selectedIndex].rotationAngle = 2; 0541 components[selectedIndex].rotateComponent.start(); 0542 } 0543 0544 function displayInfo() { 0545 var component = components[selectedIndex]; 0546 deselect(); 0547 items.infoTxt.visible = true; 0548 items.infoTxt.text = component.information; 0549 0550 if(component.infoImageSrc != undefined) { 0551 items.infoImage.imgVisible = true; 0552 items.infoImage.source = url + component.infoImageSrc; 0553 } 0554 else { 0555 items.infoImage.imgVisible = false; 0556 items.infoImage.source = ""; 0557 } 0558 } 0559 0560 function updateToolTip(toolTipText) { 0561 items.toolTip.show(toolTipText); 0562 } 0563 0564 function restartTimer() { 0565 items.netlistTimer.restart(); 0566 } 0567 0568 function createNetlist() { 0569 netlist.length = 0; 0570 netlistComponents.length = 0; 0571 vSourcesList.length = 0; 0572 for(var i = 0; i < components.length; i++) { 0573 var component = components[i]; 0574 component.addToNetlist(); 0575 } 0576 dcAnalysis(); 0577 } 0578 0579 function dcAnalysis() { 0580 var ckt = new Engine.cktsim.Circuit(); 0581 ckt.load_netlist(netlist); 0582 var voltageResults = ckt.dc(); 0583 if(ckt.GCWarning != "") { 0584 displayWarning(ckt.GCWarning); 0585 return; 0586 } else { 0587 invalidCircuit = false; 0588 } 0589 0590 var currentResults = ckt.GCCurrentResults; 0591 for(var i in currentResults) { 0592 if(vSourcesList[i] != undefined) 0593 vSourcesList[i].current = currentResults[i]; 0594 } 0595 0596 for(var i in netlistComponents) { 0597 if(netlistComponents[i].nodeVoltages == undefined) { 0598 continue; 0599 } else { 0600 for(var j = 0 ; j < netlistComponents[i].nodeVoltages.length ; j++) { 0601 if(netlistComponents[i].internalNetlistIndex != null) { 0602 netlistComponents[i].nodeVoltages[j] = voltageResults[netlistComponents[i].internalNetlistIndex[j]]; 0603 } else { 0604 netlistComponents[i].nodeVoltages[j] = voltageResults[netlistComponents[i].externalNetlistIndex[j]]; 0605 } 0606 } 0607 netlistComponents[i].updateValues(); 0608 } 0609 } 0610 } 0611 0612 function displayWarning(message_) { 0613 items.availablePieces.hideToolbar(); 0614 items.infoTxt.visible = true; 0615 items.infoTxt.text = message_; 0616 invalidCircuit = true; 0617 }