chiark / gitweb /
cp: Tweak guess selection algorithm: minimize largest bin.
[mm] / mm.c
CommitLineData
7a869046
MW
1/* -*-c-*-
2 *
3 * $Id$
4 *
5 * Simple mastermind game
6 *
7 * (c) 2006 Mark Wooding
8 */
9
10/*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of mm: a simple Mastermind game.
13 *
14 * mm is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * mm is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with mm; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29/*----- Header files ------------------------------------------------------*/
30
0fd4f375 31#include <assert.h>
7a869046
MW
32#include <ctype.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <time.h>
37
38#include <mLib/alloc.h>
0fd4f375 39#include <mLib/darray.h>
7a869046
MW
40#include <mLib/mdwopt.h>
41#include <mLib/quis.h>
42#include <mLib/report.h>
43#include <mLib/sub.h>
44
45/*----- Data structures ---------------------------------------------------*/
46
47/* --- Digits --- *
48 *
49 * The symbols which make up the code to be guessed.
50 */
51
52typedef unsigned char dig;
53
54/* --- The game parameters --- */
55
56typedef struct mm {
57 dig k; /* Number of symbols in the code */
58 dig n; /* Number of distinct symbols */
59} mm;
60
61/*----- Rating guesses ----------------------------------------------------*/
62
63/* --- Algorithm --- *
64 *
65 * Rating guesses efficiently is quite important.
66 *
67 * The rate context structure contains a copy of the game parameters, and
68 * three arrays, @v@, @s@ and @t@ allocated from the same @malloc@ed block:
69 *
70 * * %$v_i$% counts occurrences of the symbol %$i$% in the code.
71 * * %$s$% is a copy of the code.
72 * * %$t$% is temporary work space for the rating function.
73 *
74 * The rating function works by taking a pass over the guess, computing a
75 * count table %$v'$%; but for each guess symbol which matches the
76 * correspondng code symbol, decrementing the count %$v_i$% for that symbol
77 * (in a temporary copy of the table %$v$%). The black count is then the
78 * number of such matches, and the white count is given by
79 *
80 * %$w = \displaystyle \sum_{0<i\le n} \min(v_i, v'_i).$%
81 *
82 * Thus, the work is %$O(k + n)$%, rather than the %$O(k^2)$% for a
83 * %%na\"\i{}ve%% implementation.
84 */
85
86typedef struct ratectx {
87 mm m;
88 dig *v;
89 dig *s;
90 dig *t;
91} ratectx;
92
93static ratectx *rate_alloc(const mm *m)
94{
95 ratectx *r;
96 dig *v;
97
98 r = CREATE(ratectx);
99 v = xmalloc((3 * m->n + m->k) * sizeof(dig));
100 r->m = *m;
101 r->v = v;
102 r->s = r->v + m->n;
103 r->t = r->s + m->k;
104 return (r);
105}
106
107static void rate_init(ratectx *r, const dig *s)
108{
109 unsigned i;
110
111 memset(r->v, 0, r->m.n * sizeof(dig));
112 for (i = 0; i < r->m.k; i++)
113 r->v[s[i]]++;
114 memcpy(r->s, s, r->m.k * sizeof(dig));
115}
116
117static ratectx *rate_new(const mm *m, const dig *s)
118{
119 ratectx *r = rate_alloc(m);
409fd535 120
7a869046
MW
121 rate_init(r, s);
122 return (r);
123}
124
125static void rate(const ratectx *r, const dig *g, unsigned *b, unsigned *w)
126{
127 unsigned i;
128 unsigned k = r->m.k, n = r->m.n;
129 dig *v = r->t;
130 dig *vv = v + n;
131 const dig *s = r->s;
132 unsigned bb = 0, ww = 0;
133
134 memset(v, 0, n * sizeof(dig));
135 memcpy(vv, r->v, n * sizeof(dig));
136 for (i = 0; i < k; i++) {
137 if (g[i] != s[i])
138 v[g[i]]++;
139 else {
140 vv[g[i]]--;
141 bb++;
142 }
143 }
144 for (i = 0; i < n; i++)
145 ww += v[i] < vv[i] ? v[i] : vv[i];
146 *b = bb;
147 *w = ww;
148}
149
409fd535 150static void rate_free(ratectx *r) { xfree(r->v); DESTROY(r); }
7a869046
MW
151
152/*----- Computer player ---------------------------------------------------*/
153
154/* --- About the algorithms --- *
155 *
156 * At each stage, we attampt to choose the guess which will give us the most
157 * information, regardless of the outcome. For each guess candidate, we
158 * count the remaining possible codes for each outcome, and choose the
ca85e2e7 159 * candidate with the least maxmimum bin size. There are wrinkles.
7a869046
MW
160 *
161 * Firstly the number of possible guesses is large, and the number of
162 * possible codes is huge too; and our algorithm takes time proportional to
163 * the product of the two. However, a symbol we've never tried before is as
164 * good as any other, so we can narrow the list of candidate guesses by
165 * considering only %%\emph{prototypes}%% where we use only the smallest
166 * untried symbol at any point to represent introducing any new symbol. The
167 * number of initial prototypes is quite small. For the four-symbol game,
168 * they are 0000, 0001, 0011, 0012, 0111, 0112, 0122, and 0123.
169 *
170 * Secondly, when the number of possible codes become small, we want to bias
171 * the guess selection algorithm towards possible codes (so that we win if
172 * we're lucky). Since the algorithm chooses the first guess with the lowest
ca85e2e7 173 * max-bin-size value, we simply run through the possible codes before
7a869046
MW
174 * enumerating the prototype guesses.
175 */
176
177typedef struct cpc {
178 mm m; /* Game parameters */
9ea0417a
MW
179 unsigned f; /* Various flags */
180#define CPCF_QUIET 1u /* Don't produce lots of output */
7a869046
MW
181 dig *s; /* n^k * k */ /* Remaining guesses */
182 size_t ns; /* Number of remaining guesses */
183 dig *bg; /* k */ /* Current best guess */
184 dig *t; /* k */ /* Scratch-space for prototype */
ca85e2e7 185 size_t bmax; /* Best guess max partition size */
7a869046 186 dig x, bx; /* Next unused symbol index */
ca85e2e7 187 size_t *v; /* (k + 1)^2 */ /* Bin counts */
7a869046
MW
188 ratectx *r; /* Rate context for search */
189} cpc;
190
191static void print_guess(const mm *m, const dig *d)
192{
193 unsigned k = m->k, i;
194
195 for (i = 0; i < k; i++) {
196 if (i) putchar(' ');
197 printf("%u", d[i]);
198 }
199}
200
201static void dofep(cpc *c, void (*fn)(cpc *c, const dig *g, unsigned x),
202 unsigned k, unsigned n, unsigned i, unsigned x)
203{
204 unsigned j;
205 dig *t = c->t;
206
207 if (i == k)
208 fn(c, c->t, x);
209 else {
210 for (j = 0; j < x; j++) {
211 t[i] = j;
212 dofep(c, fn, k, n, i + 1, x);
213 }
214 if (x < n) {
215 t[i] = x;
216 dofep(c, fn, k, n, i + 1, x + 1);
217 }
218 }
219}
220
221static void foreach_proto(cpc *c, void (*fn)(cpc *c,
222 const dig *g,
223 unsigned x))
224{
225 unsigned k = c->m.k, n = c->m.n;
409fd535 226
7a869046
MW
227 dofep(c, fn, k, n, 0, c->x);
228}
229
230static void try_guess(cpc *c, const dig *g, unsigned x)
231{
232 size_t i;
233 unsigned b, w;
234 const dig *s;
235 unsigned k = c->m.k;
236 size_t *v = c->v;
237 size_t *vp;
ca85e2e7 238 size_t max;
7a869046
MW
239
240 rate_init(c->r, g);
241 memset(v, 0, (k + 1) * (k + 1) * sizeof(size_t));
242 for (i = c->ns, s = c->s; i; i--, s += k) {
243 rate(c->r, s, &b, &w);
244 v[b * (k + 1) + w]++;
245 }
246 max = 0;
ca85e2e7
MW
247 for (i = (k + 1) * (k + 1), vp = v; i; i--, vp++) {
248 if (*vp > max)
249 max = *vp;
250 }
251 if (!c->bmax || max < c->bmax) {
7a869046
MW
252 memcpy(c->bg, g, k * sizeof(dig));
253 c->bmax = max;
254 c->bx = x;
255 }
256}
257
258static void best_guess(cpc *c)
259{
ca85e2e7 260 c->bmax = 0;
7a869046
MW
261 if (c->ns < 1024) {
262 unsigned k = c->m.k;
263 const dig *s;
264 size_t i;
265
266 for (i = c->ns, s = c->s; i; i--, s += k)
267 try_guess(c, s, c->x);
268 }
269 foreach_proto(c, try_guess);
270 c->x = c->bx;
271}
272
273static void filter_guesses(cpc *c, const dig *g, unsigned b, unsigned w)
274{
275 unsigned k = c->m.k;
276 size_t i;
277 const dig *s;
278 unsigned bb, ww;
279 dig *ss;
280
281 rate_init(c->r, g);
282 for (i = c->ns, s = ss = c->s; i; i--, s += k) {
283 rate(c->r, s, &bb, &ww);
284 if (b == bb && w == ww) {
285 memmove(ss, s, k * sizeof(dig));
286 ss += k;
287 }
288 }
289 c->ns = (ss - c->s) / k;
290}
291
292static size_t ipow(size_t b, size_t x)
293{
294 size_t a = 1;
295 while (x) {
296 if (x & 1)
297 a *= b;
298 b *= b;
299 x >>= 1;
300 }
301 return (a);
302}
303
304static void all_guesses(dig **ss, unsigned k, unsigned n,
305 unsigned i, const dig *b)
306{
307 unsigned j;
308
309 if (i == k) {
310 (*ss) += k;
311 return;
312 }
313 for (j = 0; j < n; j++) {
314 dig *s = *ss;
315 if (i)
316 memcpy(*ss, b, i * sizeof(dig));
317 s[i] = j;
318 all_guesses(ss, k, n, i + 1, s);
319 }
320}
321
9ea0417a
MW
322#define THINK(c, what, how) do { \
323 clock_t _t0 = 0, _t1; \
324 if (!(c->f & CPCF_QUIET)) { \
325 fputs(what "...", stdout); \
326 fflush(stdout); \
327 _t0 = clock(); \
328 } \
7a869046 329 do how while (0); \
9ea0417a
MW
330 if (!(c->f & CPCF_QUIET)) { \
331 _t1 = clock(); \
332 printf(" done (%.2fs)\n", (_t1 - _t0)/(double)CLOCKS_PER_SEC); \
333 } \
7a869046
MW
334} while (0)
335
9ea0417a 336static cpc *cpc_new(const mm *m, unsigned f)
7a869046
MW
337{
338 cpc *c = CREATE(cpc);
9ea0417a
MW
339
340 c->f = f;
7a869046
MW
341 c->m = *m;
342 c->ns = ipow(c->m.n, c->m.k);
343 c->s = xmalloc((c->ns + 2) * c->m.k * sizeof(dig));
344 c->bg = c->s + c->ns * c->m.k;
345 c->t = c->bg + c->m.k;
346 c->x = 0;
347 c->v = xmalloc((c->m.k + 1) * (c->m.k + 1) * sizeof(size_t));
348 c->r = rate_alloc(m);
9ea0417a 349 THINK(c, "Setting up", {
7a869046
MW
350 dig *ss = c->s; all_guesses(&ss, c->m.k, c->m.n, 0, 0);
351 });
352 return (c);
353}
354
355static void cpc_free(cpc *c)
356{
357 xfree(c->s);
358 xfree(c->v);
359 rate_free(c->r);
360 DESTROY(c);
361}
362
363static void cp_rate(void *r, const dig *g, unsigned *b, unsigned *w)
409fd535 364 { rate(r, g, b, w); }
7a869046
MW
365
366static const dig *cp_guess(void *cc)
367{
368 cpc *c = cc;
369
370 if (c->ns == 0) {
9ea0417a
MW
371 if (!(c->f & CPCF_QUIET))
372 printf("Liar! All solutions ruled out.\n");
7a869046
MW
373 return (0);
374 }
375 if (c->ns == 1) {
9ea0417a
MW
376 if (!(c->f & CPCF_QUIET)) {
377 fputs("Done! Solution is ", stdout);
378 print_guess(&c->m, c->s);
379 putchar('\n');
380 }
7a869046
MW
381 return (c->s);
382 }
9ea0417a
MW
383 if (!(c->f & CPCF_QUIET)) {
384 printf("(Possible solutions remaining = %lu)\n",
385 (unsigned long)c->ns);
386 if (c->ns < 32) {
387 const dig *s;
388 size_t i;
389 for (i = c->ns, s = c->s; i; i--, s += c->m.k) {
390 printf(" %2lu: ", (unsigned long)(c->ns - i + 1));
391 print_guess(&c->m, s);
392 putchar('\n');
393 }
7a869046
MW
394 }
395 }
9ea0417a 396 THINK(c, "Pondering", {
7a869046
MW
397 best_guess(c);
398 });
399 return (c->bg);
400}
401
402static void cp_update(void *cc, const dig *g, unsigned b, unsigned w)
403{
404 cpc *c = cc;
9ea0417a
MW
405
406 if (!(c->f & CPCF_QUIET)) {
407 fputs("My guess = ", stdout);
408 print_guess(&c->m, g);
409 printf("; rating = %u black, %u white\n", b, w);
410 }
411 THINK(c, "Filtering", {
7a869046
MW
412 filter_guesses(c, g, b, w);
413 });
414}
415
416/*----- Human player ------------------------------------------------------*/
417
418typedef struct hpc {
419 mm m;
420 dig *t;
421} hpc;
422
423static hpc *hpc_new(const mm *m)
424{
425 hpc *h = CREATE(hpc);
426 h->t = xmalloc(m->k * sizeof(dig));
427 h->m = *m;
428 return (h);
429}
430
431static void hpc_free(hpc *h)
432{
433 xfree(h->t);
434 DESTROY(h);
435}
436
437static void hp_rate(void *mp, const dig *g, unsigned *b, unsigned *w)
438{
439 mm *m = mp;
440 fputs("Guess = ", stdout);
441 print_guess(m, g);
442 printf("; rating: ");
443 fflush(stdout);
444 scanf("%u %u", b, w);
445}
446
447static const dig *hp_guess(void *hh)
448{
449 hpc *h = hh;
450 unsigned i;
451
452 fputs("Your guess: ", stdout);
453 fflush(stdout);
454 for (i = 0; i < h->m.k; i++) {
455 unsigned x;
456 scanf("%u", &x);
457 h->t[i] = x;
458 }
459 return (h->t);
460}
461
462static void hp_update(void *cc, const dig *g, unsigned b, unsigned w)
463{
464 printf("Rating = %u black, %u white\n", b, w);
465}
466
467/*----- Solver player -----------------------------------------------------*/
468
469typedef struct spc {
470 cpc *c;
471 hpc *h;
472 int i;
473} spc;
474
475static spc *spc_new(const mm *m)
476{
477 spc *s = CREATE(spc);
9ea0417a 478 s->c = cpc_new(m, 0);
7a869046
MW
479 s->h = hpc_new(m);
480 s->i = 0;
481 return (s);
482}
483
484static void spc_free(spc *s)
485{
486 cpc_free(s->c);
487 hpc_free(s->h);
488 DESTROY(s);
489}
490
491static const dig *sp_guess(void *ss)
492{
493 spc *s = ss;
494 hpc *h = s->h;
495 unsigned i;
496 int ch;
497
498again:
499 if (s->i)
500 return (cp_guess(s->c));
501
502 fputs("Your guess (dot for end): ", stdout);
503 fflush(stdout);
504 do ch = getchar(); while (isspace(ch));
505 if (!isdigit(ch)) { s->i = 1; goto again; }
506 ungetc(ch, stdin);
507 for (i = 0; i < h->m.k; i++) {
508 unsigned x;
509 scanf("%u", &x);
510 h->t[i] = x;
511 }
512 return (h->t);
513}
514
515static void sp_update(void *ss, const dig *g, unsigned b, unsigned w)
409fd535 516 { spc *s = ss; cp_update(s->c, g, b, w); }
7a869046 517
0fd4f375
MW
518/*----- Full tournament stuff ---------------------------------------------*/
519
520DA_DECL(uint_v, unsigned);
521
522typedef struct allstats {
523 const mm *m;
524 unsigned f;
525#define AF_VERBOSE 1u
526 uint_v gmap;
527 unsigned long g;
528 unsigned long n;
529 clock_t t;
530} allstats;
531
532static void dorunone(allstats *a, dig *s)
533{
534 ratectx *r = rate_new(a->m, s);
535 clock_t t = 0, t0, t1;
536 cpc *c;
537 int n = 0;
538 const dig *g;
539 unsigned b, w;
540
541 if (a->f & AF_VERBOSE) {
542 print_guess(a->m, s);
543 fputs(": ", stdout);
544 fflush(stdout);
545 }
546
547 c = cpc_new(a->m, CPCF_QUIET);
548 for (;;) {
549 t0 = clock();
550 g = cp_guess(c);
551 t1 = clock();
552 t += t1 - t0;
553 assert(g);
554 n++;
555 rate(r, g, &b, &w);
556 if (b == a->m->k)
557 break;
558 t0 = clock();
559 cp_update(c, g, b, w);
560 t1 = clock();
561 t += t1 - t0;
562 }
563 a->t += t;
564 a->g += n;
565 while (DA_LEN(&a->gmap) <= n)
566 DA_PUSH(&a->gmap, 0);
567 DA(&a->gmap)[n]++;
568 rate_free(r);
569 cpc_free(c);
570
571 if (a->f & AF_VERBOSE)
572 printf("%2u (%5.2fs)\n", n, (double)t/CLOCKS_PER_SEC);
573}
574
575static void dorunall(allstats *a, dig *s, unsigned i)
576{
577 dig j;
578
579 if (i >= a->m->k) {
580 dorunone(a, s);
581 a->n++;
582 } else {
583 for (j = 0; j < a->m->n; j++) {
584 s[i] = j;
585 dorunall(a, s, i + 1);
586 }
587 }
588}
589
590static void run_all(const mm *m)
591{
592 dig *s = xmalloc(m->k * sizeof(dig));
593 allstats a;
594 unsigned i;
595
596 a.m = m;
597 a.f = AF_VERBOSE;
598 DA_CREATE(&a.gmap);
599 a.n = 0;
600 a.g = 0;
601 a.t = 0;
602 dorunall(&a, s, 0);
603 xfree(s);
604
605 for (i = 1; i < DA_LEN(&a.gmap); i++)
606 printf("%2u guesses: %5u games\n", i, DA(&a.gmap)[i]);
607 printf("Average: %.4f (%.2fs)\n",
608 (double)a.g/a.n, (double)a.t/(a.n * CLOCKS_PER_SEC));
609}
610
7a869046
MW
611/*----- Main game logic ---------------------------------------------------*/
612
613static int play(const mm *m,
614 void (*ratefn)(void *rr, const dig *g,
615 unsigned *b, unsigned *w),
616 void *rr,
617 const dig *(*guessfn)(void *gg),
618 void (*updatefn)(void *gg, const dig *g,
619 unsigned b, unsigned w),
620 void *gg)
621{
622 unsigned b, w;
623 const dig *g;
624 unsigned i;
625
626 i = 0;
627 for (;;) {
628 i++;
629 g = guessfn(gg);
630 if (!g)
631 return (-1);
632 ratefn(rr, g, &b, &w);
633 if (b == m->k)
634 return (i);
635 updatefn(gg, g, b, w);
636 }
637}
638
639int main(int argc, char *argv[])
640{
641 unsigned h = 0;
642 void *rr = 0;
643 void (*ratefn)(void *rr, const dig *g, unsigned *b, unsigned *w) = 0;
644 mm m;
645 int n;
646
647 ego(argv[0]);
648 for (;;) {
649 static struct option opt[] = {
650 { "computer", 0, 0, 'C' },
651 { "human", 0, 0, 'H' },
652 { "solver", 0, 0, 'S' },
0fd4f375 653 { "all", 0, 0, 'a' },
7a869046
MW
654 { 0, 0, 0, 0 }
655 };
0fd4f375 656 int i = mdwopt(argc, argv, "CHSa", opt, 0, 0, 0);
7a869046
MW
657 if (i < 0)
658 break;
659 switch (i) {
660 case 'C': h = 0; break;
661 case 'H': h = 1; break;
662 case 'S': h = 2; break;
0fd4f375 663 case 'a': h = 99; break;
7a869046
MW
664 default:
665 exit(1);
666 }
667 }
668 if (argc - optind == 0) {
669 m.k = 4;
670 m.n = 6;
671 } else if (argc - optind < 2)
672 die(1, "bad parameters");
673 else {
674 m.k = atoi(argv[optind++]);
675 m.n = atoi(argv[optind++]);
676 if (argc - optind >= m.k) {
677 dig *s = xmalloc(m.k * sizeof(dig));
678 int i;
679 for (i = 0; i < m.k; i++)
680 s[i] = atoi(argv[optind++]);
681 rr = rate_new(&m, s);
682 ratefn = cp_rate;
683 xfree(s);
684 }
685 if (argc != optind)
686 die(1, "bad parameters");
687 }
688
689 switch (h) {
690 case 1: {
691 hpc *hh = hpc_new(&m);
692 if (!rr) {
693 dig *s = xmalloc(m.k * sizeof(dig));
694 int i;
695 srand(time(0));
696 for (i = 0; i < m.k; i++)
697 s[i] = rand() % m.n;
698 rr = rate_new(&m, s);
699 ratefn = cp_rate;
700 xfree(s);
701 }
702 n = play(&m, ratefn, rr, hp_guess, hp_update, hh);
703 hpc_free(hh);
704 } break;
705 case 0: {
9ea0417a 706 cpc *cc = cpc_new(&m, 0);
7a869046
MW
707 if (rr)
708 n = play(&m, ratefn, rr, cp_guess, cp_update, cc);
709 else
710 n = play(&m, hp_rate, &m, cp_guess, cp_update, cc);
711 cpc_free(cc);
712 } break;
713 case 2: {
714 spc *ss = spc_new(&m);
715 n = play(&m, hp_rate, &m, sp_guess, sp_update, ss);
716 spc_free(ss);
717 } break;
0fd4f375
MW
718 case 99:
719 run_all(&m);
720 return (0);
721 break;
7a869046
MW
722 default:
723 abort();
724 }
725 if (n > 0)
726 printf("Solved in %d guesses\n", n);
727 else
728 die(1, "gave up");
729 return (0);
730}
731
732/*----- That's all, folks -------------------------------------------------*/