gtp_extensions.py 5.94 KB
Newer Older
Yanhui Liang's avatar
Yanhui Liang committed
1
2
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
3
# Licensed under the Apache License, Version 2.0 (the 'License');
Yanhui Liang's avatar
Yanhui Liang committed
4
5
6
7
8
9
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an 'AS IS' BASIS,
Yanhui Liang's avatar
Yanhui Liang committed
11
12
13
14
15
16
17
18
19
20
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Extends gtp.py."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import itertools
21
import sys
Yanhui Liang's avatar
Yanhui Liang committed
22
23
24
25
26
27
28
29
30

import coords
import go
import gtp
import sgf_wrapper


def parse_message(message):
  message = gtp.pre_engine(message).strip()
31
  first, rest = (message.split(' ', 1) + [None])[:2]
Yanhui Liang's avatar
Yanhui Liang committed
32
33
34
  if first.isdigit():
    message_id = int(first)
    if rest is not None:
35
      command, arguments = (rest.split(' ', 1) + [None])[:2]
Yanhui Liang's avatar
Yanhui Liang committed
36
37
38
39
40
41
    else:
      command, arguments = None, None
  else:
    message_id = None
    command, arguments = first, rest

42
  command = command.replace('-', '_')  # for kgs extensions.
Yanhui Liang's avatar
Yanhui Liang committed
43
44
45
46
47
  return message_id, command, arguments


class KgsExtensionsMixin(gtp.Engine):

48
49
  def __init__(self, game_obj, name='gtp (python, kgs-chat extensions)',
               version='0.1'):
Yanhui Liang's avatar
Yanhui Liang committed
50
    super().__init__(game_obj=game_obj, name=name, version=version)
51
    self.known_commands += ['kgs-chat']
Yanhui Liang's avatar
Yanhui Liang committed
52
53
54
55
56

  def send(self, message):
    message_id, command, arguments = parse_message(message)
    if command in self.known_commands:
      try:
57
        retval = getattr(self, 'cmd_' + command)(arguments)
Yanhui Liang's avatar
Yanhui Liang committed
58
59
60
61
62
63
        response = gtp.format_success(message_id, retval)
        sys.stderr.flush()
        return response
      except ValueError as exception:
        return gtp.format_error(message_id, exception.args[0])
    else:
64
      return gtp.format_error(message_id, 'unknown command: ' + command)
Yanhui Liang's avatar
Yanhui Liang committed
65
66
67
68
69
70
71
72
73
74
75
76

  # Nice to implement this, as KGS sends it each move.
  def cmd_time_left(self, arguments):
    pass

  def cmd_showboard(self, arguments):
    return self._game.showboard()

  def cmd_kgs_chat(self, arguments):
    try:
      arg_list = arguments.split()
      msg_type, sender, text = arg_list[0], arg_list[1], arg_list[2:]
77
      text = ' '.join(text)
Yanhui Liang's avatar
Yanhui Liang committed
78
    except ValueError:
79
      return 'Unparseable message, args: %r' % arguments
Yanhui Liang's avatar
Yanhui Liang committed
80
81
82
83
    return self._game.chat(msg_type, sender, text)


class RegressionsMixin(gtp.Engine):
84

Yanhui Liang's avatar
Yanhui Liang committed
85
86
87
88
89
  def cmd_loadsgf(self, arguments):
    args = arguments.split()
    if len(args) == 2:
      file_, movenum = args
      movenum = int(movenum)
90
      print('movenum =', movenum, file=sys.stderr)
Yanhui Liang's avatar
Yanhui Liang committed
91
92
93
94
95
96
97
98
    else:
      file_ = args[0]
      movenum = None

    try:
      with open(file_, 'r') as f:
        contents = f.read()
    except:
99
      raise ValueError('Unreadable file: ' + file_)
Yanhui Liang's avatar
Yanhui Liang committed
100
101
102
103
104
105
106

    try:
      # This is kinda bad, because replay_sgf is already calling
      # 'play move' on its internal position objects, but we really
      # want to advance the engine along with us rather than try to
      # push in some finished Position object.
      for idx, p in enumerate(sgf_wrapper.replay_sgf(contents)):
107
        print('playing #', idx, p.next_move, file=sys.stderr)
Yanhui Liang's avatar
Yanhui Liang committed
108
109
110
111
112
113
114
115
        self._game.play_move(p.next_move)
        if movenum and idx == movenum:
          break
    except:
      raise


class GoGuiMixin(gtp.Engine):
116
117
  """GTP extensions of 'analysis commands' for gogui.

Yanhui Liang's avatar
Yanhui Liang committed
118
119
120
121
  We reach into the game_obj (an instance of the players in strategies.py),
  and extract stuff from its root nodes, etc.  These could be extracted into
  methods on the Player object, but its a little weird to do that on a Player,
  which doesn't really care about GTP commands, etc.  So instead, we just
122
123
  violate encapsulation a bit.
  """
Yanhui Liang's avatar
Yanhui Liang committed
124

125
126
  def __init__(self, game_obj, name='gtp (python, gogui extensions)',
               version='0.1'):
Yanhui Liang's avatar
Yanhui Liang committed
127
    super().__init__(game_obj=game_obj, name=name, version=version)
128
    self.known_commands += ['gogui-analyze_commands']
Yanhui Liang's avatar
Yanhui Liang committed
129
130

  def cmd_gogui_analyze_commands(self, arguments):
131
132
133
134
    return '\n'.join(['var/Most Read Variation/nextplay',
                      'var/Think a spell/spin',
                      'pspairs/Visit Heatmap/visit_heatmap',
                      'pspairs/Q Heatmap/q_heatmap'])
Yanhui Liang's avatar
Yanhui Liang committed
135
136
137
138
139
140
141
142
143
144
145
146
147

  def cmd_nextplay(self, arguments):
    return self._game.root.mvp_gg()

  def cmd_visit_heatmap(self, arguments):
    sort_order = list(range(self._game.size * self._game.size + 1))
    sort_order.sort(key=lambda i: self._game.root.child_N[i], reverse=True)
    return self.heatmap(sort_order, self._game.root, 'child_N')

  def cmd_q_heatmap(self, arguments):
    sort_order = list(range(self._game.size * self._game.size + 1))
    reverse = True if self._game.root.position.to_play is go.BLACK else False
    sort_order.sort(
148
        key=lambda i: self._game.root.child_Q[i], reverse=reverse)
Yanhui Liang's avatar
Yanhui Liang committed
149
150
151
    return self.heatmap(sort_order, self._game.root, 'child_Q')

  def heatmap(self, sort_order, node, prop):
152
153
154
    return '\n'.join(['{!s:6} {}'.format(
        coords.to_kgs(coords.from_flat(key)), node.__dict__.get(prop)[key])
                      for key in sort_order if node.child_N[key] > 0][:20])
Yanhui Liang's avatar
Yanhui Liang committed
155
156

  def cmd_spin(self, arguments):
157
158
    for _ in range(50):
      for _ in range(100):
Yanhui Liang's avatar
Yanhui Liang committed
159
160
161
        self._game.tree_search()
      moves = self.cmd_nextplay(None).lower()
      moves = moves.split()
162
163
164
165
166
167
      colors = 'bw' if self._game.root.position.to_play is go.BLACK else 'wb'
      moves_cols = ' '.join(['{} {}'.format(*z)
                             for z in zip(itertools.cycle(colors), moves)])
      print('gogui-gfx: TEXT', '{:.3f} after {}'.format(
          self._game.root.Q, self._game.root.N), file=sys.stderr, flush=True)
      print('gogui-gfx: VAR', moves_cols, file=sys.stderr, flush=True)
Yanhui Liang's avatar
Yanhui Liang committed
168
169
170
171
172
    return self.cmd_nextplay(None)


class GTPDeluxe(KgsExtensionsMixin, RegressionsMixin, GoGuiMixin):
  pass