play mode

This commit is contained in:
Sander Land committed 2020-04-20 23:09:47 +02:00
1 parent 12b1b1a212
commit d6141d91d6
8 files changed
+227 -193

No files matched your search

+3 -4
View File
@@ -12,7 +12,7 @@ from game import Move
class BadukPanWidget(Widget):
board_color = ListProperty( [0.85, 0.68, 0.40] )
board_color = ListProperty([0.85, 0.68, 0.40])
def __init__(self, **kwargs):
super(BadukPanWidget, self).__init__(**kwargs)
@@ -180,10 +180,9 @@ class BadukPanWidget(Widget):
Rectangle(pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz))
ix = ix + 1
policy = current_node.policy
if not policy and current_node.parent and current_node.parent.policy and set(katrain.controls.ai_auto.active_map.values())=={True}:
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
if not policy and current_node.parent and current_node.parent.policy and set(katrain.controls.ai_auto.active_map.values()) == {True}:
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
pass_btn = katrain.board_controls.pass_btn
pass_btn.canvas.after.clear()
if katrain.controls.policy.active and policy and not katrain.controls.ownership.active:
+29 -55
View File
@@ -11,7 +11,7 @@ class Controls(BoxLayout):
def set_status(self, msg, at_node=None):
self.status = msg
self.status_node = at_node or self.parent.game.current_node
self.status_node = at_node or self.parent.game and self.parent.game.current_node
self.info.text = msg
self.update_evaluation()
@@ -38,6 +38,9 @@ class Controls(BoxLayout):
else:
self.points_lost.text = f"..."
def player_mode(self, player):
return self.player_mode_groups[player].value
def unlock(self):
if self.ai_lock.active:
self.ai_lock.checkbox.trigger_action(duration=0)
@@ -50,65 +53,36 @@ class Controls(BoxLayout):
# handles showing completed analysis and score graph
def update_evaluation(self):
katrain = self.parent
current_node = katrain.game.current_node
move = current_node.single_move
current_player_is_human_or_both_robots = not current_node.player or not self.ai_auto.active(current_node.player) or self.ai_auto.active(current_node.next_player)
current_node = katrain.game and katrain.game.current_node
info = ""
if current_node is self.status_node:
if current_node is self.status_node or (self.status is not None and self.status_node is None and current_node.is_root): # startup errors on root
info += self.status + "\n"
else:
self.status_node = None
if current_player_is_human_or_both_robots and not current_node.is_root and move:
info += current_node.comment(eval=True, hints=self.hints.active(move.player))
if current_player_is_human_or_both_robots:
self.show_evaluation_stats(current_node)
if current_node:
move = current_node.single_move
current_player_is_human_or_both_robots = not current_node.player or not self.ai_auto.active(current_node.player) or self.ai_auto.active(current_node.next_player)
if current_player_is_human_or_both_robots and not current_node.is_root and move:
info += current_node.comment(eval=True, hints=self.hints.active(move.player))
if current_player_is_human_or_both_robots:
self.show_evaluation_stats(current_node)
game_node = katrain.game.current_node
scores = [n.score for n in game_node.nodes_from_root]
# TODO: like redo, what is the node to redo / should we append? cache?
self.graph.canvas.clear()
with self.graph.canvas:
pt = []
nnscores = [s for s in scores if s is not None] + [-5, 5]
scale = max(max(*nnscores), -min(*nnscores)) * 1.05
xscale = self.graph.width * 0.9 / max(len(scores), 20)
ls = 0
for i, s in enumerate(scores):
ls = s or ls
pt.extend([self.graph.pos[0] + 0.05 * self.graph.width + i * xscale, self.graph.pos[1] + self.graph.height / 2 * (1 + ls / scale)])
Color(0, 0, 0)
Line(points=pt, width=1.0) # just set points?
self.info.text = info
game_node = katrain.game.current_node
scores = [n.score for n in game_node.nodes_from_root]
# TODO: like redo, what is the node to redo / should we append? cache?
self.graph.canvas.clear()
with self.graph.canvas:
pt = []
nnscores = [s for s in scores if s is not None] + [-5, 5]
scale = max(max(*nnscores), -min(*nnscores)) * 1.05
xscale = self.graph.width * 0.9 / max(len(scores), 20)
ls = 0
for i, s in enumerate(scores):
ls = s or ls
pt.extend([self.graph.pos[0] + 0.05 * self.graph.width + i * xscale, self.graph.pos[1] + self.graph.height / 2 * (1 + ls / scale)])
Color(0, 0, 0)
Line(points=pt, width=1.0) # just set points?
if False: # TODO: UNDO AND AI MOVE
if current_node.analysis_ready and current_node.parent and current_node.parent.analysis_ready and not current_node.children and not current_node.x_comment.get("undo"):
# handle automatic undo
if self.auto_undo.active(move.player) and not self.ai_auto.active(move.player) and not current_node.auto_undid:
ts = self.train_settings
# TODO: is this overly generous wrt low visit outdated evaluations?
evaluation = current_node.evaluation if current_node.evaluation is not None else 1 # assume move is fine if temperature is negative
move_eval = max(evaluation, current_node.outdated_evaluation or 0)
points_lost = (current_node.parent or current_node).temperature_stats[2] * (1 - move_eval)
if move_eval < ts["undo_eval_threshold"] and points_lost >= ts["undo_point_threshold"]:
if self.num_undos(current_node) == 0:
current_node.x_comment["undid"] = f"Move was below threshold, but no undo granted (probability is {ts['num_undo_prompts']:.0%}).\n"
self.update_evaluation()
else:
current_node.auto_undid = True
self.parent.game.undo()
if len(current_node.parent.children) >= ts["num_undo_prompts"] + 1:
best_move = sorted([m for m in current_node.parent.children], key=lambda m: -(m.evaluation_info[0] or 0))[0]
best_move.x_comment["undo_autoplay"] = f"Automatically played as best option after max. {ts['num_undo_prompts']} undo(s).\n"
self.parent.game.play(best_move)
self.update_evaluation()
return
# ai player doesn't technically need parent ready, but don't want to override waiting for undo
current_node = self.parent.game.current_node # this effectively checks undo didn't just happen
if self.ai_auto.active(move.opponent) and not self.parent.game.game_ended:
if current_node.children:
self.info.text = "AI paused since moves were undone. Press 'AI Move' or choose a move for the AI to continue playing."
else:
self._do_aimove()
+65 -5
View File
@@ -1,20 +1,21 @@
import random
from kivy.clock import Clock
from kivy.core.text import Label as CoreLabel
from kivy.graphics import *
from kivy.properties import BooleanProperty, StringProperty, NumericProperty
from kivy.properties import BooleanProperty, StringProperty, NumericProperty, ListProperty, ObjectProperty
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.boxlayout import BoxLayout
import re
from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.spinner import Spinner
from kivy.uix.textinput import TextInput
class StyledButton(Button):
pass
class CheckBoxHint(BoxLayout):
__events__ = ("on_active",)
@@ -30,6 +31,65 @@ class DarkLabel(Label):
pass
class StyledButton(Button):
pass
class StyledToggleButton(StyledButton, ToggleButtonBehavior):
value = StringProperty("")
class ToggleButtonContainer(GridLayout):
__events__ = ("on_selection",)
options = ListProperty([])
labels = ListProperty([])
selected = StringProperty("")
group = StringProperty(None)
autosize = BooleanProperty(True)
button_class = ObjectProperty(StyledToggleButton)
margin = ListProperty([1, 1, 0, 0])
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._build, 0)
def on_selection(self, *args):
pass
def _build(self, _dt):
self.rows = 1
self.cols = len(self.options)
self.group = self.group or str(random.random())
if not self.selected and self.options:
self.selected = self.options[0]
if len(self.labels) < len(self.options):
self.labels += self.options[len(self.labels) + 1 :]
def state_handler(btn,*args):
self.dispatch("on_selection")
btn.state = 'down' # no toggle
for i, opt in enumerate(self.options):
state = "down" if opt == self.selected else "normal"
self.add_widget(self.button_class(group=self.group, text=self.labels[i], value=opt, state=state,
margin=self.margin,on_press=state_handler))
Clock.schedule_once(self._size, 0)
def _size(self, _dt):
if self.autosize:
for tb in self.children:
tb.size_hint = (tb.texture_size[0] + 3, 1)
@property
def value(self):
for tb in self.children:
if tb.state == "down":
return tb.value
if self.options:
return self.options[0]
class BaseCircleWithText(DarkLabel):
radius = NumericProperty(0.48)