Du bist nicht angemeldet. ## Perlin Noise in PHP

Werbeanzeige kruemelkeksfan

Frischling

•  11.07.2019, 10:57

### Perlin Noise in PHP

Hallo Leute,

ich habe in den letzten Tagen mal probiert, für ein Projekt Perlin Noise in PHP zu implementieren, um prozedural Maps zu generieren. Ich habe das jetzt so weit, dass es keine Compilerfehler mehr wirft und tatsächlich etwas ausgibt, aber bin mir mit dem Endergebnis nicht sehr sicher. Erstmal mein Code: ### Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

<?php
class PerlinGenerator
{
function calculate_map(int $mapwidth, int$mapheight, int $gridwidth, int$gridheight, array $gradients = null) { // Calculate ratios between map and grid$ratiox = $mapwidth / ($gridwidth - 1);
$ratioy =$mapheight / ($gridheight - 1); // Generate Gradients if(empty($gradients))
{
$gradients = array(); for($y = 0; $y <$gridheight; ++$y) {$gradients[] = array();
for($x = 0;$x < $gridwidth; ++$x)
{
$randomx = mt_rand(-100, 100) / 100.0;$randomsign = mt_rand(0, 1) ? 1 : -1;

$gradients[$y][$x] = array($randomx, $randomsign * sqrt(1 - ($randomx * $randomx))); // Use Pythagoras to create random normalized vector } } } // Calculate Points$map = array();
for($y = 0;$y < $mapheight; ++$y)
{
$map[] = array(); for($x = 0; $x <$mapwidth; ++$x) { // Calculate corresponding grid cell$gridx = floor(($x /$mapwidth) * ($gridwidth - 1)); // TODO: really need -1 or is it off by one? Im pretty sure now its necessary$gridy = floor(($y /$mapheight) * ($gridheight - 1)); // Calculate dot-products with grid corner gradients$corners = array(array($gridx,$gridy), array($gridx + 1,$gridy), array($gridx,$gridy + 1), array($gridx + 1,$gridy + 1));
$distances = array();$dots = array();
foreach($corners as$corner)
{
$cornerposition = array($corner * $ratiox,$corner * $ratioy);$distances[] = $this->subtract_vectors(array($x, $y),$cornerposition);
$dots[] =$this->dot_product($distances[count($distances) - 1], $gradients[$corner][$corner]); }$lerpeddot = $dots; for($i = 1; $i < count($dots); ++$i) {$lengtha = $this->vector_length($distances[$i - 1]);$lengthb = $this->vector_length($distances[$i]);$lerpeddot = $this->lerp($lerpeddot, $dots[$i], $lengtha / ($lengtha + $lengthb)); }$map[$y][$x] = $lerpeddot; } } return$map;
}

// Subtracts vector b from vector a and returns the result
function subtract_vectors(array $lho, array$rho)
{
return array($lho -$rho, $lho -$rho);
}

// Returns the dot product of a and b
function dot_product(array $lho, array$rho)
{
return $lho *$rho + $lho *$rho;
}

// Returns the length of a vector
function vector_length(array $vector) { return sqrt($vector * $vector +$vector * $vector); } // Returns the vector scaled by the factor$t
function scale_vector(array $vector, float$t)
{
return array($vector *$t, $vector *$t);
}

// Linearly interpolates between $a and$b using $weight. A weight of 0 returns$a and a weight of 1.0 returns $b. function lerp(float$a, float $b, float$weight)
{
return $a + ($b - $a) *$weight;
}
}

// PRINT MAP
$perlin = new PerlinGenerator();$map = perlin->calculate_map(40, 40, 5, 5); echo('<table style="text-align: center">'); foreach(map as $maprow) { echo('<tr>'); foreach($maprow as $tile) { //echo('<td>' . number_format($tile) . '</td>');
echo('<td>');
if($tile > 4) { echo('M'); // Mountains } else if($tile > 3)
{
echo('H'); // Hills
}
else if($tile > 1) { echo('F'); // Forest } else if($tile > -1)
{
echo('P'); // Plains
}
else
{
echo('W'); // Water
}
echo('</td>');
}
echo('</tr>');
}
echo('</table>');
?>

Ein Beispiel Ergebnis (der Abstand zwischen den Perlin-Gradienten sind hier 10 Felder): ### Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   H   W   W   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   M   M   H   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   M   H   P   W   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   H   H   H   H   H   H   H   H   H   H   H   H   M   M   M   M   M   M   M   M   M   H   H   P   W   W   W   W   W   W   W   W   W
M   M   M   M   M   H   H   H   H   H   H   F   F   F   F   F   F   H   H   H   M   M   M   M   M   M   M   H   H   F   P   P   W   W   W   W   W   W   W   W
M   M   H   H   H   H   H   H   H   F   F   F   F   F   F   F   F   F   F   F   M   M   M   H   H   H   H   H   F   F   P   P   W   W   W   W   W   W   W   W
H   H   H   H   H   H   F   F   F   F   F   F   F   F   F   F   F   F   F   F   H   H   H   H   H   H   H   F   F   F   P   P   W   W   W   W   W   W   W   W
F   F   F   F   F   F   F   F   F   F   F   P   P   P   P   P   P   F   F   F   F   F   F   F   F   F   F   F   F   F   P   P   W   W   W   W   W   W   W   W
F   F   F   F   F   F   F   F   F   F   P   P   P   P   P   P   P   P   P   P   F   F   F   F   F   F   F   F   F   F   P   P   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   F   F   H   M   M   M   M   M   M   M   P   W   W   W   W   W   W   W   W   W
M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   F   F   F   H   H   M   M   M   M   M   P   W   W   W   W   W   W   W   W   W
H   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   F   F   F   F   H   H   M   M   M   M   P   P   W   W   W   W   W   W   W   W
H   H   H   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   P   P   F   F   F   H   H   H   H   H   P   P   P   W   W   W   W   W   W   W
F   F   H   H   H   H   H   M   M   M   W   W   W   W   W   W   W   W   W   W   P   P   P   F   F   F   F   H   H   H   P   P   P   P   W   W   W   W   W   W
F   F   F   F   H   H   H   H   H   H   W   W   W   W   W   W   W   W   W   W   P   P   P   P   F   F   F   F   F   F   F   P   P   P   P   W   W   W   W   W
F   F   F   F   F   F   F   F   F   H   W   W   W   W   W   W   W   W   W   W   P   P   P   P   F   F   F   F   F   F   F   P   P   P   P   P   W   W   W   W
P   F   F   F   F   F   F   F   F   F   P   W   W   W   W   W   W   W   W   W   W   P   P   P   P   F   F   F   F   F   F   F   P   P   P   P   P   W   W   W
P   P   F   F   F   F   F   F   F   F   P   P   W   W   W   W   W   W   W   W   W   P   P   P   P   P   F   F   F   F   F   F   P   P   P   P   P   P   W   W
P   P   P   P   P   P   P   P   P   P   P   P   W   W   W   W   W   W   W   W   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P
W   W   P   P   P   F   F   H   H   M   M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   P   P   F   F   F   F   H   M   M   M   M   M   M   M   M   M   M   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   P   P   P   F   F   F   F   M   M   M   M   M   M   M   M   M   M   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   P   P   P   F   F   F   H   M   M   M   M   M   M   M   M   M   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   P   P   P   F   F   H   H   H   H   M   M   M   M   M   M   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   P   P   P   P   F   F   F   H   H   H   H   M   M   M   M   P   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   W   P   P   P   F   F   F   F   F   H   H   H   H   H   H   P   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   W   W   P   P   P   F   F   F   F   F   F   F   H   H   H   P   P   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   W   W   P   P   P   P   F   F   F   F   F   F   F   F   F   P   P   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W
W   W   W   W   W   W   W   W   P   P   P   P   F   F   F   F   F   F   F   F   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P   P
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   P   P   P   P   P   P   W   W   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   P   P   P   P   P   P   P   W   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   F   F   F   P   P   P   P   W   W
P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   F   F   F   F   F   P   P   P   W
P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   F   F   F   F   F   F   P   P   P
P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   F   F   H   F   F   F   F   P   P
P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   H   H   H   H   H   F   F   F   P
P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   F   H   H   H   H   H   H   F   F   P
P   P   P   P   P   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   F   F   H   M   M   M   H   F   F   P

Wie man sieht, ist das Ganze sehr eckig und die Gridgrenzen treten sehr deutlich hervor. Ich schätze also, dass es iwo noch einen Bug in der Berechnung gibt, aber meine stichprobenartigen Überprüfungen haben mich nicht weiter gebracht und ich komme nicht wirklich drauf, was das schief läuft Dementsprechend wäre ich dankbar, wenn mal jemand über den Code drüberschauen würde und mir ein bisschen Feedback geben könnte. Auch allgemeine Anmerkungen sind willkommen (ja ich weiß, dass die Variablennamen ausschließlich für mich lesbar sind).

Ich bin mir auch bewusst, dass es Simplex Noise gibt, eine verbesserte Version des Algorithmus, die viel besser ist, aber ich tu mich grad mit Perlin schon so schwer, dass ich mir dieses Upgrade für später aufgehoben habe.

LG Max TGGC

1x Rätselkönig Beiträge: 1 814

Beruf: Software Entwickler 11.07.2019, 13:29

Weiss nicht genau, was daran Perlin Noise sein soll, weil ich nicht sehe wo die Oktaven addiert werden. Sieht eher aus als ob du 5x5 Zufallszahlen machst und da ein bisschen lerpst, und so in etwa sieht dann ja auch das Ergebnis aus. kruemelkeksfan

Frischling

•  11.07.2019, 14:14

Ich habe den Algorithmus von Wikipedia implementiert:
https://en.wikipedia.org/wiki/Perlin_noise#Algorithm_detail

Ich bin nicht ganz sicher, was du mit Oktaven meinst, aber wenn du damit meinst, dass ich verschieden große Grids generieren und zusammen samplen sollte ist das doch kein Kernkriterium für Perlin Noise? Ich kann jede Noise in verschiedenen Skalierungen generieren und dann übereinanderlegen, aber wichtig ist doch der Algorithmus, mit dem ich die einzelnen Schichten generiere. Und der muss auch funktionieren, damit man über mehrere Schichten überhaupt erst nachdenken kann.

Im Prinzip habe ich hier also Perlin Noise mit einer Schicht. Und ja, das ist "Zufallszahlen machen und ein bisschen lerpen", aber darauf läuft doch jede Noise hinaus oder nicht? Genauso wie Programmieren "ein bisschen tippen und hoffen dass das richtige rauskommt" ist. Zutreffende, aber verharmlosende Beschreibung. TGGC

1x Rätselkönig Beiträge: 1 814

Beruf: Software Entwickler 11.07.2019, 17:51

Ja, das ist dann natürlich einfach die Noise mit einer Oktave und ist natürlich genau so ein Noise eben mit anderen Parametern. Hat dann aber eben genau die von dir beschriebenem optischen Eigenschaften weswegen ich tatsächlich keine Anwendung so gesehen habe. Die deutsche Wikipedia sagt sogar "Perlin-Noise ist eine fraktale Rauschfunktion", d.h. nur der Grenzfall mit unendlich vielen Oktaven ist "Perlin Noise" und bei endlichen Oktaven handelt es sich dann logischer weise nur um eine ungefähre Näherung der Funktion, das ist meiner Meinung nach dann aber nicht konform zu dem von dir gezeigten Link der englischen Wikipedia (wohingegen dem Abbildungen wieder alle eher Fraktal wirken). kruemelkeksfan

Frischling

•  12.07.2019, 08:22

Ich orientiere mich an der Abbildung vom Endergebnis. Ich gehe mal davon aus, dass die nicht fraktal ist, weil sie das Ergebnis des Algorithmus zeigen soll, in dem die Oktaven überhaupt nicht erwähnt werden.

Ich verstehe (und sehe in den Bildern) durchaus was du meinst, denke aber immer noch, dass mein Grundalgorithmus verbuggt ist. Außerdem denke ich (muss ich aber noch durch Tests belegen), dass verschiedene Oktaven für meinen Anwendungsfall ein Overkill wären, ein Ergebnis wie das im oben verlinkten Bild reicht mir vollkommen aus. Aber dafür müssten die Kanten ineinander übergehen wie im Bild und ich bin eig. davon ausgegangen, dass genau das der Witz an dem komplizierten Gradienten-Distancen-Dot-Product-Kram ist. Ich denke wenn man in Kauf nehmen würde, dass die Kanten sichtbar sind, könnte man den Algortihmus um Einiges einfacher gestalten, ergo hält sich meine Implementierung iwo nicht an den Algorithmus. kruemelkeksfan

Frischling

•  14.07.2019, 15:39

### Solved

Nach 3 Tagen Rumprobiererei würde ich mal sagen das Problem ist gelöst. Ich vermute, meine Interpolation war nicht so gut, wie ich erst dachte. Das Endergebnis ist noch ein Stück unlesbarer geworden, aber es werden sogar 2 Oktaven generiert und übereinander gelegt (wenn auch vermutlich nicht ganz so, wie TGGC sich das vorgestellt hätte). ### Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

<?php
class PerlinGenerator
{
// Ideally Gridsize should be the number of desired Gridcells + 1
// Types: 'messy' (default), 'island', 'pond'
// Types 'island' and 'pond' work best with 2x2 Gridsize
function generate_gradients(int $gridwidth, int$gridheight, string $type = 'messy') { if($gridwidth < 2 || $gridheight < 2 ) { // TODO: handle Error } if($type !== 'messy' && $type !== 'island' &&$type !== 'pond')
{
// TODO: handle Error
}

// Generate Gradients
$randomgradients = array( 'north' => array(0.0, -1.0), 'north-east' => array(0.7071, -0.7071), 'east' => array(1.0, 0.0), 'south-east' => array(0.7071, 0.7071), 'south' => array(0.0, 1.0), 'south-west' => array(-0.7071, 0.7071), 'west' => array(-1.0, 0.0), 'north-west' => array(-0.7071, -0.7071), );$gradients = array();
for($y = 0;$y < $gridheight; ++$y)
{
$gradients[] = array(); for($x = 0; $x <$gridwidth; ++$x) {$pool = array();
foreach($randomgradients as$direction => $gradient) {$pool[$direction] =$gradient;
}
if($type == 'island') { if($x == 0)
{
unset($pool['north-west']); unset($pool['west']);
unset($pool['south-west']); } else if($x == $gridwidth - 1) { unset($pool['north-east']);
unset($pool['east']); unset($pool['south-east']);
}

if($y == 0) { unset($pool['north-west']);
unset($pool['north']); unset($pool['north-east']);
}
else if($y ==$gridheight - 1)
{
unset($pool['south-east']); unset($pool['south']);
unset($pool['south-west']); } } else if($type == 'pond')
{
if($x == 0) { unset($pool['north-east']);
unset($pool['east']); unset($pool['south-east']);
}
else if($x ==$gridwidth - 1)
{
unset($pool['north-west']); unset($pool['west']);
unset($pool['south-west']); } if($y == 0)
{
unset($pool['south-east']); unset($pool['south']);
unset($pool['south-west']); } else if($y == $gridheight - 1) { unset($pool['north-west']);
unset($pool['north']); unset($pool['north-east']);
}
}

$pool = array_values($pool);
$gradients[$y][] = $pool[mt_rand(0, count($pool) - 1)];
}
}

return $gradients; } function generate_map(int$mapwidth, int $mapheight, array$gradients)
{
// Calculate Step Sizes
$xstepwidth = (count($gradients) - 1) / $mapwidth;$ystepwidth = (count($gradients) - 1) /$mapheight;

// Calculate Map Tiles
$map = array(); for($y = $ystepwidth * 0.5;$y < count($gradients) - 1;$y += $ystepwidth) {$map[] = array();
for($x =$xstepwidth * 0.5; $x < count($gradients[$y]) - 1;$x += $xstepwidth) { // Calculate corresponding Grid Cell$gridx = floor($x);$gridy = floor($y); // Calculate Dot Products with Grid Corner Gradients$corners = array(array($gridx,$gridy), array($gridx + 1,$gridy), array($gridx,$gridy + 1), array($gridx + 1,$gridy + 1));
$distances = array();$dots = array();
foreach($corners as$corner)
{
$distances[] =$this->subtract_vectors(array($x,$y), $corner);$dots[] = $this->dot_product($distances[count($distances) - 1],$gradients[$corner][$corner]);
}

// Lerp Results together
$weight = abs($distances) / (abs($distances) + abs($distances));
$toplerp =$this->lerp($dots,$dots, $weight);$bottomlerp = $this->lerp($dots, $dots,$weight);

$weight = abs($distances) / (abs($distances) + abs($distances));
$finallerp =$this->lerp($toplerp,$bottomlerp, $weight); // Save Results$map[count($map) - 1][] =$finallerp;
}
}

return $map; } // Subtracts Vector b from Vector a and returns the Result function subtract_vectors(array$lho, array $rho) { return array($lho - $rho,$lho - $rho); } // Returns the Dot Product of a and b function dot_product(array$lho, array $rho) { return$lho * $rho +$lho * $rho; } // Linearly interpolates between$a and $b using$weight. A Weight of 0 returns $a and a Weight of 1.0 returns$b.
function lerp(float $a, float$b, float $weight) { return$a * (1 - $weight) +$b * $weight; } } // GENERATE MAPS$perlin = new PerlinGenerator();
$watermap =$perlin->generate_map(30, 15, $perlin->generate_gradients(3, 2, 'pond'));$landmap = $perlin->generate_map(30, 15,$perlin->generate_gradients(6, 4, 'messy'));

// SET HEIGHTLINES
$waterlevel = -0.4;$mountainlevel = 0.0;
$hilllevel = -0.2; // CALCULATE TERRAIN$map = array();
for($y = 0;$y < count($watermap); ++$y)
{
$map[] = array(); for($x = 0; $x < count($watermap[$y]); ++$x)
{
if($watermap[$y][$x] >$waterlevel)
{
$tilevalue =$watermap[$y][$x] + ($landmap[$y][$x] * 0.5); if($tilevalue > $mountainlevel) {$map[$y][$x] = '<span style="color: Gray;">M</span>';
}
else if($tilevalue >$hilllevel)
{
$map[$y][$x] = '<span style="color: Brown;">H</span>'; } else {$map[$y][$x] = '<span style="color: Green;">P</span>';
}
}
else
{
$map[$y][x] = '<span style="color: Blue;">W</span>'; } } } // PRINT MAP echo('<table style="text-align: center">'); foreach(map as $maprow) { echo('<tr>'); foreach($maprow as $tile) { echo('<td>'); echo($tile);
echo('</td>');
}
echo('</tr>');
}
echo('</table>');
?> ### Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   H   H   H   H   H   H   P   P   W   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   H   H   H   H   H   H   H   P   P   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   P   H   H   H   H   H   H   H   H   P   W   W
W   W   W   W   W   W   W   P   P   P   P   P   P   P   P   P   P   P   H   H   H   H   H   H   H   H   H   P   W   W
W   W   W   W   W   P   P   P   P   P   P   P   P   P   P   P   P   H   H   H   M   M   M   H   H   H   H   P   W   W
W   W   W   W   P   P   P   P   P   P   P   H   H   H   H   H   H   H   H   M   M   M   M   H   H   H   H   H   P   W
W   W   W   P   P   H   H   H   H   H   H   H   H   H   H   H   H   H   H   M   M   M   M   H   H   H   H   H   P   W
W   W   W   P   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   M   M   H   H   H   H   H   H   H   P   W
W   W   W   P   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   P   W
W   W   W   P   P   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   W   W
W   W   W   P   P   P   P   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   H   W   W
W   W   W   W   P   P   P   H   H   H   H   H   H   H   H   H   H   H   H   P   P   P   P   P   H   H   H   W   W   W
W   W   W   W   W   W   P   P   H   H   H   H   H   W   W   W   P   P   P   P   P   P   P   P   P   H   W   W   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   P   P   P   P   W   W   W   W   W   W
W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W   W

(in Farbe ist es übersichtlicher aber dafür müsst ihr es selbst ausführen - https://www.runphponline.com/)

Werbeanzeige 