webity: code läuft nicht nach der Reihe ab

Hallo zusammen,

in der Webseiten - Programmierung bin ich ganz neu. Ich arbeite gerade einen Online - Kurs durch. Neben dem Kurs möchte ich ein Merkspiel programmieren.Man muss auf vorher gezeigte divs klicken. Wenn das richtig war kommt ein neues dazu. Die Anordnung am Kreis funktioniert. Ich möchte zur Zeit, dass, wenn man den Start Button klickt, die im array gespeicherten divs nacheinander anzeigt. Scheinbar läuft das aber alles parallel ab, obwohl ich eine Pause eingebaut habe. Habt ihr vielleicht eine Idee?

Oder was noch wichtiger ist, was kann ich an dem Code verbessern(ich hab da keine Erfahrung!)

Hier mein Code:

<html>
    <head>
        <title>Merkspiel von Lars</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <script src="jquery-3.6.4.min.js"></script>
        <link href="jquery-ui-1_13_2/jquery-ui.css" rel="stylesheet">
        <script src=" jquery-ui-1_13_2/jquery-ui.js"></script>
        <style type="text/css">   
            body{        
                margin: 0px;
                padding: 0px;
            } 
            #titel{
                position:relative;
                width: 100%;
                height:75px;
                background-color: aquamarine; 
            }
            #titelText {
                position: relative;
                margin-top: 10px;
                margin-left: 200px;
                font-size: 300%;
            }
            #options{
                position: absolute;
                top: 100px;
                width: 100%;
                height: 100px;
                background-color: cornsilk;
            }
            
            #selectLabel{
                position: relative;
                margin-left: 20px;
                font-size: 25px;
            }
            #displayDiv{
                position: relative;
                background-color: azure;
                top: 200px;
                width: 150px;
                height: 200px;
                margin: 10px;  
            }

            #circleNumber, #circleNumberLabel{
                
                margin-left: 10px;
                font-size: 100%;
            }
            
            #circleToArrange{
                
                position: absolute;
                top: 300px;
                left: 400px;
                width: 400px;
                height: 400px;
                background-color: cornsilk;
                border-radius: 50%;
                
            }
            
            .smallCircle{
                
                position: absolute;
                
                left: 20px;
                top: 200px;
                width: 50px;
                height: 50px;
                background-color: red;
                border-radius: 50%;
            }

            
            .number{
                
                position: absolute;
                margin: auto;
                left: 40%;
                top: 30%;
                font-size: 100%;
                color: white;
            }
            
            #endButtonDiv{
                position: absolute;
                float: right;
                left: 1000px;
            }
        
        </style>
    
    
    
    </head>
    
    <body>
        
        <div id="titel">
        
            <h1 id="titelText">Merk dir das!</h1>
        
        </div>
        
        <div id="options">
            
            <label id="selectLabel" for="circleNumber">Anzahl der Kreise:</label>
                <select id="circleNumber" onchange="arrangeCircle()">
                    <option value="1">Eins</option>
                    <option value="2">Zwei</option>
                    <option value="3">Drei</option>
                    <option value="4">Vier</option>
                    <option value="5">Fünf</option>
                    <option value="6">Sechs</option>
                    <option value="7">Sieben</option>
                    <option value="8">Acht</option>
                    <option value="9">Neun</option>                       
                </select>
            
            

        </div>

        <div id="circleToArrange"></div>

        <div id="smallDiv1" class="smallCircle">

            <span id="number1" class="number">1</span>

        </div>

        <div id="smallDiv2" class="smallCircle">

            <p id="number2" class="number">2</p>

        </div>

        <div id="smallDiv3" class="smallCircle">

            <p id="number3" class="number">3</p>

        </div>

        <div id="smallDiv4" class="smallCircle">

            <p id="number4" class="number">4</p>

        </div>

        <div id="smallDiv5" class="smallCircle">

            <p id="number5" class="number">5</p>

        </div>

        <div id="smallDiv6" class="smallCircle">

            <p id="number6" class="number">6</p>

        </div>

        <div id="smallDiv7" class="smallCircle">

            <p id="number7" class="number">7</p>

        </div>

        <div id="smallDiv8" class="smallCircle">

            <p id="number8" class="number">8</p>

        </div>

        <div id="smallDiv9" class="smallCircle">

            <p id="number9" class="number">9</p>

        </div> 
        
        <div id="displayDiv">

            <button onclick="gameStart()">Spiel starten</button>

        </div>
        
        
        <div id="endButtonDiv">
        
            <button id="endButton" onclick="gameEnd()">Spiel beenden</button>
            
        </div>
        
       
    </body>
    
    <script type="text/javascript">
        let numberOfDiv;
        let posSmallCircle = {x_position:0, y_position:0};
        let premadeObjects = [1,3,2,3,1,1,2];
        let counterfeitObjects = [];
        let pretendOrImitate = true;
        let newObject = false;
        let endGame = false;
        unvisible();
        function gameStart(){
            if(pretendOrImitate==true){                   
                if(premadeObjects.length==0){
                    pretend(true,0);
                }else{
                    for (let i=0; i < premadeObjects.length; i++) {
                        let arrayContent = parseInt(premadeObjects[i]);
                        console.log("Array - Inhalt: " + arrayContent);
                        pretend(false,arrayContent);
                    } 
                    pretend(true,0);    
                }
            }else{
                imitate();
            }                   
        }
        function pretend(newObject,number){
                console.log("Number am anfang: " + number); 
                let element = document.getElementById('circleNumber');
                let numberOfCircle = element.options[element.selectedIndex].value;
                if (newObject == false){
                    numberOfDiv = number;   
                }else{
                    numberOfDiv = rand(1, element.options[element.selectedIndex].value);
                }       
                let numberTop = document.getElementById("number" + numberOfDiv).offsetTop;
                let numberLeft = document.getElementById("number" + numberOfDiv).offsetLeft;
                let numberFontSize = document.getElementById("number" + numberOfDiv).style.fontSize;
                document.getElementById("smallDiv" + numberOfDiv).style.height = (document.getElementById("smallDiv" + numberOfDiv).offsetHeight + 20) + "px";
                document.getElementById("smallDiv" + numberOfDiv).style.width = (document.getElementById("smallDiv" + numberOfDiv).offsetWidth    + 20) + "px";
                document.getElementById("number" + numberOfDiv).style.fontSize = "175%";     
                setTimeout(function(){
                    document.getElementById("smallDiv" + numberOfDiv).style.height= (document.getElementById("smallDiv" + numberOfDiv).offsetHeight - 20) + "px";
                    document.getElementById("smallDiv" + numberOfDiv).style.width= (document.getElementById("smallDiv" + numberOfDiv).offsetWidth    - 20) + "px";
                    document.getElementById("number" + numberOfDiv).style.fontSize = "100%";
                },2000);
                if (newObject==true){
                    premadeObjects.push(numberOfDiv);   
                }
        }
        function imitate(){
        }
        function unvisible(){
            let divName="";
            for (i=1; i <=9; i++){
                divName = 'smallDiv' + i;
                document.getElementById(divName).style.visibility = "hidden";
            }
        }
        function arrangeCircle() {
            unvisible();
            let element = document.getElementById('circleNumber');
            let numberOfCircle = element.options[element.selectedIndex].value;
            let degreeInTheCircle = 360 / numberOfCircle;
            let outOfMiddleDegree = degreeInTheCircle / 2;
            for (let i=1; i<=numberOfCircle; i++){  
                let degree = outOfMiddleDegree + degreeInTheCircle * (i-1);
                posCalculate(degree);
                document.getElementById('smallDiv' + i).style.left = posSmallCircle.x_position + "px";
                document.getElementById('smallDiv' + i).style.top = posSmallCircle.y_position + "px";
                document.getElementById('smallDiv' + i).style.visibility = "visible";
                var test = randomColour();
                document.getElementById('smallDiv' + i).style.backgroundColor = "#" + test;
            }
        }
        function posCalculate(degree) {
            var smallDivRadius = 25;
            var circleToArrangeRadius = 200;
            var el = document.getElementById('circleToArrange');
            var circleToArrangePos = el.getBoundingClientRect();
            if (degree>=0 && degree<=90) {
                degree = 90 - degree;
                posSmallCircle.x_position = (circleToArrangePos.left + circleToArrangeRadius - smallDivRadius) + Math.round(200 * Math.cos(degreeToRadians(degree)));
                posSmallCircle.y_position = (circleToArrangePos.top + circleToArrangeRadius - smallDivRadius) - Math.round(200 * Math.sin(degreeToRadians(degree)));
            } else if (degree>90 && degree<=180) {
                degree = degree - 90;
                posSmallCircle.x_position = (circleToArrangePos.left + circleToArrangeRadius - smallDivRadius) + Math.round(200 * Math.cos(degreeToRadians(degree)));
                posSmallCircle.y_position = (circleToArrangePos.top + circleToArrangeRadius - smallDivRadius) + Math.round(200 * Math.sin(degreeToRadians(degree)));
            } else if (degree>180 && degree<=270) {
                degree = 90 -(degree - 180);
                posSmallCircle.x_position = (circleToArrangePos.left + circleToArrangeRadius - smallDivRadius) - Math.round(200 * Math.cos(degreeToRadians(degree)));
                posSmallCircle.y_position = (circleToArrangePos.top + circleToArrangeRadius - smallDivRadius) + Math.round(200 * Math.sin(degreeToRadians(degree)));
            } else if (degree>270 && degree<=360) {
                degree = degree - 270;
                posSmallCircle.x_position = (circleToArrangePos.left + circleToArrangeRadius - smallDivRadius) - Math.round(200 * Math.cos(degreeToRadians(degree)));
                posSmallCircle.y_position = (circleToArrangePos.top + circleToArrangeRadius - smallDivRadius) - Math.round(200 * Math.sin(degreeToRadians(degree)));
            }
            return posSmallCircle;              
        }
        function degreeToRadians(degree){
            return degree * Math.PI / 180;  
        }
        function randomColour() {
            var randomColour = "";
            for (var zaehler = 1; zaehler < 7; zaehler++) {
                var Farbe = "0123456789ABCDEF";
                var RND = Math.round(Math.random() * 15);
                randomColour += Farbe.substr(RND, 1);
            } 
            return randomColour;
        }
        function rand (min, max) {
	       return Math.floor(Math.random() * (max - min + 1)) + min;
        }
        function gameEnd(){
           endGame = true; 
        }

    </script>
</html>

Gruß websity

  1. Hallo,

    auf den Code gehe ich jetzt mal nicht näher ein, aber in Javascript gibt es kein Wait. setTimeout „sagt“ dem System nur „mach das später“. Danach geht es sofort weiter im Programmablauf. Wenn du setTimeout in einer Schleife nutzt, werden ganz viele Aufgaben ganz schnell nacheinander auf später verschoben, und später ganz schnell nacheinander abgearbeitet

    Gruß
    Jürgen

    PS Kennst du schon unsere Tutorien?

  2. Hallo webity,

    Du bindest jQuery UI ein, nimmst aber jQuery 3.6 hinzu. Meines Wissens ist jQuery UI nur mit jQuery 1.x kompatibel - und jQuery UI ist eigentlich tot. Naja, macht nichts, du verwendest weder das eine noch das andere. Zumindest nicht in dem Code, den Du zeigst.

    Da scheint sowieso was zu fehlen, denn du hast lediglich einen change-Handler auf dem Select-Element, der die Anzahl der Kreise einstellt, und den Start-Button. Klicks auf Divs gibt's nicht. Und - äh - die sollte es auch nicht geben. Div-Elemente sind per Definition nicht interaktiv. Mach da button-Elemente draus (<button type="button">) - die kannst Du mit CSS ebenfalls so stylen, dass sie wie Kreise aussehen.

    Die Funktion "unvisible" möchte vermutlich "invisible" heißen, oder?

    Ein Problem dürfte sein, dass Du die Variable "numberOfDiv" global deklariert hast. Die brauchst Du aber nur innerhalb von pretend, und durch deinen setTimeout passiert dann etwas, was für viele JavaScript-Einsteiger schwierig ist.

    Die Funktion setTimeout wartet nicht. Sie registriert lediglich eine Funktion, die nach Ablauf der Wartezeit aufzurufen ist.

    Heißt: Deine Schleife in gameStart rennt pro Kreis einmal durch und ruft pretend auf. Bei jedem pretend-Aufruf wird eine neue Timeout-Funktion registriert. Und all diese Funktionen verwenden numberOfDiv. Sie werden aber erst ausgeführt, wenn die 2 Sekunden rum sind, d.h. gameStart ist schon längst vorbei und in numberOfDiv steht der Wert vom letzten pretend-Aufruf. Es vergehen 2 Sekunden und DANN starten die mit setTimeout registrierten Aufrufe der anonymen Funktion, die Du dort übergibst. Und alle drei Aufrufe verwenden die globale Variable numberOfDiv, worin der Wert aus dem letzten Aufruf steht. Ich nehme an, dass Du das nicht willst. Du möchtest, dass jede Funktion den numberOfDiv-Wert verwendet, den die Variable beim Registrieren der Timeout-Funktion hatte.

    Das gelingt, wenn Du den let numberOfDiv in die pretend-Funktion hinein nimmst. Dann ist das eine lokale Variable, und du kannst etwas ausnutzen, was für funktional orientierte Sprachen wie JavaScript typisch ist: eine Closure.

    Leichter Exkurs: Funktionen in JavaScript sind Objekte. Echte Objekte. Wenn eine Funktion erstellt wird, gehören dazu zwei Dinge: (1) Der Programmcode und (2) das Umfeld, in dem die Funktion erstellt wurde. Bei deiner setTimeout-Funktion besteht das Umfeld aus allen lokalen Variablen des aktuellen pretend-Aufrufs. Das Funktionsobjekt, das Du für den setTimeout-Aufruf erzeugst, schließt einen Verweis auf dieses Umfeld in sich ein (daher Closure) und hält ihn fest, auch wenn der Funktionsaufruf von pretend schon längst vorbei ist.

    Das ist kein Stoff, der wirklich einsteigerkompatibel ist. Aber versuch mal, unseren Wiki-Artikel über Closures durchzulesen.

    Ich denke, dass sich dein "es passiert alles auf einmal" darauf bezieht. Das ist nicht so. JavaScript ist streng sequenziell, es gibt keine Parallelverarbeitung (außer mit WebWorkern, aber das ist ein ganz anderes Thema). Aber die Reihenfolge, in der die Dinge passieren, ist anders, als Du gemeint hast, und wenn dann noch globale Variablen im Spiel sind, brennt der Baum lichterloh.

    Eine andere Möglichkeit, eine bessere Koordination hinzubekommen, ist der Verzicht auf globale Variablen im setTimeout. Du kannst setTimeout nämlich mehr als 2 Parameter mitgeben. Alle Parameter ab dem dritten werden der Timeout-Funktion übergeben:

    setTimeout(function(x,y) { ... }, 2000, 47, 11);
    

    setTimeout speichert sich jetzt die Funktion sowie die Werte 47 und 11. Nach Ablauf der 2000ms wird die Funktion mit x=47 und y=11 aufgerufen.

    Ich würde dann aber NICHT die Div-Nummer übergeben, sondern direkt das circle-Element. Du hast sehr viele getElementById(...+numberOfDiv) Aufrufe, das muss man nur einmal tun, das ist lesbarer.

    const circleElement = document.getElementById("smallDiv" + numberOfDiv);
    circleElement.style.height = (circleElement.offsetHeight + 20) + "px";
    circleElement.style.width = (circleElement.offsetWidth + 20) + "px";
    circleElement.querySelector("p").style.fontSize = "175%";
    
    setTimeout(function(circleElement) { ... }, 2000, circleElement);
    

    circleElement.querySelector sucht innerhalb des Elements ein Element mit CSS Selector "p" - was deine Nummer ist. Du musst das dann nur einheitlich gestalten, im Moment ist das einmal ein span und der Rest ein p.

    Mit einem Verwanden von querySelector kannst Du auch generell die Suche nach den smallcircle-Elementen einfacher gestalten: Mach einmal zu beginn:

    const smallCircles = document.querySelectorAll(".smallCircle");
    

    dann hast Du eine NodeList (sowas ähnliches wie ein Array) und kannst beispielsweise mit smallCircles[4] auf das fünfte Element mit dieser Klasse zugreifen (Indizes beginnen bei 0).

    Im übrigen weiß ich nicht, wie Du Dir dein Spiel vorstellst - soll es wirklich so sein, dass die Kreise unterschiedlich groß werden?

    Deinen Zugriff auf premadeObjects musst Du übrigens nicht mit parseInt einrahmen. Im Array stehen bereits Zahlen, die kannst Du so verwenden, wie sie sind.

    for (let i=0; i < premadeObjects.length; i++) {
       pretend(false, premadeObjects[i]);
    } 
    

    Ich will jetzt nicht zu viel schreiben, es ist eh schon reichlich. Sicherlich möchtest Du das erstmal verinnerlichen.

    Stell uns gerne weitere Arbeitsstände deines Spiels vor, dann aber möglichst als Link zu einer Online-Version und nicht als Komplett-Post im Forum, denn dann müssen wir das erst kopieren und eine eigene HTML Seite daraus machen. Du kannst Tools wie https://jsfiddle.net oder https://codepen.io verwenden, um Inhalte ohne eigene Homepage vorzustellen. Bei diesen Webseiten-Laboren trägst Du den Body des HTML, das CSS und den Script-Teil in separate Fenster ein, das Labor fügt das dann zusammen.

    Weiterhin viel Erfolg!

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Jürgen,

      vielen Dank für deine Antwort und dem Hinweis mit der setTimeout - Funktion. Das leuchtet mir ein.

      Hallo Rolf, auch dir vielen Dank für deine sehr ausführliche Antwort. Du hast Recht, nach dem ersten durchlesen habe ich noch mehr Fragen als vorher😀, aber in den nächsten Tagen arbeite ich mich Stück für Stück durch.

      Das mit der Online - Version werde ich auch mal ausprobieren.

      allen noch einen schönen Abend

      webity