Updated README: Equal sign not required with `--mode` flag.
[sgk-go] / engine / semeai.c
CommitLineData
7eeb782e
AT
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2 * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
3 * http://www.gnu.org/software/gnugo/ for more information. *
4 * *
5 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
6 * 2008 and 2009 by the Free Software Foundation. *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation - version 3 or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License in file COPYING for more details. *
17 * *
18 * You should have received a copy of the GNU General Public *
19 * License along with this program; if not, write to the Free *
20 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
21 * Boston, MA 02111, USA. *
22\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
23
24#include "gnugo.h"
25
26#include <stdio.h>
27#include <stdlib.h>
28
29#include "liberty.h"
30
31#define INFINITY 1000
32
33static void find_moves_to_make_seki(void);
34static void update_status(int dr, enum dragon_status new_status,
35 enum dragon_status new_safety);
36static int close_enough_for_proper_semeai(int apos, int bpos);
37
38/* semeai() searches for pairs of dragons of opposite color which
39 * have safety DEAD. If such a pair is found, owl_analyze_semeai is
40 * called to read out which dragon will prevail in a semeai, and
41 * whether a move now will make a difference in the outcome. The
42 * dragon statuses are revised, and if a move now will make a
43 * difference in the outcome this information is stored in
44 * dragon_data2 and an owl reason is later generated by
45 * semeai_move_reasons().
46 */
47
48#define MAX_DRAGONS 50
49
50void
51semeai()
52{
53 int semeai_results_first[MAX_DRAGONS][MAX_DRAGONS];
54 int semeai_results_second[MAX_DRAGONS][MAX_DRAGONS];
55 int semeai_move[MAX_DRAGONS][MAX_DRAGONS];
56 signed char semeai_certain[MAX_DRAGONS][MAX_DRAGONS];
57 int d1, d2;
58 int k;
59 int num_dragons = number_of_dragons;
60
61 if (num_dragons > MAX_DRAGONS) {
62 TRACE("Too many dragons!!! Semeai analysis disabled.");
63 return;
64 }
65
66 for (d1 = 0; d1 < num_dragons; d1++)
67 for (d2 = 0; d2 < num_dragons; d2++) {
68 semeai_results_first[d1][d2] = -1;
69 semeai_results_second[d1][d2] = -1;
70 }
71
72 for (d1 = 0; d1 < num_dragons; d1++)
73 for (k = 0; k < dragon2[d1].neighbors; k++) {
74 int apos = DRAGON(d1).origin;
75 int bpos = DRAGON(dragon2[d1].adjacent[k]).origin;
76 int result_certain;
77
78 d2 = dragon[bpos].id;
79
80 /* Look for semeais */
81
82 if (dragon[apos].color == dragon[bpos].color
83 || (dragon[apos].status != DEAD
84 && dragon[apos].status != CRITICAL)
85 || (dragon[bpos].status != DEAD
86 && dragon[bpos].status != CRITICAL))
87 continue;
88
89
90 /* Ignore inessential worms or dragons */
91
92 if (worm[apos].inessential
93 || DRAGON2(apos).safety == INESSENTIAL
94 || worm[bpos].inessential
95 || DRAGON2(bpos).safety == INESSENTIAL)
96 continue;
97
98 /* Sometimes the dragons are considered neighbors but are too
99 * distant to constitute a proper semeai, e.g. in nngs4:650, P2
100 * vs. R3. Then the result of semeai reading may be meaningless
101 * and can confuse the analysis. In order to avoid this we check
102 * that the dragons either are directly adjacent or at least
103 * have one common liberty.
104 */
105 if (!close_enough_for_proper_semeai(apos, bpos))
106 continue;
107
108 /* The array semeai_results_first[d1][d2] will contain the status
109 * of d1 after the d1 d2 semeai, giving d1 the first move.
110 * The array semeai_results_second[d1][d2] will contain the status
111 * of d1 after the d1 d2 semeai, giving d2 the first move.
112 */
113
114 DEBUG(DEBUG_SEMEAI, "Considering semeai between %1m and %1m\n",
115 apos, bpos);
116 owl_analyze_semeai(apos, bpos,
117 &(semeai_results_first[d1][d2]),
118 &(semeai_results_second[d1][d2]),
119 &(semeai_move[d1][d2]), 1, &result_certain);
120 DEBUG(DEBUG_SEMEAI, "results if %s moves first: %s %s, %1m%s\n",
121 board[apos] == BLACK ? "black" : "white",
122 result_to_string(semeai_results_first[d1][d2]),
123 result_to_string(semeai_results_second[d1][d2]),
124 semeai_move[d1][d2], result_certain ? "" : " (uncertain)");
125 semeai_certain[d1][d2] = result_certain;
126 }
127
128 /* Look for dragons which lose all their semeais outright. The
129 * winners in those semeais are considered safe and further semeais
130 * they are involved in are disregarded. See semeai:81-86 and
131 * nicklas5:1211 for examples of where this is useful.
132 *
133 * Note: To handle multiple simultaneous semeais properly we would
134 * have to make simultaneous semeai reading. Lacking that we can
135 * only get rough guesses of the correct status of the involved
136 * dragons. This code is not guaranteed to be correct in all
137 * situations but should usually be an improvement.
138 */
139 for (d1 = 0; d1 < num_dragons; d1++) {
140 int involved_in_semeai = 0;
141 int all_lost = 1;
142 for (d2 = 0; d2 < num_dragons; d2++) {
143 if (semeai_results_first[d1][d2] != -1) {
144 involved_in_semeai = 1;
145 if (semeai_results_first[d1][d2] != 0) {
146 all_lost = 0;
147 break;
148 }
149 }
150 }
151
152 if (involved_in_semeai && all_lost) {
153 /* Leave the status changes to the main loop below. Here we just
154 * remove the presumably irrelevant semeai results.
155 */
156 for (d2 = 0; d2 < num_dragons; d2++) {
157 if (semeai_results_first[d1][d2] == 0) {
158 int d3;
159 for (d3 = 0; d3 < num_dragons; d3++) {
160 if (semeai_results_second[d3][d2] > 0) {
161 semeai_results_first[d3][d2] = -1;
162 semeai_results_second[d3][d2] = -1;
163 semeai_results_first[d2][d3] = -1;
164 semeai_results_second[d2][d3] = -1;
165 }
166 }
167 }
168 }
169 }
170 }
171
172 for (d1 = 0; d1 < num_dragons; d1++) {
173 int semeais_found = 0;
174 int best_defense = 0;
175 int best_attack = 0;
176 int defense_move = PASS_MOVE;
177 int attack_move = PASS_MOVE;
178 int defense_certain = -1;
179 int attack_certain = -1;
180 int semeai_attack_target = NO_MOVE;
181 int semeai_defense_target = NO_MOVE;
182
183 for (d2 = 0; d2 < num_dragons; d2++) {
184 if (semeai_results_first[d1][d2] == -1)
185 continue;
186 gg_assert(semeai_results_second[d1][d2] != -1);
187 semeais_found++;
188
189 if (best_defense < semeai_results_first[d1][d2]
190 || (best_defense == semeai_results_first[d1][d2]
191 && defense_certain < semeai_certain[d1][d2])) {
192 best_defense = semeai_results_first[d1][d2];
193 defense_move = semeai_move[d1][d2];
194 defense_certain = semeai_certain[d1][d2];
195 gg_assert(board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d1].origin]));
196 semeai_defense_target = dragon2[d2].origin;
197 }
198 if (best_attack < semeai_results_second[d2][d1]
199 || (best_attack == semeai_results_second[d2][d1]
200 && attack_certain < semeai_certain[d2][d1])) {
201 best_attack = semeai_results_second[d2][d1];
202 attack_move = semeai_move[d2][d1];
203 attack_certain = semeai_certain[d2][d1];
204 semeai_attack_target = dragon2[d2].origin;
205 }
206 }
207
208 if (semeais_found) {
209 dragon2[d1].semeais = semeais_found;
210 if (best_defense != 0 && best_attack != 0)
211 update_status(DRAGON(d1).origin, CRITICAL, CRITICAL);
212 else if (best_attack == 0 && attack_certain)
213 update_status(DRAGON(d1).origin, ALIVE, ALIVE);
214 dragon2[d1].semeai_defense_code = best_defense;
215 dragon2[d1].semeai_defense_point = defense_move;
216 dragon2[d1].semeai_defense_certain = defense_certain;
217 ASSERT1(board[semeai_defense_target]
218 == OTHER_COLOR(board[dragon2[d1].origin]),
219 dragon2[d1].origin);
220 dragon2[d1].semeai_defense_target = semeai_defense_target;
221 dragon2[d1].semeai_attack_code = best_attack;
222 dragon2[d1].semeai_attack_point = attack_move;
223 dragon2[d1].semeai_attack_certain = attack_certain;
224 dragon2[d1].semeai_attack_target = semeai_attack_target;
225 }
226 }
227 find_moves_to_make_seki();
228}
229
230/* Find moves turning supposed territory into seki. This is not
231 * detected above since it either involves an ALIVE dragon adjacent to
232 * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded
233 * and turned into a seki.
234 *
235 * Currently we only search for tactically critical strings with
236 * dragon status dead, which are neighbors of only one opponent
237 * dragon, which is alive. Through semeai analysis we then determine
238 * whether such a string can in fact live in seki. Relevant testcases
239 * include gunnar:42 and gifu03:2.
240 */
241static void
242find_moves_to_make_seki()
243{
244 int str;
245 int defend_move;
246 int resulta, resultb;
247
248 for (str = BOARDMIN; str < BOARDMAX; str++) {
249 if (IS_STONE(board[str]) && is_worm_origin(str, str)
250 && attack_and_defend(str, NULL, NULL, NULL, &defend_move)
251 && dragon[str].status == DEAD
252 && DRAGON2(str).hostile_neighbors == 1) {
253 int k;
254 int color = board[str];
255 int opponent = NO_MOVE;
256 int certain;
257 struct eyevalue reduced_genus;
258
259 for (k = 0; k < DRAGON2(str).neighbors; k++) {
260 opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
261 if (board[opponent] != color)
262 break;
263 }
264
265 ASSERT1(opponent != NO_MOVE, opponent);
266
267 if (dragon[opponent].status != ALIVE)
268 continue;
269
270 /* FIXME: These heuristics are used for optimization. We don't
271 * want to call expensive semeai code if the opponent
272 * dragon has more than one eye elsewhere. However, the
273 * heuristics might still need improvement.
274 */
275 compute_dragon_genus(opponent, &reduced_genus, str);
276
277 if (min_eyes(&reduced_genus) > 1
278 || DRAGON2(opponent).moyo_size > 10
279 || DRAGON2(opponent).moyo_territorial_value > 2.999
280 || DRAGON2(opponent).escape_route > 0
281 || DRAGON2(str).escape_route > 0)
282 continue;
283
284 owl_analyze_semeai_after_move(defend_move, color, opponent, str,
285 &resulta, &resultb, NULL, 1, &certain, 0);
286
287 if (resultb == WIN) {
288 owl_analyze_semeai(str, opponent, &resultb, &resulta,
289 &defend_move, 1, &certain);
290 resulta = REVERSE_RESULT(resulta);
291 resultb = REVERSE_RESULT(resultb);
292 }
293
294 /* Do not trust uncertain results. In fact it should only take a
295 * few nodes to determine the semeai result, if it is a proper
296 * potential seki position.
297 */
298 if (resultb != WIN && certain) {
299 int d = dragon[str].id;
300 DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
301 defend_move, str, opponent);
302 dragon2[d].semeais++;
303 update_status(str, CRITICAL, CRITICAL);
304 dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb);
305 dragon2[d].semeai_defense_point = defend_move;
306 dragon2[d].semeai_defense_certain = certain;
307 gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
308 dragon2[d].semeai_defense_target = opponent;
309
310 /* We need to determine a proper attack move (the one that
311 * prevents seki). Currently we try the defense move first,
312 * and if it doesn't work -- all liberties of the string.
313 */
314 owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
315 str, opponent, &resulta, NULL,
316 NULL, 1, NULL, 0);
317 if (resulta != WIN) {
318 dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
319 dragon2[d].semeai_attack_point = defend_move;
320 }
321 else {
322 int k;
323 int libs[MAXLIBS];
324 int liberties = findlib(str, MAXLIBS, libs);
325
326 for (k = 0; k < liberties; k++) {
327 owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
328 str, opponent, &resulta, NULL,
329 NULL, 1, NULL, 0);
330 if (resulta != WIN) {
331 dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
332 dragon2[d].semeai_attack_point = libs[k];
333 break;
334 }
335 }
336
337 if (k == liberties) {
338 DEBUG(DEBUG_SEMEAI,
339 "No move to attack in semeai (%1m vs %1m), seki assumed.\n",
340 str, opponent);
341 dragon2[d].semeai_attack_code = 0;
342 dragon2[d].semeai_attack_point = NO_MOVE;
343 update_status(str, ALIVE, ALIVE_IN_SEKI);
344 }
345 }
346
347 DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
348 dragon2[d].semeai_attack_point, opponent, str);
349
350 dragon2[d].semeai_attack_certain = certain;
351 dragon2[d].semeai_attack_target = opponent;
352 }
353 }
354 }
355
356 /* Now look for dead strings inside a single eyespace of a living dragon.
357 *
358 * FIXME: Clearly this loop should share most of its code with the
359 * one above. It would also be good to reimplement so that
360 * moves invading a previously empty single eyespace to make
361 * seki can be found.
362 */
363 for (str = BOARDMIN; str < BOARDMAX; str++) {
364 if (IS_STONE(board[str]) && is_worm_origin(str, str)
365 && !find_defense(str, NULL)
366 && dragon[str].status == DEAD
367 && DRAGON2(str).hostile_neighbors == 1) {
368 int k;
369 int color = board[str];
370 int opponent = NO_MOVE;
371 int certain;
372 struct eyevalue reduced_genus;
373
374 for (k = 0; k < DRAGON2(str).neighbors; k++) {
375 opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
376 if (board[opponent] != color)
377 break;
378 }
379
380 ASSERT1(opponent != NO_MOVE, opponent);
381
382 if (dragon[opponent].status != ALIVE)
383 continue;
384
385 /* FIXME: These heuristics are used for optimization. We don't
386 * want to call expensive semeai code if the opponent
387 * dragon has more than one eye elsewhere. However, the
388 * heuristics might still need improvement.
389 */
390 compute_dragon_genus(opponent, &reduced_genus, str);
391 if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1)
392 continue;
393
394 owl_analyze_semeai(str, opponent, &resulta, &resultb,
395 &defend_move, 1, &certain);
396
397 /* Do not trust uncertain results. In fact it should only take a
398 * few nodes to determine the semeai result, if it is a proper
399 * potential seki position.
400 */
401 if (resulta != 0 && certain) {
402 int d = dragon[str].id;
403 DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
404 defend_move, str, opponent);
405 dragon2[d].semeais++;
406 update_status(str, CRITICAL, CRITICAL);
407 dragon2[d].semeai_defense_code = resulta;
408 dragon2[d].semeai_defense_point = defend_move;
409 dragon2[d].semeai_defense_certain = certain;
410 gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
411 dragon2[d].semeai_defense_target = opponent;
412
413 /* We need to determine a proper attack move (the one that
414 * prevents seki). Currently we try the defense move first,
415 * and if it doesn't work -- all liberties of the string.
416 */
417 owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
418 str, opponent, &resulta, NULL,
419 NULL, 1, NULL, 0);
420 if (resulta != WIN) {
421 dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
422 dragon2[d].semeai_attack_point = defend_move;
423 }
424 else {
425 int k;
426 int libs[MAXLIBS];
427 int liberties = findlib(str, MAXLIBS, libs);
428
429 for (k = 0; k < liberties; k++) {
430 owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
431 str, opponent, &resulta, NULL,
432 NULL, 1, NULL, 0);
433 if (resulta != WIN) {
434 dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
435 dragon2[d].semeai_attack_point = libs[k];
436 break;
437 }
438 }
439
440 if (k == liberties) {
441 DEBUG(DEBUG_SEMEAI,
442 "No move to attack in semeai (%1m vs %1m), seki assumed.\n",
443 str, opponent);
444 dragon2[d].semeai_attack_code = 0;
445 dragon2[d].semeai_attack_point = NO_MOVE;
446 update_status(str, ALIVE, ALIVE_IN_SEKI);
447 }
448 }
449
450 DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
451 dragon2[d].semeai_attack_point, opponent, str);
452
453 dragon2[d].semeai_attack_certain = certain;
454 dragon2[d].semeai_attack_target = opponent;
455 }
456 }
457 }
458}
459
460
461/* neighbor_of_dragon(pos, origin) returns true if the vertex at (pos) is a
462 * neighbor of the dragon with origin at (origin).
463 */
464static int
465neighbor_of_dragon(int pos, int origin)
466{
467 int k;
468 if (pos == NO_MOVE)
469 return 0;
470
471 for (k = 0; k < 4; k++)
472 if (ON_BOARD(pos + delta[k]) && dragon[pos + delta[k]].origin == origin)
473 return 1;
474
475 return 0;
476}
477
478/* Check whether two dragons are directly adjacent or have at least
479 * one common liberty.
480 */
481static int
482close_enough_for_proper_semeai(int apos, int bpos)
483{
484 int pos;
485 for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
486 if (board[pos] == EMPTY
487 && neighbor_of_dragon(pos, apos)
488 && neighbor_of_dragon(pos, bpos))
489 return 1;
490 else if (IS_STONE(board[pos])) {
491 if (is_same_dragon(pos, apos) && neighbor_of_dragon(pos, bpos))
492 return 1;
493 if (is_same_dragon(pos, bpos) && neighbor_of_dragon(pos, apos))
494 return 1;
495 }
496 }
497
498 return 0;
499}
500
501/* This function adds the semeai related move reasons, using the information
502 * stored in the dragon2 array.
503 *
504 * If the semeai had an uncertain result, and there is a owl move with
505 * certain result doing the same, we don't trust the semeai move.
506 */
507void
508semeai_move_reasons(int color)
509{
510 int other = OTHER_COLOR(color);
511 int d;
512 int liberties;
513 int libs[MAXLIBS];
514 int r;
515
516 for (d = 0; d < number_of_dragons; d++)
517 if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) {
518 if (DRAGON(d).color == color
519 && dragon2[d].semeai_defense_point
520 && (dragon2[d].owl_defense_point == NO_MOVE
521 || dragon2[d].semeai_defense_certain >=
522 dragon2[d].owl_defense_certain)) {
523 /* My dragon can be defended. */
524 add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin);
525 DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n",
526 DRAGON(d).origin, dragon2[d].semeai_defense_point);
527 if (neighbor_of_dragon(dragon2[d].semeai_defense_point,
528 dragon2[d].semeai_defense_target)
529 && !neighbor_of_dragon(dragon2[d].semeai_defense_point,
530 dragon2[d].origin)
531 && !is_self_atari(dragon2[d].semeai_defense_point, color)) {
532
533 /* If this is a move to fill the non-common liberties of the
534 * target, and is not a ko or snap-back, then we mark all
535 * non-common liberties of the target as potential semeai moves.
536 */
537
538 liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs);
539
540 for (r = 0; r < liberties; r++) {
541 if (!neighbor_of_dragon(libs[r], dragon2[d].origin)
542 && !is_self_atari(libs[r], color)
543 && libs[r] != dragon2[d].semeai_defense_point)
544 add_potential_semeai_defense(libs[r], dragon2[d].origin,
545 dragon2[d].semeai_defense_target);
546 }
547 }
548 }
549 else if (DRAGON(d).color == other
550 && dragon2[d].semeai_attack_point
551 && (dragon2[d].owl_attack_point == NO_MOVE
552 || dragon2[d].owl_defense_point == NO_MOVE
553 || dragon2[d].semeai_attack_certain >=
554 dragon2[d].owl_attack_certain)) {
555 /* Your dragon can be attacked. */
556 add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin);
557 DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n",
558 DRAGON(d).origin, dragon2[d].semeai_attack_point);
559 if (neighbor_of_dragon(dragon2[d].semeai_attack_point,
560 dragon2[d].origin)
561 && !neighbor_of_dragon(dragon2[d].semeai_attack_point,
562 dragon2[d].semeai_attack_target)
563 && !is_self_atari(dragon2[d].semeai_attack_point, color)) {
564
565 liberties = findlib(dragon2[d].origin, MAXLIBS, libs);
566
567 for (r = 0; r < liberties; r++) {
568 if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target)
569 && !is_self_atari(libs[r], color)
570 && libs[r] != dragon2[d].semeai_attack_point)
571 add_potential_semeai_attack(libs[r], dragon2[d].origin,
572 dragon2[d].semeai_attack_target);
573 }
574 }
575 }
576 }
577}
578
579
580/* Change the status and safety of a dragon. In addition, if the new
581 * status is not DEAD, make all worms of the dragon essential, so that
582 * results found by semeai code don't get ignored.
583 */
584static void
585update_status(int dr, enum dragon_status new_status,
586 enum dragon_status new_safety)
587{
588 int pos;
589
590 if (dragon[dr].status != new_status
591 && (dragon[dr].status != CRITICAL || new_status != DEAD)) {
592 DEBUG(DEBUG_SEMEAI, "Changing status of %1m from %s to %s.\n", dr,
593 status_to_string(dragon[dr].status),
594 status_to_string(new_status));
595 for (pos = BOARDMIN; pos < BOARDMAX; pos++)
596 if (IS_STONE(board[pos]) && is_same_dragon(dr, pos)) {
597 dragon[pos].status = new_status;
598 if (new_status != DEAD)
599 worm[pos].inessential = 0;
600 }
601 }
602
603 if (DRAGON2(dr).safety != new_safety
604 && (DRAGON2(dr).safety != CRITICAL || new_safety != DEAD)) {
605 DEBUG(DEBUG_SEMEAI, "Changing safety of %1m from %s to %s.\n", dr,
606 status_to_string(DRAGON2(dr).safety), status_to_string(new_safety));
607 DRAGON2(dr).safety = new_safety;
608 }
609}
610
611
612/*
613 * Local Variables:
614 * tab-width: 8
615 * c-basic-offset: 2
616 * End:
617 */