Bernhard Häussner
Tags: Artikel mit dem Tag «Mathematik» durchstöbern

MacMahonMosaik Online Game

09.07.2009, 18:05
Mac Mohan Mosaik

Mac Mohan Mosaik

Da ich beim Känguru-Wettbewerb 2009 mitgemacht habe, habe ich auch das MacMahonMosaik in die Hände bekommen. Es ist ein kleines Rätsel ähnlich wie Sudoku. Weil es interessant zu Modellieren ist, habe ich das MacMahonMosaik als Online-Spiel erstellt. Hier ein paar Details zu Implementierung:

Modell

Jedes Quadrat im Spiel hat 4 Kanten, die in 3 verschiedenen Farben markiert sein können. Daher lässt sich jede Anordnung als 4-Tupel einer 3-Menge auffassen. Diese 81 Tupel wiederum können den natürlichen Zahlen von 0-80 zugeordnet werden. Somit habe ich als Objekte zunächst die 24 Quadrate (die anderen fallen weg, da sie durch Drehung erreicht werden können), die jeweils eine Zahl speichern. Sie müssen sich nun z.B. drehen können. Dazu wird die Zahl in das Tupel umgerechnet und die Einträge des Tupels um eine Stelle verschoben, wobei der letzte Eintrag der erste wird. Um zu Überprüfen, ob zwei Quadrate aneinander passen, dreht man eines der beiden zweimal und schaut dann einfach ob in der gewünschten Richtung die selben Farben stehen. Dadurch sieht die Prüffunktion im Javascript sehr kurz aus:

p.checkMatch=function(other,pos){
  var mycodes=this.getColorCodes(); // Tupel aus Zahl
  var o=other.clone(); // Neues Quadrat mit selber Zahl
  var othercodes=o.rotate().rotate().getColorCodes(); // anderes Q. 2x drehen
  return (mycodes.charAt(pos)==othercodes.charAt(pos)); //prüfen
}

Zusätzlich haben die Quadrate noch ein paar Darstellungsrelevante optionale Eigenschaften und Methoden. Ist ihnen ein <Canvas>-Element im HTML-Baum delegiert, können sie ihm die passenden Event-Handler für das interaktive Drehen und Bewegen zuweisen und eine graphische Repräsentation ihrer selbst zeichnen. Und sie speichern einige Daten für drag & drop.

Doch beim Ziehen und Ablegen kommt das zweite Objekt ins Spiel: Das Feld in dem die Quadrate abgelegt werden können. Wann immer man ein Quadrat bewegt, frag dieses Quadrat beim Gitter nach, ob es sich einrasten kann. Das Gitter gäbe dann die Pixel-Koordinaten des Einrastpunkts auf dem Bildschirm und die Koordinaten innerhalb des Gitters zurück. Wird das Quadrat dann fallen gelassen, registriert es sich im Gitter.

Das Gitter hat dazu ein zweidimensionales Feld hinterlegt, in dem es Pointer zu den abgelegten Quadraten speichern kann. Wenn jetzt ein Quadrat im Raster abgelegt wird und sich registriert hat, weist es das Raster an, sich zu revalidieren - Das Gitter ruft für jedes Quadrat und je seine vier Nachbarn die Prüffunktion auf. (Da das „Passen“ symmetrisch ist, ließe sich die Komplexität des Algorithmus vielleicht noch optimieren, doch das hätte wieder mehr Code zur Folge und bei 24 Einheiten...) Da das Gitter nur Zeiger zu den Quadraten speichert, ist bei einer Drehung eines Quadrats keine weitere Registrierung nötig. Falls also ein Quadrat angeklickt wird, rotiert es sich, aktualisiert die eigene Darstellung und gibt den Revalidierungsbefehl an das Raster.

Für die Darstellung hat das Gitter auch eine recht einfache Methode, die den Feldhintergrund rötlich färbt, sollte ein Fehler vorliegen und eine Gratulation meldet, sollten alle Quadrate an einem passenden Ort sein, also das Spiel gewonnen.

Ablaufdiagramm

Hier mal ein kleiner Flowchart. Man beginne das Lesen bei den Event-Handlern: (erstellt mit U+2500-U+257F: Box Drawing)

Quadrat
├ Zahl ◁──────┐ {Umrechnung}
├ (Tupel) ◁───┘
├ Drehen() ◀────────┐◁──┐─┐▷─────┐
├ Prüfen(Quadrat) ──◆───┊─┊──────┊──┐
├ [Darstellung]     │   │ │      │  │
│  ├ Visualisieren◁─┊───┊─┘      │  │
│  └ [Handler]      │   │        │  └──┐
│     ├ Klick ──────┊───┘        │     │
│     ├ Packen  ────┊────────────┊──┐  │
│     ├ Bewegen ◀───┊───┐        │  │  │
│     └ Ablegen ────┊─────────┬▷─┤  │  │
└ Kopieren() ►──────┘   │     │  │  │  ▼ bool
                     [XY,XY]  │  │  │  │
Gitter                  │     │  │  │  │
├ Feld[X,Y]             │     │  │  │  │
├ KoordinatenBei(XY) ───┘     │  │  │  │
├ Registrieren(Quadrat)  ◀─XY─┘  │  │  │
│ └ Un-Registrieren() ◀─────XY───┊──┘  │
├ Alle Prüfen()  ►────────┐─▷────│─────┘
└ Darstellung erneuern() ─┘  ◁───┘
  └ Färben(), Gratulation()

Wie das Gitter überprüft, ob auch am Rand rote Kanten liegen? Statt weiteren Code für den Rand zu basteln, wird das Gitter mit voll-roten Quadraten ohne graphische Repräsentation außerhalb das Gitters vorbelegt. Das hat den kleinen Nebeneffekt, das auch out-of-bounds-Fehler im Prüfalgorithmus vermieden werden, da ja z.B. bei negativen Gitterkoordinaten -1 stets ein solches „virtuelles“ Quadrat liegt.

Wie man also sieht, kann man an diesem Spiel schon einige lustige Code-Spar-Methoden benutzen. Eigentlich sparen sie nicht nur Code, sondern auch Nachdenken, wenn man sie „sieht“.

Javascript Umsetzung

JS-Closures habe ich in letzter Zeit immer mehr zu schätzen gelernt. (Man muss in PHP übrigens auch nicht mehr lange darauf verzichten) Sie lassen den Programmierer Funktionen, also mehr oder weniger Code, wie ein String oder eine Zahl an andere Funktionen übergeben oder in Variablen spichern, sodass mit anonymen Funktionsobjekten als Closures in JS eigene Kontrollstrukturen definiert werden können, wie diese zweifache for-Schleife, um über Koordinaten bzw. Zweidimensionales zu loopen:

function loopXY(w,h,cb) {
  for (var x=0;x<w;x++) {
    for (var y=0;y<h;y++) {
      cb(x,y);
    }
  }
}
var m=[];
loopXY(5,7,function(x,y){
  m.push("("+x+","+y+")");
});
alert(m);

Wie man sieht stehen in Javascript in den Closures alle äußeren Variablen zur Verfügung. Das wiederholte verwenden des Namens einer äußeren Variable führt trozdem noch dazu, dass in der Closure diese lokal verwendet wird und außen der alte Wert erhalten bleibt. Ein bischen gewöhnungsbedürftig ist die Verwendung von this in JS. Im oben gezeigten Code würde z.B. in der Schleife this auf window zeigen. Das lässt sich mit der Function.call oder der Function.apply-Funktion kompensieren, wie in diesen Code-Schnippseln für Event-Handler.

Für die Erstellung von Objekten (außer reinen Datenspeichern, die mit {} einfach erstellt werden können) verwende ich folgende Grundstruktur:

function Square(id) { // constructor
	this.id=id; // Eigenschaften initialisieren
}
(function(){
  var p=Square.prototype; // shortcut zum Prototype
  p.setId=function(id) { // einfacher Setter
    this.id=id;
    return this; // macht Setter chainable
  };
  var privat; // kann nur von den Objektmethoden gesehen werden
  function privatauch() {} // dito
})();
var s=new Square (3);

Allerdings bin ich mir nicht sicher, ob das wirklich besser ist, als alles in Konstruktoren zu packen.

Der Code des Onlinegames ist vielleicht nicht ganz so interessant zu lesen, wie es war ihn zu erstellen, aber ich denke, hinein schauen lohnt sich. Ich bin natürlich immer interessiert an weiteren Javascript-Techniken und Tipps zur Code-Gestaltung.

Kommentare: keine

Processing: 3D-Koch-Kurve mit Sierpinski-Dreieck

23.05.2009, 17:34

Nachdem ich mich jetzt mal an Processing heran gewagt habe, habe ich auch gleich mal einen fraktalen Körper basteln müssen. Es handelt sich um eine Art dreidimensionale Koch-Schneeflocke.

Die Koch-Schneeflocke an sich ist eigentlich recht simpel, habe ich schon in Logo und PHP implementiert. Es wird eine Linie jeweils durch eine Linie mit einem Dreieck darin ersetzt. Auf den Raum übertragen heißt das dann eine Dreieckfläche bekommt einen Tetraeder eingesetzt. Dies wird dann mit den Flächen des Tetraeders rekursiv wiederholt, sodass dann ungefähr so etwas entsteht:

Es entsteht eigentlich eine etwas andere Form, die allerdings nicht sehr interessant anzusehen ist, nämlich ein Tetraeder, der in viele kleine Tetraeder zerlegt ist. Damit der strenge Tetraeder etwas aufgelockert wird, habe ich die neuen Tetraeder immer um 70% skaliert. Es sind also keine Tetraeder mehr, sondern, je tiefer die Rekursion geht, immer schiefere Pyramiden.

Außerdem habe ich nicht mit nur einem Dreieck begonnen, sondern mit einem Tetraeder, sodass ein Körper ensteht, ähnlich wie wenn man in 2D mit dem Dreieck beginnt. Bei 6 Rekursionsschritten ergeben sich dann 66*4 = 186 624 Dreiecke. Die werden dann von Processing z.B. mit 7%iger Transparenz gerendert, damit es etwas schöner aussieht und so kann dann dieses Video entstehen:


Interessant ist eigentlich auch, dass der Rest, der vom ursprünglichen Dreieck bleibt, wenn man die ganzen Tetraeder heraus nimmt, genau dem Sierpinski-Dreieck entspricht. So sieht der Code aus:

fraqTriang trs[]=new fraqTriang[4];

void setup(){
  size(640, 480, P3D);
  noStroke();
  fill(150,150,150,20);
  int bigrad=200;
  int req=6;
  
  PVector A=new PVector(-bigrad,0,0);
  PVector B=new PVector(bigrad/2,0,-173.21);
  PVector C=new PVector(bigrad/2,0,173.21);
  PVector D=new PVector(0,-bigrad*(sqrt(6)/3),0);
  
  trs[0]=new fraqTriang(A,B,C,req);
  trs[1]=new fraqTriang(A,D,B,req);
  trs[2]=new fraqTriang(A,C,D,req);
  trs[3]=new fraqTriang(B,D,C,req);
}


void draw(){
  background(255);
  lights();
  
  float winkel=(mouseX/float(width))*TWO_PI;
  float xpos=cos(winkel);
  float ypos=sin(winkel);
  float radius=300.000;
  camera(xpos*radius, mouseY, ypos*radius, // eyeX, eyeY, eyeZ
         0.0, -50.0, 0.0, // centerX, centerY, centerZ
         0.0, -1.0, 0.0); // upX, upY, upZ
      
  for (int i=0;i<trs.length;i++) {
    trs[i].display();
  }
  //saveFrame("frames/koch3d-####.png"); //uncomment to record
}

class fraqTriang{
  PVector PointA;
  PVector PointB;
  PVector PointC;
  fraqTriang recTris[]=new fraqTriang[6];
  int rec;
  float scaling;
  
  fraqTriang(PVector A,PVector B,PVector C, int recursion){
    scaling=0.7;
    PointA=A;
    PointB=B;
    PointC=C;
    rec=recursion;
    applyRecursion ();
  }
    
  void applyRecursion () {
    if (rec!=0) {
      PVector PointAB2=PVector.add(PointA,PointB);
      PointAB2.div(2);
      PVector PointAC2=PVector.add(PointA,PointC);
      PointAC2.div(2);
      PVector PointBC2=PVector.add(PointB,PointC);
      PointBC2.div(2);
      
      PVector PointZ=PVector.add(PointA,PointB);
      PointZ.add(PointC);
      PointZ.div(3);
      PVector PointAB=PVector.sub(PointA,PointB);
      PVector PointAC=PVector.sub(PointA,PointC);
      PVector PointH=PointAB.cross(PointAC);
      PointH.normalize();
      PVector PointAAB2=PVector.sub(PointA,PointAB2);
      float a=PointAAB2.mag();
      float pheight=a*(sqrt(6)/3)*scaling;
      PointH.mult(-pheight);
      PVector PointZH=PVector.add(PointZ,PointH);
      
      recTris[0]=new fraqTriang(PointA,PointAB2,PointAC2,rec-1);
      recTris[1]=new fraqTriang(PointB,PointBC2,PointAB2,rec-1);
      recTris[2]=new fraqTriang(PointC,PointAC2,PointBC2,rec-1);
      
      recTris[3]=new fraqTriang(PointZH,PointAC2,PointAB2,rec-1);
      recTris[4]=new fraqTriang(PointZH,PointAB2,PointBC2,rec-1);
      recTris[5]=new fraqTriang(PointZH,PointBC2,PointAC2,rec-1);
    }
  }  
  
  void display () {
    if (rec==0) {
      beginShape();
      vertex(PointA.x, PointA.y ,PointA.z);
      vertex(PointB.x, PointB.y ,PointB.z);
      vertex(PointC.x, PointC.y ,PointC.z);
      endShape(CLOSE);
    } else {
      for (int i=0; i<recTris.length;i++) {
        recTris[i].display();
      }
    }
  }
  
}

Man sieht dem Code an, wie schnell man mit Processing Graphiken erstellen kann, ohne sich mit komplizierteren APIs herumzuschlagen. Durch die vielen Beispiele, die Processing beiliegen und die übersichtliche API-Dokumentation kann man innerhalb von kurzer Zeit schon recht hübsche Ergebnisse erzielen. Viel Synatax muss man auch nicht lernen, da die Processing-Synatx mehr oder weniger der von Java entspricht.

Kommentare: keine

Wellen mit PHP rendern

30.04.2009, 19:52

Da ich heute mal wieder etwas Zeit zum Programmieren hatte (bzw. zum was-ich-will-Programmieren) habe ich unseren aktuellen Physik-Stoff visualisiert und zwar mit PHP und GD.

Ich habe ja schon vorher ein wenig mit SVG herumexperimentiert. Das war allerdings alles etwas krum und schief und ganz ohne Mathematik per Hand zusammengebastelt:


interference

Jetzt musste natürlich ein Algorithmus her. Eigentlich sind es nur 2 Klassen, die festlegen, wie eine Welle aussieht und was passiert, wenn sie von einem Zentrum ausgeht. Und zwar werden zyklische Wellenfronten von verschiedenen Zentren aus übereinandergelegt. Schwer zu erklären, einfacher im PHP-Code zu sehen:

class Wave {
  function __construct($length,$amplitude) {
    $this->length=$length;
    $this->amplitude=$amplitude;
  }
  function getPhaseAtLength($s) {
    $phase=($s%$this->length)/$this->length;
    $phaseWithDelta=fmod($phase+(isset($this->phase)?$this->phase:0)+1,2)-1;
    return $phaseWithDelta;
  }
  function getAmplitudeAtLength($s) { //sinuswelle
    $A=sin(deg2rad($this->getPhaseAtLength($s)*360))*$this->amplitude;
    return $A;
  }
  function setPhase($phase) {
    $this->phase=$phase;
    return $this;
  }
}

class Wavecenter {
  function __construct(Wave $wave,Point $center) {
    $this->wave=$wave;
    $this->center=$center;
  }
  function getAmplitudeAtPoint(Point $point) {
    $distance=$this->center->getDistance($point);
    $amp=$this->wave->getAmplitudeAtLength($distance);
    return $amp;
  }
}

Und mit ein bisschen anderem Code, der das ganze Visualisiert, entsteht dann sowas, wie die roten Wellen oben.

Das erste ist natürlich keine Sinuswelle: Am Anfang hat meine Point::getDistance() nicht funktioniert (^ statt pow()), so dass diese seltsame Figur entstanden ist. Interessant ist, dass man bereits zwei überlagerte Wellen sieht (daher die Symmetrie), diese jedoch seltsam deformiert sind.

Da ich gerade noch eine Phasenverschiebung dazu gezimmert habe, kann ich die Wellen auch noch animieren, also nicht nur in der Position des Mittelpunkts, sondern sie auch wandern lassen. Das sieht dann so aus:

Als kleinen Nachtrag noch ein paar Überlagerung einer Welle und anderen mit kleinerer Wellenlänge, wobei die Wellenlängen in (anfangs) kleinen ganzzahligen Verhältnissen stehen. Daher kann man sie auch recht leicht in der Musik finden. Die oberste Reihe wäre demnach das Schallbild, wenn man 1, 2, und 3 mal den selben Ton spielt, nur eben in der anderen Oktave. Das hört man normalerweise recht einfach, aber beim Sehen ist es etwas anders:

Jetzt wäre es natürlich irgendwie cool, auch einzelne Wellen irgendwie umher wandern zu lassen, um Doppler-Effekt und Reflexion etc. zu zeigen, aber da wären etwas kompliziertere Models nötig.

Nachtrag 26. Mai 2009: OpenGL

Da ich vor kurzem begonnen habe mit Processing Grafiken zu basteln, habe ich Processing auch gleich mal für das Wellen-Darstellen benutzt. Und das funktioniert sehr gut: Processing bietet die Grundlagen, sodass man leicht eine Welle, die von einem Zentrum ausgeht rendern kann. Das macht man dann z.B. mit 10 Frames, immer mit leichter Phasenverschiebung. Das könnte man dann einfach animieren. Wesentlich ansehnlicher ist es aber, wenn man die gerenderte Welle als animierte Textur in eine OpenGL-Szene mit additiver Überblendung einsetzt. Dann entstehen nämlich die oben gezeigten Bilder in realtime. Abgesehen vom anfänglichen Rendern und dem Laden in den Graphikspeicher der 10 2000x2000 Pixel Texturen. Screenshot:

Vor allem wenn man mehr Wellenzentren man hat, übernimmt OpenGL viele Berechnungen, weshalb man die Wellenzentren ohne Wartezeiten einfach mit der Maus verschieben kann.

Kommentare: keine

Vektor-Wallpaper

12.04.2009, 21:01

Heute habe ich mal wieder ein bisschen mit Inkscape herum gespielt, wobei diese Interessanten Fraktal-artigen, (dreh)symmetrischen Figuren entstanden sind. Ich habe dann Details ausgewählt (und mit selektierten Farben versehen) aus diesem gesamten Set:

Da alles als SVG-Datei angelegt wurde ist es auch beliebig skalierbar. Irgendwie ruft der leicht kubistische Style nach einem Kantenfindungs-Algorithmus, der das Ganze mehr oder weniger automatisch mit Fotos macht... muss ich aber wohl vertagen.

order (custom-made) prints

Kommentare: keine

Fraktale klicken

27.03.2009, 14:22
DHML fun

DHML fun

Letzte Woche bin ich über I could not stop auf dhteumeuleu gestolpert, wo es auch noch ein paar andere nette Javascript (DHTML) Spielereien zu sehen gibt. Mir waren diese roten und schwarzen Kacheln des Originals etwas zu langweilig, und so habe ich beschlossen (da die Demo ja auch unter einer CC-Lizenz steht) mal ein paar andere Bilder aus zu testen, die lustige Muster ausspucken.

Zuerst hatte ich vor etwas ähnliches wie das hier zu basteln, doch das stellte sich als nicht zu einfach heraus, da die schrägen Linien beim Unterteilen nicht richtig aneinander passen. Darum habe ich zunächst einen Rand und ein paar vertikale und horizontale Streben eingebaut, die sich dann immer weiter unterteilen Lassen.

» Demo

Dann wollte ich etwas Farbe ins Spiel bringen und habe noch die bisher weißen Zwischenfelder mit (sehr) bunten Farbvariationen gefüllt. Das ergibt dann ein paar nette, mehr oder weniger zufällig angeordnete Farbkacheln.

» Demo

Wenn sich das Ganze mit Farbe abspielt, kann man die inneren Verzweigungen eigentlich auch weg lassen. Das Ergebnis sind noch buntere Bilder. Leider hat man kaum noch Kontrolle über die Farbkomposition.

» Demo

Am Ende habe ich dann noch, nur um zu sehen, wie sich Schrägen machen, ein weiteres Derivat erstellt. Diesmal sind die Quadrate in zwei Dreiecke unterteilt und unterteilen sich bei Mausklick noch weiter. Im Endeffekt entsteht ein total zufälliger Mix aus Dreieckflächen, der ein wenig an Tangram erinnert.

» Demo

Die Muster sind dann so etwas wie teils zufällige, teils interaktiv gestaltete Fraktale.

Kommentare: 4 Einträge
 
Χρόνογραφ
© 2008-2017 by Bernhard Häussner - Impressum - Login
Kurz-Link zur Homepage: http://1.co.de/