# -*- coding: utf-8 -*- import gdb ANSI_RESET = "\x1b[0m" ANSI_CLEAR = "\x1b[2J\x1b[H" # 256 色前景色(按盘子大小循环) DISK_COLORS = [196, 208, 220, 46, 51, 27, 93, 201, 214, 118] def _chr_from_gdb_char_expr(expr: str) -> str: # C char -> int -> Python chr v = int(gdb.parse_and_eval(f"(int)({expr})")) return chr(v & 0xFF) class _HanoiViz: def __init__(self): self.enabled = False self.bp_main = None self.bp_move = None self.N = None self.pegs = None self.step = 0 self.last_move = None self._connected = False def enable(self): if self.enabled: return self.enabled = True try: gdb.execute("set pagination off", to_string=True) except gdb.error: pass # main:初始化/重置 self.bp_main = gdb.Breakpoint("main") self.bp_main.silent = True # do_move:每一步移动 self.bp_move = gdb.Breakpoint("do_move") self.bp_move.silent = True if not self._connected: gdb.events.stop.connect(self._on_stop) gdb.events.exited.connect(self._on_exited) self._connected = True gdb.write("hanoi-viz: ON(main 重置;do_move 更新一步;每次 stop 重画)\n") def disable(self): if not self.enabled: return self.enabled = False for bp in [self.bp_main, self.bp_move]: try: if bp is not None and bp.is_valid(): bp.delete() except Exception: pass self.bp_main = None self.bp_move = None gdb.write("hanoi-viz: OFF\n") def reset(self): try: self.N = int(gdb.parse_and_eval("N")) except gdb.error: self.N = 0 self.pegs = { "A": list(range(self.N, 0, -1)), # bottom..top "B": [], "C": [], } self.step = 0 self.last_move = None def _apply_move(self, frm: str, to: str): if self.pegs is None: self.reset() if frm not in self.pegs or to not in self.pegs: return if not self.pegs[frm]: return disk = self.pegs[frm].pop() # 合法性:不能大压小 if self.pegs[to] and self.pegs[to][-1] < disk: self.pegs[frm].append(disk) return self.pegs[to].append(disk) self.step += 1 self.last_move = f"{frm} -> {to}" def _disk_color(self, d: int) -> str: code = DISK_COLORS[(d - 1) % len(DISK_COLORS)] return f"\x1b[38;5;{code}m" def _disk_glyph(self, d: int) -> str: # 盘子宽度(奇数) w = 2 * d - 1 if w <= 1: return "█" # 两端“圆角”,中间实心 return "▛" + ("█" * (w - 2)) + "▜" def _cell(self, disk_or_none): maxw = 2 * self.N - 1 if disk_or_none is None: # 柱子 return " " * (self.N - 1) + "┃" + " " * (self.N - 1) d = disk_or_none body = self._disk_glyph(d) pad = (maxw - len(body)) // 2 return " " * pad + self._disk_color(d) + body + ANSI_RESET + " " * pad def _render(self) -> str: if not self.N or self.pegs is None: return "(尚未初始化:请 run 并停在 main,或执行 hanoi-viz reset)\n" lines = [] maxw = 2 * self.N - 1 info = f"N={self.N} step={self.step}" if self.last_move: info += f" last={self.last_move}" lines.append(info) # 从顶到低画 N 层 for level in range(self.N - 1, -1, -1): row = [] for peg in ["A", "B", "C"]: disk = self.pegs[peg][level] if level < len(self.pegs[peg]) else None row.append(self._cell(disk)) lines.append(" ".join(row)) base = "━" * maxw lines.append(" ".join([base, base, base])) def label(ch): return " " * (self.N - 1) + ch + " " * (self.N - 1) lines.append(" ".join([label("A"), label("B"), label("C")])) return "\n".join(lines) + "\n" def redraw(self): gdb.write(ANSI_CLEAR) gdb.write(self._render()) def _on_exited(self, event): if not self.enabled: return self.last_move = "程序已退出" self.redraw() def _on_stop(self, event): if not self.enabled: return # 命中断点时:更新状态 if isinstance(event, gdb.BreakpointEvent): bps = set(event.breakpoints) if self.bp_main in bps: self.reset() if self.bp_move in bps: # do_move(char from, char to) frm = _chr_from_gdb_char_expr("from") to = _chr_from_gdb_char_expr("to") self._apply_move(frm, to) # 每次 stop 都重画 self.redraw() _viz = _HanoiViz() class HanoiVizCmd(gdb.Command): """ hanoi-viz on|off|reset|redraw """ def __init__(self): super().__init__("hanoi-viz", gdb.COMMAND_USER) def invoke(self, arg, from_tty): argv = gdb.string_to_argv(arg) sub = argv[0] if argv else "on" if sub == "on": _viz.enable() elif sub == "off": _viz.disable() elif sub == "reset": _viz.reset() _viz.redraw() elif sub == "redraw": _viz.redraw() else: raise gdb.GdbError("用法: hanoi-viz on|off|reset|redraw") HanoiVizCmd()