board_group_other_lib(): Introduce nifty helper
[pachi.git] / playout / moggy.c
blobbdecf7d4098d0510c6154a5f44eea3dda5c63193
1 /* Playout policy by stochastically applying a fixed set of decision
2 * rules in given order - modelled after the intelligent playouts
3 * in the Mogo engine. */
5 #include <assert.h>
6 #include <math.h>
7 #include <stdio.h>
8 #include <stdlib.h>
10 #define DEBUG
11 #include "board.h"
12 #include "debug.h"
13 #include "mq.h"
14 #include "pattern3.h"
15 #include "playout.h"
16 #include "playout/moggy.h"
17 #include "random.h"
18 #include "tactics.h"
19 #include "uct/prior.h"
21 #define PLDEBUGL(n) DEBUGL_(p->debug_level, n)
23 /* Whether to avoid capturing/atariing doomed groups (this is big
24 * performance hit and may reduce playouts balance; it does increase
25 * the strength, but not quite proportionally to the performance). */
26 //#define NO_DOOMED_GROUPS
29 /* Note that the context can be shared by multiple threads! */
31 struct moggy_policy {
32 bool ladders, ladderassess, borderladders, assess_local;
33 unsigned int lcapturerate, atarirate, capturerate, patternrate, korate;
34 unsigned int selfatarirate, alwaysccaprate;
35 unsigned int fillboardtries;
36 int koage;
37 /* Whether to look for patterns around second-to-last move. */
38 bool pattern2;
39 /* Whether, when self-atari attempt is detected, to play the other
40 * group's liberty if that is non-self-atari. */
41 bool selfatari_other;
43 struct pattern3s patterns;
47 struct group_state {
48 enum {
49 G_ATARI,
50 G_2LIB, /* Unused. */
51 G_SAFE /* Unused. */
52 } status:2;
54 /* Below, we keep track of each trait for each |color_to_play */
55 int capturable_ready:2; // is @capturable meaningful?
56 int capturable:2;
58 int can_countercapture_ready:2;
59 int can_countercapture:2;
62 /* Cache of evaluation of various board features. */
63 struct board_state {
64 int bsize2;
65 hash_t hash;
66 struct group_state *groups; /* [board_size2()], indexed by group_t */
67 unsigned char *groups_known; /* Bitmap of known groups. */
70 /* Using board cache: this turns out to be actually a 10% slowdown,
71 * since we reuse data in the cache only very little within single
72 * move. */
73 // #define CACHE_STATE
74 /* Reusing board cache across moves if they are successive on the
75 * board; only cache entries within cfg distance 2 of the last move
76 * are cleared. */
77 // #define PERSISTENT_STATE
79 #ifdef CACHE_STATE
80 static __thread struct board_state *ss;
82 static bool
83 board_state_reuse(struct board_state *s, struct board *b)
85 /* Decide how much of the board state we can reuse. */
86 /* We do not cache ladder decisions, so we don't have
87 * to worry about this. */
88 coord_t c = b->last_move.coord;
90 if (unlikely(is_pass(c))) {
91 /* Passes don't change anything. */
92 return true;
95 if (unlikely(board_at(b, c) == S_NONE)) {
96 /* Suicide is hopeless. */
97 return false;
100 /* XXX: we can make some moves self-atari. */
102 if (neighbor_count_at(b, c, S_BLACK) + neighbor_count_at(b, c, S_WHITE) == 0) {
103 /* We are not taking off liberties of any other stones. */
104 return true;
107 return false;
110 static inline struct board_state *
111 board_state_init(struct board *b)
113 if (ss) {
114 if (ss->bsize2 != board_size2(b)) {
115 free(ss->groups);
116 free(ss->groups_known);
117 free(ss); ss = NULL;
119 #ifdef PERSISTENT_STATE
120 /* Only one stone added to the board, nothing removed. */
121 else if (ss->hash == (b->hash ^ hash_at(b, b->last_move.coord, b->last_move.color))) {
122 ss->hash = b->hash;
123 if (likely(board_state_reuse(ss, b)))
124 return ss;
126 #endif
128 if (!ss) {
129 ss = malloc2(sizeof(*ss));
130 ss->bsize2 = board_size2(b);
131 ss->groups = malloc2(board_size2(b) * sizeof(*ss->groups));
132 ss->groups_known = malloc2(board_size2(b) / 8 + 1);
134 ss->hash = b->hash;
135 memset(ss->groups_known, 0, board_size2(b) / 8 + 1);
136 return ss;
139 #define group_is_known(s, g) (s->groups_known[g >> 3] & (1 << (g & 7)))
140 #define group_set_known(s, g) (s->groups_known[g >> 3] |= (1 << (g & 7)))
141 #define group_trait_ready(s, g, color, gstat, trait) do { \
142 if (!group_is_known(s, g)) { \
143 memset(&s->groups[g], 0, sizeof(s->groups[g])); \
144 group_set_known(s, g); \
146 s->groups[g].status = gstat; \
147 s->groups[g].trait ## _ready |= color; \
148 } while (0)
149 #define group_trait_is_ready(s, g, color, trait) (s->groups[g].trait ## _ready & color)
150 #define group_trait_set(s, g, color, trait, val) s->groups[g].trait = (s->groups[g].trait & ~color) | (!!val * color)
151 #define group_trait_get(s, g, color, trait) (s->groups[g].trait & color)
153 #else
155 #define board_state_init(b) NULL
156 #define group_is_known(s, g) false
157 #define group_set_known(s, g)
158 #define group_trait_ready(s, g, color, gstat, trait)
159 #define group_trait_is_ready(s, g, color, trait) false
160 #define group_trait_set(s, g, color, trait, val)
161 #define group_trait_get(s, g, color, trait) false
162 #endif
165 static char moggy_patterns_src[][11] = {
166 /* hane pattern - enclosing hane */
167 "XOX"
168 "..."
169 "???",
170 /* hane pattern - non-cutting hane */
171 "XO."
172 "..."
173 "?.?",
174 /* hane pattern - magari */
175 "XO?"
176 "X.."
177 "x.?",
178 /* hane pattern - thin hane */
179 "XOO"
180 "..."
181 "?.?" "X",
182 /* generic pattern - katatsuke or diagonal attachment; similar to magari */
183 ".O."
184 "X.."
185 "...",
186 /* cut1 pattern (kiri) - unprotected cut */
187 "XO?"
188 "O.o"
189 "?o?",
190 /* cut1 pattern (kiri) - peeped cut */
191 "XO?"
192 "O.X"
193 "???",
194 /* cut2 pattern (de) */
195 "?X?"
196 "O.O"
197 "ooo",
198 /* cut keima (not in Mogo) */
199 "OX?"
200 "o.O"
201 "???", /* o?? has some pathological tsumego cases */
202 /* side pattern - chase */
203 "X.?"
204 "O.?"
205 "##?",
206 /* side pattern - weirdness (SUSPICIOUS) */
207 "?X?"
208 "X.O"
209 "###",
210 /* side pattern - sagari (SUSPICIOUS) */
211 "?XO"
212 "x.x" /* Mogo has "x.?" */
213 "###" /* Mogo has "X" */,
214 /* side pattern - throw-in (SUSPICIOUS) */
215 #if 0
216 "?OX"
217 "o.O"
218 "?##" "X",
219 #endif
220 /* side pattern - cut (SUSPICIOUS) */
221 "?OX"
222 "X.O"
223 "###" /* Mogo has "X" */,
225 #define moggy_patterns_src_n sizeof(moggy_patterns_src) / sizeof(moggy_patterns_src[0])
227 static inline bool
228 test_pattern3_here(struct playout_policy *p, struct board *b, struct move *m)
230 struct moggy_policy *pp = p->data;
231 /* Check if 3x3 pattern is matched by given move... */
232 if (!pattern3_move_here(&pp->patterns, b, m))
233 return false;
234 /* ...and the move is not obviously stupid. */
235 if (is_bad_selfatari(b, m->color, m->coord))
236 return false;
237 /* Ladder moves are stupid. */
238 group_t atari_neighbor = board_get_atari_neighbor(b, m->coord, m->color);
239 if (atari_neighbor && is_ladder(b, m->coord, atari_neighbor, pp->borderladders, pp->ladders))
240 return false;
241 return true;
244 static void
245 apply_pattern_here(struct playout_policy *p, struct board *b, coord_t c, enum stone color, struct move_queue *q)
247 struct move m2 = { .coord = c, .color = color };
248 if (board_is_valid_move(b, &m2) && test_pattern3_here(p, b, &m2))
249 mq_add(q, c);
252 /* Check if we match any pattern around given move (with the other color to play). */
253 static coord_t
254 apply_pattern(struct playout_policy *p, struct board *b, struct move *m, struct move *mm)
256 struct move_queue q;
257 q.moves = 0;
259 /* Suicides do not make any patterns and confuse us. */
260 if (board_at(b, m->coord) == S_NONE || board_at(b, m->coord) == S_OFFBOARD)
261 return pass;
263 foreach_8neighbor(b, m->coord) {
264 apply_pattern_here(p, b, c, stone_other(m->color), &q);
265 } foreach_8neighbor_end;
267 if (mm) { /* Second move for pattern searching */
268 foreach_8neighbor(b, mm->coord) {
269 if (coord_is_8adjecent(m->coord, c, b))
270 continue;
271 apply_pattern_here(p, b, c, stone_other(m->color), &q);
272 } foreach_8neighbor_end;
275 if (PLDEBUGL(5))
276 mq_print(&q, b, "Pattern");
278 return mq_pick(&q);
282 static bool
283 can_play_on_lib(struct playout_policy *p, struct board_state *s,
284 struct board *b, group_t g, enum stone to_play)
286 if (group_is_known(s, g) && group_trait_is_ready(s, g, to_play, capturable)) {
287 /* We have already seen this group. */
288 assert(s->groups[g].status == G_ATARI);
289 if (group_trait_get(s, g, to_play, capturable))
290 return true;
291 else
292 return false;
295 /* Cache miss. Set up cache entry, default at capturable = false. */
296 group_trait_ready(s, g, to_play, G_ATARI, capturable);
298 coord_t capture = board_group_info(b, g).lib[0];
299 if (PLDEBUGL(6))
300 fprintf(stderr, "can capture group %d (%s)?\n",
301 g, coord2sstr(capture, b));
302 /* Does playing on the liberty usefully capture the group? */
303 if (board_is_valid_play(b, to_play, capture)
304 && !is_bad_selfatari(b, to_play, capture)) {
305 group_trait_set(s, g, to_play, capturable, true);
306 return true;
309 return false;
312 /* For given position @c, decide if this is a group that is in danger from
313 * @capturer and @to_play can do anything about it (play at the last
314 * liberty to either capture or escape). */
315 /* Note that @to_play is important; e.g. consider snapback, it's good
316 * to play at the last liberty by attacker, but not defender. */
317 static __attribute__((always_inline)) bool
318 capturable_group(struct playout_policy *p, struct board_state *s,
319 struct board *b, enum stone capturer, coord_t c,
320 enum stone to_play)
322 group_t g = group_at(b, c);
323 if (likely(board_at(b, c) != stone_other(capturer)
324 || board_group_info(b, g).libs > 1))
325 return false;
327 return can_play_on_lib(p, s, b, g, to_play);
330 /* For given atari group @group owned by @owner, decide if @to_play
331 * can save it / keep it in danger by dealing with one of the
332 * neighboring groups. */
333 static bool
334 can_countercapture(struct playout_policy *p, struct board_state *s,
335 struct board *b, enum stone owner, group_t g,
336 enum stone to_play, struct move_queue *q)
338 if (b->clen < 2)
339 return false;
340 if (group_is_known(s, g) && group_trait_is_ready(s, g, to_play, can_countercapture)) {
341 /* We have already seen this group. */
342 assert(s->groups[g].status == G_ATARI);
343 if (group_trait_get(s, g, to_play, can_countercapture)) {
344 if (q) { /* Scan for countercapture liberties. */
345 goto scan;
347 return true;
348 } else {
349 return false;
353 /* Cache miss. Set up cache entry, default at can_countercapture = true. */
354 group_trait_ready(s, g, to_play, G_ATARI, can_countercapture);
355 group_trait_set(s, g, to_play, can_countercapture, true);
357 scan:;
358 unsigned int qmoves_prev = q ? q->moves : 0;
360 foreach_in_group(b, g) {
361 foreach_neighbor(b, c, {
362 if (!capturable_group(p, s, b, owner, c, to_play))
363 continue;
365 if (!q) {
366 return true;
368 mq_add(q, board_group_info(b, group_at(b, c)).lib[0]);
369 mq_nodup(q);
371 } foreach_in_group_end;
373 bool can = q ? q->moves > qmoves_prev : false;
374 group_trait_set(s, g, to_play, can_countercapture, can);
375 return can;
378 #ifdef NO_DOOMED_GROUPS
379 static bool
380 can_be_rescued(struct playout_policy *p, struct board_state *s,
381 struct board *b, group_t group, enum stone color)
383 /* Does playing on the liberty rescue the group? */
384 if (can_play_on_lib(p, s, b, group, color))
385 return true;
387 /* Then, maybe we can capture one of our neighbors? */
388 return can_countercapture(p, s, b, color, group, color, NULL);
390 #endif
392 /* ladder != NULL implies to always enqueue all relevant moves. */
393 static void
394 group_atari_check(struct playout_policy *p, struct board *b, group_t group, enum stone to_play,
395 struct move_queue *q, coord_t *ladder, struct board_state *s)
397 struct moggy_policy *pp = p->data;
398 int qmoves_prev = q->moves;
400 /* We don't use @to_play almost anywhere since any moves here are good
401 * for both defender and attacker. */
403 enum stone color = board_at(b, group_base(group));
404 coord_t lib = board_group_info(b, group).lib[0];
406 assert(color != S_OFFBOARD && color != S_NONE);
407 if (PLDEBUGL(5))
408 fprintf(stderr, "[%s] atariiiiiiiii %s of color %d\n",
409 coord2sstr(group, b), coord2sstr(lib, b), color);
410 assert(board_at(b, lib) == S_NONE);
412 /* Do not bother with kos. */
413 if (group_is_onestone(b, group)
414 && neighbor_count_at(b, lib, color) + neighbor_count_at(b, lib, S_OFFBOARD) == 4)
415 return;
417 /* Can we capture some neighbor? */
418 bool ccap = can_countercapture(p, s, b, color, group, to_play, q);
419 if (ccap && !ladder && pp->alwaysccaprate > fast_random(100))
420 return;
422 /* Do not suicide... */
423 if (!can_play_on_lib(p, s, b, group, to_play))
424 return;
425 #ifdef NO_DOOMED_GROUPS
426 /* Do not remove group that cannot be saved by the opponent. */
427 if (to_play != color && !can_be_rescued(p, s, b, group, color))
428 return;
429 #endif
430 if (PLDEBUGL(6))
431 fprintf(stderr, "...escape route valid\n");
433 /* ...or play out ladders. */
434 if (is_ladder(b, lib, group, pp->borderladders, pp->ladders)) {
435 /* Sometimes we want to keep the ladder move in the
436 * queue in order to discourage it. */
437 if (!ladder)
438 return;
439 else
440 *ladder = lib;
442 if (PLDEBUGL(6))
443 fprintf(stderr, "...no ladder\n");
445 if (to_play != color) {
446 /* We are the attacker! In that case, throw away the moves
447 * that defend our groups, since we can capture the culprit. */
448 q->moves = qmoves_prev;
451 mq_add(q, lib);
452 mq_nodup(q);
455 static coord_t
456 global_atari_check(struct playout_policy *p, struct board *b, enum stone to_play, struct board_state *s)
458 struct move_queue q;
459 q.moves = 0;
461 if (b->clen == 0)
462 return pass;
464 int g_base = fast_random(b->clen);
465 for (int g = g_base; g < b->clen; g++) {
466 group_atari_check(p, b, group_at(b, group_base(b->c[g])), to_play, &q, NULL, s);
467 if (q.moves > 0) {
468 if (PLDEBUGL(5))
469 mq_print(&q, b, "Global atari");
470 return mq_pick(&q);
473 for (int g = 0; g < g_base; g++) {
474 group_atari_check(p, b, group_at(b, group_base(b->c[g])), to_play, &q, NULL, s);
475 if (q.moves > 0) {
476 if (PLDEBUGL(5))
477 mq_print(&q, b, "Global atari");
478 return mq_pick(&q);
481 return pass;
484 static coord_t
485 local_atari_check(struct playout_policy *p, struct board *b, struct move *m, struct board_state *s)
487 struct move_queue q;
488 q.moves = 0;
490 /* Did the opponent play a self-atari? */
491 if (board_group_info(b, group_at(b, m->coord)).libs == 1) {
492 group_atari_check(p, b, group_at(b, m->coord), stone_other(m->color), &q, NULL, s);
495 foreach_neighbor(b, m->coord, {
496 group_t g = group_at(b, c);
497 if (!g || board_group_info(b, g).libs != 1)
498 continue;
499 group_atari_check(p, b, g, stone_other(m->color), &q, NULL, s);
502 if (PLDEBUGL(5))
503 mq_print(&q, b, "Local atari");
505 return mq_pick(&q);
508 static bool
509 miai_2lib(struct board *b, group_t group, enum stone color)
511 bool can_connect = false, can_pull_out = false;
512 /* We have miai if we can either connect on both libs,
513 * or connect on one lib and escape on another. (Just
514 * having two escape routes can be risky.) We must make
515 * sure that we don't consider following as miai:
516 * X X X O
517 * X . . O
518 * O O X O - left dot would be pull-out, right dot connect */
519 foreach_neighbor(b, board_group_info(b, group).lib[0], {
520 enum stone cc = board_at(b, c);
521 if (cc == S_NONE && cc != board_at(b, board_group_info(b, group).lib[1])) {
522 can_pull_out = true;
523 } else if (cc != color) {
524 continue;
527 group_t cg = group_at(b, c);
528 if (cg && cg != group && board_group_info(b, cg).libs > 1)
529 can_connect = true;
531 foreach_neighbor(b, board_group_info(b, group).lib[1], {
532 enum stone cc = board_at(b, c);
533 if (c == board_group_info(b, group).lib[0])
534 continue;
535 if (cc == S_NONE && can_connect) {
536 return true;
537 } else if (cc != color) {
538 continue;
541 group_t cg = group_at(b, c);
542 if (cg && cg != group && board_group_info(b, cg).libs > 1)
543 return (can_connect || can_pull_out);
545 return false;
548 static void
549 check_group_atari(struct board *b, group_t group, enum stone owner,
550 enum stone to_play, struct move_queue *q)
552 for (int i = 0; i < 2; i++) {
553 coord_t lib = board_group_info(b, group).lib[i];
554 assert(board_at(b, lib) == S_NONE);
555 if (!board_is_valid_play(b, to_play, lib))
556 continue;
558 /* Don't play at the spot if it is extremely short
559 * of liberties... */
560 /* XXX: This looks harmful, could significantly
561 * prefer atari to throwin:
563 * XXXOOOOOXX
564 * .OO.....OX
565 * XXXOOOOOOX */
566 #if 0
567 if (neighbor_count_at(b, lib, stone_other(owner)) + immediate_liberty_count(b, lib) < 2)
568 continue;
569 #endif
571 #ifdef NO_DOOMED_GROUPS
572 /* If the owner can't play at the spot, we don't want
573 * to bother either. */
574 if (is_bad_selfatari(b, owner, lib))
575 continue;
576 #endif
578 /* Of course we don't want to play bad selfatari
579 * ourselves, if we are the attacker... */
580 if (
581 #ifdef NO_DOOMED_GROUPS
582 to_play != owner &&
583 #endif
584 is_bad_selfatari(b, to_play, lib))
585 continue;
587 /* Tasty! Crispy! Good! */
588 mq_add(q, lib);
589 mq_nodup(q);
593 static void
594 group_2lib_check(struct playout_policy *p, struct board *b, group_t group, enum stone to_play,
595 struct move_queue *q, struct board_state *s)
597 enum stone color = board_at(b, group_base(group));
598 assert(color != S_OFFBOARD && color != S_NONE);
600 if (PLDEBUGL(5))
601 fprintf(stderr, "[%s] 2lib check of color %d\n",
602 coord2sstr(group, b), color);
604 /* Do not try to atari groups that cannot be harmed. */
605 if (miai_2lib(b, group, color))
606 return;
608 check_group_atari(b, group, color, to_play, q);
610 /* Can we counter-atari another group, if we are the defender? */
611 if (to_play != color)
612 return;
613 foreach_in_group(b, group) {
614 foreach_neighbor(b, c, {
615 if (board_at(b, c) != stone_other(color))
616 continue;
617 group_t g2 = group_at(b, c);
618 if (board_group_info(b, g2).libs != 2)
619 continue;
620 check_group_atari(b, g2, color, to_play, q);
622 } foreach_in_group_end;
625 static coord_t
626 local_2lib_check(struct playout_policy *p, struct board *b, struct move *m, struct board_state *s)
628 struct move_queue q;
629 q.moves = 0;
631 /* Does the opponent have just two liberties? */
632 if (board_group_info(b, group_at(b, m->coord)).libs == 2) {
633 group_2lib_check(p, b, group_at(b, m->coord), stone_other(m->color), &q, s);
634 #if 0
635 /* We always prefer to take off an enemy chain liberty
636 * before pulling out ourselves. */
637 /* XXX: We aren't guaranteed to return to that group
638 * later. */
639 if (q.moves)
640 return q.move[fast_random(q.moves)];
641 #endif
644 /* Then he took a third liberty from neighboring chain? */
645 foreach_neighbor(b, m->coord, {
646 group_t g = group_at(b, c);
647 if (!g || board_group_info(b, g).libs != 2)
648 continue;
649 group_2lib_check(p, b, g, stone_other(m->color), &q, s);
652 if (PLDEBUGL(5))
653 mq_print(&q, b, "Local 2lib");
655 return mq_pick(&q);
658 coord_t
659 playout_moggy_choose(struct playout_policy *p, struct board *b, enum stone to_play)
661 struct moggy_policy *pp = p->data;
662 coord_t c;
664 struct board_state *s = board_state_init(b);
666 if (PLDEBUGL(5))
667 board_print(b, stderr);
669 /* Ko fight check */
670 if (!is_pass(b->last_ko.coord) && is_pass(b->ko.coord)
671 && b->moves - b->last_ko_age < pp->koage
672 && pp->korate > fast_random(100)) {
673 if (board_is_valid_play(b, to_play, b->last_ko.coord)
674 && !is_bad_selfatari(b, to_play, b->last_ko.coord))
675 return b->last_ko.coord;
678 /* Local checks */
679 if (!is_pass(b->last_move.coord)) {
680 /* Local group in atari? */
681 if (pp->lcapturerate > fast_random(100)) {
682 c = local_atari_check(p, b, &b->last_move, s);
683 if (!is_pass(c))
684 return c;
687 /* Local group can be PUT in atari? */
688 if (pp->atarirate > fast_random(100)) {
689 c = local_2lib_check(p, b, &b->last_move, s);
690 if (!is_pass(c))
691 return c;
694 /* Check for patterns we know */
695 if (pp->patternrate > fast_random(100)) {
696 c = apply_pattern(p, b, &b->last_move,
697 pp->pattern2 && b->last_move2.coord >= 0 ? &b->last_move2 : NULL);
698 if (!is_pass(c))
699 return c;
703 /* Global checks */
705 /* Any groups in atari? */
706 if (pp->capturerate > fast_random(100)) {
707 c = global_atari_check(p, b, to_play, s);
708 if (!is_pass(c))
709 return c;
712 /* Fill board */
713 unsigned int fbtries = b->flen / 8;
714 for (unsigned int i = 0; i < (fbtries < pp->fillboardtries ? fbtries : pp->fillboardtries); i++) {
715 coord_t coord = b->f[fast_random(b->flen)];
716 if (immediate_liberty_count(b, coord) != 4)
717 continue;
718 foreach_diag_neighbor(b, coord) {
719 if (board_at(b, c) != S_NONE)
720 goto next_try;
721 } foreach_diag_neighbor_end;
722 return coord;
723 next_try:;
726 return pass;
730 static coord_t
731 selfatari_cousin(struct board *b, enum stone color, coord_t coord)
733 group_t groups[4]; int groups_n = 0;
734 foreach_neighbor(b, coord, {
735 enum stone s = board_at(b, c);
736 if (s != color) continue;
737 group_t g = group_at(b, c);
738 if (board_group_info(b, g).libs == 2)
739 groups[groups_n++] = g;
742 if (!groups_n)
743 return pass;
744 group_t group = groups[fast_random(groups_n)];
746 coord_t lib2 = board_group_other_lib(b, group, coord);
747 if (is_bad_selfatari(b, color, lib2))
748 return pass;
749 return lib2;
752 static int
753 assess_local_bonus(struct playout_policy *p, struct board *board, coord_t a, coord_t b, int games)
755 struct moggy_policy *pp = p->data;
756 if (!pp->assess_local)
757 return games;
759 int dx = abs(coord_x(a, board) - coord_x(b, board));
760 int dy = abs(coord_y(a, board) - coord_y(b, board));
761 /* adjecent move, directly or diagonally? */
762 if (dx + dy <= 1 + (dx && dy))
763 return games;
764 else
765 return games / 2;
768 void
769 playout_moggy_assess_group(struct playout_policy *p, struct prior_map *map, group_t g, int games,
770 struct board_state *s)
772 struct moggy_policy *pp = p->data;
773 struct board *b = map->b;
774 struct move_queue q; q.moves = 0;
776 if (board_group_info(b, g).libs > 2)
777 return;
779 if (PLDEBUGL(5)) {
780 fprintf(stderr, "ASSESS of group %s:\n", coord2sstr(g, b));
781 board_print(b, stderr);
784 if (board_group_info(b, g).libs == 2) {
785 if (!pp->atarirate)
786 return;
787 group_2lib_check(p, b, g, map->to_play, &q, s);
788 while (q.moves--) {
789 coord_t coord = q.move[q.moves];
790 if (PLDEBUGL(5))
791 fprintf(stderr, "1.0: 2lib %s\n", coord2sstr(coord, b));
792 int assess = assess_local_bonus(p, b, b->last_move.coord, coord, games) / 2;
793 add_prior_value(map, coord, 1, assess);
795 return;
798 /* This group, sir, is in atari! */
800 if (!pp->capturerate && !pp->lcapturerate && !pp->ladderassess)
801 return;
803 coord_t ladder = pass;
804 group_atari_check(p, b, g, map->to_play, &q, &ladder, s);
805 while (q.moves--) {
806 coord_t coord = q.move[q.moves];
808 /* _Never_ play here if this move plays out
809 * a caught ladder. */
810 if (coord == ladder && !board_playing_ko_threat(b)) {
811 /* Note that the opposite is not guarded against;
812 * we do not advise against capturing a laddered
813 * group (but we don't encourage it either). Such
814 * a move can simplify tactical situations if we
815 * can afford it. */
816 if (!pp->ladderassess || map->to_play != board_at(b, g))
817 continue;
818 /* FIXME: We give the malus even if this move
819 * captures another group. */
820 if (PLDEBUGL(5))
821 fprintf(stderr, "0.0: ladder %s\n", coord2sstr(coord, b));
822 add_prior_value(map, coord, 0, games);
823 continue;
826 if (!pp->capturerate && !pp->lcapturerate)
827 continue;
829 if (PLDEBUGL(5))
830 fprintf(stderr, "1.0: atari %s\n", coord2sstr(coord, b));
831 int assess = assess_local_bonus(p, b, b->last_move.coord, coord, games) * 2;
832 add_prior_value(map, coord, 1, assess);
836 void
837 playout_moggy_assess_one(struct playout_policy *p, struct prior_map *map, coord_t coord, int games)
839 struct moggy_policy *pp = p->data;
840 struct board *b = map->b;
842 if (PLDEBUGL(5)) {
843 fprintf(stderr, "ASSESS of move %s:\n", coord2sstr(coord, b));
844 board_print(b, stderr);
847 /* Is this move a self-atari? */
848 if (pp->selfatarirate) {
849 if (!board_playing_ko_threat(b) && is_bad_selfatari(b, map->to_play, coord)) {
850 if (PLDEBUGL(5))
851 fprintf(stderr, "0.0: self-atari\n");
852 add_prior_value(map, coord, 0, games);
853 if (!pp->selfatari_other)
854 return;
855 /* If we can play on the other liberty of the
856 * endangered group, do! */
857 coord = selfatari_cousin(b, map->to_play, coord);
858 if (is_pass(coord))
859 return;
860 if (PLDEBUGL(5))
861 fprintf(stderr, "1.0: self-atari redirect %s\n", coord2sstr(coord, b));
862 add_prior_value(map, coord, 1.0, games);
863 return;
867 /* Pattern check */
868 if (pp->patternrate) {
869 struct move m = { .color = map->to_play, .coord = coord };
870 if (test_pattern3_here(p, b, &m)) {
871 if (PLDEBUGL(5))
872 fprintf(stderr, "1.0: pattern\n");
873 int assess = assess_local_bonus(p, b, b->last_move.coord, coord, games);
874 add_prior_value(map, coord, 1, assess);
878 return;
881 void
882 playout_moggy_assess(struct playout_policy *p, struct prior_map *map, int games)
884 struct moggy_policy *pp = p->data;
886 struct board_state *s = board_state_init(map->b);
888 /* First, go through all endangered groups. */
889 if (pp->lcapturerate || pp->capturerate || pp->atarirate || pp->ladderassess)
890 for (group_t g = 1; g < board_size2(map->b); g++)
891 if (group_at(map->b, g) == g)
892 playout_moggy_assess_group(p, map, g, games, s);
894 /* Then, assess individual moves. */
895 if (!pp->patternrate && !pp->selfatarirate)
896 return;
897 foreach_free_point(map->b) {
898 if (map->consider[c])
899 playout_moggy_assess_one(p, map, c, games);
900 } foreach_free_point_end;
903 bool
904 playout_moggy_permit(struct playout_policy *p, struct board *b, struct move *m)
906 struct moggy_policy *pp = p->data;
908 /* The idea is simple for now - never allow self-atari moves.
909 * They suck in general, but this also permits us to actually
910 * handle seki in the playout stage. */
912 if (fast_random(100) >= pp->selfatarirate) {
913 if (PLDEBUGL(5))
914 fprintf(stderr, "skipping sar test\n");
915 return true;
917 bool selfatari = is_bad_selfatari(b, m->color, m->coord);
918 if (selfatari) {
919 if (PLDEBUGL(5))
920 fprintf(stderr, "__ Prohibiting self-atari %s %s\n",
921 stone2str(m->color), coord2sstr(m->coord, b));
922 if (pp->selfatari_other) {
923 /* Ok, try the other liberty of the atari'd group. */
924 coord_t c = selfatari_cousin(b, m->color, m->coord);
925 if (is_pass(c)) return false;
926 if (PLDEBUGL(5))
927 fprintf(stderr, "___ Redirecting to other lib %s\n",
928 coord2sstr(c, b));
929 m->coord = c;
930 return true;
932 return false;
934 return true;
938 struct playout_policy *
939 playout_moggy_init(char *arg, struct board *b)
941 struct playout_policy *p = calloc2(1, sizeof(*p));
942 struct moggy_policy *pp = calloc2(1, sizeof(*pp));
943 p->data = pp;
944 p->choose = playout_moggy_choose;
945 p->assess = playout_moggy_assess;
946 p->permit = playout_moggy_permit;
948 int rate = 90;
950 pp->lcapturerate = pp->atarirate = pp->capturerate = pp->patternrate = pp->selfatarirate
951 = -1U;
952 pp->korate = 0; pp->koage = 4;
953 pp->alwaysccaprate = 0;
954 pp->ladders = pp->borderladders = true;
955 pp->ladderassess = true;
957 if (arg) {
958 char *optspec, *next = arg;
959 while (*next) {
960 optspec = next;
961 next += strcspn(next, ":");
962 if (*next) { *next++ = 0; } else { *next = 0; }
964 char *optname = optspec;
965 char *optval = strchr(optspec, '=');
966 if (optval) *optval++ = 0;
968 if (!strcasecmp(optname, "lcapturerate") && optval) {
969 pp->lcapturerate = atoi(optval);
970 } else if (!strcasecmp(optname, "atarirate") && optval) {
971 pp->atarirate = atoi(optval);
972 } else if (!strcasecmp(optname, "capturerate") && optval) {
973 pp->capturerate = atoi(optval);
974 } else if (!strcasecmp(optname, "patternrate") && optval) {
975 pp->patternrate = atoi(optval);
976 } else if (!strcasecmp(optname, "selfatarirate") && optval) {
977 pp->selfatarirate = atoi(optval);
978 } else if (!strcasecmp(optname, "korate") && optval) {
979 pp->korate = atoi(optval);
980 } else if (!strcasecmp(optname, "alwaysccaprate") && optval) {
981 pp->alwaysccaprate = atoi(optval);
982 } else if (!strcasecmp(optname, "rate") && optval) {
983 rate = atoi(optval);
984 } else if (!strcasecmp(optname, "fillboardtries")) {
985 pp->fillboardtries = atoi(optval);
986 } else if (!strcasecmp(optname, "koage") && optval) {
987 pp->koage = atoi(optval);
988 } else if (!strcasecmp(optname, "ladders")) {
989 pp->ladders = optval && *optval == '0' ? false : true;
990 } else if (!strcasecmp(optname, "borderladders")) {
991 pp->borderladders = optval && *optval == '0' ? false : true;
992 } else if (!strcasecmp(optname, "ladderassess")) {
993 pp->ladderassess = optval && *optval == '0' ? false : true;
994 } else if (!strcasecmp(optname, "assess_local")) {
995 pp->assess_local = optval && *optval == '0' ? false : true;
996 } else if (!strcasecmp(optname, "pattern2")) {
997 pp->pattern2 = optval && *optval == '0' ? false : true;
998 } else if (!strcasecmp(optname, "selfatari_other")) {
999 pp->selfatari_other = optval && *optval == '0' ? false : true;
1000 } else {
1001 fprintf(stderr, "playout-moggy: Invalid policy argument %s or missing value\n", optname);
1002 exit(1);
1006 if (pp->lcapturerate == -1U) pp->lcapturerate = rate;
1007 if (pp->atarirate == -1U) pp->atarirate = rate;
1008 if (pp->capturerate == -1U) pp->capturerate = rate;
1009 if (pp->patternrate == -1U) pp->patternrate = rate;
1010 if (pp->selfatarirate == -1U) pp->selfatarirate = rate;
1011 if (pp->korate == -1U) pp->korate = rate;
1012 if (pp->alwaysccaprate == -1U) pp->alwaysccaprate = rate;
1014 pattern3s_init(&pp->patterns, moggy_patterns_src, moggy_patterns_src_n);
1016 return p;