Algorithms

Aus hyperdramatik
Zur Navigation springen Zur Suche springen
Die druckbare Version wird nicht mehr unterstützt und kann Darstellungsfehler aufweisen. Bitte aktualisiere deine Browser-Lesezeichen und verwende stattdessen die Standard-Druckfunktion des Browsers.

Alle Snippets sind zunächst Pseudocode und je nach verwendeter Programmiersprache anzupassen.

Println() und Debug.Log() sind eure Freund*innen

Wenn ihr auf Probleme in eurem Code stosst, so vergewissert euch bitte, ob jeder Schritt bis zu eurem Problem hin auch so ausgeführt wird, wie ihr denkt. Der beste Weg das zu tun, ist durch die Nutzung von Println() bei Arduino und Processing, bzw. Debug.Log() bei unity. Lasst euch von euren Programmen ausgeben, was sie tun. Gleiches gilt für eure Annahmen, was Variablen angeht. Welchen Wert hat eine Variable in einem bestimmten Moment? Println() oder Debug.Log() the shit out of your code, so you know what's going on.

Checkliste "Mein Code funktioniert nicht"

  • Stimmen meine Semikola (";")?
  • Stimmen die Semikola in den darüber und darunterliegenden Zeilen?
  • Stimmen meine geschweiften Klammern ("{,}")- bei if-statements, bei funktionen, bei klassen?
  • Stimmen meine runden Klammern ("(,)")- rufe ich eine Funktion auf, muss eine runde Klammer dahinter?
  • Wird mein Code überhaupt ausgeführt (siehe Println() und Debug.Log BFF)?
  • Habe ich mich bei Variablennamen vertippt?
  • Wo habe ich meine Variable deklariert? Wenn innerhalb einer Funktion, dann gibt es die Variable nur innerhalb der Funktion, will ich das so?
  • Bei IF statements - kann ich mit papier und Stift nachvollziehen, was passieren soll?

Code ist Zustandsabfrage und Reaktion

Die allermeiste Zeit versuchen wir in Code einen bestimmten Zustand abzubilden, der dann von der Nutzerin verändert wird. Diese Veränderung des Zustands wollen wir abfragen, 'möglichst genau', und dann entsprechend in Code Dinge verändern. Beim strukturieren dieser verschiedenen Zustandsabfregen kann es irgendwann recht kompliziert werden, wenn wir alles miteinander in Kontext setzen. Hier ein paar Grundüberlegungen:

  • Wenn wir Code schreiben, der nach einer Nutzerinneneingabe reagiert, dann aber wieder in denselben Zustand zurückgeht, kommen wir mit einfachen 'if statements' zurecht. Beispiel: LED leuchtet wenn ein knopf gedrückt gehalten wird. Oder in Pseudocode:
if (/*ZustandsAbfrage*/){
  DoSomething();
}

  • Wenn wir Code schreiben, der verschiedene Zustände erreichen soll, und je nach Zustand etwas anderes tun soll, benutzen wir in einfachen Fällen 'boolean Variablen'. Beispiel: Wenn ein Schalter einmal gedrückt wurde, soll immer ein Licht an sein. Oder in Pseudocode:
bool bZustandErreicht;

if (/*Zustandsabfrage*/){
  bZustandErreicht = true;
}

if (bZustandErreicht){
  DoSomething();
}

Der Unterschied zwischen diesen beiden Codebeispielen ist folgender:

  • Im ersten Beispiel reagiert das Programm auf zwei Vorgänge. Wenn der Zustand, den wir durch die Zustandsabfrage abfragen eintritt (beispielsweise digitalRead(pin) bei Arduino), dann wird Code ausgeführt (beispielsweise digitalWrite(andererPin) bei Arduino). Und wenn der Zustand sich wieder ändert, also der Schalter nicht mehr gedrückt gehalten wird um bei unserem Beispiel zu bleiben, so wird auch der Code nicht mehr ausgeführt.
  • im zweiten Beispiel reagiert das Programm einmalig auf das drücken des Schalters. Die boolean Variable bleibt dann für immer (oder bis sie wieder durch anderen Code anders gesetzt wurde) in diesem Zustand. Sollte der Schalter losgelassen werden, ändert sich nichts.


Generell

Finde die Stelle mit der höchsten Zahl in einem Array von Zahlen

int höchsteZahl = -1000000;
int stelle = -1;

for (int i=0;i<arrayName.länge;i++){

 if (arrayName[i]>höchsteZahl){
   stelle=i;
   höchsteZahl=arrayName[i];
 }

}

Arduino

Führe eine Funktion einmalig aus, wenn ein Button gedrückt ist, und einmalig etwas anderes, wenn er losgelassen wird

int buttonDown =0;
int inputPin = 5; //hier muss der Pin eingetragen sein, an dem der Button hängt

[...]

void loop(){
  
  if (digitalRead(inputPin)==LOW){
    buttonDown++;
  }
  
  if (buttonDown==1){
     DoSomethingOnceWhenPressed();
  }

  if (digitalRead(inputPin)==HIGH && buttonDown>0){
    DoSomethingOnceWhenReleased();
    buttonDown=0;
  }
  
}

Mache etwas alle x-Durchläufe

int loopCounter=0;
int alleXLoops = 15; //Führt eine Funktion alle 15 loops aus
[...]

void loop(){
  
  loopCounter++;
  
  if (loopCounter%alleXLoops==0){
    DoSomethingEveryXLoops(); 
  }

}



These next two should probably move to advanced algorithms or be more generalized:

Fadet linear in 5 Schritten von einem Punkten in einem Array zum nächsten

int werte[];
float schritt_einzeln = 0;
int schritt_einzelnInt = 0;
for (int werte_index = 0; werte_index < werte.length(); werte_index++) {
       for (int schritt = 1; schritt < 6; schritt++) { //5 Schritte
          schritt_einzeln = werte[werte_index] + ((werte[werte_index+1]-werte[werte_index])/5*schritt); //Formel
          schritt_einzelnInt = (int)schritt_einzeln; //wandelt die Kommazahl in integer um und rundet dabei
          analogWrite(A0, schritt_einzelnInt);//sendet einzelnen Zwischenwert an analogen PinA0
          delay(10);//pause zwischen den Schritten  
      }
 }

Fadet exponential in x Sekunden von einem Wert zum anderen

int wert_alt = 0;
int wert_neu = 0;
int sekunden = 0;
float b;
float zwischenWert = 0;
int zwischenWertInt = 0;
int dauerEinesSchrittesInMillisekunden;
int anzahlSchritte;
//funktion
b = pow((wert_neu/wert_alt),(1/sekunden)); //Berechnung der Exponentialformel
anzahlSchritte = sekunden*1000/dauerEinesSchrittesInMillisekunden; //berechnen wie viele Schritte es geben soll
//jeden Schritt einzeln berechnen
for(int i=1,i<anzahlSchritte, i++){
    zwischenWert = wert_alt*pow(b,i);//Wert berechnen
    zwischenWertInt = (int) zwischenWert;//float in int umwandeln und runden
    analogWrite(A0, zwischenWertInt);
    delay(dauerEinesSchrittesInMillisekunden);
}
wert_alt = wert_neu; //bereitmachen für den nächsten Wert

Processing

Wie Ordne ich Dinge im zweidimensionalen Raum an (über Code)

  • Dazu benötigen wir einen Algorithmus, der eine gewisse Anzahl an Dingen mit dem gleichen Abstand in einem definierten Platz anordnet.
    Beispielsweise eine Linie, auf der Quadrate regelmässig verteilt sind.
    Das würde als Formel so aussehen: Abstand = (Platz - (Anzahl * Grösse)) / Anzahl

int x;
int Anzahl = 8;
int Platz = 400;
int Abstand = Platz/Anzahl;

void setup() {
  size(400, 400);
  background(215, 125, 2);
}

void draw() {
  for (int i=0; i < Anzahl; i++) {
    x= i * Abstand;
    rect(x, 40, 40, 40);
  }
}

Unity

Auf eine Variable in einem anderen Script zugreifen

Wenn ihr von eurem selbstgeschriebenen Script aus auf eine Variable in einem anderen Script zugreifen wollt, müsst ihr eine Verbindung zu dieser Variable herstellen - also zu dem Ort, an dem der Wert dieser Variable im Speicher abgelegt ist.
Stellen wir uns vor, wir haben ein Script namens Peter.cs:


public class Peter: MonoBehaviour
{
   public int alter=5;

   [...anderer Code...]
}

Dieses Script liegt als Component auf einem GameObject, das wir PeterCube genannt haben:
Unity-connect-to-variable-in-editor.png
Um von einem anderen Script, nennen wir es ZugriffsScript.cs, auf die Variable alter von Peter.cs zuzugreifen, können wir in ZugriffsScript.cs eine neue public Variable vom Typ Peter anlegen, und sie, beispielsweise, meinZugriff nennen:

public class ZugriffsScript: MonoBehaviour
{
   public Peter meinZugriff;

   [...anderer Code...]
 
}

Unsere Scripte werden ja nur dann ausgeführt, wenn Sie sich als Component auf einem GameObject befinden. Wir können nun auf dem GameObject, auf dem ZugriffsScript.cs als Component liegt im Inspector diese neue, von uns soeben angelegte Variable sehen:
Unity-zugriff-peter.png
Der Unity Editor erlaubt uns nun, eine direkte Verbindung über unsere Variable zu einem Component eines anderen GameObjects herzustellen, indem wir das GameObject (PeterCube) mit der entsprechenden Komponente (Peter.cs) in diese Variable hineinziehen:
Unity-peter-zu-zugriff.png

Daraufhin haben wir in ZugriffsScript.cs die Möglichkeit, auf die Variable alter zuzugreifen. Zum Beispiel können wir Sie in Start in der Konsole ausgeben:

public class ZugriffsScript: MonoBehaviour
{
   public Peter meinZugriff;

   // Start is called before the first frame update
   void Start()
   {
     Debug.Log(meinZugriff.alter);
   }
 

...oder in Update die Variable um 1 erhöhen:

   // Update is called once per frame
   void Update()
   {
     meinZugriff.alter = meinZugriff.alter+1;    
   }

Eine Funktion in einem anderen Script ausführen

Ebenso wie wir über eigene Variablen auf die Variablen anderer Scripte in anderen GameObjects zugreifen können, können wir auch alle als public deklarierten Funktionen auf anderen Scripten aufrufen. Um beim oben genannten Beispiel zu bleiben, gesetz dem Fall es gibt eine Funktion public void Yay() in Peter.cs:

public class Peter:Monobehaviour
{

[...]

  public void Yay()
  {

    Debug.Log("Yay!");

  }
 
[...]
 
}

Dann können wir bei ZugriffsScript.cs, nachdem wir der oben beschriebenen Prozedur gefolgt sind, schreiben:

public class ZugriffsScript: MonoBehaviour
{
  public Peter meinZugriff;

  [...]

  void Start()
  {
  
     meinZugriff.Yay();
  
  }

  [...]

}

Führe eine Funktion einmalig aus, wenn ein bestimmter Zustand erreicht ist

  • die ganz einfache Methode (sehr fehlerbehaftet, wenn zustand sich zwischen Frames nicht ändert):
public bool bZustand=false;

void Update(){

  if (/*ZustandsAbfrage*/){
    bZustand=true;
  }  

  if (bZustand){
    DoSomethingOnce();
    bZustand=false;
  }

}
  • bessere Variante ist, den Zustand als ganze Zahl, (noch besser als Enum zu speichern), und die Funktion beim Zustandswechsel aufzurufen. Hier beispielweise, führen wir eine Funktion aus, wenn wir von Zustand 0 auf Zustand 1 übergehen:
public int zustand=0;

void Update(){

  if (/*Zustandsabfrage*/ && zustand != 1){
    DoSomethingOnce();
    zustand=1;
  }

}

Verändere die Position eines Objektes kontinuierlich entlang einer Achse

public GameObject meinObjekt //hier muss im Editor das Objekt das bewegt werden soll abgelegt werden

void Update(){
  meinObjekt.transform.position += new Vector3(xWert,yWert,zWert); 
}

weitere Möglichkeiten hierzu, siehe auch unter Moving Objects in Unity.

Was ist ein IEnumerator und wann benutze ich ihn

Ein IEnumerator ist eine bestimmte Art von Funktion, die es erlaubt, Dinge über Zeit auszuführen. Ich benutze IEnumerator wenn ich:

  • eine längere Sequenz von Aktionen hintereinander ausführen will, die 'nicht' alle im selben Frame stattfinden sollen
  • eine bestimmte Zeit lang etwas ausführen möchte, z.b. ein Objekt 3 Sekunden lang nach oben bewegen
  • etwas nach einer bestimmten Zeit ausführen möchte, z.B. nach 15 Sekunden einen Ton abspielen will

IEnumerator werden oft ausgeführt, wenn ein bestimmter Zustand erreicht ist (z.B. ein Button geklickt, ein Trigger ausgelöst, eine Position erreicht, etc.)

Wie rufe ich eine IEnumerator Funktion auf

Für gewöhnlich werden Funktionen über ihren Funktionsnamen und die dazugehörigen Parameter aufgerufen: MeineFunktion(int ganzeZahl); Bei IEnumerator wird die Funktion über den Befehl "StartCoroutine" aufgerufen: StartCoroutine(MeineIEnumeratorFunktion(int ganzeZahl));

Was ist bei IEnumerator zu beachten

Auch IEnumerator können mehrfach ausgeführt werden und laufen dann Parallel.


Von einer Farbe über Zeit zu einer anderen wechseln, per IEnumerator

IEnumerator ChangeColor(){

  Color colornew = new Color();
  float t = 0;
  float amountOfSeconds = 4.0f;
  while (t < amountOfSeconds)
  {
     colornew = Color.Lerp(colorBegin, Color.magenta, t);
     GetComponent<Renderer>().material.color = colornew;
     
     t += Time.deltaTime/amountOfSeconds;
     yield return new WaitForEndOfFrame();
  }

}

Mit einem IEnumerator herausfinden ob etwas innerhalb einer Zeitspanne passiert ist

bool bPassiert=false;
float zeitraum = 4.0f // es wird innerhalb von 4 sekunden abgefragt ob ein zustand hergestellt wird

IEnumerator Abfrage(){

  float t=0.0f;
  while (t<zeitraum){
    if (/*Zustandsabfrage*/){
      bPassiert = true;  
    }
    
    t+=Time.deltaTime;
    yield return new WaitForEndOfFrame();
  }
 
}