欢迎访问我的个人网站,网站正在创建期间,主要供自己学习交流使用!

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 />
       }
   })
}

AI写代码
rust
运行

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>
   }
}

AI写代码
rust
运行

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
   }

   /// 以给定概率返回true
   fn 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,          // 初始分数为0
           game_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=reset
                       class="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! {
                                   <div
                                       style="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>
   }
}
AI写代码
rust
运行

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]

————————————————


湘公网安备43012102001039    湘ICP备14007447号-1    邮箱:35744243#qq.com