|
@@ -1,12 +1,13 @@
|
1
|
|
-module cosmicai.blocks;
|
|
1
|
+module cosmic.blocks;
|
|
2
|
+
|
|
3
|
+import cosmic.base;
|
|
4
|
+import cosmic.drawing;
|
2
|
5
|
|
3
|
6
|
import std.math, std.format;
|
4
|
7
|
|
5
|
8
|
final class Board
|
6
|
9
|
{
|
7
|
|
- enum WIDTH = 21;
|
8
|
|
- enum HEIGHT = 11;
|
9
|
|
- BlockType[HEIGHT][WIDTH] grid;
|
|
10
|
+ Block[HEIGHT][WIDTH] grid;
|
10
|
11
|
Pos startA, startB;
|
11
|
12
|
|
12
|
13
|
this()
|
|
@@ -15,7 +16,7 @@ final class Board
|
15
|
16
|
{
|
16
|
17
|
foreach (y; 0..HEIGHT)
|
17
|
18
|
{
|
18
|
|
- grid[x][y] = Block(null, false, false, Pos(x, y));
|
|
19
|
+ grid[x][y] = Block(null, Access.none, Pos(x, y));
|
19
|
20
|
}
|
20
|
21
|
}
|
21
|
22
|
|
|
@@ -52,7 +53,7 @@ final class Board
|
52
|
53
|
|
53
|
54
|
while (!queue.empty)
|
54
|
55
|
{
|
55
|
|
- auto p = queue.removeAny;
|
|
56
|
+ auto p = this[queue.removeAny];
|
56
|
57
|
assert(p.type !is null);
|
57
|
58
|
foreach (offset; p.type.downstreamOffsets)
|
58
|
59
|
{
|
|
@@ -63,7 +64,7 @@ final class Board
|
63
|
64
|
}
|
64
|
65
|
seen[t] = true;
|
65
|
66
|
auto bt = this[t];
|
66
|
|
- if (bt.type !is null && bt.access & access == Access.none)
|
|
67
|
+ if (bt.type !is null && (bt.access & access) == Access.none)
|
67
|
68
|
{
|
68
|
69
|
queue.insert(t);
|
69
|
70
|
}
|
|
@@ -75,6 +76,41 @@ final class Board
|
75
|
76
|
{
|
76
|
77
|
return grid[pos.x][pos.y];
|
77
|
78
|
}
|
|
79
|
+
|
|
80
|
+ void draw(SvgSink sink)
|
|
81
|
+ {
|
|
82
|
+ foreach (x; 0..WIDTH)
|
|
83
|
+ {
|
|
84
|
+ foreach (y; 0..HEIGHT)
|
|
85
|
+ {
|
|
86
|
+ this[Pos(x, y)].draw(sink);
|
|
87
|
+ }
|
|
88
|
+ }
|
|
89
|
+ }
|
|
90
|
+}
|
|
91
|
+
|
|
92
|
+@("Access for a fresh board")
|
|
93
|
+unittest
|
|
94
|
+{
|
|
95
|
+ auto board = new Board;
|
|
96
|
+ assert(board[board.startA].access == Access.a);
|
|
97
|
+ assert(board[board.startB].access == Access.b);
|
|
98
|
+
|
|
99
|
+ // Some random points around that should be empty
|
|
100
|
+ assert(board[Pos(1, 1)].access == Access.none);
|
|
101
|
+ assert(board[Pos(20, 10)].access == Access.none);
|
|
102
|
+ assert(board[Pos(0, 0)].access == Access.none);
|
|
103
|
+
|
|
104
|
+ // The neighborhood near the start positions
|
|
105
|
+ foreach (dx; -1 .. 2)
|
|
106
|
+ {
|
|
107
|
+ foreach (dy; -1 .. 2)
|
|
108
|
+ {
|
|
109
|
+ assert(board[Pos(board.startA.x + dx, board.startA.y + dy)].access == Access.a);
|
|
110
|
+ assert(board[Pos(board.startB.x + dx, board.startB.y + dy)].access == Access.b);
|
|
111
|
+ }
|
|
112
|
+ }
|
|
113
|
+ assert(board[Pos(0, 0)].access == Access.none);
|
78
|
114
|
}
|
79
|
115
|
|
80
|
116
|
enum Access
|
|
@@ -91,42 +127,30 @@ struct Block
|
91
|
127
|
Access access;
|
92
|
128
|
Pos pos;
|
93
|
129
|
|
94
|
|
- void draw(SvgSink sink, Board board)
|
|
130
|
+ void draw(SvgSink sink)
|
95
|
131
|
{
|
96
|
132
|
string color;
|
97
|
133
|
final switch (access) with (Access)
|
98
|
134
|
{
|
99
|
135
|
case none:
|
100
|
|
- color = board.colorBg;
|
|
136
|
+ color = sink.colorBg;
|
101
|
137
|
break;
|
102
|
138
|
case both:
|
103
|
|
- color = board.colorBoth;
|
|
139
|
+ color = sink.colorBoth;
|
104
|
140
|
break;
|
105
|
141
|
case a:
|
106
|
|
- color = board.colorA;
|
|
142
|
+ color = sink.colorA;
|
107
|
143
|
break;
|
108
|
144
|
case b:
|
109
|
|
- color = board.colorB;
|
|
145
|
+ color = sink.colorB;
|
110
|
146
|
break;
|
111
|
147
|
}
|
112
|
148
|
auto center = sink.center(pos);
|
113
|
149
|
auto scale = Point(sink.halfScale, sink.halfScale);
|
114
|
|
- auto left = center - scale, right = center + scale;
|
115
|
|
- sink.put(format(`<rect x1=%s y1=%s x2=%s y2=%s fill="%s" stroke="#CAD0D1" stroke-width="%s" />`,
|
116
|
|
- left.x, left.y, right.x, right.y, color, sink.thinStroke));
|
117
|
|
- }
|
118
|
|
-}
|
119
|
|
-
|
120
|
|
-struct Pos
|
121
|
|
-{
|
122
|
|
- int x, y;
|
123
|
|
- int opCmp(Pos other)
|
124
|
|
- {
|
125
|
|
- if (x != other.x)
|
126
|
|
- {
|
127
|
|
- return x - other.x;
|
128
|
|
- }
|
129
|
|
- return y - other.y;
|
|
150
|
+ auto left = center - Point(sink.halfScale, sink.halfScale);
|
|
151
|
+ sink.put(format(`<rect x="%s" y="%s" width="%s" height="%s" fill="%s" stroke="#CAD0D1" stroke-width="%s" />`,
|
|
152
|
+ left.x, left.y, sink.scale, sink.scale, color, sink.thinStroke));
|
|
153
|
+ if (type !is null) type.draw(sink, pos);
|
130
|
154
|
}
|
131
|
155
|
}
|
132
|
156
|
|
|
@@ -143,8 +167,9 @@ abstract class BlockType
|
143
|
167
|
static BlockType oplus;
|
144
|
168
|
static BlockType star;
|
145
|
169
|
static BlockType ostar;
|
|
170
|
+ static BlockType source;
|
146
|
171
|
|
147
|
|
- static BlockType[] all;
|
|
172
|
+ static BlockType[] playable;
|
148
|
173
|
|
149
|
174
|
static this()
|
150
|
175
|
{
|
|
@@ -154,8 +179,9 @@ abstract class BlockType
|
154
|
179
|
oplus = new BlockOPlus();
|
155
|
180
|
star = new BlockStar();
|
156
|
181
|
ostar = new BlockOStar();
|
|
182
|
+ source = new BlockSource;
|
157
|
183
|
|
158
|
|
- all = [
|
|
184
|
+ playable = [
|
159
|
185
|
x, ox, plus, oplus, star, ostar,
|
160
|
186
|
new Arrow(Pos(-1, -1)),
|
161
|
187
|
new Arrow(Pos(-1, 0)),
|
|
@@ -169,57 +195,6 @@ abstract class BlockType
|
169
|
195
|
}
|
170
|
196
|
}
|
171
|
197
|
|
172
|
|
-struct Point
|
173
|
|
-{
|
174
|
|
- double x, y;
|
175
|
|
-
|
176
|
|
- Point rotate(real radians)
|
177
|
|
- {
|
178
|
|
- return this;
|
179
|
|
- }
|
180
|
|
-
|
181
|
|
- Point opBinary(string op)(Point other) if (op == "+" || op == "-")
|
182
|
|
- {
|
183
|
|
- return Point(
|
184
|
|
- mixin("this.x " ~ op ~ " other.x"),
|
185
|
|
- mixin("this.y " ~ op ~ " other.y"));
|
186
|
|
- }
|
187
|
|
-}
|
188
|
|
-
|
189
|
|
-class SvgSink
|
190
|
|
-{
|
191
|
|
- this(double scale)
|
192
|
|
- {
|
193
|
|
- this.scale = scale;
|
194
|
|
- ap.reserve(1024 * 10);
|
195
|
|
- ap.put(format(`<svg viewBox="0 0 %s %s" xmlns="http://www.w3.org/2000/svg">`));
|
196
|
|
- }
|
197
|
|
-
|
198
|
|
- Appender!string ap;
|
199
|
|
-
|
200
|
|
- void put(string s)
|
201
|
|
- {
|
202
|
|
- ap.put(s);
|
203
|
|
- ap.put("\n");
|
204
|
|
- }
|
205
|
|
-
|
206
|
|
- void write(string filename)
|
207
|
|
- {
|
208
|
|
- ap.put(`</svg>`);
|
209
|
|
- import std.stdio: toFile;
|
210
|
|
- ap.data.toFile(filename);
|
211
|
|
- }
|
212
|
|
-
|
213
|
|
- Point center(Pos p)
|
214
|
|
- {
|
215
|
|
- return Point(2 * halfScale * p.x + halfScale, 2 * halfScale * p.y + halfScale);
|
216
|
|
- }
|
217
|
|
-
|
218
|
|
- double halfScale() { return 20; }
|
219
|
|
- double strokeWidth() { return 1; }
|
220
|
|
- double thinStroke() { return 0.1; }
|
221
|
|
-}
|
222
|
|
-
|
223
|
198
|
class BlockMine : BlockType
|
224
|
199
|
{
|
225
|
200
|
override const(Pos[]) downstreamOffsets() { return null; }
|
|
@@ -333,7 +308,7 @@ class BlockStar : BlockType
|
333
|
308
|
|
334
|
309
|
class BlockOStar : BlockType
|
335
|
310
|
{
|
336
|
|
- private static immutable Pos[] offsets = [
|
|
311
|
+ static immutable Pos[] offsets = [
|
337
|
312
|
Pos(-2, 0),
|
338
|
313
|
Pos( 2, 0),
|
339
|
314
|
Pos( 0, -2),
|
|
@@ -370,13 +345,14 @@ class Arrow : BlockType
|
370
|
345
|
|
371
|
346
|
override void draw(SvgSink sink, Pos pos)
|
372
|
347
|
{
|
373
|
|
- auto scale = sink.halfScale;
|
|
348
|
+ auto scale = (toward.x == 0 || toward.y == 0) ? sink.halfScale : sink.angleScale;
|
|
349
|
+ scale *= sink.drawablePortion;
|
374
|
350
|
auto center = sink.center(pos);
|
375
|
351
|
auto xstart = scale * -toward.x + center.x;
|
376
|
352
|
auto ystart = scale * -toward.y + center.y;
|
377
|
353
|
auto xend = scale * toward.x + center.x;
|
378
|
354
|
auto yend = scale * toward.y + center.y;
|
379
|
|
- sink.put(format(`<line x1=%s y1=%s x2=%s y2=%s stroke="black" stroke-width="%s" />`,
|
|
355
|
+ sink.put(format(`<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" stroke-width="%s" />`,
|
380
|
356
|
xstart, ystart, xend, yend, sink.strokeWidth));
|
381
|
357
|
|
382
|
358
|
// Now the head. This requires a bit of math.
|
|
@@ -390,46 +366,58 @@ class Arrow : BlockType
|
390
|
366
|
auto wing1 = wingCore.rotate(PI / 4) + center;
|
391
|
367
|
auto wing2 = wingCore.rotate(PI / -4) + center;
|
392
|
368
|
sink.put(format(`<path d="M %s %s L %s %s L %s %s" stroke="black" stroke-width="%s" />`,
|
393
|
|
- wing1.x, wing1.y, xend, yend, wing2.x, wing2.y));
|
|
369
|
+ wing1.x, wing1.y, xend, yend, wing2.x, wing2.y, sink.strokeWidth));
|
394
|
370
|
}
|
395
|
371
|
}
|
396
|
372
|
|
397
|
373
|
class BlockSource : BlockType
|
398
|
374
|
{
|
|
375
|
+ override void draw(SvgSink sink, Pos pos)
|
|
376
|
+ {
|
|
377
|
+ // A half-sized circle
|
|
378
|
+ auto scale = sink.halfScale;
|
|
379
|
+ auto center = sink.center(pos);
|
|
380
|
+ sink.put(format(`<circle cx="%s" cy="%s" r="%s" stroke="black" stroke-width="%s" fill="none" />`,
|
|
381
|
+ center.x, center.y, scale * 0.5, sink.strokeWidth));
|
|
382
|
+ }
|
|
383
|
+ override const(Pos[]) downstreamOffsets()
|
|
384
|
+ {
|
|
385
|
+ return BlockOStar.offsets;
|
|
386
|
+ }
|
399
|
387
|
}
|
400
|
388
|
|
401
|
389
|
void drawPlus(SvgSink sink, Pos pos)
|
402
|
390
|
{
|
403
|
|
- auto scale = sink.halfScale;
|
|
391
|
+ auto scale = sink.halfScale * sink.drawablePortion;
|
404
|
392
|
auto center = sink.center(pos);
|
405
|
393
|
auto x1 = center.x - scale;
|
406
|
394
|
auto x2 = center.x + scale;
|
407
|
395
|
auto y1 = center.y - scale;
|
408
|
396
|
auto y2 = center.y + scale;
|
409
|
|
- sink.put(format(`<line x1=%s y1=%s x2=%s y2=%s stroke="black" stroke-width="%s" />`,
|
|
397
|
+ sink.put(format(`<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" stroke-width="%s" />`,
|
410
|
398
|
x1, center.y, x2, center.y, sink.strokeWidth));
|
411
|
|
- sink.put(format(`<line x1=%s y1=%s x2=%s y2=%s stroke="black" stroke-width="%s" />`,
|
|
399
|
+ sink.put(format(`<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" stroke-width="%s" />`,
|
412
|
400
|
center.x, y2, center.x, y1, sink.strokeWidth));
|
413
|
401
|
}
|
414
|
402
|
|
415
|
403
|
void drawX(SvgSink sink, Pos pos)
|
416
|
404
|
{
|
417
|
|
- auto scale = sink.halfScale;
|
|
405
|
+ auto scale = sink.angleScale * sink.drawablePortion;
|
418
|
406
|
auto center = sink.center(pos);
|
419
|
407
|
auto x1 = center.x - scale;
|
420
|
408
|
auto x2 = center.x + scale;
|
421
|
409
|
auto y1 = center.y - scale;
|
422
|
410
|
auto y2 = center.y + scale;
|
423
|
|
- sink.put(format(`<line x1=%s y1=%s x2=%s y2=%s stroke="black" stroke-width="%s" />`,
|
|
411
|
+ sink.put(format(`<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" stroke-width="%s" />`,
|
424
|
412
|
x1, y1, x2, y2, sink.strokeWidth));
|
425
|
|
- sink.put(format(`<line x1=%s y1=%s x2=%s y2=%s stroke="black" stroke-width="%s" />`,
|
|
413
|
+ sink.put(format(`<line x1="%s" y1="%s" x2="%s" y2="%s" stroke="black" stroke-width="%s" />`,
|
426
|
414
|
x1, y2, x2, y1, sink.strokeWidth));
|
427
|
415
|
}
|
428
|
416
|
|
429
|
417
|
void drawCircle(SvgSink sink, Pos pos)
|
430
|
418
|
{
|
431
|
|
- auto scale = sink.halfScale;
|
|
419
|
+ auto scale = sink.halfScale * sink.drawablePortion;
|
432
|
420
|
auto center = sink.center(pos);
|
433
|
|
- sink.put(format(`<circle cx=%s cy=%s r=%s stroke="black" stroke-width="%s" />`,
|
|
421
|
+ sink.put(format(`<circle cx="%s" cy="%s" r="%s" stroke="black" stroke-width="%s" fill="none" />`,
|
434
|
422
|
center.x, center.y, scale, sink.strokeWidth));
|
435
|
423
|
}
|