play mode
This commit is contained in:
1 parent
12b1b1a212
commit
d6141d91d6
8 files changed
+227
-193
No files matched your search
+3
-4
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user