summaryrefslogtreecommitdiff
path: root/apps/plugins/pdbox/PDa/src/g_graph.c
blob: 39a29932645443e7851c8f7aa64029d8281bd614 (plain)
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
/* Copyright (c) 1997-2001 Miller Puckette and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* This file deals with the behavior of glists as either "text objects" or
"graphs" inside another glist.  LATER move the inlet/outlet code of g_canvas.c 
to this file... */

#ifdef ROCKBOX
#include "plugin.h"
#include "../../pdbox.h"
#include "m_pd.h"
#include "g_canvas.h"
#define snprintf rb->snprintf
#define atof rb_atof
#else /* ROCKBOX */
#include <stdlib.h>
#include "m_pd.h"
#include "t_tk.h"
#include "g_canvas.h"
#include <stdio.h>
#include <string.h>
#endif /* ROCKBOX */

/* ---------------------- forward definitions ----------------- */

static void graph_vis(t_gobj *gr, t_glist *unused_glist, int vis);
static void graph_graphrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2);
static void graph_getrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2);

/* -------------------- maintaining the list -------------------- */

void glist_add(t_glist *x, t_gobj *y)
{
    t_object *ob;
    y->g_next = 0;
    if (!x->gl_list) x->gl_list = y;
    else
    {
    	t_gobj *y2;
    	for (y2 = x->gl_list; y2->g_next; y2 = y2->g_next);
    	y2->g_next = y;
    }
    if (x->gl_editor && (ob = pd_checkobject(&y->g_pd)))
    	rtext_new(x, ob);
    if (glist_isvisible(x))
    	gobj_vis(y, x, 1);
    if (class_isdrawcommand(y->g_pd)) 
    	canvas_redrawallfortemplate(glist_getcanvas(x));
}

    /* this is to protect against a hairy problem in which deleting
    a sub-canvas might delete an inlet on a box, after the box had
    been invisible-ized, so that we have to protect against redrawing it! */
int canvas_setdeleting(t_canvas *x, int flag)
{
    int ret = x->gl_isdeleting;
    x->gl_isdeleting = flag;
    return (ret);
}

    /* delete an object from a glist and free it */
void glist_delete(t_glist *x, t_gobj *y)
{
    t_gobj *g;
    t_object *ob;
    t_gotfn chkdsp = zgetfn(&y->g_pd, gensym("dsp"));
    t_canvas *canvas = glist_getcanvas(x);
    int drawcommand = class_isdrawcommand(y->g_pd);
    int wasdeleting;
    
    wasdeleting = canvas_setdeleting(canvas, 1);
    if (x->gl_editor)
    {
    	if (x->gl_editor->e_grab == y) x->gl_editor->e_grab = 0;
    	if (glist_isselected(x, y)) glist_deselect(x, y);

    	    /* HACK -- we had phantom outlets not getting erased on the
	    screen because the canvas_setdeleting() mechanism is too
	    crude.  LATER carefully set up rules for when the rtexts
	    should exist, so that they stay around until all the
	    steps of becoming invisible are done.  In the meantime, just
	    zap the inlets and outlets here... */
	if (pd_class(&y->g_pd) == canvas_class)
	{
	    t_glist *gl = (t_glist *)y;
	    if (gl->gl_isgraph)
	    {
	    	char tag[80];
#ifdef ROCKBOX
                snprintf(tag, sizeof(tag), "graph%lx",
                                           (unsigned long) (intptr_t) gl);
#else /* ROCKBOX */
		sprintf(tag, "graph%x", (int)gl);
#endif /* ROCKBOX */
	    	glist_eraseiofor(x, &gl->gl_obj, tag);
	    }
	    else
	    {
	    	text_eraseborder(&gl->gl_obj, x,
		    rtext_gettag(glist_findrtext(x, &gl->gl_obj)));
	    }
	}
    }
    gobj_delete(y, x);
    if (glist_isvisible(canvas))
    	gobj_vis(y, x, 0);
    if (x->gl_editor && (ob = pd_checkobject(&y->g_pd)))
    	rtext_new(x, ob);
    if (x->gl_list == y) x->gl_list = y->g_next;
    else for (g = x->gl_list; g; g = g->g_next)
    	if (g->g_next == y)
    {
    	g->g_next = y->g_next;
    	break;
    }
    pd_free(&y->g_pd);
    if (chkdsp) canvas_update_dsp();
    if (drawcommand) canvas_redrawallfortemplate(canvas);
    canvas_setdeleting(canvas, wasdeleting);
    x->gl_valid = ++glist_valid;
}

    /* remove every object from a glist.  Experimental. */
void glist_clear(t_glist *x)
{
#ifdef ROCKBOX
    t_gobj *y;
#else
    t_gobj *y, *y2;
#endif
    int dspstate = canvas_suspend_dsp();
    while((y = x->gl_list))
    	glist_delete(x, y);
    canvas_resume_dsp(dspstate);
}

void glist_retext(t_glist *glist, t_text *y)
{
#ifdef ROCKBOX
    glist_getcanvas(glist);
#else
    t_canvas *c = glist_getcanvas(glist);
#endif
    	/* check that we have built rtexts yet.  LATER need a better test. */
    if (glist->gl_editor && glist->gl_editor->e_rtext)
    {
    	t_rtext *rt = glist_findrtext(glist, y);
    	if (rt)
    	    rtext_retext(rt);
    }
}

void glist_grab(t_glist *x, t_gobj *y, t_glistmotionfn motionfn,
    t_glistkeyfn keyfn, int xpos, int ypos)
{
    t_glist *x2 = glist_getcanvas(x);
    if (motionfn)
    	x2->gl_editor->e_onmotion = MA_PASSOUT;
    else x2->gl_editor->e_onmotion = 0;
    x2->gl_editor->e_grab = y;
    x2->gl_editor->e_motionfn = motionfn;
    x2->gl_editor->e_keyfn = keyfn;
    x2->gl_editor->e_xwas = xpos;
    x2->gl_editor->e_ywas = ypos;
}

t_canvas *glist_getcanvas(t_glist *x)
{
    while (x->gl_owner && !x->gl_havewindow && x->gl_isgraph)
    	x = x->gl_owner;
    return((t_canvas *)x);
}

static float gobj_getxforsort(t_gobj *g)
{
    if (pd_class(&g->g_pd) == scalar_class)
    {
    	float x1, y1;
    	scalar_getbasexy((t_scalar *)g, &x1, &y1);
	return(x1);
    }
    else return (0);
}

static t_gobj *glist_merge(t_glist *x, t_gobj *g1, t_gobj *g2)
{
    t_gobj *g = 0, *g9 = 0;
    float f1 = 0, f2 = 0;
#ifdef ROCKBOX
    (void) x;
#endif
    if (g1)
    	f1 = gobj_getxforsort(g1);
    if (g2)
    	f2 = gobj_getxforsort(g2);
    while (1)
    {
    	if (g1)
	{
	    if (g2)
	    {
	    	if (f1 <= f2)
		    goto put1;
		else goto put2;
	    }
	    else goto put1;	
	}
	else if (g2)
	    goto put2;
	else break;
    put1:
    	if (g9)
	    g9->g_next = g1, g9 = g1;
	else g9 = g = g1;
	if((g1 = g1->g_next))
	    f1 = gobj_getxforsort(g1);
    	g9->g_next = 0;
	continue;
    put2:
    	if (g9)
	    g9->g_next = g2, g9 = g2;
	else g9 = g = g2;
	if((g2 = g2->g_next))
	    f2 = gobj_getxforsort(g2);
    	g9->g_next = 0;
	continue;
    }
    return (g);
}

static t_gobj *glist_dosort(t_glist *x,
    t_gobj *g, int nitems)
{
    if (nitems < 2)
    	return (g);
    else
    {
    	int n1 = nitems/2, n2 = nitems - n1, i;
	t_gobj *g2, *g3;
	for (g2 = g, i = n1-1; i--; g2 = g2->g_next)
	    ;
	g3 = g2->g_next;
	g2->g_next = 0;
	g = glist_dosort(x, g, n1);
	g3 = glist_dosort(x, g3, n2);
	return (glist_merge(x, g, g3));
    }
}

void glist_sort(t_glist *x)
{
    int nitems = 0, foo = 0;
    float lastx = -1e37;
    t_gobj *g;
    for (g = x->gl_list; g; g = g->g_next)
    {
    	float x1 = gobj_getxforsort(g);
	if (x1 < lastx)
	    foo = 1;
    	lastx = x1;
	nitems++;
    }
    if (foo)
    	x->gl_list = glist_dosort(x, x->gl_list, nitems);
}

void glist_cleanup(t_glist *x)
{
    freebytes(x->gl_xlabel, x->gl_nxlabels * sizeof(*(x->gl_xlabel)));
    freebytes(x->gl_ylabel, x->gl_nylabels * sizeof(*(x->gl_ylabel)));
    gstub_cutoff(x->gl_stub);
}

void glist_free(t_glist *x)
{
    glist_cleanup(x);
    freebytes(x, sizeof(*x));
}

/* --------------- inlets and outlets  ----------- */


t_inlet *canvas_addinlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_inlet *ip = inlet_new(&x->gl_obj, who, s, 0);
    if (!x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner))
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
    if (!x->gl_loading) canvas_resortinlets(x);
    return (ip);
}

void canvas_rminlet(t_canvas *x, t_inlet *ip)
{
    t_canvas *owner = x->gl_owner;
    int redraw = (owner && glist_isvisible(owner) && (!owner->gl_isdeleting)
    	&& glist_istoplevel(owner));
    
    if (owner) canvas_deletelinesforio(owner, &x->gl_obj, ip, 0);
    if (redraw)
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    inlet_free(ip);
    if (redraw)
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

extern t_inlet *vinlet_getit(t_pd *x);
extern void obj_moveinletfirst(t_object *x, t_inlet *i);

void canvas_resortinlets(t_canvas *x)
{
    int ninlets = 0, i, j, xmax;
    t_gobj *y, **vec, **vp, **maxp;
    
    for (ninlets = 0, y = x->gl_list; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == vinlet_class) ninlets++;

    if (ninlets < 2) return;
    
    vec = (t_gobj **)getbytes(ninlets * sizeof(*vec));
    
    for (y = x->gl_list, vp = vec; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == vinlet_class) *vp++ = y;
    
    for (i = ninlets; i--;)
    {
    	t_inlet *ip;
	for (vp = vec, xmax = -0x7fffffff, maxp = 0, j = ninlets;
    	    j--; vp++)
	{
    	    int x1, y1, x2, y2;
    	    t_gobj *g = *vp;
    	    if (!g) continue;
    	    gobj_getrect(g, x, &x1, &y1, &x2, &y2);
    	    if (x1 > xmax) xmax = x1, maxp = vp;
    	}
    	if (!maxp) break;
    	y = *maxp;
    	*maxp = 0;
    	ip = vinlet_getit(&y->g_pd);
    	
	obj_moveinletfirst(&x->gl_obj, ip);
    }
    freebytes(vec, ninlets * sizeof(*vec));
    if (x->gl_owner && glist_isvisible(x->gl_owner))
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
}

t_outlet *canvas_addoutlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_outlet *op = outlet_new(&x->gl_obj, s);
#ifdef ROCKBOX
    (void) who;
#endif
    if (!x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner))
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
    if (!x->gl_loading) canvas_resortoutlets(x);
    return (op);
}

void canvas_rmoutlet(t_canvas *x, t_outlet *op)
{
    t_canvas *owner = x->gl_owner;
    int redraw = (owner && glist_isvisible(owner) && (!owner->gl_isdeleting)
    	&& glist_istoplevel(owner));
    
    if (owner) canvas_deletelinesforio(owner, &x->gl_obj, 0, op);
    if (redraw)
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);

    outlet_free(op);
    if (redraw)
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

extern t_outlet *voutlet_getit(t_pd *x);
extern void obj_moveoutletfirst(t_object *x, t_outlet *i);

void canvas_resortoutlets(t_canvas *x)
{
    int noutlets = 0, i, j, xmax;
    t_gobj *y, **vec, **vp, **maxp;
    
    for (noutlets = 0, y = x->gl_list; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == voutlet_class) noutlets++;

    if (noutlets < 2) return;
    
    vec = (t_gobj **)getbytes(noutlets * sizeof(*vec));
    
    for (y = x->gl_list, vp = vec; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == voutlet_class) *vp++ = y;
    
    for (i = noutlets; i--;)
    {
    	t_outlet *ip;
	for (vp = vec, xmax = -0x7fffffff, maxp = 0, j = noutlets;
    	    j--; vp++)
	{
    	    int x1, y1, x2, y2;
    	    t_gobj *g = *vp;
    	    if (!g) continue;
    	    gobj_getrect(g, x, &x1, &y1, &x2, &y2);
    	    if (x1 > xmax) xmax = x1, maxp = vp;
    	}
    	if (!maxp) break;
    	y = *maxp;
    	*maxp = 0;
    	ip = voutlet_getit(&y->g_pd);
    	
	obj_moveoutletfirst(&x->gl_obj, ip);
    }
    freebytes(vec, noutlets * sizeof(*vec));
    if (x->gl_owner && glist_isvisible(x->gl_owner))
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
}

/* ----------calculating coordinates and controlling appearance --------- */


static void graph_bounds(t_glist *x, t_floatarg x1, t_floatarg y1,
    t_floatarg x2, t_floatarg y2)
{
    x->gl_x1 = x1;
    x->gl_x2 = x2;
    x->gl_y1 = y1;
    x->gl_y2 = y2;
    if (x->gl_x2 == x->gl_x1 ||
    	x->gl_y2 == x->gl_y1)
    {
	error("graph: empty bounds rectangle");
	x1 = y1 = 0;
	x2 = y2 = 1;
    }
    glist_redraw(x);
}

static void graph_xticks(t_glist *x,
    t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_xtick.k_point = point;
    x->gl_xtick.k_inc = inc;
    x->gl_xtick.k_lperb = f;
    glist_redraw(x);
}

static void graph_yticks(t_glist *x,
    t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_ytick.k_point = point;
    x->gl_ytick.k_inc = inc;
    x->gl_ytick.k_lperb = f;
    glist_redraw(x);
}

static void graph_xlabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
#ifdef ROCKBOX
    (void) s;
#endif
    if (argc < 1) error("graph_xlabel: no y value given");
    else
    {
    	x->gl_xlabely = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_xlabel = (t_symbol **)t_resizebytes(x->gl_xlabel, 
    	    x->gl_nxlabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nxlabels = argc;
    	for (i = 0; i < argc; i++) x->gl_xlabel[i] = atom_gensym(&argv[i]);
    }
    glist_redraw(x);
}
    
static void graph_ylabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
#ifdef ROCKBOX
    (void) s;
#endif
    if (argc < 1) error("graph_ylabel: no x value given");
    else
    {
    	x->gl_ylabelx = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_ylabel = (t_symbol **)t_resizebytes(x->gl_ylabel, 
    	    x->gl_nylabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nylabels = argc;
    	for (i = 0; i < argc; i++) x->gl_ylabel[i] = atom_gensym(&argv[i]);
    }
    glist_redraw(x);
}

/****** routines to convert pixels to X or Y value and vice versa ******/

    /* convert an x pixel value to an x coordinate value */
float glist_pixelstox(t_glist *x, float xpix)
{
    	/* if we appear as a text box on parent, our range in our
	coordinates (x1, etc.) specifies the coordinate range
	of a one-pixel square at top left of the window. */
    if (!x->gl_isgraph)
	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * xpix);

	/* if we're a graph when shown on parent, but own our own
	window right now, our range in our coordinates (x1, etc.) is spread
	over the visible window size, given by screenx1, etc. */  
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * 
            (xpix) / (x->gl_screenx2 - x->gl_screenx1));

    	/* otherwise, we appear in a graph within a parent glist,
	 so get our screen rectangle on parent and transform. */
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");	    
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * 
            (xpix - x1) / (x2 - x1));
    }
}

float glist_pixelstoy(t_glist *x, float ypix)
{
    if (!x->gl_isgraph)
    	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * ypix);
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * 
            	(ypix) / (x->gl_screeny2 - x->gl_screeny1));
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * 
            (ypix - y1) / (y2 - y1));
    }
}

    /* convert an x coordinate value to an x pixel location in window */
float glist_xtopixels(t_glist *x, float xval)
{
    if (!x->gl_isgraph)
    	return ((xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_screenx2 - x->gl_screenx1) * 
    	    (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1);
    else
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
    	return (x1 + (x2 - x1) * (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
    }
}

float glist_ytopixels(t_glist *x, float yval)
{
    if (!x->gl_isgraph)
    	return ((yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_screeny2 - x->gl_screeny1) * 
            	(yval - x->gl_y1) / (x->gl_y2 - x->gl_y1);
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
    	return (y1 + (y2 - y1) * (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
    }
}

    /* convert an X screen distance to an X coordinate increment.
      This is terribly inefficient;
      but probably not a big enough CPU hog to warrant optimizing. */
float glist_dpixtodx(t_glist *x, float dxpix)
{ 
    return (dxpix * (glist_pixelstox(x, 1) - glist_pixelstox(x, 0)));
}

float glist_dpixtody(t_glist *x, float dypix)
{
    return (dypix * (glist_pixelstoy(x, 1) - glist_pixelstoy(x, 0)));
}

    /* get the window location in pixels of a "text" object.  The
    object's x and y positions are in pixels when the glist they're
    in is toplevel.  If it's not, we convert to pixels on the parent
    window. */
int text_xpix(t_text *x, t_glist *glist)
{
    if (glist->gl_havewindow || !glist->gl_isgraph)
    	return (x->te_xpix);
    else return (glist_xtopixels(glist, 
	    glist->gl_x1 + (glist->gl_x2 - glist->gl_x1) * 
            	x->te_xpix / (glist->gl_screenx2 - glist->gl_screenx1)));
}

int text_ypix(t_text *x, t_glist *glist)
{
    if (glist->gl_havewindow || !glist->gl_isgraph)
    	return (x->te_ypix);
    else return (glist_ytopixels(glist, 
	    glist->gl_y1 + (glist->gl_y2 - glist->gl_y1) * 
            	x->te_ypix / (glist->gl_screeny2 - glist->gl_screeny1)));
}

    /* redraw all the items in a glist.  We construe this to mean
    redrawing in its own window and on parent, as needed in each case.
    This is too conservative -- for instance, when you draw an "open"
    rectangle on the parent, you shouldn't have to redraw the window!  */
void glist_redraw(t_glist *x)
{  
    if (glist_isvisible(x))
    {
    	    /* LATER fix the graph_vis() code to handle both cases */
    	if (glist_istoplevel(x))
	{
	    t_gobj *g;
	    t_linetraverser t;
	    t_outconnect *oc;
     	    for (g = x->gl_list; g; g = g->g_next)
	    {
     	    	gobj_vis(g, x, 0);
		gobj_vis(g, x, 1);
	    }
	    	/* redraw all the lines */
    	    linetraverser_start(&t, x);
    	    while((oc = linetraverser_next(&t)))
#ifdef ROCKBOX
                ;
#else /* ROCKBOX */
    		sys_vgui(".x%x.c coords l%x %d %d %d %d\n",
		    glist_getcanvas(x), oc,
			t.tr_lx1, t.tr_ly1, t.tr_lx2, t.tr_ly2);
#endif /* ROCKBOX */
	}
	if (x->gl_owner)
	{
    	    graph_vis(&x->gl_gobj, x->gl_owner, 0); 
    	    graph_vis(&x->gl_gobj, x->gl_owner, 1);
	}
    }
}

/* --------------------------- widget behavior  ------------------- */

extern t_widgetbehavior text_widgetbehavior;

    /* Note that some code in here would also be useful for drawing
    graph decorations in toplevels... */
static void graph_vis(t_gobj *gr, t_glist *parent_glist, int vis)
{
    t_glist *x = (t_glist *)gr;
    char tag[50];
    t_gobj *g;
    int x1, y1, x2, y2;
    	/* ordinary subpatches: just act like a text object */
    if (!x->gl_isgraph)
    {
    	text_widgetbehavior.w_visfn(gr, parent_glist, vis);
	return;
    }

    if (vis && canvas_showtext(x))
    	rtext_draw(glist_findrtext(parent_glist, &x->gl_obj));
    graph_getrect(gr, parent_glist, &x1, &y1, &x2, &y2);
    if (!vis)
    	rtext_erase(glist_findrtext(parent_glist, &x->gl_obj));

#ifdef ROCKBOX
    snprintf(tag, sizeof(tag), "graph%lx", (unsigned long) (intptr_t) x);
#else
    sprintf(tag, "graph%x", (int)x);
#endif
    if (vis)
    	glist_drawiofor(parent_glist, &x->gl_obj, 1,
	    tag, x1, y1, x2, y2);
    else glist_eraseiofor(parent_glist, &x->gl_obj, tag);
    	/* if we look like a graph but have been moved to a toplevel,
	just show the bounding rectangle */
    if (x->gl_havewindow)
    {
#ifndef ROCKBOX
    	if (vis)
	{
    	    sys_vgui(".x%x.c create polygon\
 %d %d %d %d %d %d %d %d %d %d -tags %s -fill #c0c0c0\n",
	    	glist_getcanvas(x->gl_owner),
    	    	x1, y1, x1, y2, x2, y2, x2, y1, x1, y1, tag);
	}
	else
	{
    	    sys_vgui(".x%x.c delete %s\n",
    	    	glist_getcanvas(x->gl_owner), tag);
	}
#endif
	return;
    }
    	/* otherwise draw (or erase) us as a graph inside another glist. */
    if (vis)
    {
    	int i;
    	float f;

    	    /* draw a rectangle around the graph */
#ifndef ROCKBOX
    	sys_vgui(".x%x.c create line\
    	    %d %d %d %d %d %d %d %d %d %d -tags %s\n",
	    glist_getcanvas(x->gl_owner),
    	    x1, y1, x1, y2, x2, y2, x2, y1, x1, y1, tag);
#endif

    	    /* draw ticks on horizontal borders.  If lperb field is
    	    zero, this is disabled. */
    	if (x->gl_xtick.k_lperb)
    	{
	    float upix, lpix;
	    if (y2 < y1)
		upix = y1, lpix = y2;
	    else upix = y2, lpix = y1;
    	    for (i = 0, f = x->gl_xtick.k_point;
    		f < 0.99 * x->gl_x2 + 0.01*x->gl_x1; i++,
    		    f += x->gl_xtick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)upix,
	    	    (int)glist_xtopixels(x, f), (int)upix - tickpix, tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)lpix,
	    	    (int)glist_xtopixels(x, f), (int)lpix + tickpix, tag);
#endif
    	    }
    	    for (i = 1, f = x->gl_xtick.k_point - x->gl_xtick.k_inc;
    		f > 0.99 * x->gl_x1 + 0.01*x->gl_x2;
    		    i++, f -= x->gl_xtick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)upix,
	    	    (int)glist_xtopixels(x, f), (int)upix - tickpix, tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)lpix,
	    	    (int)glist_xtopixels(x, f), (int)lpix + tickpix, tag);
#endif
    	    }
    	}

    	    /* draw ticks in vertical borders*/
    	if (x->gl_ytick.k_lperb)
    	{
	    float ubound, lbound;
	    if (x->gl_y2 < x->gl_y1)
		ubound = x->gl_y1, lbound = x->gl_y2;
	    else ubound = x->gl_y2, lbound = x->gl_y1;
    	    for (i = 0, f = x->gl_ytick.k_point;
    		f < 0.99 * ubound + 0.01 * lbound;
    		    i++, f += x->gl_ytick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x1, (int)glist_ytopixels(x, f), 
	    	    x1 + tickpix, (int)glist_ytopixels(x, f), tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x2, (int)glist_ytopixels(x, f), 
	    	    x2 - tickpix, (int)glist_ytopixels(x, f), tag);
#endif
    	    }
    	    for (i = 1, f = x->gl_ytick.k_point - x->gl_ytick.k_inc;
    		f > 0.99 * lbound + 0.01 * ubound;
    		    i++, f -= x->gl_ytick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x1, (int)glist_ytopixels(x, f), 
	    	    x1 + tickpix, (int)glist_ytopixels(x, f), tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x2, (int)glist_ytopixels(x, f), 
	    	    x2 - tickpix, (int)glist_ytopixels(x, f), tag);
#endif
    	    }
    	}
    	    /* draw x labels */
    	for (i = 0; i < x->gl_nxlabels; i++)
#ifdef ROCKBOX
            ;
#else /* ROCKBOX */
    	    sys_vgui(".x%x.c create text\
    	%d %d -text {%s} -font -*-courier-bold--normal--%d-* -tags %s\n",
    	    	glist_getcanvas(x),
	    	(int)glist_xtopixels(x, atof(x->gl_xlabel[i]->s_name)),
	    	(int)glist_ytopixels(x, x->gl_xlabely), x->gl_xlabel[i]->s_name,
	    	glist_getfont(x), tag);
#endif /* ROCKBOX */

    	    /* draw y labels */
    	for (i = 0; i < x->gl_nylabels; i++)
#ifdef ROCKBOX
            ;
#else /* ROCKBOX */
    	    sys_vgui(".x%x.c create text\
    	%d %d -text {%s} -font -*-courier-bold--normal--%d-* -tags %s\n",
    	    	glist_getcanvas(x),
	    	(int)glist_xtopixels(x, x->gl_ylabelx),
	    	(int)glist_ytopixels(x, atof(x->gl_ylabel[i]->s_name)),
	    	x->gl_ylabel[i]->s_name,
	    	glist_getfont(x), tag);
#endif /* ROCKBOX */

    	    /* draw contents of graph as glist */
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 1);
    }
    else
    {
#ifndef ROCKBOX
    	sys_vgui(".x%x.c delete %s\n",
    	    glist_getcanvas(x->gl_owner), tag);
#endif
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 0);
    }
}

    /* get the graph's rectangle, not counting extra swelling for controls
    to keep them inside the graph.  This is the "logical" pixel size. */

static void graph_graphrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    t_glist *x = (t_glist *)z;
    int x1 = text_xpix(&x->gl_obj, glist);
    int y1 = text_ypix(&x->gl_obj, glist);
    int x2, y2;
#if 0	/* this used to adjust graph size when it was in another graph;
    	    now we just preserve the size. */
    	/* same logic here as in text_xpix(): */
    if (glist->gl_havewindow)
    {
	x2 = x1 + x->gl_pixwidth;
	y2 = y1 + x->gl_pixheight;
    }
    else
    {
    	x2 = glist_xtopixels(glist, 
	    glist->gl_x1 + (glist->gl_x2 - glist->gl_x1) * 
            	(x->gl_obj.te_xpix + x->gl_pixwidth) /
		    (glist->gl_screenx2 - glist->gl_screenx1));
    	y2 = glist_ytopixels(glist, 
	    glist->gl_y1 + (glist->gl_y2 - glist->gl_y1) * 
            	(x->gl_obj.te_ypix + x->gl_pixheight) /
		    (glist->gl_screeny2 - glist->gl_screeny1));
    }
#endif
    x2 = x1 + x->gl_pixwidth;
    y2 = y1 + x->gl_pixheight;

    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2;
}

    /* get the rectangle, enlarged to contain all the "contents" --
    meaning their formal bounds rectangles. */
static void graph_getrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    int x1 = 0x7fffffff, y1 = 0x7fffffff, x2 = -0x7fffffff, y2 = -0x7fffffff;
    t_glist *x = (t_glist *)z;
    if (x->gl_isgraph)
    {
    	int hadwindow;
	t_gobj *g;
	t_text *ob;
	int x21, y21, x22, y22;

    	graph_graphrect(z, glist, &x1, &y1, &x2, &y2);
	if (canvas_showtext(x))
	{
	    text_widgetbehavior.w_getrectfn(z, glist, &x21, &y21, &x22, &y22);
    	    if (x22 > x2) 
		x2 = x22;
    	    if (y22 > y2) 
		y2 = y22;
	}
	    /* lie about whether we have our own window to affect gobj_getrect
	    calls below.  (LATER add argument to gobj_getrect()?) */
	hadwindow = x->gl_havewindow;
	x->gl_havewindow = 0;
	for (g = x->gl_list; g; g = g->g_next)
	    if ((!(ob = pd_checkobject(&g->g_pd))) || text_shouldvis(ob, x))
	{
	    	/* don't do this for arrays, just let them hang outsize the
		box. */
	    if (pd_class(&g->g_pd) == garray_class)
	    	continue;
    	    gobj_getrect(g, x, &x21, &y21, &x22, &y22);
    	    if (x22 > x2) 
	    	x2 = x22;
    	    if (y22 > y2) 
	    	y2 = y22;
    	}
	x->gl_havewindow = hadwindow;
    }
    else text_widgetbehavior.w_getrectfn(z, glist, &x1, &y1, &x2, &y2);
    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2;
}

static void graph_displace(t_gobj *z, t_glist *glist, int dx, int dy)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_displacefn(z, glist, dx, dy);
    else
    {
    	x->gl_obj.te_xpix += dx;
    	x->gl_obj.te_ypix += dy;
    	glist_redraw(x);
    	canvas_fixlinesfor(glist_getcanvas(glist), &x->gl_obj);
    }
}

static void graph_select(t_gobj *z, t_glist *glist, int state)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_selectfn(z, glist, state);
    else
    {
	t_rtext *y = glist_findrtext(glist, &x->gl_obj);
    	if (canvas_showtext(x))
	    rtext_select(y, state);
#ifndef ROCKBOX
	sys_vgui(".x%x.c itemconfigure %sR -fill %s\n", glist, 
    	rtext_gettag(y), (state? "blue" : "black"));
    	sys_vgui(".x%x.c itemconfigure graph%x -fill %s\n",
    	    glist_getcanvas(glist), z, (state? "blue" : "black"));
#endif
    }
}

static void graph_activate(t_gobj *z, t_glist *glist, int state)
{
    t_glist *x = (t_glist *)z;
    if (canvas_showtext(x))
    	text_widgetbehavior.w_activatefn(z, glist, state);
}

#if 0
static void graph_delete(t_gobj *z, t_glist *glist)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_deletefn(z, glist);
    else
    {
	t_gobj *y;
	while (y = x->gl_list) glist_delete(x, y);
#if 0	    /* I think this was just wrong. */
	if (glist_isvisible(x))
    	    sys_vgui(".x%x.c delete graph%x\n", glist_getcanvas(glist), x);
#endif
    }
}
#endif

static void graph_delete(t_gobj *z, t_glist *glist)
{
    t_glist *x = (t_glist *)z;
    t_gobj *y;
    text_widgetbehavior.w_deletefn(z, glist);
    while((y = x->gl_list))
    	glist_delete(x, y);
}

#ifndef ROCKBOX
static float graph_lastxpix, graph_lastypix;

static void graph_motion(void *z, t_floatarg dx, t_floatarg dy)
{
    t_glist *x = (t_glist *)z;
    float newxpix = graph_lastxpix + dx, newypix = graph_lastypix + dy;
    t_garray *a = (t_garray *)(x->gl_list);
    int oldx = 0.5 + glist_pixelstox(x, graph_lastxpix);
    int newx = 0.5 + glist_pixelstox(x, newxpix);
    t_sample *vec;
    int nelem, i;
    float oldy = glist_pixelstoy(x, graph_lastypix);
    float newy = glist_pixelstoy(x, newypix);
    graph_lastxpix = newxpix;
    graph_lastypix = newypix;
    	/* verify that the array is OK */
    if (!a || pd_class((t_pd *)a) != garray_class)
    	return;
    if (!garray_getfloatarray(a, &nelem, &vec))
    	return;
    if (oldx < 0) oldx = 0;
    if (oldx >= nelem)
    	oldx = nelem - 1;
    if (newx < 0) newx = 0;
    if (newx >= nelem)
    	newx = nelem - 1;
    if (oldx < newx - 1)
    {
    	for (i = oldx + 1; i <= newx; i++)
	    vec[i] = newy + (oldy - newy) *
	    	((float)(newx - i))/(float)(newx - oldx);
    }
    else if (oldx > newx + 1)
    {
    	for (i = oldx - 1; i >= newx; i--)
	    vec[i] = newy + (oldy - newy) *
	    	((float)(newx - i))/(float)(newx - oldx);
    }
    else vec[newx] = newy;
    garray_redraw(a);
}
#endif

static int graph_click(t_gobj *z, struct _glist *glist,
    int xpix, int ypix, int shift, int alt, int dbl, int doit)
{
    t_glist *x = (t_glist *)z;
    t_gobj *y;
    int clickreturned = 0;
    if (!x->gl_isgraph)
    	return (text_widgetbehavior.w_clickfn(z, glist,
	    xpix, ypix, shift, alt, dbl, doit));
    else if (x->gl_havewindow)
    	return (0);
    else
    {
	for (y = x->gl_list; y; y = y->g_next)
	{
    	    int x1, y1, x2, y2;
		/* check if the object wants to be clicked */
    	    if (canvas_hitbox(x, y, xpix, ypix, &x1, &y1, &x2, &y2)
		&&  (clickreturned = gobj_click(y, x, xpix, ypix,
		    shift, alt, 0, doit)))
    	    		break;
	}
	if (!doit)
	{
	    if (y)
		canvas_setcursor(glist_getcanvas(x), clickreturned);
	    else canvas_setcursor(glist_getcanvas(x), CURSOR_RUNMODE_NOTHING);
	}
	return (clickreturned);	
    }
}

void garray_properties(t_garray *x);

t_widgetbehavior graph_widgetbehavior =
{
    graph_getrect,
    graph_displace,
    graph_select,
    graph_activate,
    graph_delete,
    graph_vis,
    graph_click,
};

void graph_properties(t_gobj *z, t_glist *owner)
{
    t_glist *x = (t_glist *)z;
    {
	t_gobj *y;
#ifdef ROCKBOX
        (void) owner;
#else /* ROCKBOX */
	char graphbuf[200];

	sprintf(graphbuf, "pdtk_graph_dialog %%s %g %g %g %g %d %d\n",
	    x->gl_x1, x->gl_y1, x->gl_x2, x->gl_y2,
		x->gl_pixwidth, x->gl_pixheight);
	gfxstub_new(&x->gl_pd, x, graphbuf);
#endif /* ROCKBOX */

	for (y = x->gl_list; y; y = y->g_next)
    	    if (pd_class(&y->g_pd) == garray_class) 
		garray_properties((t_garray *)y);
    }
}

    /* find the graph most recently added to this glist;
    	if none exists, return 0. */

t_glist *glist_findgraph(t_glist *x)
{
    t_gobj *y = 0, *z;
    for (z = x->gl_list; z; z = z->g_next)
    	if (pd_class(&z->g_pd) == canvas_class && ((t_glist *)z)->gl_isgraph)
	    y = z;
    return ((t_glist *)y);
}

    /* message back from dialog GUI to set parameters.  Args are:
    	1-4: bounds in our coordinates; 5-6: size in parent */
static void graph_dialog(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float x1 = atom_getfloatarg(0, argc, argv);
    t_float y1 = atom_getfloatarg(1, argc, argv);
    t_float x2 = atom_getfloatarg(2, argc, argv);
    t_float y2 = atom_getfloatarg(3, argc, argv);
    t_float xpix = atom_getfloatarg(4, argc, argv);
    t_float ypix = atom_getfloatarg(5, argc, argv);
#ifdef ROCKBOX
    (void) s;
#endif
    if (x1 != x->gl_x1 || x2 != x->gl_x2 ||
    	y1 != x->gl_y1 || y2 != x->gl_y2)
    	    graph_bounds(x, x1, y1, x2, y2);
    if (xpix != x->gl_pixwidth || ypix != x->gl_pixheight)
    {
    	x->gl_pixwidth = xpix;
	x->gl_pixheight = ypix;
	glist_redraw(x);
	if (x->gl_owner)
	    canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

extern void canvas_menuarray(t_glist *canvas);

void g_graph_setup(void)
{
    class_setwidget(canvas_class, &graph_widgetbehavior);
    class_addmethod(canvas_class, (t_method)graph_bounds, gensym("bounds"),
    	A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_xticks, gensym("xticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_xlabel, gensym("xlabel"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)graph_yticks, gensym("yticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_ylabel, gensym("ylabel"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)graph_array, gensym("array"),
    	A_SYMBOL, A_FLOAT, A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_menuarray,
    	gensym("menuarray"), A_NULL);
    class_addmethod(canvas_class, (t_method)graph_dialog, gensym("dialog"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)glist_arraydialog,
    	gensym("arraydialog"), A_SYMBOL, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_sort,
    	gensym("sort"), A_NULL);
}

um">1, 1, 0, 0}, dys[4] = {0, 0, -1, 1}; int n, x, y; /* this function needs optimising. */ for (n = 0; n < 4; n++) { x = sp->x+dxs[n]; y = sp->y+dys[n]; if (INGRID(state, x, y)) { a1s[n] = &SPACE(state, x, y); x += dxs[n]; y += dys[n]; if (INGRID(state, x, y)) a2s[n] = &SPACE(state, x, y); else a2s[n] = NULL; } else { a1s[n] = a2s[n] = NULL; } } } static int outline_tile_fordot(game_state *state, space *tile, int mark) { struct space *tadj[4], *eadj[4]; int i, didsth = 0, edge, same; assert(tile->type == s_tile); adjacencies(state, tile, eadj, tadj); for (i = 0; i < 4; i++) { if (!eadj[i]) continue; edge = (eadj[i]->flags & F_EDGE_SET) ? 1 : 0; if (tadj[i]) { if (!(tile->flags & F_TILE_ASSOC)) same = (tadj[i]->flags & F_TILE_ASSOC) ? 0 : 1; else same = ((tadj[i]->flags & F_TILE_ASSOC) && tile->dotx == tadj[i]->dotx && tile->doty == tadj[i]->doty) ? 1 : 0; } else same = 0; if (!edge && !same) { if (mark) eadj[i]->flags |= F_EDGE_SET; didsth = 1; } else if (edge && same) { if (mark) eadj[i]->flags &= ~F_EDGE_SET; didsth = 1; } } return didsth; } static void tiles_from_edge(struct game_state *state, struct space *sp, struct space **ts) { int xs[2], ys[2]; if (IS_VERTICAL_EDGE(sp->x)) { xs[0] = sp->x-1; ys[0] = sp->y; xs[1] = sp->x+1; ys[1] = sp->y; } else { xs[0] = sp->x; ys[0] = sp->y-1; xs[1] = sp->x; ys[1] = sp->y+1; } ts[0] = INGRID(state, xs[0], ys[0]) ? &SPACE(state, xs[0], ys[0]) : NULL; ts[1] = INGRID(state, xs[1], ys[1]) ? &SPACE(state, xs[1], ys[1]) : NULL; } /* Returns a move string for use by 'solve', including the initial * 'S' if issolve is true. */ static char *diff_game(game_state *src, game_state *dest, int issolve) { int movelen = 0, movesize = 256, x, y, len; char *move = snewn(movesize, char), buf[80], *sep = ""; char achar = issolve ? 'a' : 'A'; space *sps, *spd; assert(src->sx == dest->sx && src->sy == dest->sy); if (issolve) { move[movelen++] = 'S'; sep = ";"; } move[movelen] = '\0'; for (x = 0; x < src->sx; x++) { for (y = 0; y < src->sy; y++) { sps = &SPACE(src, x, y); spd = &SPACE(dest, x, y); assert(sps->type == spd->type); len = 0; if (sps->type == s_tile) { if ((sps->flags & F_TILE_ASSOC) && (spd->flags & F_TILE_ASSOC)) { if (sps->dotx != spd->dotx || sps->doty != spd->doty) /* Both associated; change association, if different */ len = sprintf(buf, "%s%c%d,%d,%d,%d", sep, (int)achar, x, y, spd->dotx, spd->doty); } else if (sps->flags & F_TILE_ASSOC) /* Only src associated; remove. */ len = sprintf(buf, "%sU%d,%d", sep, x, y); else if (spd->flags & F_TILE_ASSOC) /* Only dest associated; add. */ len = sprintf(buf, "%s%c%d,%d,%d,%d", sep, (int)achar, x, y, spd->dotx, spd->doty); } else if (sps->type == s_edge) { if ((sps->flags & F_EDGE_SET) != (spd->flags & F_EDGE_SET)) /* edge flags are different; flip them. */ len = sprintf(buf, "%sE%d,%d", sep, x, y); } if (len) { if (movelen + len >= movesize) { movesize = movelen + len + 256; move = sresize(move, movesize, char); } strcpy(move + movelen, buf); movelen += len; sep = ";"; } } } debug(("diff_game src then dest:\n")); dbg_state(src); dbg_state(dest); debug(("diff string %s\n", move)); return move; } /* Returns 1 if a dot here would not be too close to any other dots * (and would avoid other game furniture). */ static int dot_is_possible(game_state *state, space *sp, int allow_assoc) { int bx = 0, by = 0, dx, dy; space *adj; #ifdef STANDALONE_PICTURE_GENERATOR int col = -1; #endif switch (sp->type) { case s_tile: bx = by = 1; break; case s_edge: if (IS_VERTICAL_EDGE(sp->x)) { bx = 2; by = 1; } else { bx = 1; by = 2; } break; case s_vertex: bx = by = 2; break; } for (dx = -bx; dx <= bx; dx++) { for (dy = -by; dy <= by; dy++) { if (!INGRID(state, sp->x+dx, sp->y+dy)) continue; adj = &SPACE(state, sp->x+dx, sp->y+dy); #ifdef STANDALONE_PICTURE_GENERATOR /* * Check that all the squares we're looking at have the * same colour. */ if (picture) { if (adj->type == s_tile) { int c = picture[(adj->y / 2) * state->w + (adj->x / 2)]; if (col < 0) col = c; if (c != col) return 0; /* colour mismatch */ } } #endif if (!allow_assoc && (adj->flags & F_TILE_ASSOC)) return 0; if (dx != 0 || dy != 0) { /* Other than our own square, no dots nearby. */ if (adj->flags & (F_DOT)) return 0; } /* We don't want edges within our rectangle * (but don't care about edges on the edge) */ if (abs(dx) < bx && abs(dy) < by && adj->flags & F_EDGE_SET) return 0; } } return 1; } /* ---------------------------------------------------------- * Game generation, structure creation, and descriptions. */ static game_state *blank_game(int w, int h) { game_state *state = snew(game_state); int x, y; state->w = w; state->h = h; state->sx = (w*2)+1; state->sy = (h*2)+1; state->grid = snewn(state->sx * state->sy, struct space); state->completed = state->used_solve = 0; for (x = 0; x < state->sx; x++) { for (y = 0; y < state->sy; y++) { struct space *sp = &SPACE(state, x, y); memset(sp, 0, sizeof(struct space)); sp->x = x; sp->y = y; if ((x % 2) == 0 && (y % 2) == 0) sp->type = s_vertex; else if ((x % 2) == 0 || (y % 2) == 0) { sp->type = s_edge; if (x == 0 || y == 0 || x == state->sx-1 || y == state->sy-1) sp->flags |= F_EDGE_SET; } else sp->type = s_tile; } } state->ndots = 0; state->dots = NULL; state->me = NULL; /* filled in by new_game. */ state->cdiff = -1; return state; } static void game_update_dots(game_state *state) { int i, n, sz = state->sx * state->sy; if (state->dots) sfree(state->dots); state->ndots = 0; for (i = 0; i < sz; i++) { if (state->grid[i].flags & F_DOT) state->ndots++; } state->dots = snewn(state->ndots, space *); n = 0; for (i = 0; i < sz; i++) { if (state->grid[i].flags & F_DOT) state->dots[n++] = &state->grid[i]; } } static void clear_game(game_state *state, int cleardots) { int x, y; /* don't erase edge flags around outline! */ for (x = 1; x < state->sx-1; x++) { for (y = 1; y < state->sy-1; y++) { if (cleardots) SPACE(state, x, y).flags = 0; else SPACE(state, x, y).flags &= (F_DOT|F_DOT_BLACK); } } if (cleardots) game_update_dots(state); } static game_state *dup_game(game_state *state) { game_state *ret = blank_game(state->w, state->h); ret->completed = state->completed; ret->used_solve = state->used_solve; memcpy(ret->grid, state->grid, ret->sx*ret->sy*sizeof(struct space)); game_update_dots(ret); ret->me = state->me; ret->cdiff = state->cdiff; return ret; } static void free_game(game_state *state) { if (state->dots) sfree(state->dots); sfree(state->grid); sfree(state); } /* Game description is a sequence of letters representing the number * of spaces (a = 0, y = 24) before the next dot; a-y for a white dot, * and A-Y for a black dot. 'z' is 25 spaces (and no dot). * * I know it's a bitch to generate by hand, so we provide * an edit mode. */ static char *encode_game(game_state *state) { char *desc, *p; int run, x, y, area; unsigned int f; area = (state->sx-2) * (state->sy-2); desc = snewn(area, char); p = desc; run = 0; for (y = 1; y < state->sy-1; y++) { for (x = 1; x < state->sx-1; x++) { f = SPACE(state, x, y).flags; /* a/A is 0 spaces between, b/B is 1 space, ... * y/Y is 24 spaces, za/zA is 25 spaces, ... * It's easier to count from 0 because we then * don't have to special-case the top left-hand corner * (which could be a dot with 0 spaces before it). */ if (!(f & F_DOT)) run++; else { while (run > 24) { *p++ = 'z'; run -= 25; } *p++ = ((f & F_DOT_BLACK) ? 'A' : 'a') + run; run = 0; } } } assert(p - desc < area); *p++ = '\0'; desc = sresize(desc, p - desc, char); return desc; } struct movedot { int op; space *olddot, *newdot; }; enum { MD_CHECK, MD_MOVE }; static int movedot_cb(game_state *state, space *tile, void *vctx) { struct movedot *md = (struct movedot *)vctx; space *newopp = NULL; assert(tile->type == s_tile); assert(md->olddot && md->newdot); if (!(tile->flags & F_TILE_ASSOC)) return 0; if (tile->dotx != md->olddot->x || tile->doty != md->olddot->y) return 0; newopp = space_opposite_dot(state, tile, md->newdot); switch (md->op) { case MD_CHECK: /* If the tile is associated with the old dot, check its * opposite wrt the _new_ dot is empty or same assoc. */ if (!newopp) return -1; /* no new opposite */ if (newopp->flags & F_TILE_ASSOC) { if (newopp->dotx != md->olddot->x || newopp->doty != md->olddot->y) return -1; /* associated, but wrong dot. */ } #ifdef STANDALONE_PICTURE_GENERATOR if (picture) { /* * Reject if either tile and the dot don't match in colour. */ if (!(picture[(tile->y/2) * state->w + (tile->x/2)]) ^ !(md->newdot->flags & F_DOT_BLACK)) return -1; if (!(picture[(newopp->y/2) * state->w + (newopp->x/2)]) ^ !(md->newdot->flags & F_DOT_BLACK)) return -1; } #endif break; case MD_MOVE: /* Move dot associations: anything that was associated * with the old dot, and its opposite wrt the new dot, * become associated with the new dot. */ assert(newopp); debug(("Associating %d,%d and %d,%d with new dot %d,%d.\n", tile->x, tile->y, newopp->x, newopp->y, md->newdot->x, md->newdot->y)); add_assoc(state, tile, md->newdot); add_assoc(state, newopp, md->newdot); return 1; /* we did something! */ } return 0; } /* For the given dot, first see if we could expand it into all the given * extra spaces (by checking for empty spaces on the far side), and then * see if we can move the dot to shift the CoG to include the new spaces. */ static int dot_expand_or_move(game_state *state, space *dot, space **toadd, int nadd) { space *tileopp; int i, ret, nnew, cx, cy; struct movedot md; debug(("dot_expand_or_move: %d tiles for dot %d,%d\n", nadd, dot->x, dot->y)); for (i = 0; i < nadd; i++) debug(("dot_expand_or_move: dot %d,%d\n", toadd[i]->x, toadd[i]->y)); assert(dot->flags & F_DOT); #ifdef STANDALONE_PICTURE_GENERATOR if (picture) { /* * Reject the expansion totally if any of the new tiles are * the wrong colour. */ for (i = 0; i < nadd; i++) { if (!(picture[(toadd[i]->y/2) * state->w + (toadd[i]->x/2)]) ^ !(dot->flags & F_DOT_BLACK)) return 0; } } #endif /* First off, could we just expand the current dot's tile to cover * the space(s) passed in and their opposites? */ for (i = 0; i < nadd; i++) { tileopp = space_opposite_dot(state, toadd[i], dot); if (!tileopp) goto noexpand; if (tileopp->flags & F_TILE_ASSOC) goto noexpand; #ifdef STANDALONE_PICTURE_GENERATOR if (picture) { /* * The opposite tiles have to be the right colour as well. */ if (!(picture[(tileopp->y/2) * state->w + (tileopp->x/2)]) ^ !(dot->flags & F_DOT_BLACK)) goto noexpand; } #endif } /* OK, all spaces have valid empty opposites: associate spaces and * opposites with our dot. */ for (i = 0; i < nadd; i++) { tileopp = space_opposite_dot(state, toadd[i], dot); add_assoc(state, toadd[i], dot); add_assoc(state, tileopp, dot); debug(("Added associations %d,%d and %d,%d --> %d,%d\n", toadd[i]->x, toadd[i]->y, tileopp->x, tileopp->y, dot->x, dot->y)); dbg_state(state); } return 1; noexpand: /* Otherwise, try to move dot so as to encompass given spaces: */ /* first, calculate the 'centre of gravity' of the new dot. */ nnew = dot->nassoc + nadd; /* number of tiles assoc. with new dot. */ cx = dot->x * dot->nassoc; cy = dot->y * dot->nassoc; for (i = 0; i < nadd; i++) { cx += toadd[i]->x; cy += toadd[i]->y; } /* If the CoG isn't a whole number, it's not possible. */ if ((cx % nnew) != 0 || (cy % nnew) != 0) { debug(("Unable to move dot %d,%d, CoG not whole number.\n", dot->x, dot->y)); return 0; } cx /= nnew; cy /= nnew; /* Check whether all spaces in the old tile would have a good * opposite wrt the new dot. */ md.olddot = dot; md.newdot = &SPACE(state, cx, cy); md.op = MD_CHECK; ret = foreach_tile(state, movedot_cb, IMPOSSIBLE_QUITS, &md); if (ret == -1) { debug(("Unable to move dot %d,%d, new dot not symmetrical.\n", dot->x, dot->y)); return 0; } /* Also check whether all spaces we're adding would have a good * opposite wrt the new dot. */ for (i = 0; i < nadd; i++) { tileopp = space_opposite_dot(state, toadd[i], md.newdot); if (tileopp && (tileopp->flags & F_TILE_ASSOC) && (tileopp->dotx != dot->x || tileopp->doty != dot->y)) { tileopp = NULL; } if (!tileopp) { debug(("Unable to move dot %d,%d, new dot not symmetrical.\n", dot->x, dot->y)); return 0; } #ifdef STANDALONE_PICTURE_GENERATOR if (picture) { if (!(picture[(tileopp->y/2) * state->w + (tileopp->x/2)]) ^ !(dot->flags & F_DOT_BLACK)) return 0; } #endif } /* If we've got here, we're ok. First, associate all of 'toadd' * with the _old_ dot (so they'll get fixed up, with their opposites, * in the next step). */ for (i = 0; i < nadd; i++) { debug(("Associating to-add %d,%d with old dot %d,%d.\n", toadd[i]->x, toadd[i]->y, dot->x, dot->y)); add_assoc(state, toadd[i], dot); } /* Finally, move the dot and fix up all the old associations. */ debug(("Moving dot at %d,%d to %d,%d\n", dot->x, dot->y, md.newdot->x, md.newdot->y)); { #ifdef STANDALONE_PICTURE_GENERATOR int f = dot->flags & F_DOT_BLACK; #endif remove_dot(dot); add_dot(md.newdot); #ifdef STANDALONE_PICTURE_GENERATOR md.newdot->flags |= f; #endif } md.op = MD_MOVE; ret = foreach_tile(state, movedot_cb, 0, &md); assert(ret == 1); dbg_state(state); return 1; } /* Hard-code to a max. of 2x2 squares, for speed (less malloc) */ #define MAX_TOADD 4 #define MAX_OUTSIDE 8 #define MAX_TILE_PERC 20 static int generate_try_block(game_state *state, random_state *rs, int x1, int y1, int x2, int y2) { int x, y, nadd = 0, nout = 0, i, maxsz; space *sp, *toadd[MAX_TOADD], *outside[MAX_OUTSIDE], *dot; if (!INGRID(state, x1, y1) || !INGRID(state, x2, y2)) return 0; /* We limit the maximum size of tiles to be ~2*sqrt(area); so, * a 5x5 grid shouldn't have anything >10 tiles, a 20x20 grid * nothing >40 tiles. */ maxsz = (int)sqrt((double)(state->w * state->h)) * 2; debug(("generate_try_block, maxsz %d\n", maxsz)); /* Make a static list of the spaces; if any space is already * associated then quit immediately. */ for (x = x1; x <= x2; x += 2) { for (y = y1; y <= y2; y += 2) { assert(nadd < MAX_TOADD); sp = &SPACE(state, x, y); assert(sp->type == s_tile); if (sp->flags & F_TILE_ASSOC) return 0; toadd[nadd++] = sp; } } /* Make a list of the spaces outside of our block, and shuffle it. */ #define OUTSIDE(x, y) do { \ if (INGRID(state, (x), (y))) { \ assert(nout < MAX_OUTSIDE); \ outside[nout++] = &SPACE(state, (x), (y)); \ } \ } while(0) for (x = x1; x <= x2; x += 2) { OUTSIDE(x, y1-2); OUTSIDE(x, y2+2); } for (y = y1; y <= y2; y += 2) { OUTSIDE(x1-2, y); OUTSIDE(x2+2, y); } shuffle(outside, nout, sizeof(space *), rs); for (i = 0; i < nout; i++) { if (!(outside[i]->flags & F_TILE_ASSOC)) continue; dot = &SPACE(state, outside[i]->dotx, outside[i]->doty); if (dot->nassoc >= maxsz) { debug(("Not adding to dot %d,%d, large enough (%d) already.\n", dot->x, dot->y, dot->nassoc)); continue; } if (dot_expand_or_move(state, dot, toadd, nadd)) return 1; } return 0; } #ifdef STANDALONE_SOLVER int maxtries; #define MAXTRIES maxtries #else #define MAXTRIES 50 #endif static int solver_obvious_dot(game_state *state,space *dot); #define GP_DOTS 1 static void generate_pass(game_state *state, random_state *rs, int *scratch, int perc, unsigned int flags) { int sz = state->sx*state->sy, nspc, i, ret; shuffle(scratch, sz, sizeof(int), rs); /* This bug took me a, er, little while to track down. On PalmOS, * which has 16-bit signed ints, puzzles over about 9x9 started * failing to generate because the nspc calculation would start * to overflow, causing the dots not to be filled in properly. */ nspc = (int)(((long)perc * (long)sz) / 100L); debug(("generate_pass: %d%% (%d of %dx%d) squares, flags 0x%x\n", perc, nspc, state->sx, state->sy, flags)); for (i = 0; i < nspc; i++) { space *sp = &state->grid[scratch[i]]; int x1 = sp->x, y1 = sp->y, x2 = sp->x, y2 = sp->y; if (sp->type == s_edge) { if (IS_VERTICAL_EDGE(sp->x)) { x1--; x2++; } else { y1--; y2++; } } if (sp->type != s_vertex) { /* heuristic; expanding from vertices tends to generate lots of * too-big regions of tiles. */ if (generate_try_block(state, rs, x1, y1, x2, y2)) continue; /* we expanded successfully. */ } if (!(flags & GP_DOTS)) continue; if ((sp->type == s_edge) && (i % 2)) { debug(("Omitting edge %d,%d as half-of.\n", sp->x, sp->y)); continue; } /* If we've got here we might want to put a dot down. Check * if we can, and add one if so. */ if (dot_is_possible(state, sp, 0)) { add_dot(sp); #ifdef STANDALONE_PICTURE_GENERATOR if (picture) { if (picture[(sp->y/2) * state->w + (sp->x/2)]) sp->flags |= F_DOT_BLACK; } #endif ret = solver_obvious_dot(state, sp); assert(ret != -1); debug(("Added dot (and obvious associations) at %d,%d\n", sp->x, sp->y)); dbg_state(state); } } dbg_state(state); } static int check_complete(game_state *state, int *dsf, int *colours); static int solver_state(game_state *state, int maxdiff); static char *new_game_desc(game_params *params, random_state *rs, char **aux, int interactive) { game_state *state = blank_game(params->w, params->h), *copy; char *desc; int *scratch, sz = state->sx*state->sy, i; int diff, ntries = 0, cc; /* Random list of squares to try and process, one-by-one. */ scratch = snewn(sz, int); for (i = 0; i < sz; i++) scratch[i] = i; generate: clear_game(state, 1); ntries++; /* generate_pass(state, rs, scratch, 10, GP_DOTS); */ /* generate_pass(state, rs, scratch, 100, 0); */ generate_pass(state, rs, scratch, 100, GP_DOTS); game_update_dots(state); #ifdef DEBUGGING { char *tmp = encode_game(state); debug(("new_game_desc state %dx%d:%s\n", params->w, params->h, tmp)); sfree(tmp); } #endif for (i = 0; i < state->sx*state->sy; i++) if (state->grid[i].type == s_tile) outline_tile_fordot(state, &state->grid[i], TRUE); cc = check_complete(state, NULL, NULL); assert(cc); copy = dup_game(state); clear_game(copy, 0); dbg_state(copy); diff = solver_state(copy, params->diff); free_game(copy); assert(diff != DIFF_IMPOSSIBLE); if (diff != params->diff) { /* * We'll grudgingly accept a too-easy puzzle, but we must * _not_ permit a too-hard one (one which the solver * couldn't handle at all). */ if (diff > params->diff || ntries < MAXTRIES) goto generate; } #ifdef STANDALONE_PICTURE_GENERATOR /* * Postprocessing pass to prevent excessive numbers of adjacent * singletons. Iterate over all edges in random shuffled order; * for each edge that separates two regions, investigate * whether removing that edge and merging the regions would * still yield a valid and soluble puzzle. (The two regions * must also be the same colour, of course.) If so, do it. * * This postprocessing pass is slow (due to repeated solver * invocations), and seems to be unnecessary during normal * unconstrained game generation. However, when generating a * game under colour constraints, excessive singletons seem to * turn up more often, so it's worth doing this. */ { int *posns, nposns; int i, j, newdiff; game_state *copy2; nposns = params->w * (params->h+1) + params->h * (params->w+1); posns = snewn(nposns, int); for (i = j = 0; i < state->sx*state->sy; i++) if (state->grid[i].type == s_edge) posns[j++] = i; assert(j == nposns); shuffle(posns, nposns, sizeof(*posns), rs); for (i = 0; i < nposns; i++) { int x, y, x0, y0, x1, y1, cx, cy, cn, cx0, cy0, cx1, cy1, tx, ty; space *s0, *s1, *ts, *d0, *d1, *dn; int ok; /* Coordinates of edge space */ x = posns[i] % state->sx; y = posns[i] / state->sx; /* Coordinates of square spaces on either side of edge */ x0 = ((x+1) & ~1) - 1; /* round down to next odd number */ y0 = ((y+1) & ~1) - 1; x1 = 2*x-x0; /* and reflect about x to get x1 */ y1 = 2*y-y0; if (!INGRID(state, x0, y0) || !INGRID(state, x1, y1)) continue; /* outermost edge of grid */ s0 = &SPACE(state, x0, y0); s1 = &SPACE(state, x1, y1); assert(s0->type == s_tile && s1->type == s_tile); if (s0->dotx == s1->dotx && s0->doty == s1->doty) continue; /* tiles _already_ owned by same dot */ d0 = &SPACE(state, s0->dotx, s0->doty); d1 = &SPACE(state, s1->dotx, s1->doty); if ((d0->flags ^ d1->flags) & F_DOT_BLACK) continue; /* different colours: cannot merge */ /* * Work out where the centre of gravity of the new * region would be. */ cx = d0->nassoc * d0->x + d1->nassoc * d1->x; cy = d0->nassoc * d0->y + d1->nassoc * d1->y; cn = d0->nassoc + d1->nassoc; if (cx % cn || cy % cn) continue; /* CoG not at integer coordinates */ cx /= cn; cy /= cn; assert(INUI(state, cx, cy)); /* * Ensure that the CoG would actually be _in_ the new * region, by verifying that all its surrounding tiles * belong to one or other of our two dots. */ cx0 = ((cx+1) & ~1) - 1; /* round down to next odd number */ cy0 = ((cy+1) & ~1) - 1; cx1 = 2*cx-cx0; /* and reflect about cx to get cx1 */ cy1 = 2*cy-cy0; ok = TRUE; for (ty = cy0; ty <= cy1; ty += 2) for (tx = cx0; tx <= cx1; tx += 2) { ts = &SPACE(state, tx, ty); assert(ts->type == s_tile); if ((ts->dotx != d0->x || ts->doty != d0->y) && (ts->dotx != d1->x || ts->doty != d1->y)) ok = FALSE; } if (!ok) continue; /* * Verify that for every tile in either source region, * that tile's image in the new CoG is also in one of * the two source regions. */ for (ty = 1; ty < state->sy; ty += 2) { for (tx = 1; tx < state->sx; tx += 2) { int tx1, ty1; ts = &SPACE(state, tx, ty); assert(ts->type == s_tile); if ((ts->dotx != d0->x || ts->doty != d0->y) && (ts->dotx != d1->x || ts->doty != d1->y)) continue; /* not part of these tiles anyway */ tx1 = 2*cx-tx; ty1 = 2*cy-ty; if (!INGRID(state, tx1, ty1)) { ok = FALSE; break; } ts = &SPACE(state, cx+cx-tx, cy+cy-ty); if ((ts->dotx != d0->x || ts->doty != d0->y) && (ts->dotx != d1->x || ts->doty != d1->y)) { ok = FALSE; break; } } if (!ok) break; } if (!ok) continue; /* * Now we're clear to attempt the merge. We take a copy * of the game state first, so we can revert it easily * if the resulting puzzle turns out to have become * insoluble. */ copy2 = dup_game(state); remove_dot(d0); remove_dot(d1); dn = &SPACE(state, cx, cy); add_dot(dn); dn->flags |= (d0->flags & F_DOT_BLACK); for (ty = 1; ty < state->sy; ty += 2) { for (tx = 1; tx < state->sx; tx += 2) { ts = &SPACE(state, tx, ty); assert(ts->type == s_tile); if ((ts->dotx != d0->x || ts->doty != d0->y) && (ts->dotx != d1->x || ts->doty != d1->y)) continue; /* not part of these tiles anyway */ add_assoc(state, ts, dn); } } copy = dup_game(state); clear_game(copy, 0); dbg_state(copy); newdiff = solver_state(copy, params->diff); free_game(copy); if (diff == newdiff) { /* Still just as soluble. Let the merge stand. */ free_game(copy2); } else { /* Became insoluble. Revert. */ free_game(state); state = copy2; } } sfree(posns); } #endif desc = encode_game(state); #ifndef STANDALONE_SOLVER debug(("new_game_desc generated: \n")); dbg_state(state); #endif free_game(state); sfree(scratch); return desc; } static int solver_obvious(game_state *state); static int dots_too_close(game_state *state) { /* Quick-and-dirty check, using half the solver: * solver_obvious will only fail if the dots are * too close together, so dot-proximity associations * overlap. */ game_state *tmp = dup_game(state); int ret = solver_obvious(tmp); free_game(tmp); return (ret == -1) ? 1 : 0; } static game_state *load_game(game_params *params, char *desc, char **why_r) { game_state *state = blank_game(params->w, params->h); char *why = NULL; int i, x, y, n; unsigned int df; i = 0; while (*desc) { n = *desc++; if (n == 'z') { i += 25; continue; } if (n >= 'a' && n <= 'y') { i += n - 'a'; df = 0; } else if (n >= 'A' && n <= 'Y') { i += n - 'A'; df = F_DOT_BLACK; } else { why = "Invalid characters in game description"; goto fail; } /* if we got here we incremented i and have a dot to add. */ y = (i / (state->sx-2)) + 1; x = (i % (state->sx-2)) + 1; if (!INUI(state, x, y)) { why = "Too much data to fit in grid"; goto fail; } add_dot(&SPACE(state, x, y)); SPACE(state, x, y).flags |= df; i++; } game_update_dots(state); if (dots_too_close(state)) { why = "Dots too close together"; goto fail; } return state; fail: free_game(state); if (why_r) *why_r = why; return NULL; } static char *validate_desc(game_params *params, char *desc) { char *why = NULL; game_state *dummy = load_game(params, desc, &why); if (dummy) { free_game(dummy); assert(!why); } else assert(why); return why; } static game_state *new_game(midend *me, game_params *params, char *desc) { game_state *state = load_game(params, desc, NULL); if (!state) { assert("Unable to load ?validated game."); return NULL; } #ifdef EDITOR state->me = me; #endif return state; } /* ---------------------------------------------------------- * Solver and all its little wizards. */ int solver_recurse_depth; typedef struct solver_ctx { game_state *state; int sz; /* state->sx * state->sy */ space **scratch; /* size sz */ } solver_ctx; static solver_ctx *new_solver(game_state *state) { solver_ctx *sctx = snew(solver_ctx); sctx->state = state; sctx->sz = state->sx*state->sy; sctx->scratch = snewn(sctx->sz, space *); return sctx; } static void free_solver(solver_ctx *sctx) { sfree(sctx->scratch); sfree(sctx); } /* Solver ideas so far: * * For any empty space, work out how many dots it could associate * with: * it needs line-of-sight * it needs an empty space on the far side * any adjacent lines need corresponding line possibilities. */ /* The solver_ctx should keep a list of dot positions, for quicker looping. * * Solver techniques, in order of difficulty: * obvious adjacency to dots * transferring tiles to opposite side * transferring lines to opposite side * one possible dot for a given tile based on opposite availability * tile with 3 definite edges next to an associated tile must associate with same dot. * * one possible dot for a given tile based on line-of-sight */ static int solver_add_assoc(game_state *state, space *tile, int dx, int dy, const char *why) { space *dot, *tile_opp; dot = &SPACE(state, dx, dy); tile_opp = space_opposite_dot(state, tile, dot); assert(tile->type == s_tile); if (tile->flags & F_TILE_ASSOC) { if ((tile->dotx != dx) || (tile->doty != dy)) { solvep(("%*sSet %d,%d --> %d,%d (%s) impossible; " "already --> %d,%d.\n", solver_recurse_depth*4, "", tile->x, tile->y, dx, dy, why, tile->dotx, tile->doty)); return -1; } return 0; /* no-op */ } if (!tile_opp) { solvep(("%*s%d,%d --> %d,%d impossible, no opposite tile.\n", solver_recurse_depth*4, "", tile->x, tile->y, dx, dy)); return -1; } if (tile_opp->flags & F_TILE_ASSOC && (tile_opp->dotx != dx || tile_opp->doty != dy)) { solvep(("%*sSet %d,%d --> %d,%d (%s) impossible; " "opposite already --> %d,%d.\n", solver_recurse_depth*4, "", tile->x, tile->y, dx, dy, why, tile_opp->dotx, tile_opp->doty)); return -1; } add_assoc(state, tile, dot); add_assoc(state, tile_opp, dot); solvep(("%*sSetting %d,%d --> %d,%d (%s).\n", solver_recurse_depth*4, "", tile->x, tile->y,dx, dy, why)); solvep(("%*sSetting %d,%d --> %d,%d (%s, opposite).\n", solver_recurse_depth*4, "", tile_opp->x, tile_opp->y, dx, dy, why)); return 1; } static int solver_obvious_dot(game_state *state, space *dot) { int dx, dy, ret, didsth = 0; space *tile; debug(("%*ssolver_obvious_dot for %d,%d.\n", solver_recurse_depth*4, "", dot->x, dot->y)); assert(dot->flags & F_DOT); for (dx = -1; dx <= 1; dx++) { for (dy = -1; dy <= 1; dy++) { if (!INGRID(state, dot->x+dx, dot->y+dy)) continue; tile = &SPACE(state, dot->x+dx, dot->y+dy); if (tile->type == s_tile) { ret = solver_add_assoc(state, tile, dot->x, dot->y, "next to dot"); if (ret < 0) return -1; if (ret > 0) didsth = 1; } } } return didsth; } static int solver_obvious(game_state *state) { int i, didsth = 0, ret; debug(("%*ssolver_obvious.\n", solver_recurse_depth*4, "")); for (i = 0; i < state->ndots; i++) { ret = solver_obvious_dot(state, state->dots[i]); if (ret < 0) return -1; if (ret > 0) didsth = 1; } return didsth; } static int solver_lines_opposite_cb(game_state *state, space *edge, void *ctx) { int didsth = 0, n, dx, dy; space *tiles[2], *tile_opp, *edge_opp; assert(edge->type == s_edge); tiles_from_edge(state, edge, tiles); /* if tiles[0] && tiles[1] && they're both associated * and they're both associated with different dots, * ensure the line is set. */ if (!(edge->flags & F_EDGE_SET) && tiles[0] && tiles[1] && (tiles[0]->flags & F_TILE_ASSOC) && (tiles[1]->flags & F_TILE_ASSOC) && (tiles[0]->dotx != tiles[1]->dotx || tiles[0]->doty != tiles[1]->doty)) { /* No edge, but the two adjacent tiles are both * associated with different dots; add the edge. */ solvep(("%*sSetting edge %d,%d - tiles different dots.\n", solver_recurse_depth*4, "", edge->x, edge->y)); edge->flags |= F_EDGE_SET; didsth = 1; } if (!(edge->flags & F_EDGE_SET)) return didsth; for (n = 0; n < 2; n++) { if (!tiles[n]) continue; assert(tiles[n]->type == s_tile); if (!(tiles[n]->flags & F_TILE_ASSOC)) continue; tile_opp = tile_opposite(state, tiles[n]); if (!tile_opp) { solvep(("%*simpossible: edge %d,%d has assoc. tile %d,%d" " with no opposite.\n", solver_recurse_depth*4, "", edge->x, edge->y, tiles[n]->x, tiles[n]->y)); /* edge of tile has no opposite edge (off grid?); * this is impossible. */ return -1; } dx = tiles[n]->x - edge->x; dy = tiles[n]->y - edge->y; assert(INGRID(state, tile_opp->x+dx, tile_opp->y+dy)); edge_opp = &SPACE(state, tile_opp->x+dx, tile_opp->y+dy); if (!(edge_opp->flags & F_EDGE_SET)) { solvep(("%*sSetting edge %d,%d as opposite %d,%d\n", solver_recurse_depth*4, "", tile_opp->x-dx, tile_opp->y-dy, edge->x, edge->y)); edge_opp->flags |= F_EDGE_SET; didsth = 1; } } return didsth; } static int solver_spaces_oneposs_cb(game_state *state, space *tile, void *ctx) { int n, eset, ret; struct space *edgeadj[4], *tileadj[4]; int dotx, doty; assert(tile->type == s_tile); if (tile->flags & F_TILE_ASSOC) return 0; adjacencies(state, tile, edgeadj, tileadj); /* Empty tile. If each edge is either set, or associated with * the same dot, we must also associate with dot. */ eset = 0; dotx = -1; doty = -1; for (n = 0; n < 4; n++) { assert(edgeadj[n]); assert(edgeadj[n]->type == s_edge); if (edgeadj[n]->flags & F_EDGE_SET) { eset++; } else { assert(tileadj[n]); assert(tileadj[n]->type == s_tile); /* If an adjacent tile is empty we can't make any deductions.*/ if (!(tileadj[n]->flags & F_TILE_ASSOC)) return 0; /* If an adjacent tile is assoc. with a different dot * we can't make any deductions. */ if (dotx != -1 && doty != -1 && (tileadj[n]->dotx != dotx || tileadj[n]->doty != doty)) return 0; dotx = tileadj[n]->dotx; doty = tileadj[n]->doty; } } if (eset == 4) { solvep(("%*simpossible: empty tile %d,%d has 4 edges\n", solver_recurse_depth*4, "", tile->x, tile->y)); return -1; } assert(dotx != -1 && doty != -1); ret = solver_add_assoc(state, tile, dotx, doty, "rest are edges"); if (ret == -1) return -1; assert(ret != 0); /* really should have done something. */ return 1; } /* Improved algorithm for tracking line-of-sight from dots, and not spaces. * * The solver_ctx already stores a list of dots: the algorithm proceeds by * expanding outwards from each dot in turn, expanding first to the boundary * of its currently-connected tile and then to all empty tiles that could see * it. Empty tiles will be flagged with a 'can see dot <x,y>' sticker. * * Expansion will happen by (symmetrically opposite) pairs of squares; if * a square hasn't an opposite number there's no point trying to expand through * it. Empty tiles will therefore also be tagged in pairs. * * If an empty tile already has a 'can see dot <x,y>' tag from a previous dot, * it (and its partner) gets untagged (or, rather, a 'can see two dots' tag) * because we're looking for single-dot possibilities. * * Once we've gone through all the dots, any which still have a 'can see dot' * tag get associated with that dot (because it must have been the only one); * any without any tag (i.e. that could see _no_ dots) cause an impossibility * marked. * * The expansion will happen each time with a stored list of (space *) pairs, * rather than a mark-and-sweep idea; that's horrifically inefficient. * * expansion algorithm: * * * allocate list of (space *) the size of s->sx*s->sy. * * allocate second grid for (flags, dotx, doty) size of sx*sy. * * clear second grid (flags = 0, all dotx and doty = 0) * flags: F_REACHABLE, F_MULTIPLE * * * * for each dot, start with one pair of tiles that are associated with it -- * * vertex --> (dx+1, dy+1), (dx-1, dy-1) * * edge --> (adj1, adj2) * * tile --> (tile, tile) ??? * * mark that pair of tiles with F_MARK, clear all other F_MARKs. * * add two tiles to start of list. * * set start = 0, end = next = 2 * * from (start to end-1, step 2) { * * we have two tiles (t1, t2), opposites wrt our dot. * * for each (at1) sensible adjacent tile to t1 (i.e. not past an edge): * * work out at2 as the opposite to at1 * * assert at1 and at2 have the same F_MARK values. * * if at1 & F_MARK ignore it (we've been there on a previous sweep) * * if either are associated with a different dot * * mark both with F_MARK (so we ignore them later) * * otherwise (assoc. with our dot, or empty): * * mark both with F_MARK * * add their space * values to the end of the list, set next += 2. * } * * if (end == next) * * we didn't add any new squares; exit the loop. * else * * set start = next+1, end = next. go round again * * We've finished expanding from the dot. Now, for each square we have * in our list (--> each square with F_MARK): * * if the tile is empty: * * if F_REACHABLE was already set * * set F_MULTIPLE * * otherwise * * set F_REACHABLE, set dotx and doty to our dot. * * Then, continue the whole thing for each dot in turn. * * Once we've done for each dot, go through the entire grid looking for * empty tiles: for each empty tile: * if F_REACHABLE and not F_MULTIPLE, set that dot (and its double) * if !F_REACHABLE, return as impossible. * */ /* Returns 1 if this tile is either already associated with this dot, * or blank. */ static int solver_expand_checkdot(space *tile, space *dot) { if (!(tile->flags & F_TILE_ASSOC)) return 1; if (tile->dotx == dot->x && tile->doty == dot->y) return 1; return 0; } static void solver_expand_fromdot(game_state *state, space *dot, solver_ctx *sctx) { int i, j, x, y, start, end, next; space *sp; /* Clear the grid of the (space) flags we'll use. */ /* This is well optimised; analysis showed that: for (i = 0; i < sctx->sz; i++) state->grid[i].flags &= ~F_MARK; took up ~85% of the total function time! */ for (y = 1; y < state->sy; y += 2) { sp = &SPACE(state, 1, y); for (x = 1; x < state->sx; x += 2, sp += 2) sp->flags &= ~F_MARK; } /* Seed the list of marked squares with two that must be associated * with our dot (possibly the same space) */ if (dot->type == s_tile) { sctx->scratch[0] = sctx->scratch[1] = dot; } else if (dot->type == s_edge) { tiles_from_edge(state, dot, sctx->scratch); } else if (dot->type == s_vertex) { /* pick two of the opposite ones arbitrarily. */ sctx->scratch[0] = &SPACE(state, dot->x-1, dot->y-1); sctx->scratch[1] = &SPACE(state, dot->x+1, dot->y+1); } assert(sctx->scratch[0]->flags & F_TILE_ASSOC); assert(sctx->scratch[1]->flags & F_TILE_ASSOC); sctx->scratch[0]->flags |= F_MARK; sctx->scratch[1]->flags |= F_MARK; debug(("%*sexpand from dot %d,%d seeded with %d,%d and %d,%d.\n", solver_recurse_depth*4, "", dot->x, dot->y, sctx->scratch[0]->x, sctx->scratch[0]->y, sctx->scratch[1]->x, sctx->scratch[1]->y)); start = 0; end = 2; next = 2; expand: debug(("%*sexpand: start %d, end %d, next %d\n", solver_recurse_depth*4, "", start, end, next)); for (i = start; i < end; i += 2) { space *t1 = sctx->scratch[i]/*, *t2 = sctx->scratch[i+1]*/; space *edges[4], *tileadj[4], *tileadj2; adjacencies(state, t1, edges, tileadj); for (j = 0; j < 4; j++) { assert(edges[j]); if (edges[j]->flags & F_EDGE_SET) continue; assert(tileadj[j]); if (tileadj[j]->flags & F_MARK) continue; /* seen before. */ /* We have a tile adjacent to t1; find its opposite. */ tileadj2 = space_opposite_dot(state, tileadj[j], dot); if (!tileadj2) { debug(("%*sMarking %d,%d, no opposite.\n", solver_recurse_depth*4, "", tileadj[j]->x, tileadj[j]->y)); tileadj[j]->flags |= F_MARK; continue; /* no opposite, so mark for next time. */ } /* If the tile had an opposite we should have either seen both of * these, or neither of these, before. */ assert(!(tileadj2->flags & F_MARK)); if (solver_expand_checkdot(tileadj[j], dot) && solver_expand_checkdot(tileadj2, dot)) { /* Both tiles could associate with this dot; add them to * our list. */ debug(("%*sAdding %d,%d and %d,%d to possibles list.\n", solver_recurse_depth*4, "", tileadj[j]->x, tileadj[j]->y, tileadj2->x, tileadj2->y)); sctx->scratch[next++] = tileadj[j]; sctx->scratch[next++] = tileadj2; } /* Either way, we've seen these tiles already so mark them. */ debug(("%*sMarking %d,%d and %d,%d.\n", solver_recurse_depth*4, "", tileadj[j]->x, tileadj[j]->y, tileadj2->x, tileadj2->y)); tileadj[j]->flags |= F_MARK; tileadj2->flags |= F_MARK; } } if (next > end) { /* We added more squares; go back and try again. */ start = end; end = next; goto expand; } /* We've expanded as far as we can go. Now we update the main flags * on all tiles we've expanded into -- if they were empty, we have * found possible associations for this dot. */ for (i = 0; i < end; i++) { if (sctx->scratch[i]->flags & F_TILE_ASSOC) continue; if (sctx->scratch[i]->flags & F_REACHABLE) { /* This is (at least) the second dot this tile could * associate with. */ debug(("%*sempty tile %d,%d could assoc. other dot %d,%d\n", solver_recurse_depth*4, "", sctx->scratch[i]->x, sctx->scratch[i]->y, dot->x, dot->y)); sctx->scratch[i]->flags |= F_MULTIPLE; } else { /* This is the first (possibly only) dot. */ debug(("%*sempty tile %d,%d could assoc. 1st dot %d,%d\n", solver_recurse_depth*4, "", sctx->scratch[i]->x, sctx->scratch[i]->y, dot->x, dot->y)); sctx->scratch[i]->flags |= F_REACHABLE; sctx->scratch[i]->dotx = dot->x; sctx->scratch[i]->doty = dot->y; } } dbg_state(state); } static int solver_expand_postcb(game_state *state, space *tile, void *ctx) { assert(tile->type == s_tile); if (tile->flags & F_TILE_ASSOC) return 0; if (!(tile->flags & F_REACHABLE)) { solvep(("%*simpossible: space (%d,%d) can reach no dots.\n", solver_recurse_depth*4, "", tile->x, tile->y)); return -1; } if (tile->flags & F_MULTIPLE) return 0; return solver_add_assoc(state, tile, tile->dotx, tile->doty, "single possible dot after expansion"); } static int solver_expand_dots(game_state *state, solver_ctx *sctx) { int i; for (i = 0; i < sctx->sz; i++) state->grid[i].flags &= ~(F_REACHABLE|F_MULTIPLE); for (i = 0; i < state->ndots; i++) solver_expand_fromdot(state, state->dots[i], sctx); return foreach_tile(state, solver_expand_postcb, IMPOSSIBLE_QUITS, sctx); } struct recurse_ctx { space *best; int bestn; }; static int solver_recurse_cb(game_state *state, space *tile, void *ctx) { struct recurse_ctx *rctx = (struct recurse_ctx *)ctx; int i, n = 0; assert(tile->type == s_tile); if (tile->flags & F_TILE_ASSOC) return 0; /* We're unassociated: count up all the dots we could associate with. */ for (i = 0; i < state->ndots; i++) { if (dotfortile(state, tile, state->dots[i])) n++; } if (n > rctx->bestn) { rctx->bestn = n; rctx->best = tile; } return 0; } static int solver_state(game_state *state, int maxdiff); #define MAXRECURSE 5 static int solver_recurse(game_state *state, int maxdiff) { int diff = DIFF_IMPOSSIBLE, ret, n, gsz = state->sx * state->sy; space *ingrid, *outgrid = NULL, *bestopp; struct recurse_ctx rctx; if (solver_recurse_depth >= MAXRECURSE) { solvep(("Limiting recursion to %d, returning.", MAXRECURSE)); return DIFF_UNFINISHED; } /* Work out the cell to recurse on; go through all unassociated tiles * and find which one has the most possible dots it could associate * with. */ rctx.best = NULL; rctx.bestn = 0; foreach_tile(state, solver_recurse_cb, 0, &rctx); if (rctx.bestn == 0) return DIFF_IMPOSSIBLE; /* or assert? */ assert(rctx.best); solvep(("%*sRecursing around %d,%d, with %d possible dots.\n", solver_recurse_depth*4, "", rctx.best->x, rctx.best->y, rctx.bestn)); #ifdef STANDALONE_SOLVER solver_recurse_depth++; #endif ingrid = snewn(gsz, struct space); memcpy(ingrid, state->grid, gsz * sizeof(struct space)); for (n = 0; n < state->ndots; n++) { memcpy(state->grid, ingrid, gsz * sizeof(struct space)); if (!dotfortile(state, rctx.best, state->dots[n])) continue; /* set cell (temporarily) pointing to that dot. */ solver_add_assoc(state, rctx.best, state->dots[n]->x, state->dots[n]->y, "Attempting for recursion"); ret = solver_state(state, maxdiff); if (diff == DIFF_IMPOSSIBLE && ret != DIFF_IMPOSSIBLE) { /* we found our first solved grid; copy it away. */ assert(!outgrid); outgrid = snewn(gsz, struct space); memcpy(outgrid, state->grid, gsz * sizeof(struct space)); } /* reset cell back to unassociated. */ bestopp = tile_opposite(state, rctx.best); assert(bestopp && bestopp->flags & F_TILE_ASSOC); remove_assoc(state, rctx.best); remove_assoc(state, bestopp); if (ret == DIFF_AMBIGUOUS || ret == DIFF_UNFINISHED) diff = ret; else if (ret == DIFF_IMPOSSIBLE) /* no change */; else { /* precisely one solution */ if (diff == DIFF_IMPOSSIBLE) diff = DIFF_UNREASONABLE; else diff = DIFF_AMBIGUOUS; } /* if we've found >1 solution, or ran out of recursion, * give up immediately. */ if (diff == DIFF_AMBIGUOUS || diff == DIFF_UNFINISHED) break; } #ifdef STANDALONE_SOLVER solver_recurse_depth--; #endif if (outgrid) { /* we found (at least one) soln; copy it back to state */ memcpy(state->grid, outgrid, gsz * sizeof(struct space)); sfree(outgrid); } sfree(ingrid); return diff; } static int solver_state(game_state *state, int maxdiff) { solver_ctx *sctx = new_solver(state); int ret, diff = DIFF_NORMAL; #ifdef STANDALONE_PICTURE_GENERATOR /* hack, hack: set picture to NULL during solving so that add_assoc * won't complain when we attempt recursive guessing and guess wrong */ int *savepic = picture; picture = NULL; #endif ret = solver_obvious(state); if (ret < 0) { diff = DIFF_IMPOSSIBLE; goto got_result; } #define CHECKRET(d) do { \ if (ret < 0) { diff = DIFF_IMPOSSIBLE; goto got_result; } \ if (ret > 0) { diff = max(diff, (d)); goto cont; } \ } while(0) while (1) { cont: ret = foreach_edge(state, solver_lines_opposite_cb, IMPOSSIBLE_QUITS, sctx); CHECKRET(DIFF_NORMAL); ret = foreach_tile(state, solver_spaces_oneposs_cb, IMPOSSIBLE_QUITS, sctx); CHECKRET(DIFF_NORMAL); ret = solver_expand_dots(state, sctx); CHECKRET(DIFF_NORMAL); if (maxdiff <= DIFF_NORMAL) break; /* harder still? */ /* if we reach here, we've made no deductions, so we terminate. */ break; } if (check_complete(state, NULL, NULL)) goto got_result; diff = (maxdiff >= DIFF_UNREASONABLE) ? solver_recurse(state, maxdiff) : DIFF_UNFINISHED; got_result: free_solver(sctx); #ifndef STANDALONE_SOLVER debug(("solver_state ends, diff %s:\n", galaxies_diffnames[diff])); dbg_state(state); #endif #ifdef STANDALONE_PICTURE_GENERATOR picture = savepic; #endif return diff; } #ifndef EDITOR static char *solve_game(game_state *state, game_state *currstate, char *aux, char **error) { game_state *tosolve; char *ret; int i; int diff; tosolve = dup_game(currstate); diff = solver_state(tosolve, DIFF_UNREASONABLE); if (diff != DIFF_UNFINISHED && diff != DIFF_IMPOSSIBLE) { debug(("solve_game solved with current state.\n")); goto solved; } free_game(tosolve); tosolve = dup_game(state); diff = solver_state(tosolve, DIFF_UNREASONABLE); if (diff != DIFF_UNFINISHED && diff != DIFF_IMPOSSIBLE) { debug(("solve_game solved with original state.\n")); goto solved; } free_game(tosolve); return NULL; solved: /* * Clear tile associations: the solution will only include the * edges. */ for (i = 0; i < tosolve->sx*tosolve->sy; i++) tosolve->grid[i].flags &= ~F_TILE_ASSOC; ret = diff_game(currstate, tosolve, 1); free_game(tosolve); return ret; } #endif /* ---------------------------------------------------------- * User interface. */ struct game_ui { int dragging; int dx, dy; /* pixel coords of drag pos. */ int dotx, doty; /* grid coords of dot we're dragging from. */ int srcx, srcy; /* grid coords of drag start */ int cur_x, cur_y, cur_visible; }; static game_ui *new_ui(game_state *state) { game_ui *ui = snew(game_ui); ui->dragging = FALSE; ui->cur_x = ui->cur_y = 1; ui->cur_visible = 0; return ui; } static void free_ui(game_ui *ui) { sfree(ui); } static char *encode_ui(game_ui *ui) { return NULL; } static void decode_ui(game_ui *ui, char *encoding) { } static void game_changed_state(game_ui *ui, game_state *oldstate, game_state *newstate) { } #define FLASH_TIME 0.15F #define PREFERRED_TILE_SIZE 32 #define TILE_SIZE (ds->tilesize) #define DOT_SIZE (TILE_SIZE / 4) #define EDGE_THICKNESS (max(TILE_SIZE / 16, 2)) #define BORDER TILE_SIZE #define COORD(x) ( (x) * TILE_SIZE + BORDER ) #define SCOORD(x) ( ((x) * TILE_SIZE)/2 + BORDER ) #define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE ) #define DRAW_WIDTH (BORDER * 2 + ds->w * TILE_SIZE) #define DRAW_HEIGHT (BORDER * 2 + ds->h * TILE_SIZE) #define CURSOR_SIZE DOT_SIZE struct game_drawstate { int started; int w, h; int tilesize; unsigned long *grid; int *dx, *dy; blitter *bl; int dragging, dragx, dragy; int *colour_scratch; int cx, cy, cur_visible; blitter *cur_bl; }; #define CORNER_TOLERANCE 0.15F #define CENTRE_TOLERANCE 0.15F /* * Round FP coordinates to the centre of the nearest edge. */ #ifndef EDITOR static void coord_round_to_edge(float x, float y, int *xr, int *yr) { float xs, ys, xv, yv, dx, dy; /* * Find the nearest square-centre. */ xs = (float)floor(x) + 0.5F; ys = (float)floor(y) + 0.5F; /* * Find the nearest grid vertex. */ xv = (float)floor(x + 0.5F); yv = (float)floor(y + 0.5F); /* * Determine whether the horizontal or vertical edge from that * vertex alongside that square is closer to us, by comparing * distances from the square cente. */ dx = (float)fabs(x - xs); dy = (float)fabs(y - ys); if (dx > dy) { /* Vertical edge: x-coord of corner, * y-coord of square centre. */ *xr = 2 * (int)xv; *yr = 1 + 2 * (int)floor(ys); } else { /* Horizontal edge: x-coord of square centre, * y-coord of corner. */ *xr = 1 + 2 * (int)floor(xs); *yr = 2 * (int)yv; } } #endif #ifdef EDITOR static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, int x, int y, int button) { char buf[80]; int px, py; struct space *sp; px = 2*FROMCOORD((float)x) + 0.5; py = 2*FROMCOORD((float)y) + 0.5; state->cdiff = -1; if (button == 'C' || button == 'c') return dupstr("C"); if (button == 'S' || button == 's') { char *ret; game_state *tmp = dup_game(state); state->cdiff = solver_state(tmp, DIFF_UNREASONABLE-1); ret = diff_game(state, tmp, 0); free_game(tmp); return ret; } if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { if (!INUI(state, px, py)) return NULL; sp = &SPACE(state, px, py); if (!dot_is_possible(state, sp, 1)) return NULL; sprintf(buf, "%c%d,%d", (char)((button == LEFT_BUTTON) ? 'D' : 'd'), px, py); return dupstr(buf); } return NULL; } #else static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, int x, int y, int button) { /* UI operations (play mode): * * Toggle edge (set/unset) (left-click on edge) * Associate space with dot (left-drag from dot) * Unassociate space (left-drag from space off grid) * Autofill lines around shape? (right-click?) * * (edit mode; will clear all lines/associations) * * Add or remove dot (left-click) */ char buf[80]; const char *sep = ""; int px, py; struct space *sp, *dot; buf[0] = '\0'; if (button == 'H' || button == 'h') { char *ret; game_state *tmp = dup_game(state); solver_obvious(tmp); ret = diff_game(state, tmp, 0); free_game(tmp); return ret; } if (button == LEFT_BUTTON) { ui->cur_visible = 0; coord_round_to_edge(FROMCOORD((float)x), FROMCOORD((float)y), &px, &py); if (!INUI(state, px, py)) return NULL; sp = &SPACE(state, px, py); assert(sp->type == s_edge); { sprintf(buf, "E%d,%d", px, py); return dupstr(buf); } } else if (button == RIGHT_BUTTON) { int px1, py1; ui->cur_visible = 0; px = (int)(2*FROMCOORD((float)x) + 0.5); py = (int)(2*FROMCOORD((float)y) + 0.5); dot = NULL; /* * If there's a dot anywhere nearby, we pick up an arrow * pointing at that dot. */ for (py1 = py-1; py1 <= py+1; py1++) for (px1 = px-1; px1 <= px+1; px1++) { if (px1 >= 0 && px1 < state->sx && py1 >= 0 && py1 < state->sy && x >= SCOORD(px1-1) && x < SCOORD(px1+1) && y >= SCOORD(py1-1) && y < SCOORD(py1+1) && SPACE(state, px1, py1).flags & F_DOT) { /* * Found a dot. Begin a drag from it. */ dot = &SPACE(state, px1, py1); ui->srcx = px1; ui->srcy = py1; goto done; /* multi-level break */ } } /* * Otherwise, find the nearest _square_, and pick up the * same arrow as it's got on it, if any. */ if (!dot) { px = 2*FROMCOORD(x+TILE_SIZE) - 1; py = 2*FROMCOORD(y+TILE_SIZE) - 1; if (px >= 0 && px < state->sx && py >= 0 && py < state->sy) { sp = &SPACE(state, px, py); if (sp->flags & F_TILE_ASSOC) { dot = &SPACE(state, sp->dotx, sp->doty); ui->srcx = px; ui->srcy = py; } } } done: /* * Now, if we've managed to find a dot, begin a drag. */ if (dot) { ui->dragging = TRUE; ui->dx = x; ui->dy = y; ui->dotx = dot->x; ui->doty = dot->y; return ""; } } else if (button == RIGHT_DRAG && ui->dragging) { /* just move the drag coords. */ ui->dx = x; ui->dy = y; return ""; } else if (button == RIGHT_RELEASE && ui->dragging) { ui->dragging = FALSE; /* * Drags are always targeted at a single square. */ px = 2*FROMCOORD(x+TILE_SIZE) - 1; py = 2*FROMCOORD(y+TILE_SIZE) - 1; /* * Dragging an arrow on to the same square it started from * is a null move; just update the ui and finish. */ if (px == ui->srcx && py == ui->srcy) return ""; /* * Otherwise, we remove the arrow from its starting * square if we didn't start from a dot... */ if ((ui->srcx != ui->dotx || ui->srcy != ui->doty) && SPACE(state, ui->srcx, ui->srcy).flags & F_TILE_ASSOC) { sprintf(buf + strlen(buf), "%sU%d,%d", sep, ui->srcx, ui->srcy); sep = ";"; } /* * ... and if the square we're moving it _to_ is valid, we * add one there instead. */ if (INUI(state, px, py)) { sp = &SPACE(state, px, py); if (!(sp->flags & F_DOT) && !(sp->flags & F_TILE_ASSOC)) sprintf(buf + strlen(buf), "%sA%d,%d,%d,%d", sep, px, py, ui->dotx, ui->doty); } if (buf[0]) return dupstr(buf); else return ""; } else if (IS_CURSOR_MOVE(button)) { move_cursor(button, &ui->cur_x, &ui->cur_y, state->sx-1, state->sy-1, 0); if (ui->cur_x < 1) ui->cur_x = 1; if (ui->cur_y < 1) ui->cur_y = 1; ui->cur_visible = 1; if (ui->dragging) { ui->dx = SCOORD(ui->cur_x); ui->dy = SCOORD(ui->cur_y); } return ""; } else if (IS_CURSOR_SELECT(button)) { if (!ui->cur_visible) { ui->cur_visible = 1; return ""; } sp = &SPACE(state, ui->cur_x, ui->cur_y); if (ui->dragging) { ui->dragging = FALSE; if ((ui->srcx != ui->dotx || ui->srcy != ui->doty) && SPACE(state, ui->srcx, ui->srcy).flags & F_TILE_ASSOC) { sprintf(buf, "%sU%d,%d", sep, ui->srcx, ui->srcy); sep = ";"; } if (sp->type == s_tile && !(sp->flags & F_DOT) && !(sp->flags & F_TILE_ASSOC)) { sprintf(buf + strlen(buf), "%sA%d,%d,%d,%d", sep, ui->cur_x, ui->cur_y, ui->dotx, ui->doty); } return dupstr(buf); } else if (sp->flags & F_DOT) { ui->dragging = TRUE; ui->dx = SCOORD(ui->cur_x); ui->dy = SCOORD(ui->cur_y); ui->dotx = ui->srcx = ui->cur_x; ui->doty = ui->srcy = ui->cur_y; return ""; } else if (sp->flags & F_TILE_ASSOC) { assert(sp->type == s_tile); ui->dragging = TRUE; ui->dx = SCOORD(ui->cur_x); ui->dy = SCOORD(ui->cur_y); ui->dotx = sp->dotx; ui->doty = sp->doty; ui->srcx = ui->cur_x; ui->srcy = ui->cur_y; return ""; } else if (sp->type == s_edge) { sprintf(buf, "E%d,%d", ui->cur_x, ui->cur_y); return dupstr(buf); } } return NULL; } #endif static int check_complete(game_state *state, int *dsf, int *colours) { int w = state->w, h = state->h; int x, y, i, ret; int free_dsf; struct sqdata { int minx, miny, maxx, maxy; int cx, cy; int valid, colour; } *sqdata; if (!dsf) { dsf = snew_dsf(w*h); free_dsf = TRUE; } else { dsf_init(dsf, w*h); free_dsf = FALSE; } /* * During actual game play, completion checking is done on the * basis of the edges rather than the square associations. So * first we must go through the grid figuring out the connected * components into which the edges divide it. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { if (y+1 < h && !(SPACE(state, 2*x+1, 2*y+2).flags & F_EDGE_SET)) dsf_merge(dsf, y*w+x, (y+1)*w+x); if (x+1 < w && !(SPACE(state, 2*x+2, 2*y+1).flags & F_EDGE_SET)) dsf_merge(dsf, y*w+x, y*w+(x+1)); } /* * That gives us our connected components. Now, for each * component, decide whether it's _valid_. A valid component is * one which: * * - is 180-degree rotationally symmetric * - has a dot at its centre of symmetry * - has no other dots anywhere within it (including on its * boundary) * - contains no internal edges (i.e. edges separating two * squares which are both part of the component). */ /* * First, go through the grid finding the bounding box of each * component. */ sqdata = snewn(w*h, struct sqdata); for (i = 0; i < w*h; i++) { sqdata[i].minx = w+1; sqdata[i].miny = h+1; sqdata[i].maxx = sqdata[i].maxy = -1; sqdata[i].valid = FALSE; } for (y = 0; y < h; y++) for (x = 0; x < w; x++) { i = dsf_canonify(dsf, y*w+x); if (sqdata[i].minx > x) sqdata[i].minx = x; if (sqdata[i].maxx < x) sqdata[i].maxx = x; if (sqdata[i].miny > y) sqdata[i].miny = y; if (sqdata[i].maxy < y) sqdata[i].maxy = y; sqdata[i].valid = TRUE; } /* * Now we're in a position to loop over each actual component * and figure out where its centre of symmetry has to be if * it's anywhere. */ for (i = 0; i < w*h; i++) if (sqdata[i].valid) { int cx, cy; cx = sqdata[i].cx = sqdata[i].minx + sqdata[i].maxx + 1; cy = sqdata[i].cy = sqdata[i].miny + sqdata[i].maxy + 1; if (!(SPACE(state, sqdata[i].cx, sqdata[i].cy).flags & F_DOT)) sqdata[i].valid = FALSE; /* no dot at centre of symmetry */ if (dsf_canonify(dsf, (cy-1)/2*w+(cx-1)/2) != i || dsf_canonify(dsf, (cy)/2*w+(cx-1)/2) != i || dsf_canonify(dsf, (cy-1)/2*w+(cx)/2) != i || dsf_canonify(dsf, (cy)/2*w+(cx)/2) != i) sqdata[i].valid = FALSE; /* dot at cx,cy isn't ours */ if (SPACE(state, sqdata[i].cx, sqdata[i].cy).flags & F_DOT_BLACK) sqdata[i].colour = 2; else sqdata[i].colour = 1; } /* * Now we loop over the whole grid again, this time finding * extraneous dots (any dot which wholly or partially overlaps * a square and is not at the centre of symmetry of that * square's component disqualifies the component from validity) * and extraneous edges (any edge separating two squares * belonging to the same component also disqualifies that * component). */ for (y = 1; y < state->sy-1; y++) for (x = 1; x < state->sx-1; x++) { space *sp = &SPACE(state, x, y); if (sp->flags & F_DOT) { /* * There's a dot here. Use it to disqualify any * component which deserves it. */ int cx, cy; for (cy = (y-1) >> 1; cy <= y >> 1; cy++) for (cx = (x-1) >> 1; cx <= x >> 1; cx++) { i = dsf_canonify(dsf, cy*w+cx); if (x != sqdata[i].cx || y != sqdata[i].cy) sqdata[i].valid = FALSE; } } if (sp->flags & F_EDGE_SET) { /* * There's an edge here. Use it to disqualify a * component if necessary. */ int cx1 = (x-1) >> 1, cx2 = x >> 1; int cy1 = (y-1) >> 1, cy2 = y >> 1; assert((cx1==cx2) ^ (cy1==cy2)); i = dsf_canonify(dsf, cy1*w+cx1); if (i == dsf_canonify(dsf, cy2*w+cx2)) sqdata[i].valid = FALSE; } } /* * And finally we test rotational symmetry: for each square in * the grid, find which component it's in, test that that * component also has a square in the symmetric position, and * disqualify it if it doesn't. */ for (y = 0; y < h; y++) for (x = 0; x < w; x++) { int x2, y2; i = dsf_canonify(dsf, y*w+x); x2 = sqdata[i].cx - 1 - x; y2 = sqdata[i].cy - 1 - y; if (i != dsf_canonify(dsf, y2*w+x2)) sqdata[i].valid = FALSE; } /* * That's it. We now have all the connected components marked * as valid or not valid. So now we return a `colours' array if * we were asked for one, and also we return an overall * true/false value depending on whether _every_ square in the * grid is part of a valid component. */ ret = TRUE; for (i = 0; i < w*h; i++) { int ci = dsf_canonify(dsf, i); int thisok = sqdata[ci].valid; if (colours) colours[i] = thisok ? sqdata[ci].colour : 0; ret = ret && thisok; } sfree(sqdata); if (free_dsf) sfree(dsf); return ret; } static game_state *execute_move(game_state *state, char *move) { int x, y, ax, ay, n, dx, dy; game_state *ret = dup_game(state); struct space *sp, *dot; debug(("%s\n", move)); while (*move) { char c = *move; if (c == 'E' || c == 'U' || c == 'M' #ifdef EDITOR || c == 'D' || c == 'd' #endif ) { move++; if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 || !INUI(state, x, y)) goto badmove; sp = &SPACE(ret, x, y); #ifdef EDITOR if (c == 'D' || c == 'd') { unsigned int currf, newf, maskf; if (!dot_is_possible(state, sp, 1)) goto badmove; newf = F_DOT | (c == 'd' ? F_DOT_BLACK : 0); currf = GRID(ret, grid, x, y).flags; maskf = F_DOT | F_DOT_BLACK; /* if we clicked 'white dot': * white --> empty, empty --> white, black --> white. * if we clicker 'black dot': * black --> empty, empty --> black, white --> black. */ if (currf & maskf) { sp->flags &= ~maskf; if ((currf & maskf) != newf) sp->flags |= newf; } else sp->flags |= newf; sp->nassoc = 0; /* edit-mode disallows associations. */ game_update_dots(ret); } else #endif if (c == 'E') { if (sp->type != s_edge) goto badmove; sp->flags ^= F_EDGE_SET; } else if (c == 'U') { if (sp->type != s_tile || !(sp->flags & F_TILE_ASSOC)) goto badmove; remove_assoc(ret, sp); } else if (c == 'M') { if (!(sp->flags & F_DOT)) goto badmove; sp->flags ^= F_DOT_HOLD; } move += n; } else if (c == 'A' || c == 'a') { move++; if (sscanf(move, "%d,%d,%d,%d%n", &x, &y, &ax, &ay, &n) != 4 || x < 1 || y < 1 || x >= (state->sx-1) || y >= (state->sy-1) || ax < 1 || ay < 1 || ax >= (state->sx-1) || ay >= (state->sy-1)) goto badmove; dot = &GRID(ret, grid, ax, ay); if (!(dot->flags & F_DOT))goto badmove; if (dot->flags & F_DOT_HOLD) goto badmove; for (dx = -1; dx <= 1; dx++) { for (dy = -1; dy <= 1; dy++) { sp = &GRID(ret, grid, x+dx, y+dy); if (sp->type != s_tile) continue; if (sp->flags & F_TILE_ASSOC) { space *dot = &SPACE(state, sp->dotx, sp->doty); if (dot->flags & F_DOT_HOLD) continue; } add_assoc(state, sp, dot); } } move += n; #ifdef EDITOR } else if (c == 'C') { move++; clear_game(ret, 1); #endif } else if (c == 'S') { move++; ret->used_solve = 1; } else goto badmove; if (*move == ';') move++; else if (*move) goto badmove; } if (check_complete(ret, NULL, NULL)) ret->completed = 1; return ret; badmove: free_game(ret); return NULL; } /* ---------------------------------------------------------------------- * Drawing routines. */ /* Lines will be much smaller size than squares; say, 1/8 the size? * * Need a 'top-left corner of location XxY' to take this into account; * alternaticaly, that could give the middle of that location, and the * drawing code would just know the expected dimensions. * * We also need something to take a click and work out what it was * we were interested in. Clicking on vertices is required because * we may want to drag from them, for example. */ static void game_compute_size(game_params *params, int sz, int *x, int *y) { struct { int tilesize, w, h; } ads, *ds = &ads; ds->tilesize = sz; ds->w = params->w; ds->h = params->h; *x = DRAW_WIDTH; *y = DRAW_HEIGHT; } static void game_set_size(drawing *dr, game_drawstate *ds, game_params *params, int sz) { ds->tilesize = sz; assert(TILE_SIZE > 0); assert(!ds->bl); ds->bl = blitter_new(dr, TILE_SIZE, TILE_SIZE); assert(!ds->cur_bl); ds->cur_bl = blitter_new(dr, TILE_SIZE, TILE_SIZE); } static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); int i; /* * We call game_mkhighlight to ensure the background colour * isn't completely white. We don't actually use the high- and * lowlight colours it generates. */ game_mkhighlight(fe, ret, COL_BACKGROUND, COL_WHITEBG, COL_BLACKBG); for (i = 0; i < 3; i++) { /* * Currently, white dots and white-background squares are * both pure white. */ ret[COL_WHITEDOT * 3 + i] = 1.0F; ret[COL_WHITEBG * 3 + i] = 1.0F; /* * But black-background squares are a dark grey, whereas * black dots are really black. */ ret[COL_BLACKDOT * 3 + i] = 0.0F; ret[COL_BLACKBG * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.3F; /* * In unfilled squares, we draw a faint gridwork. */ ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F; /* * Edges and arrows are filled in in pure black. */ ret[COL_EDGE * 3 + i] = 0.0F; ret[COL_ARROW * 3 + i] = 0.0F; } #ifdef EDITOR /* tinge the edit background to bluey */ ret[COL_BACKGROUND * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; ret[COL_BACKGROUND * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; ret[COL_BACKGROUND * 3 + 2] = max(ret[COL_BACKGROUND * 3 + 0] * 1.4F, 1.0F); #endif ret[COL_CURSOR * 3 + 0] = max(ret[COL_BACKGROUND * 3 + 0] * 1.4F, 1.0F); ret[COL_CURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; ret[COL_CURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.8F; *ncolours = NCOLOURS; return ret; } static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) { struct game_drawstate *ds = snew(struct game_drawstate); int i; ds->started = 0; ds->w = state->w; ds->h = state->h; ds->grid = snewn(ds->w*ds->h, unsigned long); for (i = 0; i < ds->w*ds->h; i++) ds->grid[i] = 0xFFFFFFFFUL; ds->dx = snewn(ds->w*ds->h, int); ds->dy = snewn(ds->w*ds->h, int); ds->bl = NULL; ds->dragging = FALSE; ds->dragx = ds->dragy = 0; ds->colour_scratch = snewn(ds->w * ds->h, int); ds->cur_bl = NULL; ds->cx = ds->cy = 0; ds->cur_visible = 0; return ds; } static void game_free_drawstate(drawing *dr, game_drawstate *ds) { if (ds->cur_bl) blitter_free(dr, ds->cur_bl); sfree(ds->colour_scratch); if (ds->bl) blitter_free(dr, ds->bl); sfree(ds->dx); sfree(ds->dy); sfree(ds->grid); sfree(ds); } #define DRAW_EDGE_L 0x0001 #define DRAW_EDGE_R 0x0002 #define DRAW_EDGE_U 0x0004 #define DRAW_EDGE_D 0x0008 #define DRAW_CORNER_UL 0x0010 #define DRAW_CORNER_UR 0x0020 #define DRAW_CORNER_DL 0x0040 #define DRAW_CORNER_DR 0x0080 #define DRAW_WHITE 0x0100 #define DRAW_BLACK 0x0200 #define DRAW_ARROW 0x0400 #define DRAW_CURSOR 0x0800 #define DOT_SHIFT_C 12 #define DOT_SHIFT_M 2 #define DOT_WHITE 1UL #define DOT_BLACK 2UL /* * Draw an arrow centred on (cx,cy), pointing in the direction * (ddx,ddy). (I.e. pointing at the point (cx+ddx, cy+ddy). */ static void draw_arrow(drawing *dr, game_drawstate *ds, int cx, int cy, int ddx, int ddy, int col) { float vlen = (float)sqrt(ddx*ddx+ddy*ddy); float xdx = ddx/vlen, xdy = ddy/vlen; float ydx = -xdy, ydy = xdx; int e1x = cx + (int)(xdx*TILE_SIZE/3), e1y = cy + (int)(xdy*TILE_SIZE/3); int e2x = cx - (int)(xdx*TILE_SIZE/3), e2y = cy - (int)(xdy*TILE_SIZE/3); int adx = (int)((ydx-xdx)*TILE_SIZE/8), ady = (int)((ydy-xdy)*TILE_SIZE/8); int adx2 = (int)((-ydx-xdx)*TILE_SIZE/8), ady2 = (int)((-ydy-xdy)*TILE_SIZE/8); draw_line(dr, e1x, e1y, e2x, e2y, col); draw_line(dr, e1x, e1y, e1x+adx, e1y+ady, col); draw_line(dr, e1x, e1y, e1x+adx2, e1y+ady2, col); } static void draw_square(drawing *dr, game_drawstate *ds, int x, int y, unsigned long flags, int ddx, int ddy) { int lx = COORD(x), ly = COORD(y); int dx, dy; int gridcol; clip(dr, lx, ly, TILE_SIZE, TILE_SIZE); /* * Draw the tile background. */ draw_rect(dr, lx, ly, TILE_SIZE, TILE_SIZE, (flags & DRAW_WHITE ? COL_WHITEBG : flags & DRAW_BLACK ? COL_BLACKBG : COL_BACKGROUND)); /* * Draw the grid. */ gridcol = (flags & DRAW_BLACK ? COL_BLACKDOT : COL_GRID); draw_rect(dr, lx, ly, 1, TILE_SIZE, gridcol); draw_rect(dr, lx, ly, TILE_SIZE, 1, gridcol); /* * Draw the arrow, if present, or the cursor, if here. */ if (flags & DRAW_ARROW) draw_arrow(dr, ds, lx + TILE_SIZE/2, ly + TILE_SIZE/2, ddx, ddy, (flags & DRAW_CURSOR) ? COL_CURSOR : COL_ARROW); else if (flags & DRAW_CURSOR) draw_rect_outline(dr, lx + TILE_SIZE/2 - CURSOR_SIZE, ly + TILE_SIZE/2 - CURSOR_SIZE, 2*CURSOR_SIZE+1, 2*CURSOR_SIZE+1, COL_CURSOR); /* * Draw the edges. */ if (flags & DRAW_EDGE_L) draw_rect(dr, lx, ly, EDGE_THICKNESS, TILE_SIZE, COL_EDGE); if (flags & DRAW_EDGE_R) draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1, ly, EDGE_THICKNESS - 1, TILE_SIZE, COL_EDGE); if (flags & DRAW_EDGE_U) draw_rect(dr, lx, ly, TILE_SIZE, EDGE_THICKNESS, COL_EDGE); if (flags & DRAW_EDGE_D) draw_rect(dr, lx, ly + TILE_SIZE - EDGE_THICKNESS + 1, TILE_SIZE, EDGE_THICKNESS - 1, COL_EDGE); if (flags & DRAW_CORNER_UL) draw_rect(dr, lx, ly, EDGE_THICKNESS, EDGE_THICKNESS, COL_EDGE); if (flags & DRAW_CORNER_UR) draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1, ly, EDGE_THICKNESS - 1, EDGE_THICKNESS, COL_EDGE); if (flags & DRAW_CORNER_DL) draw_rect(dr, lx, ly + TILE_SIZE - EDGE_THICKNESS + 1, EDGE_THICKNESS, EDGE_THICKNESS - 1, COL_EDGE); if (flags & DRAW_CORNER_DR) draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1, ly + TILE_SIZE - EDGE_THICKNESS + 1, EDGE_THICKNESS - 1, EDGE_THICKNESS - 1, COL_EDGE); /* * Draw the dots. */ for (dy = 0; dy < 3; dy++) for (dx = 0; dx < 3; dx++) { int dotval = (flags >> (DOT_SHIFT_C + DOT_SHIFT_M*(dy*3+dx))); dotval &= (1 << DOT_SHIFT_M)-1; if (dotval) draw_circle(dr, lx+dx*TILE_SIZE/2, ly+dy*TILE_SIZE/2, DOT_SIZE, (dotval == 1 ? COL_WHITEDOT : COL_BLACKDOT), COL_BLACKDOT); } unclip(dr); draw_update(dr, lx, ly, TILE_SIZE, TILE_SIZE); } static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, game_state *state, int dir, game_ui *ui, float animtime, float flashtime) { int w = ds->w, h = ds->h; int x, y, flashing = FALSE; if (flashtime > 0) { int frame = (int)(flashtime / FLASH_TIME); flashing = (frame % 2 == 0); } if (ds->dragging) { assert(ds->bl); blitter_load(dr, ds->bl, ds->dragx, ds->dragy); draw_update(dr, ds->dragx, ds->dragy, TILE_SIZE, TILE_SIZE); ds->dragging = FALSE; } if (ds->cur_visible) { assert(ds->cur_bl); blitter_load(dr, ds->cur_bl, ds->cx, ds->cy); draw_update(dr, ds->cx, ds->cy, CURSOR_SIZE*2+1, CURSOR_SIZE*2+1); ds->cur_visible = FALSE; } if (!ds->started) { draw_rect(dr, 0, 0, DRAW_WIDTH, DRAW_HEIGHT, COL_BACKGROUND); draw_rect(dr, BORDER - EDGE_THICKNESS + 1, BORDER - EDGE_THICKNESS + 1, w*TILE_SIZE + EDGE_THICKNESS*2 - 1, h*TILE_SIZE + EDGE_THICKNESS*2 - 1, COL_EDGE); draw_update(dr, 0, 0, DRAW_WIDTH, DRAW_HEIGHT); ds->started = TRUE; } check_complete(state, NULL, ds->colour_scratch); for (y = 0; y < h; y++) for (x = 0; x < w; x++) { unsigned long flags = 0; int ddx = 0, ddy = 0; space *sp; int dx, dy; /* * Set up the flags for this square. Firstly, see if we * have edges. */ if (SPACE(state, x*2, y*2+1).flags & F_EDGE_SET) flags |= DRAW_EDGE_L; if (SPACE(state, x*2+2, y*2+1).flags & F_EDGE_SET) flags |= DRAW_EDGE_R; if (SPACE(state, x*2+1, y*2).flags & F_EDGE_SET) flags |= DRAW_EDGE_U; if (SPACE(state, x*2+1, y*2+2).flags & F_EDGE_SET) flags |= DRAW_EDGE_D; /* * Also, mark corners of neighbouring edges. */ if ((x > 0 && SPACE(state, x*2-1, y*2).flags & F_EDGE_SET) || (y > 0 && SPACE(state, x*2, y*2-1).flags & F_EDGE_SET)) flags |= DRAW_CORNER_UL; if ((x+1 < w && SPACE(state, x*2+3, y*2).flags & F_EDGE_SET) || (y > 0 && SPACE(state, x*2+2, y*2-1).flags & F_EDGE_SET)) flags |= DRAW_CORNER_UR; if ((x > 0 && SPACE(state, x*2-1, y*2+2).flags & F_EDGE_SET) || (y+1 < h && SPACE(state, x*2, y*2+3).flags & F_EDGE_SET)) flags |= DRAW_CORNER_DL; if ((x+1 < w && SPACE(state, x*2+3, y*2+2).flags & F_EDGE_SET) || (y+1 < h && SPACE(state, x*2+2, y*2+3).flags & F_EDGE_SET)) flags |= DRAW_CORNER_DR; /* * If this square is part of a valid region, paint it * that region's colour. Exception: if we're flashing, * everything goes briefly back to background colour. */ sp = &SPACE(state, x*2+1, y*2+1); if (ds->colour_scratch[y*w+x] && !flashing) { flags |= (ds->colour_scratch[y*w+x] == 2 ? DRAW_BLACK : DRAW_WHITE); } /* * If this square is associated with a dot but it isn't * part of a valid region, draw an arrow in it pointing * in the direction of that dot. * * Exception: if this is the source point of an active * drag, we don't draw the arrow. */ if ((sp->flags & F_TILE_ASSOC) && !ds->colour_scratch[y*w+x]) { if (ui->dragging && ui->srcx == x*2+1 && ui->srcy == y*2+1) { /* don't do it */ } else if (sp->doty != y*2+1 || sp->dotx != x*2+1) { flags |= DRAW_ARROW; ddy = sp->doty - (y*2+1); ddx = sp->dotx - (x*2+1); } } /* * Now go through the nine possible places we could * have dots. */ for (dy = 0; dy < 3; dy++) for (dx = 0; dx < 3; dx++) { sp = &SPACE(state, x*2+dx, y*2+dy); if (sp->flags & F_DOT) { unsigned long dotval = (sp->flags & F_DOT_BLACK ? DOT_BLACK : DOT_WHITE); flags |= dotval << (DOT_SHIFT_C + DOT_SHIFT_M*(dy*3+dx)); } } /* * Now work out if we have to draw a cursor for this square; * cursors-on-lines are taken care of below. */ if (ui->cur_visible && ui->cur_x == x*2+1 && ui->cur_y == y*2+1 && !(SPACE(state, x*2+1, y*2+1).flags & F_DOT)) flags |= DRAW_CURSOR; /* * Now we have everything we're going to need. Draw the * square. */ if (ds->grid[y*w+x] != flags || ds->dx[y*w+x] != ddx || ds->dy[y*w+x] != ddy) { draw_square(dr, ds, x, y, flags, ddx, ddy); ds->grid[y*w+x] = flags; ds->dx[y*w+x] = ddx; ds->dy[y*w+x] = ddy; } } /* * Draw a cursor. This secondary blitter is much less invasive than trying * to fix up all of the rest of the code with sufficient flags to be able to * display this sensibly. */ if (ui->cur_visible) { space *sp = &SPACE(state, ui->cur_x, ui->cur_y); ds->cur_visible = TRUE; ds->cx = SCOORD(ui->cur_x) - CURSOR_SIZE; ds->cy = SCOORD(ui->cur_y) - CURSOR_SIZE; blitter_save(dr, ds->cur_bl, ds->cx, ds->cy); if (sp->flags & F_DOT) { /* draw a red dot (over the top of whatever would be there already) */ draw_circle(dr, SCOORD(ui->cur_x), SCOORD(ui->cur_y), DOT_SIZE, COL_CURSOR, COL_BLACKDOT); } else if (sp->type != s_tile) { /* draw an edge/vertex square; tile cursors are dealt with above. */ int dx = (ui->cur_x % 2) ? CURSOR_SIZE : CURSOR_SIZE/3; int dy = (ui->cur_y % 2) ? CURSOR_SIZE : CURSOR_SIZE/3; int x1 = SCOORD(ui->cur_x)-dx, y1 = SCOORD(ui->cur_y)-dy; int xs = dx*2+1, ys = dy*2+1; draw_rect(dr, x1, y1, xs, ys, COL_CURSOR); } draw_update(dr, ds->cx, ds->cy, CURSOR_SIZE*2+1, CURSOR_SIZE*2+1); } if (ui->dragging) { ds->dragging = TRUE; ds->dragx = ui->dx - TILE_SIZE/2; ds->dragy = ui->dy - TILE_SIZE/2; blitter_save(dr, ds->bl, ds->dragx, ds->dragy); draw_arrow(dr, ds, ui->dx, ui->dy, SCOORD(ui->dotx) - ui->dx, SCOORD(ui->doty) - ui->dy, COL_ARROW); } #ifdef EDITOR { char buf[256]; if (state->cdiff != -1) sprintf(buf, "Puzzle is %s.", galaxies_diffnames[state->cdiff]); else buf[0] = '\0'; status_bar(dr, buf); } #endif } static float game_anim_length(game_state *oldstate, game_state *newstate, int dir, game_ui *ui) { return 0.0F; } static float game_flash_length(game_state *oldstate, game_state *newstate, int dir, game_ui *ui) { if ((!oldstate->completed && newstate->completed) && !(newstate->used_solve)) return 3 * FLASH_TIME; else return 0.0F; } static int game_is_solved(game_state *state) { return state->completed; } static int game_timing_state(game_state *state, game_ui *ui) { return TRUE; } #ifndef EDITOR static void game_print_size(game_params *params, float *x, float *y) { int pw, ph; /* * 8mm squares by default. (There isn't all that much detail * that needs to go in each square.) */ game_compute_size(params, 800, &pw, &ph); *x = pw / 100.0F; *y = ph / 100.0F; } static void game_print(drawing *dr, game_state *state, int sz) { int w = state->w, h = state->h; int white, black, blackish; int x, y, i, j; int *colours, *dsf; int *coords = NULL; int ncoords = 0, coordsize = 0; /* Ick: fake up `ds->tilesize' for macro expansion purposes */ game_drawstate ads, *ds = &ads; ds->tilesize = sz; white = print_mono_colour(dr, 1); black = print_mono_colour(dr, 0); blackish = print_hatched_colour(dr, HATCH_X); /* * Get the completion information. */ dsf = snewn(w * h, int); colours = snewn(w * h, int); check_complete(state, dsf, colours); /* * Draw the grid. */ print_line_width(dr, TILE_SIZE / 64); for (x = 1; x < w; x++) draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black); for (y = 1; y < h; y++) draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black); /* * Shade the completed regions. Just in case any particular * printing platform deals badly with adjacent * similarly-hatched regions, we'll fill each one as a single * polygon. */ for (i = 0; i < w*h; i++) { j = dsf_canonify(dsf, i); if (colours[j] != 0) { int dx, dy, t; /* * This is the first square we've run into belonging to * this polyomino, which means an edge of the polyomino * is certain to be to our left. (After we finish * tracing round it, we'll set the colours[] entry to * zero to prevent accidentally doing it again.) */ x = i % w; y = i / w; dx = -1; dy = 0; ncoords = 0; while (1) { /* * We are currently sitting on square (x,y), which * we know to be in our polyomino, and we also know * that (x+dx,y+dy) is not. The way I visualise * this is that we're standing to the right of a * boundary line, stretching our left arm out to * point to the exterior square on the far side. */ /* * First, check if we've gone round the entire * polyomino. */ if (ncoords > 0 && (x == i%w && y == i/w && dx == -1 && dy == 0)) break; /* * Add to our coordinate list the coordinate * backwards and to the left of where we are. */ if (ncoords + 2 > coordsize) { coordsize = (ncoords * 3 / 2) + 64; coords = sresize(coords, coordsize, int); } coords[ncoords++] = COORD((2*x+1 + dx + dy) / 2); coords[ncoords++] = COORD((2*y+1 + dy - dx) / 2); /* * Follow the edge round. If the square directly in * front of us is not part of the polyomino, we * turn right; if it is and so is the square in * front of (x+dx,y+dy), we turn left; otherwise we * go straight on. */ if (x-dy < 0 || x-dy >= w || y+dx < 0 || y+dx >= h || dsf_canonify(dsf, (y+dx)*w+(x-dy)) != j) { /* Turn right. */ t = dx; dx = -dy; dy = t; } else if (x+dx-dy >= 0 && x+dx-dy < w && y+dy+dx >= 0 && y+dy+dx < h && dsf_canonify(dsf, (y+dy+dx)*w+(x+dx-dy)) == j) { /* Turn left. */ x += dx; y += dy; t = dx; dx = dy; dy = -t; x -= dx; y -= dy; } else { /* Straight on. */ x -= dy; y += dx; } } /* * Now we have our polygon complete, so fill it. */ draw_polygon(dr, coords, ncoords/2, colours[j] == 2 ? blackish : -1, black); /* * And mark this polyomino as done. */ colours[j] = 0; } } /* * Draw the edges. */ for (y = 0; y <= h; y++) for (x = 0; x <= w; x++) { if (x < w && SPACE(state, x*2+1, y*2).flags & F_EDGE_SET) draw_rect(dr, COORD(x)-EDGE_THICKNESS, COORD(y)-EDGE_THICKNESS, EDGE_THICKNESS * 2 + TILE_SIZE, EDGE_THICKNESS * 2, black); if (y < h && SPACE(state, x*2, y*2+1).flags & F_EDGE_SET) draw_rect(dr, COORD(x)-EDGE_THICKNESS, COORD(y)-EDGE_THICKNESS, EDGE_THICKNESS * 2, EDGE_THICKNESS * 2 + TILE_SIZE, black); } /* * Draw the dots. */ for (y = 0; y <= 2*h; y++) for (x = 0; x <= 2*w; x++) if (SPACE(state, x, y).flags & F_DOT) { draw_circle(dr, (int)COORD(x/2.0), (int)COORD(y/2.0), DOT_SIZE, (SPACE(state, x, y).flags & F_DOT_BLACK ? black : white), black); } sfree(dsf); sfree(colours); sfree(coords); } #endif #ifdef COMBINED #define thegame galaxies #endif const struct game thegame = { "Galaxies", "games.galaxies", "galaxies", default_params, game_fetch_preset, decode_params, encode_params, free_params, dup_params, TRUE, game_configure, custom_params, validate_params, new_game_desc, validate_desc, new_game, dup_game, free_game, #ifdef EDITOR FALSE, NULL, #else TRUE, solve_game, #endif TRUE, game_can_format_as_text_now, game_text_format, new_ui, free_ui, encode_ui, decode_ui, game_changed_state, interpret_move, execute_move, PREFERRED_TILE_SIZE, game_compute_size, game_set_size, game_colours, game_new_drawstate, game_free_drawstate, game_redraw, game_anim_length, game_flash_length, game_is_solved, #ifdef EDITOR FALSE, FALSE, NULL, NULL, TRUE, /* wants_statusbar */ #else TRUE, FALSE, game_print_size, game_print, FALSE, /* wants_statusbar */ #endif FALSE, game_timing_state, REQUIRE_RBUTTON, /* flags */ }; #ifdef STANDALONE_SOLVER const char *quis; #include <time.h> static void usage_exit(const char *msg) { if (msg) fprintf(stderr, "%s: %s\n", quis, msg); fprintf(stderr, "Usage: %s [--seed SEED] --soak <params> | [game_id [game_id ...]]\n", quis); exit(1); } static void dump_state(game_state *state) { char *temp = game_text_format(state); printf("%s\n", temp); sfree(temp); } static int gen(game_params *p, random_state *rs, int debug) { char *desc; int diff; game_state *state; #ifndef DEBUGGING solver_show_working = debug; #endif printf("Generating a %dx%d %s puzzle.\n", p->w, p->h, galaxies_diffnames[p->diff]); desc = new_game_desc(p, rs, NULL, 0); state = new_game(NULL, p, desc); dump_state(state); diff = solver_state(state, DIFF_UNREASONABLE); printf("Generated %s game %dx%d:%s\n", galaxies_diffnames[diff], p->w, p->h, desc); dump_state(state); free_game(state); sfree(desc); return diff; } static void soak(game_params *p, random_state *rs) { time_t tt_start, tt_now, tt_last; char *desc; game_state *st; int diff, n = 0, i, diffs[DIFF_MAX], ndots = 0, nspaces = 0; #ifndef DEBUGGING solver_show_working = 0; #endif tt_start = tt_now = time(NULL); for (i = 0; i < DIFF_MAX; i++) diffs[i] = 0; maxtries = 1; printf("Soak-generating a %dx%d grid, max. diff %s.\n", p->w, p->h, galaxies_diffnames[p->diff]); printf(" ["); for (i = 0; i < DIFF_MAX; i++) printf("%s%s", (i == 0) ? "" : ", ", galaxies_diffnames[i]); printf("]\n"); while (1) { desc = new_game_desc(p, rs, NULL, 0); st = new_game(NULL, p, desc); diff = solver_state(st, p->diff); nspaces += st->w*st->h; for (i = 0; i < st->sx*st->sy; i++) if (st->grid[i].flags & F_DOT) ndots++; free_game(st); sfree(desc); diffs[diff]++; n++; tt_last = time(NULL); if (tt_last > tt_now) { tt_now = tt_last; printf("%d total, %3.1f/s, [", n, (double)n / ((double)tt_now - tt_start)); for (i = 0; i < DIFF_MAX; i++) printf("%s%.1f%%", (i == 0) ? "" : ", ", 100.0 * ((double)diffs[i] / (double)n)); printf("], %.1f%% dots\n", 100.0 * ((double)ndots / (double)nspaces)); } } } int main(int argc, char **argv) { game_params *p; char *id = NULL, *desc, *err; game_state *s; int diff, do_soak = 0, verbose = 0; random_state *rs; time_t seed = time(NULL); quis = argv[0]; while (--argc > 0) { char *p = *++argv; if (!strcmp(p, "-v")) { verbose = 1; } else if (!strcmp(p, "--seed")) { if (argc == 0) usage_exit("--seed needs an argument"); seed = (time_t)atoi(*++argv); argc--; } else if (!strcmp(p, "--soak")) { do_soak = 1; } else if (*p == '-') { usage_exit("unrecognised option"); } else { id = p; } } maxtries = 50; p = default_params(); rs = random_new((void*)&seed, sizeof(time_t)); if (do_soak) { if (!id) usage_exit("need one argument for --soak"); decode_params(p, *argv); soak(p, rs); return 0; } if (!id) { while (1) { p->w = random_upto(rs, 15) + 3; p->h = random_upto(rs, 15) + 3; p->diff = random_upto(rs, DIFF_UNREASONABLE); diff = gen(p, rs, 0); } return 0; } desc = strchr(id, ':'); if (!desc) { decode_params(p, id); gen(p, rs, verbose); } else { #ifndef DEBUGGING solver_show_working = 1; #endif *desc++ = '\0'; decode_params(p, id); err = validate_desc(p, desc); if (err) { fprintf(stderr, "%s: %s\n", argv[0], err); exit(1); } s = new_game(NULL, p, desc); diff = solver_state(s, DIFF_UNREASONABLE); dump_state(s); printf("Puzzle is %s.\n", galaxies_diffnames[diff]); free_game(s); } free_params(p); return 0; } #endif #ifdef STANDALONE_PICTURE_GENERATOR /* * Main program for the standalone picture generator. To use it, * simply provide it with an XBM-format bitmap file (note XBM, not * XPM) on standard input, and it will output a game ID in return. * For example: * * $ ./galaxiespicture < badly-drawn-cat.xbm * 11x11:eloMBLzFeEzLNMWifhaWYdDbixCymBbBMLoDdewGg * * If you want a puzzle with a non-standard difficulty level, pass * a partial parameters string as a command-line argument (e.g. * `./galaxiespicture du < foo.xbm', where `du' is the same suffix * which if it appeared in a random-seed game ID would set the * difficulty level to Unreasonable). However, be aware that if the * generator fails to produce an adequately difficult puzzle too * many times then it will give up and return an easier one (just * as it does during normal GUI play). To be sure you really have * the difficulty you asked for, use galaxiessolver to * double-check. * * (Perhaps I ought to include an option to make this standalone * generator carry on looping until it really does get the right * difficulty. Hmmm.) */ #include <time.h> int main(int argc, char **argv) { game_params *par; char *params, *desc; random_state *rs; time_t seed = time(NULL); char buf[4096]; int i; int x, y; par = default_params(); if (argc > 1) decode_params(par, argv[1]); /* get difficulty */ par->w = par->h = -1; /* * Now read an XBM file from standard input. This is simple and * hacky and will do very little error detection, so don't feed * it bogus data. */ picture = NULL; x = y = 0; while (fgets(buf, sizeof(buf), stdin)) { buf[strcspn(buf, "\r\n")] = '\0'; if (!strncmp(buf, "#define", 7)) { /* * Lines starting `#define' give the width and height. */ char *num = buf + strlen(buf); char *symend; while (num > buf && isdigit((unsigned char)num[-1])) num--; symend = num; while (symend > buf && isspace((unsigned char)symend[-1])) symend--; if (symend-5 >= buf && !strncmp(symend-5, "width", 5)) par->w = atoi(num); else if (symend-6 >= buf && !strncmp(symend-6, "height", 6)) par->h = atoi(num); } else { /* * Otherwise, break the string up into words and take * any word of the form `0x' plus hex digits to be a * byte. */ char *p, *wordstart; if (!picture) { if (par->w < 0 || par->h < 0) { printf("failed to read width and height\n"); return 1; } picture = snewn(par->w * par->h, int); for (i = 0; i < par->w * par->h; i++) picture[i] = -1; } p = buf; while (*p) { while (*p && (*p == ',' || isspace((unsigned char)*p))) p++; wordstart = p; while (*p && !(*p == ',' || *p == '}' || isspace((unsigned char)*p))) p++; if (*p) *p++ = '\0'; if (wordstart[0] == '0' && (wordstart[1] == 'x' || wordstart[1] == 'X') && !wordstart[2 + strspn(wordstart+2, "0123456789abcdefABCDEF")]) { unsigned long byte = strtoul(wordstart+2, NULL, 16); for (i = 0; i < 8; i++) { int bit = (byte >> i) & 1; if (y < par->h && x < par->w) picture[y * par->w + x] = bit; x++; } if (x >= par->w) { x = 0; y++; } } } } } for (i = 0; i < par->w * par->h; i++) if (picture[i] < 0) { fprintf(stderr, "failed to read enough bitmap data\n"); return 1; } rs = random_new((void*)&seed, sizeof(time_t)); desc = new_game_desc(par, rs, NULL, FALSE); params = encode_params(par, FALSE); printf("%s:%s\n", params, desc); sfree(desc); sfree(params); free_params(par); random_free(rs); return 0; } #endif /* vim: set shiftwidth=4 tabstop=8: */