Rolf B: code läuft nicht nach der Reihe ab

Beitrag lesen

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