Skip to content
This repository has been archived by the owner on Jun 23, 2024. It is now read-only.

Commit

Permalink
【新功能】v0.3.0:支持撤销/重做 (#1)
Browse files Browse the repository at this point in the history
- 支持使用快捷键 Ctrl-Z 和 Ctrl-R 完成撤销与重做,提供更好的解题体验
  - 使用 `collections.deque` 提供两个栈进行实现
  - 新增 `Activity` 类作为玩家解题时每个操作步骤的封装
- 压缩数织游戏面板底层的状态矩阵数据结构,从 `numpy.dtypes.IntDType` 调整为
`numpy.dtypes.Int8DType`
- 修复部分文档与实际不符的情况

---------

Signed-off-by: 是蓝兔哟~ <[email protected]>
Co-authored-by: funny_233 <[email protected]>
  • Loading branch information
Dragon1573 and funny233-github authored Apr 18, 2024
1 parent 926604d commit bb43b10
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "tk-nonogram"
version = "0.2.1"
version = "0.3.0"
description = "Default template for PDM package"
authors = [
{ name = "是蓝兔哟~", email = "[email protected]" },
Expand Down
6 changes: 3 additions & 3 deletions src/tk_nonogram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from tkinter.simpledialog import askinteger

from numpy import array
from numpy.dtypes import IntDType
from numpy.dtypes import Int8DType
from numpy.random import randint

from .entities import Nonogram # type: ignore[import-untyped]
from .utils import Puzzle, generate_clues # type: ignore[import-untyped]

__version__ = "0.2.1"
__version__ = "0.3.0"
__all__ = ["main"]


Expand Down Expand Up @@ -38,7 +38,7 @@ def load_file() -> Puzzle:
if filename := askopenfilename(filetypes=[("Text files", "*.txt")]):
with open(filename, encoding="UTF-8") as file:
data = file.readlines()
return array([[int(col) for col in row.strip()] for row in data], dtype=IntDType)
return array([[int(col) for col in row.strip()] for row in data], dtype=Int8DType)
return generate_puzzle()


Expand Down
55 changes: 51 additions & 4 deletions src/tk_nonogram/entities.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
from _tkinter import Tcl_Obj
from collections import deque
from functools import partial
from tkinter import Button, Canvas, Event, Label, Misc, Tk, Toplevel
from tkinter.font import Font
from tkinter.messagebox import askyesno, showerror, showinfo, showwarning
from typing import Any

from numpy import ndarray, zeros
from numpy.dtypes import IntDType
from numpy.dtypes import Int8DType

from .utils import Clues, Puzzle, generate_clues


class Activity:
"""单元格操作事件"""

def __init__(self, x: int, y: int, is_context: bool) -> None:
"""
Args:
x (int): 单元格列
y (int): 单元格行
is_context (bool): 是否为上下文(鼠标右键)单击?
"""
self.x, self.y, self.is_context = x, y, is_context


class Direction:
"""Tkinter 文本对齐方向"""

Expand Down Expand Up @@ -92,7 +106,9 @@ def __init__(self, clues: Clues, answer: Puzzle, cell_size: int = 25) -> None:
self.__answer = "\n".join("".join(map(lambda _: "[X]" if _ else "[ ]", r)) for r in answer)
self.level, self.cell_size, self.clues = answer.shape[0], cell_size, clues
self.offset = self.level * self.cell_size // 2
self.grid: Puzzle = zeros((self.level, self.level), dtype=IntDType)
self.grid: Puzzle = zeros((self.level, self.level), dtype=Int8DType)
self.undo_log: deque[Activity] = deque()
self.redo_log: deque[Activity] = deque()
self.__init_window()

def toggle_cell(self, x: int, y: int, is_context: bool) -> None:
Expand Down Expand Up @@ -132,6 +148,33 @@ def update_cell_visuals(self, col: int, row: int, is_context: bool) -> None:
else:
self.canvas.create_rectangle(x1, y1, x2, y2, fill=("grey" if is_context else "blue"), tag=tag) # type: ignore

def undo(self, _: Event) -> None:
"""
撤销至上一步
Args:
_ (Event): TKinter 事件
"""
if self.undo_log:
# pop() 操作会在空双向队列是触发 IndexError
activity = self.undo_log.pop()
self.toggle_cell(activity.x, activity.y, activity.is_context)
self.update_cell_visuals(activity.x, activity.y, activity.is_context)
self.redo_log.append(activity)

def redo(self, _: Event) -> None:
"""
重做下一步
Args:
_ (Event): TKinter 事件
"""
if self.redo_log:
activity = self.redo_log.pop()
self.toggle_cell(activity.x, activity.y, activity.is_context)
self.update_cell_visuals(activity.x, activity.y, activity.is_context)
self.undo_log.append(activity)

def on_click(self, event: Event, is_context: bool) -> None:
"""
鼠标事件处理器
Expand All @@ -145,11 +188,15 @@ def on_click(self, event: Event, is_context: bool) -> None:
if 0 <= x < self.level and 0 <= y < self.level:
self.toggle_cell(x, y, is_context)
self.update_cell_visuals(x, y, is_context)
self.undo_log.append(Activity(x, y, is_context))
self.redo_log.clear()

def bind_events(self) -> None:
"""绑定鼠标事件"""
self.canvas.bind("<Button-1>", partial(self.on_click, is_context=False))
self.canvas.bind("<Button-3>", partial(self.on_click, is_context=True))
self.canvas.bind_all("<Button-1>", partial(self.on_click, is_context=False))
self.canvas.bind_all("<Button-3>", partial(self.on_click, is_context=True))
self.canvas.bind_all("<Control-z>", self.undo)
self.canvas.bind_all("<Control-r>", self.redo)

def draw_grid_with_clues(self) -> None:
"""绘制带有题目线索的数织网格"""
Expand Down
19 changes: 10 additions & 9 deletions src/tk_nonogram/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Sequence

from numpy import array, ndarray
from numpy.dtypes import IntDType
from numpy import ndarray
from numpy.dtypes import Int8DType

MatrixLike = Sequence[Sequence[int]]
Puzzle = ndarray[tuple[int, int], IntDType]
Puzzle = ndarray[tuple[int, int], Int8DType]
Row = ndarray[tuple[int], Int8DType]


class Clues:
Expand All @@ -18,23 +19,21 @@ def __eq__(self, value: object) -> bool:
return (self.rows == value.rows and self.columns == value.columns) if isinstance(value, Clues) else False


def count_continues(_array: ndarray[tuple[int], IntDType]) -> tuple[int]:
def count_continues(_row: Row) -> tuple[int]:
"""
统计 NumPy 矩阵单行/列中连续 1 的数量
Args:
_array (matrix): NumPy 二维矩阵
_array (Row): NumPy 二维矩阵
Returns:
tuple[int]: 数量列表,用作解题线索
"""
from numpy import append, diff, insert, where

# 参考文献: [计算连续出现次数](https://geek-docs.com/numpy/numpy-ask-answer/231_numpy_count_consecutive_occurences_of_values_varying_in_length_in_a_numpy_array.html#ftoc-heading-2) ## noqa: B950
# 将矩阵展平成一维
_array = _array.flatten()
# 找到数组中所有的1
ones = where(_array == 1)[0]
ones = where(_row == 1)[0]
# 计算连续1的起始索引和结束索引
diff = diff(ones) # type: ignore[assignment]
starts = insert(where(diff != 1)[0] + 1, 0, 0)
Expand All @@ -50,11 +49,13 @@ def generate_clues(_matrix: Puzzle | MatrixLike) -> Clues:
生成线索(谜面)
Args:
_matrix (matrix[tuple[int, int], IntDType] | MatrixLike): 游戏板状态二维矩阵
_matrix (Puzzle | MatrixLike): 游戏板状态二维矩阵
Returns:
dict[str, list[int]]: 线索字典
"""
from numpy import array

if not isinstance(_matrix, ndarray):
# 当传入参数不是 NumPy 矩阵时,转换为矩阵
_matrix = array(_matrix)
Expand Down

0 comments on commit bb43b10

Please sign in to comment.