Ticket #107: check_eye_patterns.pike

File check_eye_patterns.pike, 41.6 kB (added by gunnar, 2 years ago)

automated testing of eyes.db

Line 
1#!/usr/bin/env pike
2
3array(Eyegraph) database = ({});
4
5class Eyegraph
6{
7  int number;
8  mapping(string:Eyevertex) vertices = ([]);
9  Eyevalue eyevalue = Eyevalue("0000");
10  int esize = 0;
11  int msize = 0;
12  string position = "center";
13  string coded_string;
14
15  void create(int n)
16  {
17    number = n;
18  }
19
20  void add_vertex(Eyevertex vertex)
21  {
22    vertices[vertex->name] = vertex;
23    if (vertex->is_margin())
24      msize++;
25    esize++;
26    if (vertex->edge == 1 && position == "center")
27      position = "edge";
28    else if (vertex->edge == 2)
29      position = "corner";
30  }
31
32  void eliminate_vertex(string v, string recurse)
33  {
34    array(string) neighbors = vertices[v]->neighbors;
35    foreach (neighbors, string v2)
36      vertices[v2]->neighbors -= ({v});
37
38    if (vertices[v]->is_margin())
39      msize--;
40    esize--;
41
42    if (recurse == "recursive")
43      foreach (neighbors, string v2) {
44        if (!vertices[v2])
45          continue;
46        switch (vertices[v2]->symbol) {
47        case ".":
48          vertices[v2]->symbol = "!";
49          msize++;
50          break;
51        case "X":
52        case "$":
53          eliminate_vertex(v2, "recursive");
54          break;
55        }
56      }
57
58    m_delete(vertices, v);
59  }
60
61  Eyevalue guess_eyevalue()
62  {
63    int margins_adjacent_to_margin = 0;
64    foreach (indices(vertices), string v) {
65      if (vertices[v]->is_margin()) {
66        foreach (vertices[v]->neighbors, string v2) {
67          if (vertices[v2]->is_margin()) {
68            margins_adjacent_to_margin++;
69            break;
70          }
71        }
72      }
73    }
74    int effective_eyesize = esize - 2*msize - margins_adjacent_to_margin;
75
76    if (effective_eyesize > 3)
77      return Eyevalue("2222");
78
79    if (effective_eyesize > 0)
80      return Eyevalue("1111");
81
82    if (esize - msize > 2)
83      return Eyevalue("0011");
84
85    return Eyevalue("0000");
86  }
87
88  array(string) find_subgraph(string v, void|array(string) subgraph)
89  {
90    if (!subgraph)
91      subgraph = ({});
92
93    subgraph += ({v});
94    foreach (vertices[v]->neighbors, string v2) {
95      if (search(subgraph, v2) < 0
96          && (!vertices[v]->is_margin()
97              || !vertices[v2]->is_margin()))
98        subgraph = find_subgraph(v2, subgraph);
99    }
100   
101    return subgraph;
102  }
103
104  void remove_captured_stones()
105  {
106    multiset checked = (<>);
107    foreach (indices(vertices), string v) {
108      if (checked[v])
109        continue;
110
111      array(string) connected = find_subgraph(v);
112
113      int captured = 1;
114      foreach (connected, string v2) {
115        if (search("X$", vertices[v2]->symbol) < 0) {
116          captured = 0;
117          break;
118        }
119      }
120     
121      foreach (connected, string v2) {
122        if (captured)
123          vertices[v2]->symbol = (["X":".", "$":"!"])[vertices[v2]->symbol];
124        checked[v2] = 1;
125      }
126    }
127  }
128
129  array(Eyegraph) split_into_subgraphs()
130  {
131    multiset checked = (<>);
132    array(Eyegraph) subgraphs = ({});
133
134    foreach (indices(vertices), string v) {
135      if (checked[v])
136        continue;
137
138      array(string) connected = find_subgraph(v);
139      Eyegraph g = Eyegraph(-1);
140
141      foreach (connected, string v2) {
142        g->add_vertex(vertices[v2]->copy());
143        checked[v2] = 1;
144      }
145
146      g->prune_invalid_neighbors();
147
148      subgraphs += ({g});
149    }
150   
151    return subgraphs;
152  }
153
154  void prune_invalid_neighbors()
155  {
156    foreach (values(vertices), Eyevertex v)
157      for (int k = sizeof(v->neighbors) - 1; k >= 0; k--)
158        if (!vertices[v->neighbors[k]])
159          v->neighbors -= ({v->neighbors[k]});
160  }
161
162  array(string) attack_points(void|mapping(string:string) map)
163  {
164    array(string) result = ({});
165    foreach (indices(vertices), string v)
166      if (vertices[v]->is_attack_point()) {
167        if (!map)
168          result += ({v});
169        else
170          result += ({map[v]});
171      }
172
173    return result;
174  }
175
176  array(string) defense_points(void|mapping(string:string) map)
177  {
178    array(string) result = ({});
179    foreach (indices(vertices), string v)
180      if (vertices[v]->is_defense_point()) {
181        if (!map)
182          result += ({v});
183        else
184          result += ({map[v]});
185      }
186
187    return result;
188  }
189
190  array(string) empty_points(void|mapping(string:string) map)
191  {
192    array(string) result = ({});
193    foreach (indices(vertices), string v)
194      if (vertices[v]->is_empty_point()) {
195        if (!map)
196          result += ({v});
197        else
198          result += ({map[v]});
199      }
200
201    return result;
202  }
203
204  Eyegraph copy()
205  {
206    Eyegraph g = Eyegraph(number);
207    g->eyevalue = eyevalue;
208    g->esize = esize;
209    g->msize = msize;
210    foreach (indices(vertices), string s)
211      g->vertices[s] = vertices[s]->copy();
212    return g;
213  }
214
215  void print()
216  {
217    write("%d: %s %d %d\n", number, (string) eyevalue, esize, msize);
218    foreach (values(vertices), Eyevertex vertex)
219      vertex->print();
220  }
221
222  array(string) stringify()
223  {
224    array(string) result = ({});
225    foreach (values(vertices), Eyevertex v) {
226      int i, j;
227      sscanf(v->name, "%d,%d", i, j);
228      i--;
229      j--;
230      while (sizeof(result) < i + 1)
231        result += ({""});
232      while (strlen(result[i]) < j+1)
233        result[i] += " ";
234      result[i][j] = v->symbol[0];
235    }
236    return result;
237  }
238
239  void prettyprint()
240  {
241    write(stringify() * "\n" + "\n");
242    write("%d: %s\n", number, (string) eyevalue);
243  }
244}
245
246class Eyevertex
247{
248  string symbol;
249  string name;
250  array(string) neighbors = ({});
251  int edge = 0;
252  array(string) edgenames = ({"", ", edge", ", corner"});
253
254  void create(string s, int i, int j)
255  {
256    symbol = s;
257    name = i + "," + j;
258  }
259
260  void add_neighbor(array(string) diagram, int i, int j)
261  {
262    if (diagram[i][j] == '-' || diagram[i][j] == '|')
263      edge++;
264    else if (diagram[i][j] != ' ')
265      neighbors += ({i + "," + j});
266  }
267
268  int is_margin()
269  {
270    return (search("!$@()", symbol) >= 0);
271  }
272
273  int is_attack_point()
274  {
275    return (search("*<@(", symbol) >= 0);
276  }
277
278  int is_defense_point()
279  {
280    return (search("*>@)", symbol) >= 0);
281  }
282
283  int is_empty_point()
284  {
285    return (search(".*<>!@()", symbol) >= 0);
286  }
287
288  Eyevertex copy()
289  {
290    Eyevertex v = Eyevertex("", 0, 0);
291    v->symbol = symbol;
292    v->name = name;
293    v->neighbors = neighbors;
294    v->edge = edge;
295    return v;
296  }
297
298  void print()
299  {
300    write("%s (%s%s): %s\n", name, symbol, edgenames[edge], neighbors * ", ");
301  }
302}
303
304class Eyevalue
305{
306  int a, b, c, d;
307 
308  void create(string|Eyevalue x)
309  {
310    if (stringp(x))
311      [a, b, c, d] = (array(int)) (x / "");
312    else {
313      a = x->a;
314      b = x->b;
315      c = x->c;
316      d = x->d;
317    }
318  }
319
320  Eyevalue `+(Eyevalue v)
321  {
322    Eyevalue result = Eyevalue("0000");
323    result->b = min(max(b + v->b, min(a + v->d, b + v->c)),
324                    max(b + v->b, min(d + v->a, c + v->b)));
325    result->c = max(min(c + v->c, max(d + v->a, c + v->b)),
326                    min(c + v->c, max(a + v->d, b + v->c)));
327    result->a = min(a + v->c, c + v->a, max(a + v->b, b + v->a));
328    result->d = max(d + v->b, b + v->d, min(d + v->c, c + v->d));
329
330    // Annoying exception, but 0011+0002 must not be 0012.
331    if ((d - c == 2 && v->c - v->b == 1)
332        || (c - b == 1 && v->d - v->c == 2)) {
333      result->d = max(min(c + v->d, d + v->b), min(d + v->c, b + v->d));
334    }
335
336    return result;
337  }
338
339  int `>(Eyevalue v)
340  {
341    return !(a <= v->a && b <= v->b && c <= v->c && d <= v->d);
342  }
343
344  int `<(Eyevalue v)
345  {
346    return !(a >= v->a && b >= v->b && c >= v->c && d >= v->d);
347  }
348
349  int severity()
350  {
351    if (b == c)
352      return 0;
353    else
354      return (d + c - b - a);
355  }
356
357  int check_consistency(Eyevalue v, string attack_or_defend)
358  {
359    string s1 = (string) this_object()->normalize();
360    string s2 = (string) v->normalize();
361    if (attack_or_defend == "attack") {
362      if (s1 == "0000" || s1 == "0001" || s1 == "0002")
363        return 1;
364      if ((s1 == "0011" || s1 == "0012" || s1 == "0022")
365          && (s2 == "0000" || s2 == "0001" || s2 == "0002"))
366        return 1;
367      if ((s1 == "0111" || s1 == "0112" || s1 == "1111" || s1 == "1112")
368          && v >= Eyevalue("0011"))
369        return 1;
370      if (s1 == "0122" && s2 == "0011")
371        return 1;
372      if ((s1 == "0222" || s1 == "1222" || s1 == "2222")
373          && v >= Eyevalue("0022"))
374        return 1;
375      if (s1 == "1122"
376          && (s2 == "0012" || s2 == "0111" || s2 == "0112"
377              || s2 == "1111" || s2 == "1112"))
378        return 1;
379
380      return 0;
381    //      return (min(a, 2) <= v->b && min(b, 2) <= v->c);
382    }
383    else {
384      if ((s1 == "0000" || s1 == "0001" || s1 == "0002")
385          && v->normalize() <= Eyevalue("0022"))
386        return 1;
387      if (s1 == "0011"
388          && (s2 == "0122" || s2 == "1112" || s2 == "0112"
389              || s2 == "1111" || s2 == "0111"))
390        return 1;
391      if (s1 == "0012" && s2 == "1122")
392        return 1;
393#if 0
394      if ((s1 == "0111" || s1 == "0112" || s1 == "1111" || s1 == "1112")
395          && v->normalize() <= Eyevalue("1122"))
396        return 1;
397#endif
398      if ((s1 == "0111" || s1 == "1111")
399          && (s2 == "0122" || v->normalize() <= Eyevalue("1112")))
400        return 1;
401      if ((s1 == "0112" || s1 == "1112")
402          && v->normalize() <= Eyevalue("1122"))
403        return 1;
404      if ((s1 == "1122" || s1 == "0122" || s1 == "0022")
405          && (s2 == "2222" || s2 == "1222" || s2 == "0222"))
406        return 1;
407      if (s1 == "0222" || s1 == "1222" || s1 == "2222")
408        return 1;
409
410      return 0;
411  //    return (c >= min(v->b, 2) && (d >= min(v->c, 2)
412  //                              || (c > v->a && c >= v->b)));
413    }
414  }
415
416  int min_eyes()
417  {
418    return b;
419  }
420
421  int max_eyes()
422  {
423    return c;
424  }
425 
426  Eyevalue normalize()
427  {
428    Eyevalue e = Eyevalue("0000");
429    e->a = min(a, 2);
430    e->b = min(b, 2);
431    e->c = min(c, 2);
432    e->d = min(d, 2);
433    return e;
434  }
435
436  mixed cast(string type)
437  {
438    if (type == "string")
439      return sprintf("%d%d%d%d", a, b, c, d);
440  }
441}
442
443mapping(string:mapping(string:string)) addition_table =
444(["0"  : (["0":"0", "1/2":"1/2", "1":"1", "1*":"1*", "3/2":"3/2", "2":"2"]),
445  "1/2": (["0":"1/2", "1/2":"1", "1":"3/2", "1*":"3/2", "3/2":"2", "2":"2"]),
446  "1"  : (["0":"1", "1/2":"3/2", "1":"2", "1*":"3/2", "3/2":"2", "2":"2"]),
447  "1*" : (["0":"1*", "1/2":"3/2", "1":"3/2", "1*":"2", "3/2":"2", "2":"2"]),
448  "3/2": (["0":"3/2", "1/2":"2", "1":"2", "1*":"2", "3/2":"2", "2":"2"]),
449  "2":   (["0":"2", "1/2":"2", "1":"2", "1*":"2", "3/2":"2", "2":"2"]),
450]);
451
452string add_eye_values(string e1, string e2)
453{
454  // write("add: %s %s %s\n", e1, e2, addition_table[e1][e2]);
455  return addition_table[e1][e2];
456}
457
458class GtpEngine
459{
460  object f1 = Stdio.File();
461  object pipe1 = f1->pipe();
462  object f2 = Stdio.FILE();
463  object pipe2 = f2->pipe();
464
465  void create()
466  {
467    array(string) program_start_array = ({"../interface/gnugo", "--quiet", "--mode", "gtp"});
468    Process.create_process(program_start_array, (["stdin":pipe1, "stdout":pipe2]));
469  }
470 
471  string command(string s)
472  {
473    array(string) result = ({});
474    string a;
475    f1->write(s + "\n");
476    while (1) {
477      a = f2->gets();
478      if (a == "")
479        break;
480      result += ({a});
481    }
482    result[0] = result[0][2..];
483    return result * "\n";
484  }
485
486  string shutdown()
487  {
488    command("quit");
489    f1->close();
490    f2->close();
491  }
492}
493
494void play_in_graph(Eyegraph g, string v, string who)
495{
496  string symbol = g->vertices[v]->symbol;
497  if (search("X$", symbol) >= 0) {
498    werror("Invalid play!\n");
499    return;
500  }
501 
502  if (who == "O")
503    g->eliminate_vertex(v, "nonrecursive");
504  else {
505    if (symbol == ".")
506      g->vertices[v]->symbol = "X";
507    else
508      g->eliminate_vertex(v, "recursive");
509  }
510
511  g->remove_captured_stones();
512}
513
514mapping(string:string)
515graph_match(Eyegraph eyespace, Eyegraph graph,
516            void|mapping(string:string) map)
517{
518  int debug = 0;
519
520  if (!map) {
521    if (graph->esize != eyespace->esize
522        || graph->msize != eyespace->msize)
523      return 0;
524    else
525      map = ([]);
526  }
527
528  if (debug)
529    write("%O\n", map);
530
531  array(string) remaining_indices = indices(graph->vertices) - values(map);
532  if (sizeof(remaining_indices) == 0)
533    return map;
534 
535  string next_vertex = (indices(eyespace->vertices) - indices(map))[0];
536
537  if (debug)
538    write("next_vertex = %s\n", next_vertex);
539
540  foreach (remaining_indices, string v) {
541    map[next_vertex] = v;
542    Eyevertex v1 = eyespace->vertices[next_vertex];
543    Eyevertex v2 = graph->vertices[v];
544
545    // Number of neighbors.
546    if (sizeof(v1->neighbors) != sizeof(v2->neighbors)) {
547      if (debug)
548        write("mismatch 1\n");
549      continue;
550    }
551   
552    // Is marginal?
553    if (v1->is_margin() && !v2->is_margin()) {
554      if (debug)
555        write("mismatch 2\n");
556      continue;
557    }
558
559    // Is not marginal?
560    if (!v1->is_margin() && v2->is_margin()) {
561      if (debug)
562        write("mismatch 3\n");
563      continue;
564    }
565
566    // Is stone?
567    if (search("X$", v1->symbol) >= 0
568        && search("Xx$", v2->symbol) < 0) {
569      if (debug)
570        write("mismatch 4\n");
571      continue;
572    }
573
574    // Is empty?
575    if (search(".!", v1->symbol) >= 0
576        && search("X$", v2->symbol) >= 0) {
577      if (debug)
578        write("mismatch 5\n");
579      continue;
580    }
581
582    // Is edge?
583    if (v1->edge < v2->edge) {
584      if (debug)
585        write("mismatch 6\n");
586      continue;
587    }
588
589    // Check neighbors.
590    array(string) mapped_neighbors1 =
591      v1->neighbors - (v1->neighbors - indices(map));
592    array(string) mapped_neighbors2 =
593      v2->neighbors - (v2->neighbors - values(map));
594
595    if (debug) {
596      write("mapped_neighbors1: %s\n", mapped_neighbors1 * " ");
597      write("mapped_neighbors2: %s\n", mapped_neighbors2 * " ");
598    }
599
600    int ok = 1;
601    foreach (mapped_neighbors1, string mn)
602      if (search(mapped_neighbors2, map[mn]) < 0) {
603        ok = 0;
604        break;
605      }
606   
607    if (!ok) {
608      if (debug)
609      write("mismatch 7\n");
610      continue;
611    }
612
613    mapping(string:string) result_map = graph_match(eyespace, graph, map);
614    if (result_map)
615      return result_map;
616  }
617 
618  m_delete(map, next_vertex);
619
620  return 0;
621}
622
623array(mixed)
624match_database(Eyegraph eyespace)
625{
626  mapping(string:string) map;
627
628  foreach (database, Eyegraph graph) {
629    map = graph_match(eyespace, graph);
630    if (map)
631      return ({graph, map});
632  }
633 
634  return ({0, 0});
635}
636
637Eyegraph
638finish_pattern(int n, array(string) diagram, string colon_line)
639{
640  int i, j;
641  Eyegraph graph = Eyegraph(n);
642 
643  graph->coded_string = replace(diagram * "%", " ", "O");
644 
645  diagram = ({""}) + diagram + ({""});
646  for (i = 0; i < sizeof(diagram); i++)
647    diagram[i] = (" " + diagram[i] + " "*15)[0..14];
648  for (i = 1; i < sizeof(diagram) - 1; i++) {
649    string row = diagram[i];
650    for (j = 1; j < strlen(row); j++) {
651      if (search(" |-+", row[j..j]) < 0) {
652        Eyevertex vertex = Eyevertex(row[j..j], i, j);
653        vertex->add_neighbor(diagram, i-1, j);
654        vertex->add_neighbor(diagram, i+1, j);
655        vertex->add_neighbor(diagram, i, j-1);
656        vertex->add_neighbor(diagram, i, j+1);
657        graph->add_vertex(vertex);
658      }
659    }
660  }
661 
662  graph->eyevalue = Eyevalue(colon_line - ":");
663  return graph;
664}
665
666void
667read_eyes_db()
668{
669  string eyes_db = Stdio.read_file("eyes.db");
670  int n;
671  Eyegraph graph;
672  string state = "waiting for pattern";
673  array(string) diagram = ({});
674
675  foreach (eyes_db / "\n", string row) {
676    if (row[0..0] == "#")
677      continue;
678    if (row == "")
679      continue;
680
681    switch (state) {
682    case "waiting for pattern":
683      if (sscanf(row, "Pattern %d", n) == 1) {
684        state = "reading diagram";
685      }
686      break;
687    case "reading diagram":
688      if (row[0..0] == ":") {
689        graph = finish_pattern(n, diagram, row);
690        database += ({graph});
691        diagram = ({});
692        state = "waiting for pattern";
693      }
694      else {
695        diagram += ({row});
696      }
697      break;
698    default:
699      werror("Unknown state: '%s'\n", state);
700      break;
701    }
702  }
703}
704
705mapping(string:int) severity_values =
706(["0":0, "1/2":1, "1":0, "1*":2, "3/2":1, "2":0]);
707
708array(mixed) compute_eye(Eyegraph g, void|int normalize)
709{
710  Eyevalue eyevalue = Eyevalue("0000");
711  array(string) apoints = ({});
712  array(string) dpoints = ({});
713  int severity = 0;
714  array(string) matched_by = ({});
715
716  foreach (g->split_into_subgraphs(), Eyegraph g2) {
717    Eyegraph match;
718    mapping(string:string) map;
719    [match, map] = match_database(g2);
720    if (match) {
721      matched_by += ({(string) match->number});
722      map = mkmapping(values(map), indices(map));
723      if (match->eyevalue->severity() == severity && severity >= 0) {
724        apoints += match->attack_points(map);
725        dpoints += match->defense_points(map);
726      }
727      else if (match->eyevalue->severity() > severity) {
728        apoints = match->attack_points(map);
729        dpoints = match->defense_points(map);
730        severity = match->eyevalue->severity();
731      }
732      eyevalue = eyevalue + match->eyevalue;
733    }
734    else {
735      matched_by += ({"guess"});
736      eyevalue = eyevalue + g2->guess_eyevalue();
737    }
738  }
739
740  if (0 && eyevalue->severity() == 0) {
741    apoints = ({});
742    dpoints = ({});
743  }
744
745  if (normalize)
746    eyevalue = eyevalue->normalize();
747
748  return ({eyevalue, matched_by, apoints, dpoints});
749}
750
751void complain_about_inconsistency(Eyegraph g, Eyegraph g2,
752                                  Eyevalue eyevalue, Eyevalue new_eyevalue,
753                                  array(string) matched_by,
754                                  array(string) new_matched_by)
755{
756  write("===========\n");
757  g->prettyprint();
758  write("valued %s by %s\n", (string) eyevalue, matched_by * ", ");
759  g2->prettyprint();
760  write("valued %s by %s\n", (string) new_eyevalue, new_matched_by * ", ");
761}
762
763void test_consistency(Eyegraph g)
764{
765  array(string) attack_points = ({});
766  array(string) defense_points = ({});
767  Eyevalue eyevalue;
768  Eyevalue new_eyevalue;
769  array(string) matched_by;
770  array(string) new_matched_by;
771  [eyevalue, matched_by, attack_points, defense_points] = compute_eye(g);
772
773  if (eyevalue->severity() > 0) {
774    foreach (attack_points, string v) {
775      Eyegraph g2 = g->copy();
776      play_in_graph(g2, v, "X");
777      [new_eyevalue, new_matched_by] = compute_eye(g2)[0..1];
778      if (!eyevalue->check_consistency(new_eyevalue, "attack"))
779        complain_about_inconsistency(g, g2, eyevalue, new_eyevalue,
780                                     matched_by, new_matched_by);
781    }
782     
783    foreach (defense_points, string v) {
784      Eyegraph g2 = g->copy();
785      play_in_graph(g2, v, "O");
786      [new_eyevalue, new_matched_by] = compute_eye(g2)[0..1];
787      if (!eyevalue->check_consistency(new_eyevalue, "defense"))
788        complain_about_inconsistency(g, g2, eyevalue, new_eyevalue,
789                                     matched_by, new_matched_by);
790    }
791  }
792  else {
793    foreach (g->empty_points(), string v) {
794      if (eyevalue->max_eyes() > 0) {
795        Eyegraph g2 = g->copy();
796        play_in_graph(g2, v, "X");
797        [new_eyevalue, new_matched_by] = compute_eye(g2)[0..1];
798        if (!eyevalue->check_consistency(new_eyevalue, "attack"))
799          complain_about_inconsistency(g, g2, eyevalue, new_eyevalue,
800                                       matched_by, new_matched_by);
801      }
802     
803      if (eyevalue->min_eyes() < 2) {
804        Eyegraph g2 = g->copy();
805        play_in_graph(g2, v, "O");
806        [new_eyevalue, new_matched_by] = compute_eye(g2)[0..1];
807        if (!eyevalue->check_consistency(new_eyevalue, "defense"))
808          complain_about_inconsistency(g, g2, eyevalue, new_eyevalue,
809                                       matched_by, new_matched_by);
810      }
811    }
812  }
813}
814
815void test_template(array(string) template, int|void max_margins)
816{
817  int i, j;
818
819  array(array(int)) vertices = ({});
820 
821  int configurations = 1;
822  for (i = 0; i < sizeof(template); i++)
823    for (j = 0; j < strlen(template[i]); j++)
824      if (template[i][j] == '.') {
825        vertices += ({ ({i, j}) });
826        configurations *= 3;
827      }
828
829  if (zero_type(max_margins))
830    max_margins = sizeof(vertices);
831
832  for (i = 0; i < configurations; i++) {
833    int k = i;
834    for (j = 0; j < sizeof(vertices); j++) {
835      template[vertices[j][0]][vertices[j][1]] = ({'.', '!', 'X'})[k%3];
836      k /= 3;
837    }
838    Eyegraph g = finish_pattern(-1, template, ":0000");
839
840    // eyespaces with marginal vertices with all neighbors in the
841    // eyespace are unreasonable and should be ignored.
842    int unreasonable = 0;
843    int margins = 0;
844    foreach (values(g->vertices), Eyevertex v) {
845      if (v->is_margin()) {
846        margins++;
847        if (sizeof(v->neighbors) == 4)
848          unreasonable = 1;
849      }
850    }
851
852    if (!unreasonable && margins <= max_margins)
853      test_consistency(g);
854  }
855}
856
857void test_database_correctness()
858{
859  int i;
860 
861  GtpEngine gtp = GtpEngine();
862
863  for (i = 0; i < sizeof(database); i++) {
864    int number = database[i]->number;
865    if (has_value(({63619}), number))  // unrealizable by analyze_eyegraph
866      continue;
867#if 0
868    write("-------\n");
869#endif
870    string coded_eyegraph = database[i]->coded_string;
871    coded_eyegraph = replace(coded_eyegraph, ")", "!");
872    coded_eyegraph = replace(coded_eyegraph, "(", "!");
873    coded_eyegraph = replace(coded_eyegraph, "@", "!");
874    coded_eyegraph = replace(coded_eyegraph, ">", ".");
875    coded_eyegraph = replace(coded_eyegraph, "<", ".");
876    coded_eyegraph = replace(coded_eyegraph, "*", ".");
877   
878#if 0
879    database[i]->prettyprint();
880#endif
881    if (has_value(coded_eyegraph, "$")) {
882#if 0
883      write("Skipping.\n");
884#endif
885      continue;
886    }
887
888    array(string) coded_eyegraphs = ({coded_eyegraph});
889    while (has_value(coded_eyegraphs[0], "x")) {
890      string a, b;
891      sscanf(coded_eyegraphs[0], "%sx%s", a, b);
892      coded_eyegraphs = coded_eyegraphs[1..] + ({a + "." + b, a + "X" + b});
893    }
894
895    int matches = 0;
896    array(string) other_matches = ({});
897    array(string) trace_messages = ({});
898    trace_messages += ({database[i]->stringify() * "\n"});
899    trace_messages += ({sprintf("%d: %s\n", database[i]->number,
900                                (string) database[i]->eyevalue)});
901
902    foreach (coded_eyegraphs, coded_eyegraph) {
903      if (!has_value(coded_eyegraph, ".") && !has_value(coded_eyegraph, "!"))
904        continue;
905
906      array(string) eyegraph = replace(coded_eyegraph, "O", " ") / "%";
907      Eyegraph e = finish_pattern(-1, eyegraph, ":0000");
908      array(string) attack_points = ({});
909      array(string) defense_points = ({});
910      Eyevalue eyevalue;
911      array(string) matched_by;
912      [eyevalue, matched_by, attack_points, defense_points] = compute_eye(e);
913#if 0
914      write(sprintf("%s: %s\n", matched_by * ",", (string) eyevalue));
915#endif
916      if (matched_by[0] != (string) database[i]->number)
917        other_matches |= ({matched_by[0]});
918      else {
919        matches++;
920
921        string analyzed_eyegraph = gtp->command("analyze_eyegraph " + coded_eyegraph);
922       
923        trace_messages += ({analyzed_eyegraph + "\n"});
924
925        if (analyzed_eyegraph == "failed to analyze")
926          trace_messages += ({"!!!!!!!!!!!!! failed to analyze\n"});
927        else {
928          string eyevalue2 = (analyzed_eyegraph / "\n")[0];
929          if (eyevalue2 != (string) eyevalue)
930            trace_messages += ({"!!!!!!!!!!!!! eyevalue mismatch\n"});
931          else {
932            array(string) eyegraph2 = (analyzed_eyegraph / "\n")[1..];
933            foreach (attack_points, string apoints) {
934              int a, b;
935              sscanf(apoints, "%d,%d", a, b);
936              if (!has_value("(<@*", eyegraph2[a-1][b-1..b-1]))
937                trace_messages += ({"!!!!!!!!!!!!! bad attack point at " + a + "," + b + "\n"});
938            }
939            foreach (defense_points, string dpoints) {
940              int a, b;
941              sscanf(dpoints, "%d,%d", a, b);
942              if (!has_value(")>@*", eyegraph2[a-1][b-1..b-1]))
943                trace_messages += ({"!!!!!!!!!!!!! bad defense point at " + a + "," + b + "\n"});
944            }
945          }
946        }
947      }
948    }
949
950    if (matches == 0)
951      trace_messages += ({"!!!!!!!!!!!!! Unmatched, covered by " + sort(other_matches) * "," + "\n"});
952
953    if (has_value(trace_messages * "", "!!!!")) {
954      write("---------\n");
955      write(trace_messages * "\n" + "\n");
956    }
957  }
958  gtp->shutdown();
959}
960
961// FIXME: Refactor common parts with previous function.
962void test_template_correctness(string coded_eyegraph, int|void lax)
963{
964  GtpEngine gtp = GtpEngine();
965
966  array(string) coded_eyegraphs = ({coded_eyegraph});
967  while (has_value(coded_eyegraphs[0], "x")) {
968    string a, b;
969    sscanf(coded_eyegraphs[0], "%sx%s", a, b);
970    coded_eyegraphs = coded_eyegraphs[1..] + ({a + "." + b, a + "X" + b});
971  }
972  while (has_value(coded_eyegraphs[0], "?")) {
973    string a, b;
974    sscanf(coded_eyegraphs[0], "%s?%s", a, b);
975    coded_eyegraphs = coded_eyegraphs[1..] + ({a + "." + b, a + "X" + b,
976                                               a + "!" + b});
977  }
978 
979  foreach (coded_eyegraphs, coded_eyegraph) {
980    if (!has_value(coded_eyegraph, ".") && !has_value(coded_eyegraph, "!"))
981      continue;
982   
983    array(string) eyegraph = replace(coded_eyegraph, "O", " ") / "%";
984    Eyegraph e = finish_pattern(-1, eyegraph, ":0000");
985    array(string) attack_points = ({});
986    array(string) defense_points = ({});
987    Eyevalue eyevalue;
988    array(string) matched_by;
989    [eyevalue, matched_by, attack_points, defense_points] = compute_eye(e, 1);
990    string match_message = sprintf("%s: %s\n", matched_by * ",",
991                                   (string) eyevalue);
992#if 0
993    write(match_message);
994#endif
995     
996    string analyzed_eyegraph = gtp->command("analyze_eyegraph "
997                                            + coded_eyegraph);
998   
999    array(string) problems = ({});
1000   
1001    if (analyzed_eyegraph == "failed to analyze")
1002      problems += ({"!!!!!!!!!!!!! failed to analyze\n"});
1003    else {
1004      string eyevalue2 = (analyzed_eyegraph / "\n")[0];
1005      if ((!lax && eyevalue2 != (string) eyevalue)
1006          || (lax && (eyevalue2[1..2] != ((string) eyevalue)[1..2]
1007                      || (eyevalue2[1] != eyevalue2[2]
1008                          && eyevalue2 != (string) eyevalue)))) {
1009        if (eyevalue2[1..1] == eyevalue2[2..2]
1010            || (sizeof(analyzed_eyegraph / "" & "(<@*" / "") > 0
1011                && sizeof(analyzed_eyegraph / "" & ")>@*" / "") > 0))
1012          problems += ({"!!!!!!!!!!!!! eyevalue mismatch\n"});
1013      }
1014      else {
1015        array(string) eyegraph2 = (analyzed_eyegraph / "\n")[1..];
1016        foreach (attack_points, string apoints) {
1017          int a, b;
1018          sscanf(apoints, "%d,%d", a, b);
1019          if (!has_value("(<@*", eyegraph2[a-1][b-1..b-1]))
1020            problems += ({"!!!!!!!!!!!!! bad attack point at "
1021                          + a + "," + b + "\n"});
1022        }
1023        foreach (defense_points, string dpoints) {
1024          int a, b;
1025          sscanf(dpoints, "%d,%d", a, b);
1026          if (!has_value(")>@*", eyegraph2[a-1][b-1..b-1]))
1027            problems += ({"!!!!!!!!!!!!! bad defense point at "
1028                          + a + "," + b + "\n"});
1029        }
1030      }
1031    }
1032   
1033    if (has_value(problems * "", "!!!!")) {
1034      write("---------\n");
1035      write(match_message);
1036      write(analyzed_eyegraph + "\n");
1037      write(problems * "\n" + "\n");
1038    }
1039  }
1040
1041  gtp->shutdown();
1042}
1043
1044
1045int main(int argc, array(string) argv)
1046{
1047  read_eyes_db();
1048
1049  // Check that all existing patterns are consistent with
1050  // analyze_eyegraph evaluations. Also check whether there are
1051  // patterns which will never be used because earlier patterns match
1052  // before it.
1053#if 1
1054  test_database_correctness();
1055#endif
1056
1057  // Check that the specified configurations are evaluated identically
1058  // by analyze_eyegraph and database matching plus guess_eye rules.
1059#if 1
1060  // 100
1061  test_template_correctness("?", 0);
1062  // 200
1063  test_template_correctness("??", 0);
1064  // 300
1065  test_template_correctness("???", 0);
1066  // 4000
1067  test_template_correctness("|?%|xx?%+---", 0);
1068  // 4100
1069  test_template_correctness("????", 0);
1070  // 4200
1071  test_template_correctness("O?%???", 0);
1072  // 4300
1073  test_template_correctness("??%??", 0);
1074  // 5000
1075  test_template_correctness("|x%|x%|xxx%+---", 0);
1076  test_template_correctness("|!%|.%|xxx%+---", 0);
1077  test_template_correctness("|!%|.%|x.!%+---", 0);
1078  test_template_correctness("|x%|xxxx%+---", 0);
1079  test_template_correctness("|!%|.xxx%+---", 0);
1080  test_template_correctness("|x%|xx.!%+---", 0);
1081  test_template_correctness("|!%|.x.!%+---", 0);
1082  test_template_correctness("|xxx%|xOx%+---", 0);
1083  test_template_correctness("|xx%|Oxx?%+----", 0);
1084  // 5100
1085  test_template_correctness("?????", 0);
1086  // 5200
1087  test_template_correctness("Ox%xxxx", 0);
1088  test_template_correctness("Ox%!xxx", 0);
1089  test_template_correctness("Ox%x!xx", 0);
1090  test_template_correctness("Ox%xx!x", 0);
1091  test_template_correctness("Ox%xxx!", 0);
1092  test_template_correctness("O!%!xxx", 0);
1093  test_template_correctness("Ox%!!xx", 0);
1094  test_template_correctness("Ox%!x!x", 0);
1095  test_template_correctness("Ox%!xx!", 0);
1096  test_template_correctness("Ox%x!x!", 0);
1097  test_template_correctness("O!%!x!x", 0);
1098  test_template_correctness("O!%!xx!", 0);
1099  // 5300
1100  test_template_correctness("xx%xxx", 0);
1101  test_template_correctness("!x%xxx", 0);
1102  test_template_correctness("x!%xxx", 0);
1103  test_template_correctness("xx%x!x", 0);
1104  test_template_correctness("xx%xx!", 0);
1105  test_template_correctness("!!%xxx", 0);
1106  test_template_correctness("!x%x!x", 0);
1107  test_template_correctness("!x%xx!", 0);
1108  test_template_correctness("x!%!xx", 0);
1109  test_template_correctness("x!%x!x", 0);
1110  test_template_correctness("x!%xx!", 0);
1111  test_template_correctness("!!%!xx", 0);
1112  test_template_correctness("!!%xx!", 0);
1113  test_template_correctness("x!%!x!", 0);
1114  test_template_correctness("!!%!x!", 0);
1115  // 5400
1116  test_template_correctness("Ox%xxx%Ox", 0);
1117  test_template_correctness("Ox%!xx%Ox", 0);
1118  test_template_correctness("O!%!xx%Ox", 0);
1119  test_template_correctness("O!%!x!%Ox", 0);
1120  test_template_correctness("O!%!x!%O!", 0);
1121  // 60000
1122  test_template_correctness("??????", 0);
1123  test_template_correctness("?xxxx?%------", 0);
1124  test_template_correctness("|?%|xxxx?%+-----", 0);
1125  test_template_correctness("|?%|x%|xxx?%+----", 0);
1126  test_template_correctness("|!%|.%|xx%|Oxx%+---", 0);
1127  test_template_correctness("|xxx%|xOx!%+----", 0);
1128  // 61000
1129  test_template_correctness("Ox%xxxxx", 0);
1130  test_template_correctness("O!%xxxxx", 0);
1131  test_template_correctness("Ox%!xxxx", 0);
1132  test_template_correctness("Ox%x!xxx", 0);
1133  test_template_correctness("Ox%xx!xx", 0);
1134  test_template_correctness("Ox%xxx!x", 0);
1135  test_template_correctness("Ox%xxxx!", 0);
1136  test_template_correctness("O!%!xxxx", 0);
1137  test_template_correctness("O!%xx!xx", 0);
1138  test_template_correctness("O!%xxx!x", 0);
1139  test_template_correctness("O!%xxxx!", 0);
1140  test_template_correctness("Ox%x!x!x", 0);
1141  test_template_correctness("Ox%x!xx!", 0);
1142  test_template_correctness("Ox%xx!x!", 0);
1143  test_template_correctness("O!%!x!xx", 0);
1144  test_template_correctness("O!%!xx!x", 0);
1145  test_template_correctness("O!%!xxx!", 0);
1146  test_template_correctness("O!%xx!x!", 0);
1147  test_template_correctness("O!%!x!x!", 0);
1148  // 62000
1149  test_template_correctness("OOx%xxxxx", 0);
1150  test_template_correctness("OO!%xxxxx", 0);
1151  test_template_correctness("OOx%!xxxx", 0);
1152  test_template_correctness("OOx%x!xxx", 0);
1153  test_template_correctness("OOx%xx!xx", 0);
1154  test_template_correctness("OO!%!xxxx", 0);
1155  test_template_correctness("OO!%x!xxx", 0);
1156  test_template_correctness("OOx%!x!xx", 0);
1157  test_template_correctness("OOx%!xx!x", 0);
1158  test_template_correctness("OOx%!xxx!", 0);
1159  test_template_correctness("OOx%x!x!x", 0);
1160  test_template_correctness("OO!%!xx!x", 0);
1161  test_template_correctness("OO!%!xxx!", 0);
1162  test_template_correctness("OOx%!x!x!", 0);
1163  // 63000
1164  test_template_correctness("Oxx%xxxx", 0);
1165  test_template_correctness("Oxx%xxxx%----", 0);
1166  test_template_correctness("Oxx%!xxx", 0);
1167  test_template_correctness("Oxx%!xxx%----", 0);
1168  test_template_correctness("Oxx%x!xx", 0);
1169  test_template_correctness("O!x%xxxx", 0);
1170  test_template_correctness("Oxx%!x!x", 0);
1171  test_template_correctness("Oxx%!xx!", 0);
1172  test_template_correctness("Oxx%!xx!%----", 0);
1173  test_template_correctness("O!x%!xxx", 0);
1174  test_template_correctness("Ox!%!xxx", 0);
1175  test_template_correctness("Ox!%x!xx", 0);
1176  test_template_correctness("O!x%!x!x", 0);
1177  test_template_correctness("O!x%!xx!", 0);
1178  // 64000
1179  test_template_correctness("xx%xxxx", 0);
1180  test_template_correctness("xx%xxxx%----", 0);
1181  test_template_correctness("|xx%|xxxx%+----", 0);
1182  test_template_correctness("xx%xxx!", 0);
1183  test_template_correctness("xx%xxx!%----", 0);
1184  test_template_correctness("|xx%|xxx!%+----", 0);
1185  test_template_correctness("xx%xx!x", 0);
1186  test_template_correctness("xx%x!xx", 0);
1187  test_template_correctness("xx%!xxx", 0);
1188  test_template_correctness("!x%xxxx", 0);
1189  test_template_correctness("xx%x!x!", 0);
1190  test_template_correctness("xx%!xx!", 0);
1191  test_template_correctness("!x%xxx!", 0);
1192  test_template_correctness("xx%!x!x", 0);
1193  test_template_correctness("!x%xx!x", 0);
1194  test_template_correctness("!x%x!xx", 0);
1195  test_template_correctness("x!%!xxx", 0);
1196  test_template_correctness("!x%x!x!", 0);
1197  test_template_correctness("x!%!xx!", 0);
1198  test_template_correctness("x!%!x!x", 0);
1199  // 65000
1200  test_template_correctness("xxx%xxx", 0);
1201  test_template_correctness("xxx%xxx%---", 0);
1202  test_template_correctness("|xxx%|xxx%+---", 0);
1203  test_template_correctness("!xx%xxx", 0);
1204  test_template_correctness("x!x%xxx", 0);
1205  test_template_correctness("!x!%xxx", 0);
1206  test_template_correctness("!xx%x!x", 0);
1207  test_template_correctness("!xx%xx!", 0);
1208  test_template_correctness("!xx%!xx", 0);
1209  // 66000
1210  test_template_correctness("Oxxx%xxx", 0);
1211  test_template_correctness("Oxxx%!xx", 0);
1212  test_template_correctness("O!xx%xxx", 0);
1213  test_template_correctness("Ox!x%xxx", 0);
1214  test_template_correctness("Oxx!%!xx", 0);
1215  test_template_correctness("Oxxx%!x!", 0);
1216  // 67000
1217  test_template_correctness("Ox%xxxx%Ox", 0);
1218  test_template_correctness("Ox%!xxx%Ox", 0);
1219  test_template_correctness("Ox%xxx!%Ox", 0);
1220  test_template_correctness("O!%!xxx%Ox", 0);
1221  test_template_correctness("Ox%!xx!%Ox", 0);
1222  test_template_correctness("O!%!xxx%O!", 0);
1223  test_template_correctness("O!%!xx!%Ox", 0);
1224  test_template_correctness("O!%!xx!%O!", 0);
1225  // 68000
1226  test_template_correctness("Ox%xxxx%OOx", 0);
1227  test_template_correctness("Ox%!xxx%OOx", 0);
1228  test_template_correctness("O!%!xxx%OOx", 0);
1229  test_template_correctness("Ox%!xx!%OOx", 0);
1230  test_template_correctness("O!%!xx!%OOx", 0);
1231  test_template_correctness("O!%!xx!%OO!", 0);
1232  // 69000
1233  test_template_correctness("xx%xxx%Ox", 0);
1234  test_template_correctness("!x%xxx%Ox", 0);
1235  test_template_correctness("x!%xxx%Ox", 0);
1236  test_template_correctness("xx%xx!%Ox", 0);
1237  test_template_correctness("!!%xxx%Ox", 0);
1238  test_template_correctness("!x%xx!%Ox", 0);
1239  test_template_correctness("x!%!xx%Ox", 0);
1240  test_template_correctness("x!%xx!%Ox", 0);
1241  test_template_correctness("x!%xxx%O!", 0);
1242  test_template_correctness("xx%xx!%O!", 0);
1243  test_template_correctness("!x%xx!%O!", 0);
1244  test_template_correctness("x!%xx!%O!", 0);
1245  test_template_correctness("x!%!x!%O!", 0);
1246  // 70000
1247  test_template_correctness("xxxxxxx", 0);
1248  test_template_correctness("!.xxxxx", 0);
1249  test_template_correctness("!.xxx.!", 0);
1250  test_template_correctness("|x%|x%|x%|xxxx%+----", 0);
1251  test_template_correctness("|!%|.%|x%|xxxx%+----", 0);
1252  test_template_correctness("|!%|.%|x%|xx.!%+----", 0);
1253  // 70500
1254  test_template_correctness("Ox%xxxxxx", 0);
1255  test_template_correctness("Ox%xxxx.!", 0);
1256  test_template_correctness("Ox%!.xxxx", 0);
1257  test_template_correctness("Ox%!.xx.!", 0);
1258  test_template_correctness("O!%!.xxxx", 0);
1259  test_template_correctness("O!%!.xx.!", 0);
1260  // 71000
1261  test_template_correctness("OOx%xxxxxx", 0);
1262  test_template_correctness("OOx%!.xxxx", 0);
1263  test_template_correctness("OOx%xxxx.!", 0);
1264  test_template_correctness("OO!%xx.xxx", 0);
1265  test_template_correctness("OOx%!.xx.!", 0);
1266  test_template_correctness("OO!%xx.x.!", 0);
1267  test_template_correctness("OO!%!..xxx", 0);
1268  test_template_correctness("OO!%!..x.!", 0);
1269  // 71500
1270  test_template_correctness("OxOx%xxxxx", 0);
1271  test_template_correctness("OxOx%!.xxx", 0);
1272  test_template_correctness("OxOx%!.x.!", 0);
1273  test_template_correctness("O!Ox%!.xxx", 0);
1274  test_template_correctness("O!Ox%!.x.!", 0);
1275  test_template_correctness("O!O!%!.x.!", 0);
1276  // 72000
1277  test_template_correctness("Ox%xxxxx%Ox", 0);
1278  test_template_correctness("Ox%xxx.!%Ox", 0);
1279  test_template_correctness("Ox%!.xxx%Ox", 0);
1280  test_template_correctness("Ox%!.x.!%Ox", 0);
1281  test_template_correctness("O!%!.xxx%Ox", 0);
1282  test_template_correctness("O!%!.x.!%Ox", 0);
1283  test_template_correctness("O!%!.xxx%O!", 0);
1284  test_template_correctness("O!%!.x.!%O!", 0);
1285  // 72500
1286  test_template_correctness("OOx%xxxxx%Ox", 0);
1287  test_template_correctness("OOx%!.xxx%Ox", 0);
1288  test_template_correctness("OOx%xxx.!%Ox", 0);
1289  test_template_correctness("OO!%xx.xx%Ox", 0);
1290  test_template_correctness("OO!%xx..!%Ox", 0);
1291  test_template_correctness("OOx%!.x.!%Ox", 0);
1292  // 73000
1293  test_template_correctness("OOx%xxxxx%OOx", 0);
1294  test_template_correctness("OOx%?.xxx%OOx", 0);
1295  test_template_correctness("OO?%xx.xx%OOx", 0);
1296  test_template_correctness("OOx%?.x.?%OOx", 0);
1297  test_template_correctness("OO?%?..xx%OOx", 0);
1298  // 73500
1299  test_template_correctness("OOx%OOx%xxxxx", 0);
1300  test_template_correctness("OO?%OO.%xxxxx", 0);
1301  test_template_correctness("OO?%OO.%?.xxx", 0);
1302  test_template_correctness("OO?%OO.%?.x.?", 0);
1303  // 74000
1304  test_template_correctness("xx%xxxxx", 0);
1305  test_template_correctness("xx%xxx.!", 0);
1306  test_template_correctness("|xx%|xxx.!%+-----", 0);
1307  test_template_correctness("xx%xx.!.", 0);
1308  // 74500
1309  test_template_correctness("Oxx%xxxxx", 0);
1310  test_template_correctness("Oxx%xxxxx%-----", 0);
1311  test_template_correctness("|x%|x%|xxx%|xx%+--", 0);
1312  test_template_correctness("Oxx%!.xxx", 0);
1313  test_template_correctness("Oxx%!.xxx%-----", 0);
1314  test_template_correctness("|x%|x%|x.!%|xx%+--", 0);
1315  test_template_correctness("Oxx%xxx.!", 0);
1316  test_template_correctness("Oxx%xxx.!%-----", 0);
1317  test_template_correctness("|!%|.%|xxx%|xx%+--", 0);
1318  test_template_correctness("Oxx%!.x.!", 0);
1319  test_template_correctness("Oxx%!.x.!%-----", 0);
1320  test_template_correctness("|!%|.%|x.!%|xx%+--", 0);
1321  // 75000
1322  test_template_correctness("OOxxx%xxxx", 0);
1323  test_template_correctness("OOxxx%xxxx%----", 0);
1324  test_template_correctness("Oxxxx%xxx%---", 0);
1325  test_template_correctness("|x%|x%|xx%|xxx%+---", 0);
1326  test_template_correctness("OOxxx%!.xx", 0);
1327  test_template_correctness("OOxxx%!.xx%----", 0);
1328  test_template_correctness("Oxx.!%xxx%---", 0);
1329  test_template_correctness("|!%|.%|xx%|xxx%+---", 0);
1330  test_template_correctness("OOx.!%xxxx", 0);
1331  test_template_correctness("OOx.!%xxxx%----", 0);
1332  test_template_correctness("Oxxxx%!.x%---", 0);
1333  test_template_correctness("|x%|x%|xx%|x.!%+---", 0);
1334  test_template_correctness("OOx.!%!.xx", 0);
1335  test_template_correctness("OOx.!%!.xx%----", 0);
1336  test_template_correctness("Oxx.!%!.x%---", 0);
1337  test_template_correctness("|!%|.%|xx%|x.!%+---", 0);
1338  // 75500
1339  test_template_correctness("xx%xxxx%Ox", 0);
1340  test_template_correctness("|Ox%|xxxx%|xx%+--", 0);
1341  test_template_correctness("xx%xx.!%Ox", 0);
1342  test_template_correctness("|Ox%|xx.!%|xx%+--", 0);
1343  // 76000
1344  test_template_correctness("Oxx%xxxx%Ox", 0);
1345  test_template_correctness("Oxx%xx.!%Ox", 0);
1346  test_template_correctness("Oxx%!.xx%Ox", 0);
1347  test_template_correctness("Oxx%!..!%Ox", 0);
1348  // 76500
1349  test_template_correctness("Ox%xxx%Oxxx", 0);
1350  test_template_correctness("Ox%!.x%Oxxx", 0);
1351  test_template_correctness("Ox%xxx%Ox.!", 0);
1352  test_template_correctness("Ox%!.x%Ox.!", 0);
1353  // 77000
1354  test_template_correctness("Ox%Oxx%xxxx", 0);
1355  test_template_correctness("Ox%Oxx%xx.!", 0);
1356  test_template_correctness("Ox%Oxx%!.xx", 0);
1357  test_template_correctness("O!%O.x%xxxx", 0);
1358  test_template_correctness("O!%O.x%!.xx", 0);
1359  test_template_correctness("Ox%Oxx%!..!", 0);
1360  test_template_correctness("O!%O.x%xx.!", 0);
1361  test_template_correctness("O!%O.x%!..!", 0);
1362  // 77500
1363  test_template_correctness("OOx%xxxx%xx", 0);
1364  test_template_correctness("OOx%xx.!%xx", 0);
1365  test_template_correctness("OO!%xx.!%xx", 0);
1366  // 78000
1367  test_template_correctness("xx%xxx%Oxx", 0);
1368  test_template_correctness("|Oxx%|xxx%|xx%+--", 0);
1369  test_template_correctness("!.%.xx%Oxx", 0);
1370  test_template_correctness("|O.!%|xx.%|xx%+--", 0);
1371  // 78500
1372  test_template_correctness("xxx%xxxx", 0);
1373  test_template_correctness("----%xxxx%xxx", 0);