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"
36 #include "graph_region.h"
38 /* Mesh1 has three primary considerations in mind:
39 1) By default, act like the algorithm in the original planarity
40 2) Conform to a population contraint that is easy to work with/modify
41 3) Playability; short edges result in graphs that are easier to solve.
43 Mesh2 is intended to be a freeform populator with two different
44 uses; harder levels that disrupt the easy solution algorithms that
45 mesh1 allows, as well as being able to densely populate arbitrary
56 // check for intersections with other edges
57 static int check_intersects_edge(mesh
*m
, edge
*e
, int intersections
){
58 edge
*ge
= m
->g
->edges
;
64 // edges that aren't in this region don't exist (for
66 if(ge
->A
->active
== m
->active_current
|| ge
->B
->active
== m
->active_current
){
67 // edges that share a vertex don't intersect by definition
68 if(ge
->A
!=e
->A
&& ge
->A
!=e
->B
&& ge
->B
!=e
->A
&& ge
->B
!=e
->B
)
69 if(intersects(ge
->A
->orig_x
,ge
->A
->orig_y
,
70 ge
->B
->orig_x
,ge
->B
->orig_y
,
71 e
->A
->orig_x
,e
->A
->orig_y
,
72 e
->B
->orig_x
,e
->B
->orig_y
,
75 if(count
>intersections
)return 1;
83 static float dot(vertex
*A
, vertex
*B
, vertex
*C
){
84 return (float)(B
->orig_x
-A
->orig_x
)*(float)(C
->orig_x
-B
->orig_x
) +
85 (float)(B
->orig_y
-A
->orig_y
)*(float)(C
->orig_y
-B
->orig_y
);
88 static float cross(vertex
*A
, vertex
*B
, vertex
*C
){
89 return (float)(B
->orig_x
-A
->orig_x
)*(float)(C
->orig_y
-A
->orig_y
) -
90 (float)(B
->orig_y
-A
->orig_y
)*(float)(C
->orig_x
-A
->orig_x
);
93 static float sq_point_distance(vertex
*A
, vertex
*B
){
94 float xd
= A
->orig_x
-B
->orig_x
;
95 float yd
= A
->orig_y
-B
->orig_y
;
99 static float sq_line_distance(edge
*e
, vertex
*v
){
101 if(dot(e
->A
,e
->B
,v
) > 0)
102 return sq_point_distance(e
->B
,v
);
103 if(dot(e
->B
,e
->A
,v
) > 0)
104 return sq_point_distance(e
->A
,v
);
107 float c
= cross(e
->A
,e
->B
,v
);
108 return c
*c
/sq_point_distance(e
->A
,e
->B
);
112 // Does this edge pass within ten pixels of another vertex
113 static int check_intersects_vertex(mesh
*m
, edge
*e
){
114 vertex
*v
= m
->g
->verticies
;
117 if(v
->active
== m
->active_current
)
118 if(v
!=e
->A
&& v
!=e
->B
&& sq_line_distance(e
,v
)<16)return 1;
125 static int select_available(mesh
*m
,vertex
*current
,float length_limit
,int intersections
){
127 vertex
*v
= m
->g
->verticies
;
129 // mark all possible choices
132 if(v
!=current
&& current
->active
== m
->active_current
){
133 if(length_limit
==0 || sq_point_distance(v
,current
)<=length_limit
){
134 if(!exists_edge(v
,current
)){
138 if(!region_intersects(&e
)){
139 if(!check_intersects_edge(m
,&e
,intersections
)){
140 if(!check_intersects_vertex(m
,&e
)){
155 // Although very inefficient, it is simple and correct. Even
156 // impossibly large boards generate in a fraction of a second on old
157 // boxen. There's likely no need to bother optimizing this step of
158 // board creation. */
160 typedef struct insort
{
165 static int insort_c(const void *a
, const void *b
){
166 insort
*A
=(insort
*)a
;
167 insort
*B
=(insort
*)b
;
168 return(A
->metric
-B
->metric
);
171 static vertex
*vertex_num_sel(graph
*g
,int num
){
172 vertex
*v
=g
->verticies
;
186 static void prepopulate(mesh
*m
,int length_limit
){
187 // sort all verticies in ascending order by their number of potential edges
190 insort index
[m
->g
->vertex_num
];
191 vertex
*v
=m
->g
->verticies
;
194 if(v
->active
== m
->active_current
){
196 index
[num
].metric
= select_available(m
,v
,0,0);
201 qsort(index
,num
,sizeof(*index
),insort_c
);
203 // populate in ascending order
209 // does this vertex already have edges?
211 edge_list
*el
=v
->edges
;
218 if(edges
>=2)continue;
220 // it's possible some intersections will be necessary, but go for
222 while(edges
<2 && intersections
<10){
223 int count
= select_available(m
,v
,length_limit
,intersections
);
227 vertex
*w
=m
->g
->verticies
;
232 // choose two at random
233 int a
=random_number()%count
;
238 b
=random_number()%count
;
242 short0
=vertex_num_sel(m
->g
,a
);
243 short1
=vertex_num_sel(m
->g
,b
);
246 // used with region-constrined meshes
247 // of the possible edges, choose the shortest two
249 if(w
!=v
&& w
->selected
){
250 int xd
=w
->orig_x
-v
->orig_x
;
251 int yd
=w
->orig_y
-v
->orig_y
;
256 }else if(!short1
|| d
<d1
){
273 add_edge(m
->g
,v
,short0
);
275 m
->g
->objective
+=intersections
;
276 if(intersections
)m
->g
->objective_lessthan
=1;
278 if(edges
<2 && short1
){
279 add_edge(m
->g
,v
,short1
);
281 m
->g
->objective
+=intersections
;
282 if(intersections
)m
->g
->objective_lessthan
=1;
290 // the spanning walk is to make an attempt at a single, connected graph.
291 static void span_depth_first2(mesh
*m
,vertex
*current
, float length_limit
){
293 current
->grabbed
=1; // overloaded; "we walked this already"
295 // prefer walking along edges that already exist
297 edge_list
*el
=current
->edges
;
308 span_depth_first2(m
, v
, length_limit
);
315 // now walk any possible edges that have not been walked
317 int count
=select_available(m
,current
,length_limit
,0);
320 vertex
*v
= m
->g
->verticies
;
322 // filter out already-walked edges
324 if(v
->grabbed
&& v
->selected
){ // grabbed is also overloaded to mean walked
331 if(count
== 0) return;
333 choice
= random_number()%count
;
337 if(count2
++ == choice
){
338 add_edge(m
->g
,v
,current
);
339 span_depth_first2(m
,v
, length_limit
);
346 if(count
== 1) return; // because we just took care of it
351 static void random_populate(mesh
*m
,vertex
*current
,int dense_128
, float length_limit
){
352 if(current
->active
== m
->active_current
){
353 int count
=select_available(m
,current
,length_limit
,0);
355 vertex
*v
= m
->g
->verticies
;
357 if(v
->selected
&& random_yes(dense_128
)){
358 add_edge(m
->g
,v
,current
);
367 /* Initial generation setup */
369 static void mesh_setup(graph
*g
, mesh
*m
, int order
, int divis
){
388 n
=m
->width
*m
->height
;
390 // is this divisible by our requested divisor if any?
391 if(divis
>0 && n
%divis
){
395 if(!((n
+wiggle
)%divis
)) break;
397 if(n
-wiggle
>6 && !((n
-wiggle
)%divis
)){
403 // refactor the rectangular mesh's dimensions.
405 int h
= (int)sqrt(n
+wiggle
),w
;
407 while( (n
+wiggle
)%h
)h
--;
410 // double it and be content with a working result
423 new_board(g
, m
->width
* m
->height
);
424 region_init(); // clear it
426 // used for intersection calcs
429 vertex
*v
= g
->verticies
;
430 for(y
=0;y
<m
->height
;y
++)
431 for(x
=0;x
<m
->width
;x
++){
432 v
->orig_x
=x
*50; // not a random number
433 v
->orig_y
=y
*50; // not a random number
439 g
->objective_lessthan
= 0;
443 static void generate_mesh2(mesh
*m
, int density_128
, float length_limit
){
448 length_limit
*=length_limit
;
450 for(i
=0;i
<=m
->active_max
;i
++){
456 /* connect the graph into as few discrete sections as possible */
464 // make sure we walk all verticies
466 if(v
->active
== m
->active_current
&& !v
->grabbed
)
467 span_depth_first2(m
, m
->g
->verticies
, length_limit
);
472 prepopulate(m
,length_limit
);
474 /* now iterate the whole mesh adding random edges */
477 random_populate(m
, v
, density_128
, length_limit
);
483 void generate_freeform(graph
*g
, int order
){
485 random_seed(order
+1);
486 mesh_setup(g
, &m
, order
, 0);
488 generate_mesh2(&m
,48,4);
490 randomize_verticies(g
);
492 arrange_verticies_polycircle(g
,4,0,order
*.03,0,0,0);
494 arrange_verticies_polycircle(g
,4,0,.3,0,0,0);
498 void generate_shape(graph
*g
, int order
){
503 random_seed(order
+1);
510 case 2: // dashed circle
544 mesh_setup(g
, &m
, (order
>min
?order
:min
), mod
);
545 randomize_verticies(g
);
549 arrange_region_star(g
); break; //4
551 arrange_region_rainbow(g
); break; //9
552 case 2: // dashed circle
553 arrange_region_dashed_circle(g
); break; //0
555 arrange_region_bifur(g
); break; //0
556 case 4: // dairyqueen
557 arrange_region_dairyqueen(g
); break; //0
559 arrange_region_cloud(g
); break; //0
561 arrange_region_storm(g
); break; //11
563 arrange_region_plus(g
); break; //2
565 arrange_region_hole3(g
); break; //4
567 arrange_region_hole4(g
); break; //15
569 arrange_region_ring(g
); break; //29
571 arrange_region_ovals(g
); break; //95
573 arrange_region_target(g
); break; //108
576 m
.active_max
=region_layout(g
);
577 generate_mesh2(&m
,dens
,0);