4 * The geeky little puzzle game with a big noodly crunch!
6 * gPlanarity copyright (C) 2005 Monty <monty@xiph.org>
7 * Original Flash game by John Tantalo <john.tantalo@case.edu>
8 * Original game concept by Mary Radcliffe
10 * gPlanarity is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
15 * gPlanarity is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with Postfish; see the file COPYING. If not, write to the
22 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
33 #include "gameboard.h"
34 #include "graph_generate.h"
35 #include "graph_arrange.h"
39 edge_list
*embed_list
;
55 /* The 'embed_list' is a set of edges that don't obey or neighboring
56 intersection calculation mode and are thus tracked
57 seperately/explicitly. They're added to the main graph after the
58 rest of the graph is generated. */
60 /* add edge to the embed_list */
61 static void embedlist_add_edge(mesh
*m
, vertex
*A
, vertex
*B
){
62 edge
*e
= new_edge(A
,B
);
63 m
->embed_list
= add_edge_to_list(m
->embed_list
,e
);
66 /* move embed_list edges into the real graph */
67 static void embedlist_add_to_mesh(graph
*g
, mesh
*m
){
68 edge_list
*el
= m
->embed_list
;
70 /* move the edges out of the embed_list and add them to the main graph */
79 release_edge_list(m
->embed_list
);
80 m
->embed_list
=0; /* be pedantic */
84 static int embedlist_intersects(mesh
*m
, vertex
*A
, vertex
*B
){
85 edge_list
*el
= m
->embed_list
;
86 double dummy_x
,dummy_y
;
91 if(intersectsV(A
,B
,e
->A
,e
->B
,&dummy_x
,&dummy_y
))
100 static void embedlist_filter_intersections(neighbors_grid
*ng
){
102 vertex
*A
= ng
->center
;
105 if(ng
->vnum
[i
] != -1){
106 vertex
*B
= ng
->m
->v
[ng
->vnum
[i
]];
108 if(embedlist_intersects(ng
->m
,A
,B
))
114 static int embedlist_contains_vertex(mesh
*m
,vertex
*v
){
115 edge_list
*el
= m
->embed_list
;
120 if(e
->A
== v
) return 1;
121 if(e
->B
== v
) return 1;
128 static int embedlist_vertex_poisoned(mesh
*m
, vertex
*v
){
132 static void poison_vertex(mesh
*m
, vertex
*v
){
136 /* neighboring intersection model */
138 static void populate_neighbors(int vnum
, mesh
*m
,
140 int width
= m
->width
;
142 int x
= vnum
- (y
*width
);
145 for(i
=0;i
<8;i
++)ng
->vnum
[i
]=-1;
148 ng
->center
= m
->v
[vnum
];
152 if(x
-1 >= 0) ng
->vnum
[0]= (y
-1)*width
+(x
-1);
153 ng
->vnum
[1]= (y
-1)*width
+x
;
154 if(x
+1 < m
->width
) ng
->vnum
[2]= (y
-1)*width
+(x
+1);
157 if(x
-1 >= 0) ng
->vnum
[3]= y
*width
+(x
-1);
158 if(x
+1 < m
->width
) ng
->vnum
[4]= y
*width
+(x
+1);
161 if(x
-1 >= 0) ng
->vnum
[5]= (y
+1)*width
+(x
-1);
162 ng
->vnum
[6]= (y
+1)*width
+x
;
163 if(x
+1 < m
->width
) ng
->vnum
[7]= (y
+1)*width
+(x
+1);
168 // eliminate from neighbor structs the verticies that already have at
170 static void filter_spanned_neighbors(neighbors_grid
*ng
,
175 if(ng
->vnum
[i
]==-1 || ng
->m
->v
[ng
->vnum
[i
]]->edges
){
178 nl
->vnum
[count
++]=ng
->vnum
[i
];
184 // eliminate from neighbor struct any verticies to which we can't make
185 // an edge without crossing another edge. Only 0,2,5,7 possible.
186 static void filter_intersections(neighbors_grid
*ng
){
191 if(ng
->vnum
[1] != -1 &&
193 exists_edge(ng
->m
->v
[ng
->vnum
[1]],
194 ng
->m
->v
[ng
->vnum
[3]]))
199 if(ng
->vnum
[1] != -1 &&
201 exists_edge(ng
->m
->v
[ng
->vnum
[1]],
202 ng
->m
->v
[ng
->vnum
[4]]))
207 if(ng
->vnum
[3] != -1 &&
209 exists_edge(ng
->m
->v
[ng
->vnum
[3]],
210 ng
->m
->v
[ng
->vnum
[6]]))
215 if(ng
->vnum
[4] != -1 &&
217 exists_edge(ng
->m
->v
[ng
->vnum
[4]],
218 ng
->m
->v
[ng
->vnum
[6]]))
223 embedlist_filter_intersections(ng
);
226 /* eliminate verticies we've already connected to */
227 static void filter_edges(neighbors_grid
*ng
,
230 vertex
*v
=ng
->center
;
234 if(!exists_edge(v
,ng
->m
->v
[ng
->vnum
[i
]]))
235 nl
->vnum
[count
++]=ng
->vnum
[i
];
243 static void random_populate(graph
*g
, int current
, mesh
*m
, int min_connect
, int prob_128
){
247 populate_neighbors(current
, m
, &ng
);
248 filter_intersections(&ng
);
249 filter_edges(&ng
,&nl
);
252 edge_list
*el
=m
->v
[current
]->edges
;
259 while(num_edges
<min_connect
&& nl
.num
){
260 int choice
= random_number() % nl
.num
;
261 add_edge(g
,m
->v
[current
], m
->v
[nl
.vnum
[choice
]]);
263 filter_intersections(&ng
);
264 filter_edges(&ng
,&nl
);
267 for(i
=0;i
<nl
.num
;i
++)
268 if(random_yes(prob_128
)){
270 add_edge(g
,m
->v
[current
], m
->v
[nl
.vnum
[i
]]);
274 static void span_depth_first(graph
*g
,int current
, mesh
*m
){
279 populate_neighbors(current
, m
, &ng
);
280 // don't reverse the order of the next two
281 filter_intersections(&ng
);
282 filter_spanned_neighbors(&ng
,&nl
);
283 if(nl
.num
== 0) break;
286 int choice
= random_number() % nl
.num
;
287 add_edge(g
,m
->v
[current
], m
->v
[nl
.vnum
[choice
]]);
289 span_depth_first(g
,nl
.vnum
[choice
], m
);
294 /* nastiness adds long edges along the outer perimeter to make it
295 harder to rely on verticies always being near each other; mesh 2
296 takes this further, but we can add some of the same flavor to
299 static void nasty_horizontal(graph
*g
, mesh
*m
, int A
, int B
, int limit
){
300 if(limit
== 0) return;
301 if(A
+2 > B
)return; /* adjacent is too close */
303 add_edge(g
,m
->v
[A
],m
->v
[B
]);
307 nasty_horizontal(g
,m
,A
,B
,limit
-1);
310 static void nasty_vertical(graph
*g
, mesh
*m
, int A
, int B
, int limit
){
311 if(limit
== 0) return;
312 if(A
+(m
->width
*2) > B
)return; /* adjacent is too close */
314 add_edge(g
,m
->v
[A
],m
->v
[B
]);
318 nasty_vertical(g
,m
,A
,B
,limit
-1);
321 /* Don't use this along with k5 embedding; the assumptions the
322 nastiness algorithm makes about solvable conditions won't always
323 coexist with the assumptions the k5 embedding makes about solvable
325 static void mesh_nastiness(graph
*g
, mesh
*m
, int limit
){
327 nasty_horizontal(g
,m
,0,m
->width
-1, limit
);
328 nasty_horizontal(g
,m
,(m
->height
-1)*m
->width
,m
->width
*m
->height
-1, limit
);
330 nasty_vertical(g
,m
,0,(m
->height
-1)*m
->width
,limit
);
331 nasty_vertical(g
,m
,m
->width
-1,m
->width
*m
->height
-1, limit
);
334 /* Embed one k5 in the solved graph */
335 /* Don't use this along with 'nastiness'; the assumptions the
336 nastiness algorithm makes about solvable conditions won't always
337 coexist with the assumptions that non-planar embedding makes about
338 solvable conditions. */
340 static void mesh_embed_k5(graph
*g
, mesh
*m
,int x
, int y
){
342 /* Add the k5s up front in their own special edge list; This list
343 will also be checked explicitly by the various neighboring
344 algorithms as the k5's edges don't all conceptually work within
345 the implicit neighboring algorithm we're using. Also, by using a
346 special edge list and not adding the k5 edges to the vertex edge
347 lists up front, we can still use the unmodified initial spanning
352 vertex
*A
= m
->v
[y
*w
+x
+1];
353 vertex
*B
= m
->v
[(y
+1)*w
+x
+1];
354 vertex
*C
= m
->v
[(y
+1)*w
+x
+2];
355 vertex
*D
= m
->v
[(y
+1)*w
+x
+3];
356 vertex
*E
= m
->v
[(y
+2)*w
+x
];
358 // poisoned verticies are already inside another kernel (the regular
359 // mesh is deflectable and thus not really regular)
360 if(embedlist_vertex_poisoned(m
,A
))return;
361 if(embedlist_vertex_poisoned(m
,B
))return;
362 if(embedlist_vertex_poisoned(m
,C
))return;
363 if(embedlist_vertex_poisoned(m
,D
))return;
364 if(embedlist_vertex_poisoned(m
,E
))return;
366 // the way k5 works we don't need to poison the internal verticies
368 embedlist_add_edge(m
, A
,B
);
369 embedlist_add_edge(m
, A
,C
);
370 embedlist_add_edge(m
, A
,D
);
371 embedlist_add_edge(m
, A
,E
);
372 embedlist_add_edge(m
, B
,C
);
373 embedlist_add_edge(m
, B
,D
);
374 embedlist_add_edge(m
, B
,E
);
375 embedlist_add_edge(m
, C
,D
);
376 embedlist_add_edge(m
, C
,E
);
377 embedlist_add_edge(m
, D
,E
);
381 /* Embed one k3,3 in the solved graph */
382 /* Don't use this along with 'nastiness'; the assumptions the
383 nastiness algorithm makes about solvable conditions won't always
384 coexist with the assumptions that k5 embedding makes about solvable
386 static void mesh_embed_k33(graph
*g
, mesh
*m
, int x
, int y
){
388 /* same disclaimers as k5 */
389 /* the k3,3 embedding works with the standard walk algorithm only
390 because an edge with an endpoint exactly on another edge is
391 considered an intersection. */
392 /* the way it is added, the walk/population can add additional edges
393 inside the embedded kernel; this is fine, the population will be
394 certain not to introduce intersections. */
398 vertex
*A
= m
->v
[y
*w
+x
];
399 vertex
*B
= m
->v
[y
*w
+x
+1];
400 vertex
*C
= m
->v
[y
*w
+x
+2];
401 vertex
*D
= m
->v
[(y
+1)*w
+x
];
402 vertex
*E
= m
->v
[(y
+1)*w
+x
+1];
403 vertex
*F
= m
->v
[(y
+1)*w
+x
+2];
405 // poisoned verticies are already inside another kernel (the regular
406 // mesh is deflectable and thus not really regular)
407 if(embedlist_vertex_poisoned(m
,A
))return;
408 if(embedlist_vertex_poisoned(m
,B
))return;
409 if(embedlist_vertex_poisoned(m
,C
))return;
410 if(embedlist_vertex_poisoned(m
,D
))return;
411 if(embedlist_vertex_poisoned(m
,E
))return;
412 if(embedlist_vertex_poisoned(m
,F
))return;
414 // check that verticies we want to poison ourselves are not already in use
415 if(embedlist_contains_vertex(m
,B
))return;
416 if(embedlist_contains_vertex(m
,E
))return;
417 /* B and E are internal according to x/y, but according to the
418 position in the mesh, they're on the outside. Poison them so
419 that they're explicitly marked inside. */
423 /* need to mode two of the intersections to avoid unwanted
424 intersections (not spurious; they are in fact intersections until
430 embedlist_add_edge(m
, A
,C
);
431 embedlist_add_edge(m
, A
,D
);
432 embedlist_add_edge(m
, A
,E
);
433 embedlist_add_edge(m
, B
,C
);
434 embedlist_add_edge(m
, B
,D
);
435 embedlist_add_edge(m
, B
,E
);
436 embedlist_add_edge(m
, C
,F
);
437 embedlist_add_edge(m
, D
,F
);
438 embedlist_add_edge(m
, E
,F
);
443 /* Embed one non-miminal k3,3 in the solved graph */
444 /* Don't use this along with 'nastiness'; the assumptions the
445 nastiness algorithm makes about solvable conditions won't always
446 coexist with the assumptions that k5 embedding makes about solvable
448 static void mesh_embed_bigk33(graph
*g
, mesh
*m
, int x
, int y
){
454 vertex
*A
= m
->v
[(y
+2)*w
+x
];
455 vertex
*B
= m
->v
[(y
+1)*w
+x
+2];
456 vertex
*C
= m
->v
[y
*w
+x
+4];
457 vertex
*D
= m
->v
[(y
+4)*w
+x
+1];
458 vertex
*E
= m
->v
[(y
+3)*w
+x
+3];
459 vertex
*F
= m
->v
[(y
+2)*w
+x
+5];
461 // poisoned verticies are already inside another kernel (the regular
462 // mesh is deflectable and thus not really regular)
463 if(embedlist_vertex_poisoned(m
,A
))return;
464 if(embedlist_vertex_poisoned(m
,B
))return;
465 if(embedlist_vertex_poisoned(m
,C
))return;
466 if(embedlist_vertex_poisoned(m
,D
))return;
467 if(embedlist_vertex_poisoned(m
,E
))return;
468 if(embedlist_vertex_poisoned(m
,F
))return;
470 // check that verticies we want to poison ourselves are not already in use
471 if(embedlist_contains_vertex(m
,B
))return;
472 if(embedlist_contains_vertex(m
,E
))return;
473 /* B and E are internal according to x/y, but according to the
474 position in the mesh, they're on the outside. Poison them so
475 that they're explicitly marked inside. */
479 /* need to move two of the intersections to avoid unwanted
480 intersections (not spurious; they are in fact intersections until
486 embedlist_add_edge(m
, A
,C
);
487 embedlist_add_edge(m
, A
,D
);
488 embedlist_add_edge(m
, A
,E
);
489 embedlist_add_edge(m
, B
,C
);
490 embedlist_add_edge(m
, B
,D
);
491 embedlist_add_edge(m
, B
,E
);
492 embedlist_add_edge(m
, C
,F
);
493 embedlist_add_edge(m
, D
,F
);
494 embedlist_add_edge(m
, E
,F
);
499 static void mesh_embed_recurse(graph
*g
,mesh
*m
,int x
, int y
, int w
, int h
, int k5
, int k33
, int bigk33
){
502 // not minimal spacing; the k33 needs vertical offset, but the others are larger just to space them out.
503 if( bigk33
&& w
>=6 && h
>=5 ){
506 xd
= random_number() % (w
-wd
);
507 yd
= random_number() % (h
-hd
);
508 mesh_embed_bigk33(g
,m
,x
+xd
,y
+yd
);
509 }else if(k5
&& w
>=4 && h
>=3){
512 xd
= random_number() % (w
-wd
);
513 yd
= random_number() % (h
-hd
);
514 mesh_embed_k5(g
,m
,x
+xd
,y
+yd
);
515 }else if(k33
&& w
>=3 && h
>=2 ){
518 xd
= random_number() % (w
-wd
);
519 yd
= random_number() % (h
-hd
);
520 mesh_embed_k33(g
,m
,x
+xd
,y
+yd
);
524 mesh_embed_recurse(g
,m
, x
, y
, w
, yd
+1, k5
,k33
,bigk33
);
525 mesh_embed_recurse(g
,m
, x
, y
+yd
+hd
, w
, h
-yd
-hd
, k5
,k33
,bigk33
);
527 mesh_embed_recurse(g
,m
, x
, y
+yd
, xd
+1, hd
+1, k5
,k33
,bigk33
);
528 mesh_embed_recurse(g
,m
, x
+xd
+wd
,y
+yd
, w
-xd
-wd
, hd
+1, k5
,k33
,bigk33
);
531 /* Embed k5s and k3,3s in the solved graph in such a way that we know each added
532 non-planar kernel adds exactly one and only one certain intersection. */
533 /* Don't use this along with 'nastiness'; the assumptions the
534 nastiness algorithm makes about solvable conditions won't always
535 coexist with the assumptions that non-planar embedding makes about
536 solvable conditions. */
539 static void mesh_embed_nonplanar(graph
*g
, mesh
*m
,int k5
, int k33
, int bigk33
){
540 // selection is used as a poison flag during embedding
541 deselect_verticies(g
);
542 mesh_embed_recurse(g
, m
, 0,0,m
->width
,m
->height
, k5
,k33
,bigk33
);
543 deselect_verticies(g
);
546 /* Rogues are added lines inserted between verticies on neighboring
547 rows/columns; the idea is to choose the longest ones that cross the
548 smallest number of lines. */
549 /* Right now, the rogue insertion doesn't take embedded kernel
550 poisoning or niceness constraints into account, so don't mix
553 static int count_intersections(graph
*g
, vertex
*A
, vertex
*B
){
555 double dummy_x
,dummy_y
;
559 if(intersectsV(A
,B
,e
->A
,e
->B
,&dummy_x
,&dummy_y
))
566 static void scan_rogue(graph
*g
, mesh
*m
, int aoff
,int boff
, int step
, int end
,
567 float *metric
, edge
*best
, int *cross
){
570 for(a
=0;a
+1<end
;a
++){
571 for(b
=a
+1;b
<end
;b
++){
572 vertex
*va
= m
->v
[a
*step
+aoff
];
573 vertex
*vb
= m
->v
[b
*step
+boff
];
575 if(!va
->selected
&& !vb
->selected
){
576 if(!exists_edge(va
,vb
)){
577 int count
= count_intersections(g
,va
,vb
);
579 float test
= (b
-a
)/count
;
593 /* scan the entire mesh looking for the candidate edge with the highest rogue objective value */
594 static void mesh_add_rogues(graph
*g
, mesh
*m
){
598 deselect_verticies(g
);
608 scan_rogue(g
, m
,(i
+1)*w
,i
*w
,1,w
, &metric
,&best
,&cross
);
609 scan_rogue(g
, m
,i
*w
,(i
+1)*w
,1,w
, &metric
,&best
,&cross
);
613 scan_rogue(g
, m
, i
, i
+1, w
,h
, &metric
,&best
,&cross
);
614 scan_rogue(g
, m
, i
+1, i
, w
,h
, &metric
,&best
,&cross
);
617 if(best
.A
&& best
.B
){
618 add_edge(g
,best
.A
,best
.B
);
619 // poison verticies against later selection
623 g
->objective_lessthan
= 1;
628 deselect_verticies(g
);
631 /* Initial generation setup */
633 static void mesh_setup(graph
*g
, mesh
*m
, int order
, int divis
){
650 n
=m
->width
*m
->height
;
652 // is this divisible by our requested divisor if any?
653 if(divis
>0 && n
%divis
){
657 if(!((n
+wiggle
)%divis
)) break;
659 if(n
-wiggle
>6 && !((n
-wiggle
)%divis
)){
665 // refactor the rectangular mesh's dimensions.
667 int h
= (int)sqrt(n
+wiggle
),w
;
669 while( (n
+wiggle
)%h
)h
--;
672 // double it and be content with a working result
685 new_board(g
, m
->width
* m
->height
);
688 // used for rogue calcs
691 vertex
*v
= g
->verticies
;
692 for(y
=0;y
<m
->height
;y
++)
693 for(x
=0;x
<m
->width
;x
++){
694 v
->x
=x
*50; // not a random number; other things depend on this
695 v
->y
=y
*50; // not a random number; other things depend on this
701 g
->objective_lessthan
= 0;
705 static void mesh_flatten(graph
*g
,mesh
*m
){
706 /* a flat vector is easier to address while building the mesh */
708 vertex
*v
=g
->verticies
;
709 for(i
=0;i
<m
->width
*m
->height
;i
++){
715 static void generate_mesh(graph
*g
, mesh
*m
,
718 /* first walk a random spanning tree */
719 span_depth_first(g
, 0, m
);
721 /* now iterate the whole mesh adding random edges */
724 for(i
=0;i
<m
->width
*m
->height
;i
++)
725 random_populate(g
, i
, m
, 2, density_128
);
729 void generate_simple(graph
*g
, int order
){
732 mesh_setup(g
,&m
, order
, 0);
733 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
736 generate_mesh(g
,&m
,40);
737 randomize_verticies(g
);
739 if((m
.width
*m
.height
)&1)
740 arrange_verticies_circle(g
,0,0);
742 arrange_verticies_circle(g
,M_PI
/2,M_PI
/2);
745 void generate_sparse(graph
*g
, int order
){
748 mesh_setup(g
,&m
, order
, 3);
749 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
752 generate_mesh(g
,&m
,2);
753 mesh_nastiness(g
,&m
,-1);
754 randomize_verticies(g
);
756 if((m
.width
*m
.height
)&1)
757 arrange_verticies_circle(g
,0,0);
759 arrange_verticies_circle(g
,M_PI
/2,M_PI
/2);
762 void generate_dense(graph
*g
, int order
){
765 mesh_setup(g
,&m
, order
, 3);
766 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
769 generate_mesh(g
,&m
,96);
770 mesh_nastiness(g
,&m
,-1);
771 randomize_verticies(g
);
773 if((m
.width
*m
.height
)&1)
774 arrange_verticies_circle(g
,0,0);
776 arrange_verticies_circle(g
,M_PI
/2,M_PI
/2);
779 void generate_nasty(graph
*g
, int order
){
781 random_seed(order
+8236);
782 mesh_setup(g
,&m
, order
,4);
783 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
786 generate_mesh(g
,&m
,32);
787 mesh_nastiness(g
,&m
,-1);
788 randomize_verticies(g
);
789 arrange_verticies_polycircle(g
,3,0,.3,25,0,25);
792 void generate_rogue(graph
*g
, int order
){
794 random_seed(order
+3005);
795 mesh_setup(g
,&m
, order
,5);
796 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
799 generate_mesh(g
,&m
,24);
800 mesh_add_rogues(g
,&m
);
801 randomize_verticies(g
);
804 arrange_verticies_polycircle(g
,5,0,order
*.03,0,0,0);
806 arrange_verticies_polycircle(g
,5,0,.3,0,0,0);
809 void generate_embed(graph
*g
, int order
){
811 random_seed(order
+347);
812 mesh_setup(g
,&m
, order
, 6);
813 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
816 mesh_embed_nonplanar(g
,&m
,1,1,1);
817 generate_mesh(g
,&m
,48);
818 embedlist_add_to_mesh(g
,&m
);
820 randomize_verticies(g
);
823 arrange_verticies_polycircle(g
,6,0,order
*.03,0,0,0);
825 arrange_verticies_polycircle(g
,6,0,.3,0,0,0);
829 void generate_crest(graph
*g
, int order
){
833 mesh_setup(g
,&m
, order
,0);
834 m
.v
=alloca(m
.width
*m
.height
* sizeof(*m
.v
));
837 generate_mesh(g
,&m
,128);
839 arrange_verticies_circle(g
,M_PI
/n
,M_PI
/n
);