游戏逻辑
这一步用你选择的编程语言实现游戏规则。
Slint 的总体理念是:在 Slint 中实现用户界面,在你喜欢的编程 语言中实现业务逻辑。
游戏规则规定最多只能有两张牌的帘幕处于打开状态。如果牌匹配,则游戏 认为它们已解开并保持打开。否则,游戏会短暂等待,让玩家记住 图标的位置,然后再次合上帘幕。
在 MainWindow 组件内添加以下代码,以便在用户点击牌时向 Rust 代码发送信号。
export component MainWindow inherits Window { width: 326px; height: 326px;
callback check_if_pair_solved(); // Added in property <bool> disable_tiles; // Added
in-out property <[TileData]> memory_tiles: [ { image: @image-url("icons/at.png") },这一改动为 MainWindow 提供了一种方式,通知 Rust 代码它应该 检查玩家是否解开了一对牌。Rust 代码需要一个额外的属性用于切换,以禁用进一步的 牌交互,防止玩家打开超过允许数量的牌。不允许作弊!
代码的最后一项改动是在 MemoryTile 发出玩家点击了它的信号时采取行动。
在 MainWindow 的 for 循环 clicked 处理器中添加以下处理器:
for tile[i] in memory_tiles : MemoryTile { x: mod(i, 4) * 74px; y: floor(i / 4) * 74px; width: 64px; height: 64px; icon: tile.image; open_curtain: tile.image_visible || tile.solved; // propagate the solved status from the model to the tile solved: tile.solved; clicked => { // old: tile.image_visible = !tile.image_visible; // new: if (!root.disable_tiles) { tile.image_visible = true; root.check_if_pair_solved(); } } }在 Rust 一侧,你现在可以为 check_if_pair_solved 回调添加一个处理器,检查玩家是否打开了两张牌。
如果它们匹配,代码将模型中的 solved 属性设置为 true。如果它们不匹配,
则启动一个计时器,在一秒后合上牌。计时器运行期间,禁用每一张牌,这样
玩家在这段时间内无法点击任何东西。
在 main_window.run().unwrap(); 调用之前添加这段代码:
let main_window_weak = main_window.as_weak(); main_window.on_check_if_pair_solved(move || { let mut flipped_tiles = tiles_model.iter().enumerate().filter(|(_, tile)| tile.image_visible && !tile.solved);
if let (Some((t1_idx, mut t1)), Some((t2_idx, mut t2))) = (flipped_tiles.next(), flipped_tiles.next()) { let is_pair_solved = t1 == t2; if is_pair_solved { t1.solved = true; tiles_model.set_row_data(t1_idx, t1); t2.solved = true; tiles_model.set_row_data(t2_idx, t2); } else { let main_window = main_window_weak.unwrap(); main_window.set_disable_tiles(true); let tiles_model = tiles_model.clone(); slint::Timer::single_shot(std::time::Duration::from_secs(1), move || { main_window.set_disable_tiles(false); t1.image_visible = false; tiles_model.set_row_data(t1_idx, t1); t2.image_visible = false; tiles_model.set_row_data(t2_idx, t2); }); } } });代码使用了 main_window 的 Weak 指针。这很
重要,因为在回调处理器内部捕获 main_window 本身的副本会导致循环所有权。
MainWindow 拥有回调处理器,而处理器本身又拥有对 MainWindow 的引用,这个引用必须是弱引用
而不是强引用,以避免内存泄漏。
在 MainWindow 组件内添加以下代码,以便在用户点击牌时向 C++ 代码发送信号。
export component MainWindow inherits Window { width: 326px; height: 326px;
callback check_if_pair_solved(); // Added in property <bool> disable_tiles; // Added
in-out property <[TileData]> memory_tiles: [ { image: @image-url("icons/at.png") },这一改动为 MainWindow 提供了一种方式,通知 C++ 代码它应该 检查玩家是否解开了一对牌。C++ 代码需要一个额外的属性用于切换,以禁用进一步的 牌交互,防止玩家打开超过允许数量的牌。不允许作弊!
代码的最后一项改动是在 MemoryTile 发出玩家点击了它的信号时采取行动。
在 MainWindow 的 for 循环 clicked 处理器中添加以下处理器:
for tile[i] in memory_tiles : MemoryTile { x: mod(i, 4) * 74px; y: floor(i / 4) * 74px; width: 64px; height: 64px; icon: tile.image; open_curtain: tile.image_visible || tile.solved; // propagate the solved status from the model to the tile solved: tile.solved; clicked => { // old: tile.image_visible = !tile.image_visible; // new: if (!root.disable_tiles) { tile.image_visible = true; root.check_if_pair_solved(); } } }在 C++ 一侧,你现在可以为 check_if_pair_solved 回调添加一个处理器,检查玩家是否打开了两张牌。
如果它们匹配,代码将模型中的 solved 属性设置为 true。如果它们不匹配,
则启动一个计时器,在一秒后合上牌。计时器运行期间,禁用每一张牌,这样
玩家在这段时间内无法点击任何东西。
在 main_window->run() 调用之前插入这段代码:
main_window->on_check_if_pair_solved( [main_window_weak = slint::ComponentWeakHandle(main_window)] { auto main_window = *main_window_weak.lock(); auto tiles_model = main_window->get_memory_tiles(); int first_visible_index = -1; TileData first_visible_tile; for (int i = 0; i < tiles_model->row_count(); ++i) { auto tile = *tiles_model->row_data(i); if (!tile.image_visible || tile.solved) continue; if (first_visible_index == -1) { first_visible_index = i; first_visible_tile = tile; continue; } bool is_pair_solved = tile == first_visible_tile; if (is_pair_solved) { first_visible_tile.solved = true; tiles_model->set_row_data(first_visible_index, first_visible_tile); tile.solved = true; tiles_model->set_row_data(i, tile); return; } main_window->set_disable_tiles(true);
slint::Timer::single_shot(std::chrono::seconds(1), [=]() mutable { main_window->set_disable_tiles(false); first_visible_tile.image_visible = false; tiles_model->set_row_data(first_visible_index, first_visible_tile); tile.image_visible = false; tiles_model->set_row_data(i, tile); }); } });代码使用了 main_window 的 ComponentWeakHandle 指针。这很
重要,因为在回调处理器内部捕获 main_window 本身的副本会导致循环所有权。
MainWindow 拥有回调处理器,而处理器本身又拥有对 MainWindow 的引用,这个引用必须是弱引用
而不是强引用,以避免内存泄漏。
在 MainWindow 组件内添加以下代码,以便在用户点击牌时向 JavaScript 代码发送信号。
export component MainWindow inherits Window { width: 326px; height: 326px;
callback check_if_pair_solved(); // Added in property <bool> disable_tiles; // Added
in-out property <[TileData]> memory_tiles: [ { image: @image-url("icons/at.png") },这一改动为 MainWindow 提供了一种方式,通知 JavaScript 代码它应该 检查玩家是否解开了一对牌。JavaScript 代码需要一个额外的属性用于切换,以禁用进一步的 牌交互,防止玩家打开超过允许数量的牌。不允许作弊!
代码的最后一项改动是在 MemoryTile 发出玩家点击了它的信号时采取行动。
在 MainWindow 的 for 循环 clicked 处理器中添加以下处理器:
for tile[i] in memory_tiles : MemoryTile { x: mod(i, 4) * 74px; y: floor(i / 4) * 74px; width: 64px; height: 64px; icon: tile.image; open_curtain: tile.image_visible || tile.solved; // propagate the solved status from the model to the tile solved: tile.solved; clicked => { // old: tile.image_visible = !tile.image_visible; // new: if (!root.disable_tiles) { tile.image_visible = true; root.check_if_pair_solved(); } } }在 JavaScript 一侧,现在为 check_if_pair_solved 回调添加一个处理器,检查玩家是否打开了两张牌。如果它们匹配,代码将模型中的 solved 属性设置为 true。如果它们不匹配,
则启动一个计时器,在一秒后合上牌。计时器运行期间,禁用每一张牌,这样
玩家在这段时间内无法点击任何东西。
在 mainWindow.run() 调用之前插入这段代码:
mainWindow.check_if_pair_solved = function () { const flipped_tiles = []; tiles.forEach((tile, index) => { if (tile.image_visible && !tile.solved) { flipped_tiles.push({ index, tile, }); } });
if (flipped_tiles.length === 2) { const { tile: tile1, index: tile1_index } = flipped_tiles[0];
const { tile: tile2, index: tile2_index } = flipped_tiles[1];
const is_pair_solved = tile1.image.path === tile2.image.path; if (is_pair_solved) { tile1.solved = true; model.setRowData(tile1_index, tile1); tile2.solved = true; model.setRowData(tile2_index, tile2); } else { mainWindow.disable_tiles = true; setTimeout(() => { mainWindow.disable_tiles = false; tile1.image_visible = false; model.setRowData(tile1_index, tile1); tile2.image_visible = false; model.setRowData(tile2_index, tile2); }, 1000); } }};在 MainWindow 组件内添加以下代码,以便在用户点击牌时向 Python 代码发送信号。
export component MainWindow inherits Window { width: 326px; height: 326px;
callback check_if_pair_solved(); // Added in property <bool> disable_tiles; // Added
in-out property <[TileData]> memory_tiles: [ { image: @image-url("icons/at.png") },这一改动为 MainWindow 提供了一种方式,通知 Python 代码它应该 检查玩家是否解开了一对牌。Python 代码需要一个额外的属性用于切换,以禁用进一步的 牌交互,防止玩家打开超过允许数量的牌。不允许作弊!
代码的最后一项改动是在 MemoryTile 发出玩家点击了它的信号时采取行动。
在 MainWindow 的 for 循环 clicked 处理器中添加以下处理器:
for tile[i] in memory_tiles : MemoryTile { x: mod(i, 4) * 74px; y: floor(i / 4) * 74px; width: 64px; height: 64px; icon: tile.image; open_curtain: tile.image_visible || tile.solved; // propagate the solved status from the model to the tile solved: tile.solved; clicked => { // old: tile.image_visible = !tile.image_visible; // new: if (!root.disable_tiles) { tile.image_visible = true; root.check_if_pair_solved(); } } }在 Python 一侧,现在为 check_if_pair_solved 回调添加一个处理器,检查玩家是否打开了两张牌。如果它们匹配,代码将模型中的 solved 属性设置为 true。如果它们不匹配,
则启动一个计时器,在一秒后合上牌。计时器运行期间,禁用每一张牌,这样
玩家在这段时间内无法点击任何东西。
在 MainWindow 类中插入这个函数,并用 @slint.callback 标注,以将其关联到 check_if_pair_solved:
class MainWindow(slint.loader.ui.app_window.MainWindow): def __init__(self): super().__init__() initial_tiles = self.memory_tiles tiles = slint.ListModel( itertools.chain( map(copy.copy, initial_tiles), map(copy.copy, initial_tiles) ) ) random.shuffle(tiles) self.memory_tiles = tiles
@slint.callback def check_if_pair_solved(self): flipped_tiles = [ (index, copy.copy(tile)) for index, tile in enumerate(self.memory_tiles) if tile.image_visible and not tile.solved ] if len(flipped_tiles) == 2: tile1_index, tile1 = flipped_tiles[0] tile2_index, tile2 = flipped_tiles[1] is_pair_solved = tile1.image.path == tile2.image.path if is_pair_solved: tile1.solved = True self.memory_tiles[tile1_index] = tile1 tile2.solved = True self.memory_tiles[tile2_index] = tile2 else: self.disable_tiles = True
def reenable_tiles(): self.disable_tiles = False tile1.image_visible = False self.memory_tiles[tile1_index] = tile1 tile2.image_visible = False self.memory_tiles[tile2_index] = tile2
slint.Timer.single_shot(datetime.timedelta(seconds=1), reenable_tiles)这些就是最后的改动,运行代码会打开一个窗口,让玩家可以按规则进行游戏。
© 2026 SixtyFPS GmbH