xref: /plan9/sys/src/games/sudoku/sudoku.c (revision d8f3f65ed104b5f1fe09dbf8d8d4b5289e0e4acf)
1 /* code from mark huckvale: http://www.phon.ucl.ac.uk/home/mark/sudoku/ */
2 
3 #include <u.h>
4 #include <libc.h>
5 #include <draw.h>
6 #include <event.h>
7 
8 #include "sudoku.h"
9 
10 char *imgdir = "/sys/games/lib/sudoku/images";
11 char *lvldir = "/sys/games/lib/sudoku/boards";	/* level library dir */
12 
13 int selected;	/* which digit do we have selected? */
14 
15 Image *background;	/* DPaleyellow */
16 Image *backselect;	/* DPalebluegreen */
17 Image *blink;		/* DDarkyellow */
18 Image *brdr;		/* 0x55555555 */
19 Image *fixed;		/* DBlue */
20 Image *wrong;		/* DRed */
21 Image *dig[10];		/* digit masks */
22 
23 Dir *dir;
24 int numlevels;
25 int curlevel;
26 
27 char *buttons[] =
28 {
29 	"new",
30 	"check",
31 	"solve",
32 	"clear",
33 	"save",
34 	"load",
35 	"print",
36 	"offline",
37 	"exit",
38 	0
39 };
40 
41 Menu menu =
42 {
43 	buttons
44 };
45 
46 Menu lmenu =
47 {
48 	nil,
49 	genlevels,
50 	0,
51 };
52 
53 int
readlevels(char * leveldir)54 readlevels(char *leveldir)
55 {
56 	int fd, n;
57 
58 	if((fd = open(leveldir, OREAD)) < 0)
59 		return -1;
60 
61 	n = dirreadall(fd, &dir);
62 	close(fd);
63 
64 	return n;
65 }
66 
67 char *
genlevels(int i)68 genlevels(int i)
69 {
70 	if(numlevels == 0)
71 		numlevels = readlevels(lvldir);
72 
73 	if(numlevels > 0 && i < numlevels)
74 		return (dir+i)->name;
75 
76 	return nil;
77 }
78 
79 void
convert(Cell * brd,int * board)80 convert(Cell *brd, int *board)
81 {
82 	int i;
83 
84 	for(i = 0; i < Psize; i++) {
85 		brd[i].digit = board[i] & Digit;
86 		if(brd[i].digit < 0 || brd[i].digit > 9)
87 			brd[i].digit = -1;
88 		brd[i].solve = (board[i] & Solve) >> 4;
89 		brd[i].locked = board[i] & MLock;
90 	}
91 	memcpy(obrd, brd, Psize * sizeof(Cell));
92 }
93 
94 Image *
eallocimage(Rectangle r,int repl,uint color)95 eallocimage(Rectangle r, int repl, uint color)
96 {
97 	Image *tmp;
98 
99 	tmp = allocimage(display, r, screen->chan, repl, color);
100 	if(tmp == nil)
101 		sysfatal("cannot allocate buffer image: %r");
102 
103 	return tmp;
104 }
105 
106 Image *
eloadfile(char * path)107 eloadfile(char *path)
108 {
109 	Image *img;
110 	int fd;
111 
112 	fd = open(path, OREAD);
113 	if(fd < 0) {
114 		fprint(2, "cannot open image file %s: %r\n", path);
115 		exits("image");
116 	}
117 	img = readimage(display, fd, 0);
118 	if(img == nil)
119 		sysfatal("cannot load image: %r");
120 	close(fd);
121 
122 	return img;
123 }
124 
125 
126 void
clearboard(Cell * board)127 clearboard(Cell *board)
128 {
129 	int i;
130 
131 	for(i = 0; i < Psize; i++) {
132 		board[i].digit = -1;
133 		board[i].solve = 0;
134 		board[i].locked = 0;
135 	}
136 }
137 
138 void
solveboard(Cell * board)139 solveboard(Cell *board)
140 {
141 	int i;
142 
143 	for(i = 0; i < Psize; i++) {
144 		board[i].digit = board[i].solve;
145 	}
146 }
147 
148 
149 int
checkpossible(Cell * board,int x,int y,int num)150 checkpossible(Cell *board, int x, int y, int num)
151 {
152 	int i, j;
153 
154 	for(i = 0; i < Brdsize; i++) {
155 		if(board[i*Brdsize + y].digit == num && i != x)
156 			return 0;
157 		if(board[x*Brdsize + i].digit == num && i != y)
158 			return 0;
159 	}
160 
161 	for(i = x - (x%3); i < x - (x%3) + 3; i++)
162 		for(j = y - (y%3); j < y - (y%3) + 3; j++)
163 			if((i != x && j != y) && board[i*Brdsize + j].digit == num)
164 				return 0;
165 
166 	return 1;
167 }
168 
169 void
resize(void)170 resize(void)
171 {
172 	int fd;
173 
174 	fd = open("/dev/wctl", OWRITE);
175 	if(fd >= 0){
176 		fprint(fd, "resize -dx %d -dy %d", Maxx, Maxy);
177 		close(fd);
178 	}
179 
180 }
181 
182 void
drawcell(int x,int y,int num,Image * col)183 drawcell(int x, int y, int num, Image *col)
184 {
185 	Rectangle r = Rect(x*Square, y*Square, (x+1)*Square, (y+1)*Square);
186 
187 	if(num < 0 || num > 9)
188 		return;
189 
190 	r = insetrect(r, Border);
191 	r = rectaddpt(r, Pt(0, Square));
192 	r.max = addpt(r.max, Pt(2, 2));
193 
194 	draw(screen, rectaddpt(r, screen->r.min), col, dig[num], ZP);
195 }
196 
197 void
drawboard(void)198 drawboard(void)
199 {
200 	int i;
201 
202 	for(i = 0; i < Psize; i++) {
203 		drawcell(i / Brdsize, i % Brdsize, brd[i].digit, brd[i].locked ? fixed : display->black);
204 	}
205 }
206 
207 void
drawchecked(Cell * brd)208 drawchecked(Cell *brd)
209 {
210 	int i;
211 
212 	for(i = 0; i < Psize; i++) {
213 		if(brd[i].locked)
214 			drawcell(i / Brdsize, i % Brdsize, brd[i].digit, fixed);
215 		else
216 			drawcell(i / Brdsize, i % Brdsize, brd[i].digit,
217 					checkpossible(brd, i / Brdsize, i % Brdsize, brd[i].digit) ? display->black : wrong);
218 	}
219 }
220 
221 void
drawscreen(void)222 drawscreen(void)
223 {
224 	Point l1, l2;
225 	int i;
226 
227 	draw(screen, screen->r, brdr, nil, ZP);
228 	draw(screen, insetrect(screen->r, Border), background, nil, ZP);
229 	for(i = 0; i < Brdsize; i++) {
230 		l1 = addpt(screen->r.min, Pt(i*Square, Square));
231 		l2 = addpt(screen->r.min, Pt(i*Square, Maxy));
232 		line(screen, l1, l2, Endsquare, Endsquare, (i%3) == 0 ? Thickline : Line, brdr, ZP);
233 		l1 = addpt(screen->r.min, Pt(0, (i+1)*Square));
234 		l2 = addpt(screen->r.min, Pt(Maxx, (i+1)*Square));
235 		line(screen, l1, l2, Endsquare, Endsquare, (i%3) == 0 ? Thickline : Line, brdr, ZP);
236 	}
237 	for(i = 1; i < 10; i++) {
238 		drawbar(i, (selected == i) ? 1 : 0);
239 	}
240 	drawboard();
241 	flushimage(display, 1);
242 }
243 
244 
245 void
drawbar(int digit,int selected)246 drawbar(int digit, int selected)
247 {
248 	Rectangle r = Rect((digit - 1)*Square, 0, digit*Square, Square);
249 
250 	if(digit < 1 || digit > 9)
251 		return;
252 
253 	r = insetrect(r, Border);
254 	r.max = addpt(r.max, Pt(2, 2));
255 	draw(screen, rectaddpt(r, screen->r.min), selected ? backselect : background, nil, ZP);
256 	draw(screen, rectaddpt(r, screen->r.min), display->black, dig[digit-1], ZP);
257 }
258 
259 void
eresized(int new)260 eresized(int new)
261 {
262 	Point p;
263 	char path[256];
264 	int i;
265 
266 	if(new && getwindow(display, Refnone) < 0)
267 		sysfatal("can't reattach to window");
268 
269 	if(background == nil)
270 		background = eallocimage(Rect(0, 0, 1, 1), 1, DPaleyellow);
271 	if(backselect == nil)
272 		backselect = eallocimage(Rect(0, 0, 1, 1), 1, DPalebluegreen);
273 	if(blink == nil)
274 		blink = eallocimage(Rect(0, 0, 1, 1), 1, DDarkyellow);
275 	if(brdr == nil)
276 		brdr = eallocimage(Rect(0, 0, 1, 1), 1, 0x55555555);
277 	if(fixed == nil)
278 		fixed = eallocimage(Rect(0, 0, 1, 1), 1, DBlue);
279 	if(wrong == nil)
280 		wrong = eallocimage(Rect(0, 0, 1, 1), 1, DRed);
281 	if(dig[0] == nil) {
282 		for(i = 0; i < 9; i++) {
283 			snprint(path, 256, "%s/%d.bit", imgdir, i+1);
284 			dig[i] = eloadfile(path);
285 		}
286 	}
287 
288 	p = Pt(Dx(screen->r), Dy(screen->r));
289 	if(!new || !eqpt(p, Pt(Maxx - 8, Maxy - 8)))
290 		resize();
291 
292 	drawscreen();
293 }
294 
295 void
main(int argc,char * argv[])296 main(int argc, char *argv[])
297 {
298 	Mouse m;
299 	Event e;
300 	Point p;
301 	int last1 = 0;	/* was the button clicked last time? */
302 
303 	USED(argc, argv);
304 
305 	if(initdraw(nil, nil, "sudoku") < 0)
306 		sysfatal("initdraw failed: %r");
307 
308 	einit(Emouse|Ekeyboard);
309 
310 
311 	clearboard(brd);
312 	eresized(0);
313 
314 	srand(time(0)*getpid());
315 	makep();
316 	convert(brd, board);
317 
318 	drawscreen();
319 	for(;;) {
320 		switch(event(&e)) {
321 		case Emouse:
322 			m = e.mouse;
323 			if(m.buttons&1) {
324 				if(last1 == 0) {
325 					last1 = 1;
326 					p = subpt(m.xy, screen->r.min);
327 					if(ptinrect(p, Rect(0, 0, Maxx, Square+Border))) {
328 						if(p.x/Square == selected - 1) {
329 							drawbar(selected, 0);
330 							selected = 0;
331 						} else {
332 							selected = p.x/Square + 1;
333 						}
334 					} else {
335 						Point lp = divpt(p, Square);
336 						lp.y--;
337 
338 						if(brd[lp.x * Brdsize + lp.y].locked)
339 							break;
340 
341 						if(selected) {
342 							brd[lp.x * Brdsize + lp.y].digit = selected - 1;
343 						} else {
344 							brd[lp.x * Brdsize + lp.y].digit = -1;
345 						}
346 					}
347 					drawscreen();
348 				}
349 			} else {
350 				last1 = 0;
351 			}
352 
353 			if(m.buttons&2) {
354 				char *str;
355 				int l;
356 				/* levels start from 1 */
357 				lmenu.lasthit = curlevel;
358 				l = emenuhit(2, &m, &lmenu);
359 				if(l >= 0){
360 					curlevel = l;
361 					str = smprint("%s/%s", lvldir, (dir+curlevel)->name);
362 					if(loadlevel(str, brd) < 0)
363 						clearboard(brd);
364 					memcpy(obrd, brd, Psize * sizeof(Cell));
365 					free(str);
366 				}
367 				drawscreen();
368 			}
369 			if(m.buttons&4) {
370 				switch(emenuhit(3, &m, &menu)) {
371 				case 0: 	/* new */
372 					makep();
373 					convert(brd, board);
374 					drawscreen();
375 					break;
376 				case 1:		/* solve */
377 					drawchecked(brd);
378 					break;
379 				case 2:		/* solve */
380 					solveboard(brd);
381 					drawscreen();
382 					break;
383 				case 3:		/* clear */
384 					memcpy(brd, obrd, Psize * sizeof(Cell));
385 					drawscreen();
386 					break;
387 				case 4:		/* save */
388 					savegame(brd);
389 					drawscreen();
390 					break;
391 				case 5:		/* load */
392 					if(loadgame(brd) < 0) {
393 						clearboard(brd);
394 					}
395 					memcpy(obrd, brd, Psize * sizeof(Cell));
396 					drawscreen();
397 					break;
398 				case 6:		/* print */
399 					printboard(brd);
400 					break;
401 				case 7:		/* offline */
402 					fprettyprintbrd(brd);
403 					break;
404 				case 8:		/* exit */
405 					exits(nil);
406 				}
407 			}
408 			break;
409 
410 		case Ekeyboard:
411 			switch(e.kbdc) {
412 			case 127:
413 			case 'q':
414 			case 'Q':
415 				exits(nil);
416 			default:
417 				break;
418 			}
419 			break;
420 		}
421 	}
422 }
423