Files
katrain-qt/katrain/gui/controlspanel.py
T
c5587bf6e1 1.8.0 (#356)
* distributed option

* i18n, pause, etc

* fixes galore

* max buffer etc

* test override config

* maxgame

* visit

* contribute menu

* restore vars

* i18n

* actually start

* retry distr and errors

* pw eye / move speed

* cfg/no log

* shutdown fix

* shutdown fix

* download fix

* wut

* stats and desc

* esc fix

* Omit terminate queries from KataGoEngine.queries. (#353)

* press key h to clear current node analysis. (#352)

* clear current node all analysis

* add reset key binding

* reset all analysis off.

* code refactor

* code change according to review.

* remove useless variable

* code review changes

* clean up reset node

* oops

* Setup position feature (#354)

* go for zebras

* setup working!

* uhh

* tweak

* img

* fix i18n

* AEEEEEEEEEE and stop

* link in readme

* new game options for edit/new/setup (#357)

* new game options for edit/new/setup

* feedback

* fix

* ai thoughts

* no tristate cb

* branch fixing (#358)

* new game options for edit/new/setup

* feedback

* fix

* ai thoughts

* no tristate cb

* fix comma etc

* normal katago

* kata

* dll

* save window pos/size

* BAD comma

* new linux binary + allow default engine

* fix

* unused icon, build mac

* exe

* player new game fix

* fix i18n

* test test

* just print

* 한국어

* –활성화

* Add new Traditional Chinese translation (#362)

Improve some translations as well.

* cn (#363)

* cn

* rm old

* rm old

* informal you

* mo

* jp (#364)

* config and readme

* mo

* kgtraining

* fix score

* lower stddev a bit

* tweak weights

* 1.8 French Po (#365)

* fr and getattr fix

* -DBUILD_DISTRIBUTED=1

* openssl

* openssl

* openssl

* openssl

* config

* jp (num undos) (#366)

* clarify undo in en, po->mo

* fix player name

* new kata

* new kata

* Load game option save config (#367)

* in the load dialog,  "load fast analysis" and "rewind to the start" option ,save to config file

* in the load dialog,  "load fast analysis" and "rewind to the start" option ,save to config file

* default option

* remove useless override

* save check state when file dialog's submit event ,

* save config in callback

* readme

* active games

* de

* log games as json always

* black

* Russian localization for 1.8.0 (#375)

* ru

Co-authored-by: pdeblanc <peter@spaceandgames.com>
Co-authored-by: xiaoyifang <105986+xiaoyifang@users.noreply.github.com>
Co-authored-by: Liou, Guan-Cheng <Tony-Liou@users.noreply.github.com>
Co-authored-by: kaorahi <38910552+kaorahi@users.noreply.github.com>
Co-authored-by: Dontbtme <66470440+Dontbtme@users.noreply.github.com>
Co-authored-by: Alexander Kiselev <satoshi.yoda.register.264@gmail.com>
2021-03-05 20:40:48 +01:00

261 lines
11 KiB
Python

import time
from kivy.clock import Clock
from kivy.core.audio import SoundLoader
from kivy.properties import ObjectProperty, OptionProperty
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from katrain.core.constants import (
MODE_ANALYZE,
MODE_PLAY,
PLAYER_HUMAN,
STATUS_ANALYSIS,
STATUS_ERROR,
AI_DEFAULT,
PLAYER_AI,
)
from katrain.core.lang import rank_label
from katrain.gui.kivyutils import AnalysisToggle, CollapsablePanel
from katrain.gui.theme import Theme
class PlayAnalyzeSelect(MDFloatLayout):
katrain = ObjectProperty(None)
mode = OptionProperty(MODE_PLAY, options=[MODE_PLAY, MODE_ANALYZE])
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.load_ui_state, 1)
def save_ui_state(self):
self.katrain._config["ui_state"] = self.katrain._config.get("ui_state", {})
self.katrain._config["ui_state"][self.mode] = {
"analysis_controls": {
id: toggle.active
for id, toggle in self.katrain.analysis_controls.ids.items()
if isinstance(toggle, AnalysisToggle)
},
"panels": {
id: (panel.state, panel.option_state)
for id, panel in self.katrain.controls.ids.items()
if isinstance(panel, CollapsablePanel)
},
}
self.katrain.save_config("ui_state")
def load_ui_state(self, _dt=None):
state = self.katrain.config(f"ui_state/{self.mode}", {})
for id, active in state.get("analysis_controls", {}).items():
cb = self.katrain.analysis_controls.ids[id].checkbox
cb.active = bool(active)
for id, (panel_state, button_state) in state.get("panels", {}).items():
self.katrain.controls.ids[id].set_option_state(button_state)
self.katrain.controls.ids[id].state = panel_state
def select_mode(self, new_mode): # actual switch state handler
if self.mode == new_mode:
return
self.save_ui_state()
self.mode = new_mode
self.katrain.controls.timer_or_movetree.mode = self.mode
self.load_ui_state()
self.katrain.update_state() # for lock ai even if nothing changed
def switch_ui_mode(self): # on tab press, fake ui click and trigger everything top down
if self.mode == MODE_PLAY:
Clock.schedule_once(
lambda _dt: self.analyze.trigger_action(duration=0)
) # normal trigger does not cross thread
else:
Clock.schedule_once(lambda _dt: self.play.trigger_action(duration=0))
class ControlsPanel(BoxLayout):
katrain = ObjectProperty(None)
button_controls = ObjectProperty(None)
def __init__(self, **kwargs):
super(ControlsPanel, self).__init__(**kwargs)
self.status_state = (None, -1e9, None)
self.active_comment_node = None
self.last_timer_update = (None, 0, False)
self.beep = SoundLoader.load(Theme.COUNTDOWN_SOUND)
self.boing = SoundLoader.load(Theme.MINIMUM_TIME_PASSED_SOUND)
if self.boing:
self.boing.volume = 0.1
self.beep_start = 5.2
self.timer_interval = 0.07
Clock.schedule_interval(self.update_timer, self.timer_interval)
def update_players(self, *_args):
for bw, player_info in self.katrain.players_info.items():
self.players[bw].player_type = player_info.player_type
self.players[bw].player_subtype = player_info.player_subtype
self.players[bw].name = player_info.name
self.players[bw].rank = (
player_info.sgf_rank
if player_info.player_type == PLAYER_HUMAN
else rank_label(player_info.calculated_rank)
)
def set_status(self, msg, status_type, at_node=None, check_level=True):
at_node = at_node or self.katrain and self.katrain.game and self.katrain.game.current_node
if (
at_node != self.status_state[2]
or not check_level
or int(status_type) >= int(self.status_state[1])
or msg == ""
):
if self.status_state != (msg, status_type, at_node): # prevent loop if error in update eval
Clock.schedule_once(self.update_evaluation, 0)
self.status_state = (msg, status_type, at_node)
self.status.text = msg
self.status.error = status_type == STATUS_ERROR
# handles showing completed analysis and score graph
def update_evaluation(self, *_args):
katrain = self.katrain
game = katrain and katrain.game
if not game:
return
current_node, move = game.current_node, game.current_node.move
if (
game.current_node is not self.status_state[2]
and not (self.status_state[1] == STATUS_ERROR and self.status_state[2] is None)
) or (
self.katrain.engine.is_idle() and self.status_state[1] == STATUS_ANALYSIS
): # clear status if node changes, except startup errors on root. also clear analysis message when no queries
self.status.text = ""
self.status_state = (None, -1e9, None)
last_player_was_ai_playing_human = katrain.last_player_info.ai and katrain.next_player_info.human
both_players_are_robots = katrain.last_player_info.ai and katrain.next_player_info.ai
self.active_comment_node = current_node
if katrain.play_analyze_mode == MODE_PLAY and last_player_was_ai_playing_human:
if katrain.next_player_info.being_taught and current_node.children and current_node.children[-1].auto_undo:
self.active_comment_node = current_node.children[-1]
elif current_node.parent:
self.active_comment_node = current_node.parent
elif both_players_are_robots and not current_node.analysis_exists and current_node.parent:
self.active_comment_node = current_node.parent
lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY
details = self.info.detailed and not lock_ai
info = ""
if katrain.contributing:
info += katrain.engine.status()
game_id = getattr(katrain.engine, "showing_game", None)
game = getattr(katrain.engine, "active_games", {}).get(game_id)
if game is not None:
info += f"Showing game {game_id}\n"
for bw in "BW":
self.players[bw].rank = None
network = game.root.get_property(f"P{bw}", AI_DEFAULT)
parts = network.split("-")
if len(parts) == 4:
self.players[bw].player_type = parts[1]
self.players[bw].player_subtype = parts[2]
else:
self.players[bw].player_type = PLAYER_AI
self.players[bw].player_subtype = network
if move or current_node.is_root:
info += self.active_comment_node.comment(
teach=katrain.players_info[self.active_comment_node.player].being_taught, details=details
)
if self.active_comment_node.analysis_exists:
self.stats.score = self.active_comment_node.format_score() or ""
self.stats.winrate = self.active_comment_node.format_winrate() or ""
self.stats.points_lost = self.active_comment_node.points_lost
self.stats.player = self.active_comment_node.player
else:
self.stats.score = ""
self.stats.winrate = ""
self.stats.points_lost = None
self.stats.player = ""
self.graph.update_value(current_node)
self.note.text = current_node.note
self.info.text = info
def update_timer(self, _dt):
game = self.katrain and self.katrain.game
current_node = game and self.katrain.game.current_node
if current_node:
last_update_node, last_update_time, beeping = self.last_timer_update
new_beeping = beeping
now = time.time()
main_time = self.katrain.config("timer/main_time", 0) * 60
byo_len = max(1, self.katrain.config("timer/byo_length"))
byo_num = max(1, self.katrain.config("timer/byo_periods"))
sounds_on = self.katrain.config("timer/sound")
player = self.katrain.next_player_info
ai = player.ai
used_period = False
min_use = self.katrain.config("timer/minimal_use", 0)
boing_at_remaining = byo_len - min_use
main_time_remaining = main_time - game.main_time_used
if not self.timer.paused and not ai and self.katrain.play_analyze_mode == MODE_PLAY:
if last_update_node == current_node and not current_node.children:
if main_time_remaining > 0:
game.main_time_used += now - last_update_time
else:
current_node.time_used += now - last_update_time
else:
current_node.time_used = 0
new_beeping = False
time_remaining = byo_len - current_node.time_used
while time_remaining < 0 and player.periods_used < byo_num:
current_node.time_used -= byo_len
time_remaining += byo_len
player.periods_used += 1
used_period = True
if (
self.beep_start - 2 * self.timer_interval < time_remaining < self.beep_start
and player.periods_used < byo_num
):
new_beeping = True
elif time_remaining > self.beep_start:
new_beeping = False
if (
min_use
and not new_beeping
and self.boing
and boing_at_remaining - self.timer_interval
< time_remaining
< boing_at_remaining + self.timer_interval
and player.periods_used < byo_num
):
self.boing.play()
else:
new_beeping = False
if player.periods_used == byo_num:
time_remaining = 0
else:
time_remaining = byo_len - current_node.time_used
periods_rem = byo_num - player.periods_used
if sounds_on:
if beeping and not new_beeping and not used_period:
self.beep.stop()
elif not beeping and new_beeping and self.beep:
self.beep.volume = 0.5 if periods_rem > 1 else 1
self.beep.play()
self.last_timer_update = (current_node, now, new_beeping)
if main_time_remaining > 0:
self.timer.state = (main_time_remaining, None, ai)
else:
self.timer.state = (max(0, time_remaining), max(0, periods_rem), ai)