1import random 2from io import StringIO 3 4from gi.repository import Gtk 5 6from pychess.compat import create_task 7from pychess.System.prefix import addDataPrefix 8from pychess.Utils.const import WHITE, BLACK, LOCAL, NORMALCHESS, ARTIFICIAL, chr2Sign, chrU2Sign, FAN_PIECES, HINT, ENDGAME 9from pychess.Utils.LearnModel import LearnModel 10from pychess.Utils.TimeModel import TimeModel 11from pychess.Utils.lutils.attack import isAttacked 12from pychess.Utils.lutils.LBoard import LBoard 13from pychess.Utils.lutils.lmove import FILE, RANK 14from pychess.Variants import variants 15from pychess.Players.Human import Human 16from pychess.Players.engineNest import discoverer 17from pychess.System import conf 18from pychess.perspectives import perspective_manager 19from pychess.Savers import fen as fen_loader 20 21__title__ = _("Endgames") 22 23__icon__ = addDataPrefix("glade/panel_book.svg") 24 25__desc__ = _("Practice endgames with computer") 26 27 28# TODO: get it from a text file 29ENDGAMES = ( 30 ("kpk", "King and Pawn vs King"), 31 ("kbnk", "King, Bishop and Knight vs King"), 32 ("kbbk", "King and 2 Bishops vs King"), 33 ("krk", "King and Rook vs King"), 34 ("kqk", "King and Queen vs King"), 35 ("kqkr", "King and Queen vs King and Rook"), 36 ("krpkr", "King, Rook and Pawn vs King and Rook"), 37 ("kppkp", "King and 2 Pawns vs King and Pawn"), 38 ("kpkp", "King and Pawn vs King and Pawn"), 39 ("kqpkq", "King, Queen and Pawn vs King and Queen"), 40 ("knnkp", "King and Two Knights and vs King and Pawn"), 41 ("kppkpp", "King and two pawns vs King and two pawns"), 42 ("kqqkqr", "King and two queens vs King and Queen"), 43) 44 45 46class Sidepanel(): 47 def load(self, persp): 48 self.persp = persp 49 self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 50 51 self.tv = Gtk.TreeView() 52 53 renderer = Gtk.CellRendererText() 54 renderer.props.font = "Times 14" 55 column = Gtk.TreeViewColumn(_("White"), renderer, text=0) 56 self.tv.append_column(column) 57 58 renderer = Gtk.CellRendererText() 59 renderer.props.font = "Times 14" 60 column = Gtk.TreeViewColumn(_("Black"), renderer, text=1) 61 self.tv.append_column(column) 62 63 renderer = Gtk.CellRendererText() 64 column = Gtk.TreeViewColumn(_("Title"), renderer, text=2) 65 self.tv.append_column(column) 66 67 self.tv.connect("row-activated", self.row_activated) 68 69 self.store = Gtk.ListStore(str, str, str) 70 71 for pieces, title in ENDGAMES: 72 if pieces.count("k") != 2: 73 print("Game needs exactly 2 kings! %s" % pieces) 74 continue 75 elif len(pieces) > 6: 76 print("Max 6 pieces, please! %s" % pieces) 77 continue 78 else: 79 for piece in pieces: 80 if piece not in ("kqrbnp"): 81 print("Invalid piece %s in %s" % (piece, pieces)) 82 continue 83 84 pos = pieces.rfind("k") 85 white_pieces, black_pieces = pieces[:pos], pieces[pos:] 86 wfan = [] 87 for piece in white_pieces: 88 wfan.append(FAN_PIECES[0][chr2Sign[piece]]) 89 bfan = [] 90 for piece in black_pieces: 91 bfan.append(FAN_PIECES[1][chr2Sign[piece]]) 92 self.store.append(["".join(wfan), "".join(bfan), title]) 93 94 self.tv.set_model(self.store) 95 self.tv.get_selection().set_mode(Gtk.SelectionMode.BROWSE) 96 self.tv.set_cursor(conf.get("learncombo%s" % ENDGAME)) 97 98 scrollwin = Gtk.ScrolledWindow() 99 scrollwin.add(self.tv) 100 scrollwin.show_all() 101 102 self.box.pack_start(scrollwin, True, True, 0) 103 self.box.show_all() 104 105 return self.box 106 107 def row_activated(self, widget, path, col): 108 if path is None: 109 return 110 else: 111 pieces = ENDGAMES[path[0]][0].lower() 112 conf.set("categorycombo", ENDGAME) 113 from pychess.widgets.TaskerManager import learn_tasker 114 learn_tasker.learn_combo.set_active(path[0]) 115 start_endgame_from(pieces) 116 117 118def start_endgame_from(pieces): 119 fen = create_fen(pieces) 120 121 timemodel = TimeModel(0, 0) 122 gamemodel = LearnModel(timemodel) 123 gamemodel.set_learn_data(ENDGAME, pieces) 124 125 player_name = conf.get("firstName") 126 p0 = (LOCAL, Human, (WHITE, player_name), player_name) 127 128 engine = discoverer.getEngineByName(discoverer.getEngineLearn()) 129 ponder_off = True 130 engine_name = discoverer.getName(engine) 131 p1 = (ARTIFICIAL, discoverer.initPlayerEngine, 132 (engine, BLACK, 20, variants[NORMALCHESS], 60, 0, 0, ponder_off), engine_name) 133 134 def restart_analyzer(gamemodel): 135 create_task(gamemodel.restart_analyzer(HINT)) 136 gamemodel.connect("learn_success", restart_analyzer) 137 138 def on_game_started(gamemodel): 139 perspective.activate_panel("annotationPanel") 140 create_task(gamemodel.start_analyzer(HINT, force_engine=discoverer.getEngineLearn())) 141 gamemodel.connect("game_started", on_game_started) 142 143 perspective = perspective_manager.get_perspective("games") 144 create_task(perspective.generalStart(gamemodel, p0, p1, loaddata=(StringIO(fen), fen_loader, 0, -1))) 145 146 147def create_fen(pieces): 148 """ Create a random FEN position using given pieces """ 149 150 pos = pieces.rfind("k") 151 pieces = pieces[:pos], pieces[pos:] 152 153 ok = False 154 while not ok: 155 lboard = LBoard() 156 lboard.applyFen("8/8/8/8/8/8/8/8 w - - 0 1") 157 bishop_cords = [[], []] 158 bishop_colors_ok = True 159 160 cords = list(range(0, 64)) 161 pawn_cords = list(range(0 + 8, 64 - 8)) 162 163 # Order of color is important here to prevent offering 164 # positions with trivial captures in first move 165 for color in (WHITE, BLACK): 166 for char in pieces[color]: 167 piece = chrU2Sign[char.upper()] 168 attacked = True 169 limit = 100 170 while attacked and limit > 0: 171 cord = random.choice(pawn_cords if char == "p" else cords) 172 attacked = isAttacked(lboard, cord, 1 - color) 173 limit -= 1 174 lboard._addPiece(cord, piece, color) 175 cords.remove(cord) 176 if cord in pawn_cords: 177 pawn_cords.remove(cord) 178 if char == "b": 179 bishop_cords[color].append(cord) 180 181 # 2 same color bishop is not ok 182 if len(bishop_cords[color]) == 2 and bishop_colors_ok: 183 b0, b1 = bishop_cords[color] 184 b0_color = BLACK if RANK(b0) % 2 == FILE(b0) % 2 else WHITE 185 b1_color = BLACK if RANK(b1) % 2 == FILE(b1) % 2 else WHITE 186 if b0_color == b1_color: 187 bishop_colors_ok = False 188 break 189 190 ok = (not lboard.isChecked()) and (not lboard.opIsChecked()) and bishop_colors_ok 191 192 fen = lboard.asFen() 193 return fen 194