当前位置: 首页 > news >正文

Tauri(2.5.1)+Leptos(0.8.2)开发自用桌面小程序

 在之前工作(Tauri(2.5.1)+Leptos(0.7.8)开发桌面应用--简单的工作进度管理-CSDN博客)的基础上,添加一个休闲数字游戏2048。具体效果如下:

使用leptos-router新建一个标签页,用于2048游戏界面。 

 1. src/main.rs

mod app;use app::*;
use leptos::prelude::*;//打开trunk serve --open 以开始开发您的应用程序。 Trunk 服务器将在文件更改时重新加载您的应用程序,从而使开发相对无缝。fn main() {console_error_panic_hook::set_once();   //浏览器中运行 WASM 代码发生 panic 时可以获得一个实际的 Rust 堆栈跟踪,其中包括 Rust 源代码中的一行。mount_to_body(|| {view! {<App />}})
}

2. src/app.rs

#[warn(unused_imports)]
use leptos::prelude::*;
use leptos_router::components::{Route, Router, Routes};
use leptos_router::path;
mod acidinput;
mod schedule;
mod game2048;
mod game5;use acidinput::*;
use schedule::*;
use game2048::*;
use game5::*;#[component]
pub fn App() -> impl IntoView {view! {<Router><nav><a class="nav" href="/">"工作进度表"</a><a class="nav" href="/acidinput">"产品录入"</a><a class="nav" href="/game2048">"2048数字游戏"</a><a class="nav" href="/game5">"五子棋游戏"</a></nav><main><Routes fallback=|| "Not found.">// / just has an un-nested "Home"<Route path=path!("/") view= || view! {<WorkSchedule />} /><Route path=path!("/acidinput") view=|| view! {<AcidInput />} /><Route path=path!("/game2048") view=|| view! {<GameBoard />} /><Route path=path!("/game5") view=|| view! {<GomokuGame />} /></Routes>                </main></Router>}
}

3. src/app/game2048.rs

use leptos::*;
use leptos::prelude::*;
use leptos::component;
use leptos::view;/// 定义移动方向的枚举
#[derive(Clone, Copy, PartialEq)]
pub enum Direction {Up,    // 向上移动Down,  // 向下移动Left,  // 向左移动Right, // 向右移动
}/// 游戏状态结构体
#[derive(Clone)]
pub struct Game {pub grid: [[u32; 4]; 4], // 4x4游戏网格pub score: u32,          // 当前得分pub game_over: bool,     // 游戏是否结束pub win: bool,           // 是否获胜(达到2048)
}impl Game {/// 生成[min, max)范围内的随机数fn random_range(&self, min: usize, max: usize) -> usize {use rand::random;min + (random::<f64>() * (max - min) as f64).floor() as usize}/// 以给定概率返回truefn random_bool(&self, probability: f64) -> bool {use rand::random;random::<f64>() < probability}/// 创建新游戏实例pub fn new() -> Self {let mut game = Game {grid: [[0; 4]; 4], // 初始化4x4空网格score: 0,          // 初始分数为0game_over: false,  // 游戏未结束win: false,       // 未获胜};game.add_tile(); // 添加第一个方块game.add_tile(); // 添加第二个方块game}/// 在随机空位置添加新方块(90%概率为2,10%概率为4)pub fn add_tile(&mut self) {let mut empty_positions = Vec::new();// 收集所有空位置for (i, row) in self.grid.iter().enumerate() {for (j, &cell) in row.iter().enumerate() {if cell == 0 {empty_positions.push((i, j));}}}// 如果有空位置,随机选择一个添加新方块if !empty_positions.is_empty() {let (i, j) = empty_positions[self.random_range(0, empty_positions.len())];self.grid[i][j] = if self.random_bool(0.9) { 2 } else { 4 };}}/// 根据方向移动方块pub fn move_tiles(&mut self, direction: Direction) {let mut moved = false; // 标记是否有方块移动let mut grid = self.grid;match direction {Direction::Left => {// 向左移动每行for row in &mut grid {moved |= self.slide_row(row);}}Direction::Right => {// 向右移动: 先反转行,滑动后再反转回来for row in &mut grid {row.reverse();moved |= self.slide_row(row);row.reverse();}}Direction::Up => {// 向上移动: 先转置网格,滑动每行后再转置回来self.transpose(&mut grid);for row in &mut grid {moved |= self.slide_row(row);}self.transpose(&mut grid);}Direction::Down => {// 向下移动: 转置网格,反转每行,滑动后再反转并转置回来self.transpose(&mut grid);for row in &mut grid {row.reverse();moved |= self.slide_row(row);row.reverse();}self.transpose(&mut grid);}}// 如果有方块移动,更新网格并添加新方块if moved {self.grid = grid;self.add_tile();self.check_game_over(); // 检查游戏是否结束}}/// 滑动单行方块并合并相同数字fn slide_row(&mut self, row: &mut [u32; 4]) -> bool {let mut moved = false; // 标记是否有移动let mut merged = [false; 4]; // 标记已合并的方块// 第一步: 将所有方块向左滑动(消除空格)for _ in 0..3 {for i in 0..3 {if row[i] == 0 && row[i + 1] != 0 {row[i] = row[i + 1];row[i + 1] = 0;moved = true;}}}// 第二步: 合并相邻相同数字for i in 0..3 {if row[i] != 0 && row[i] == row[i + 1] && !merged[i] {row[i] *= 2; // 合并方块self.score += row[i]; // 增加分数if row[i] == 2048 {self.win = true; // 达到2048,获胜}row[i + 1] = 0; // 清空合并后的位置merged[i] = true; // 标记已合并moved = true;}}// 第三步: 再次滑动消除合并后产生的空格for _ in 0..3 {for i in 0..3 {if row[i] == 0 && row[i + 1] != 0 {row[i] = row[i + 1];row[i + 1] = 0;moved = true;}}}moved // 返回是否有移动发生}/// 转置4x4网格(行列互换)fn transpose(&self, grid: &mut [[u32; 4]; 4]) {for i in 0..4 {for j in i + 1..4 {let temp = grid[i][j];grid[i][j] = grid[j][i];grid[j][i] = temp;}}}/// 检查游戏是否结束(无空格且无法合并)fn check_game_over(&mut self) {if self.win {return; // 已经获胜,不需要检查}// 检查是否有空格for row in &self.grid {for &cell in row {if cell == 0 {return; // 有空位,游戏继续}}}// 检查是否有可合并的相邻方块for i in 0..4 {for j in 0..4 {let cell = self.grid[i][j];if (j < 3 && cell == self.grid[i][j + 1]) ||(i < 3 && cell == self.grid[i + 1][j]) {return; // 有可合并方块,游戏继续}}}self.game_over = true; // 无空格且无法合并,游戏结束}
}/// 游戏界面组件
#[component]
pub fn GameBoard() -> impl IntoView {// 创建游戏状态信号let (game, set_game) = signal(Game::new());// 监听键盘事件window_event_listener(ev::keydown, move |ev| {if game.get().game_over || game.get().win {return; // 游戏结束或已获胜,不处理输入}// 根据按键确定移动方向let direction = match &ev.key()[..] {"ArrowUp" => Some(Direction::Up),"ArrowDown" => Some(Direction::Down),"ArrowLeft" => Some(Direction::Left),"ArrowRight" => Some(Direction::Right),_ => None,};// 如果有有效方向,移动方块if let Some(dir) = direction {set_game.update(|g| g.move_tiles(dir));}});// 渲染方块内容(空方块显示空字符串)fn render_tile(value: u32) -> String {if value == 0 {"".to_string()} else {value.to_string()}}// 根据方块值返回对应的CSS颜色类fn tile_color(value: u32) -> &'static str {match value {0 => "bg-gray-300",2 => "bg-yellow-100",4 => "bg-yellow-200",8 => "bg-orange-200",16 => "bg-orange-300",32 => "bg-red-300",64 => "bg-red-400",128 => "bg-amber-400",256 => "bg-amber-500",512 => "bg-amber-600",1024 => "bg-yellow-700 text-white",2048 => "bg-yellow-800 text-white",_ => "bg-purple-500 text-white",}}// 重置游戏函数let reset = move |_| {set_game.update(|g| {*g = Game::new();g.win = false;});};// 游戏界面视图view! {<div class="container mx-auto p-4"><div class="flex justify-between items-center mb-4"><h1 class="text-3xl font-bold">"2048小游戏"</h1><div class="flex gap-4"><div class="bg-gray-200 p-2 rounded"><div class="text-xs">"得分"</div><div class="text-xl font-bold">{move || game.get().score}</div></div><button on:click=resetclass="bg-blue-500 text-black px-4 py-2 rounded hover:bg-blue-600">"新游戏"</button></div></div><p></p><div class="bg-gray-400 p-2 rounded-lg" style="width: fit-content; margin: 0 auto;"><div style="display: grid; grid-template-columns: repeat(4, 96px); gap: 4px;">{move || {game.get().grid.iter().flat_map(|row| {row.iter().map(|&value| {view! {<divstyle="width: 96px; height: 96px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-weight: bold; font-size: 2.0rem;"class=format!("{}", tile_color(value))>{render_tile(value)}</div>}})}).collect::<Vec<_>>()}}</div></div>{/* 获胜提示 */}<Show when=move || game.get().win><div class="mt-4 p-4 bg-green-500 text-black rounded text-center">"You Win! Final Score: " {move || game.get().score}</div></Show>{/* 游戏结束提示 */}<Show when=move || game.get().game_over><div class="mt-4 p-4 bg-red-500 text-black rounded text-center">"Game Over! Final Score: " {move || game.get().score}</div></Show></div>}
}

 4. cargo.toml

[package]
name = "schedule-app"
version = "0.1.0"
description = "A Work-schedule"
authors = ["you"]
edition = "2021"[lib]
name = "acid_index_lib"
crate-type = ["staticlib", "cdylib", "rlib"][build-dependencies]
tauri-build = { version = "2.2.0", features = [] }[dependencies]
tauri = { version = "2.5.1", features = ["tray-icon", "devtools"] }
wasm-bindgen = "0.2.100"
serde-wasm-bindgen = "0.6.5"
tauri-utils="2.4.0"
tauri-plugin="2.2.0"
tauri-plugin-opener = "2"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio"] }
tokio = { version ="1", features = ["full"] }
futures = "0.3.31"
log = "0.4.22"
chrono = "0.4.39"
plotters = { version = "0.3.7"}
plotters-iced = "0.11"
iced = { version = "0.13.1", features = ["canvas", "tokio"] }
base64 = "0.22.1"
image = "0.25.5"
uuid = { version = "1.8.0", features = ["v4"] }
video-rs = { version = "0.10", features = ["ndarray"] }
ndarray = "0.16"

相关文章:

  • Python爬虫-批量爬取快手视频并将视频下载保存到本地
  • day034-rsync异地容灾
  • Java内存模型与垃圾回收:提升程序性能与稳定性!
  • 【Zephyr 系列 24】设备日志、事件与远程调试机制设计:为每一块硬件留痕,助力稳定性分析
  • Android Studio Windows安装与配置指南
  • 基于Java开发的浏览器自动化Playwright-MCP服务器
  • 开源统一数据库管理平台完全指南:私有化部署方案与技术解析
  • 理解跨域与预检请求:魔法屋与通行证的故事
  • 微软云注册被阻止怎么解决?
  • Java大模型开发入门 (7/15):让AI拥有记忆 - 使用LangChain4j实现多轮对话
  • 问题记录_如何让程序以root权限启动_如何无视系统的路径问题
  • 如何在FastAPI中构建一个既安全又灵活的多层级权限系统?
  • React--》使用vite构建器打造高效的React组件库
  • 云平台|Linux部分指令
  • 深度解析Java泛型:从原理到实战应用
  • 物联网中的 TCP 和 UDP:选择正确的协议
  • 从 PPO、DPO 到 GRPO:大语言模型策略优化算法解析
  • AI 技术动态周报:商业化与硬件的爆发时代
  • 芯片的起点——从硅到晶圆制造
  • MongoDB 事务有哪些限制和注意事项?
  • 十堰网站制作公司电话/如何在百度上发布广告
  • 单页网站上传教程/宣传推广策略
  • 做.net网站流程/深圳网站制作
  • 网站开发应该学哪门语言/手机系统优化软件
  • 站长统计向日葵app下载/关键词下载
  • 做架构图的网站/大庆网络推广