【AI 加持下的 Python 编程实战 2_09】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(上)

DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(上)


1 起因

最近在看去年刚出了第 2 版《Learn AI-assisted Python Programming》,梳理完 第七章 的知识点后,总感觉这一章的话题很好——问题分解能力的培养——美中不足的是演示案例过于简单,有点意犹未尽。今天就用一个前端扫雷小游戏的全流程开发再来落地实操一回。

最终效果如下:

图 1:前端扫雷游戏最终效果图

【图 1:前端扫雷游戏最终效果图】

2 思路分析

为了突出训练重点,这里将扫雷游戏略作简化,只保留核心功能:难度选择和扫雷操作,其他像计时、声音、排行榜等功能暂不考虑。因此整理后的需求描述如下:

  1. 支持 “初级”、“中级”、“高级” 三种难度;
  2. 记录每局总地雷数以及实时排雷数;
  3. 根据选择的难度生成指定尺寸、固定地雷数的网格区域,通过左键探雷、右键标记地雷(不考虑 “待定” 标记);
  4. 探雷时:
    1. 如果踩到地雷则游戏结束;
    2. 为安全区域,则:
      1. 周围有雷:标记出周围 8 个单元格存在的地雷总数;
      2. 周围无雷:从当前单元格向四周扩散,直到发现标有地雷数的边界区域。
  5. 排雷时,点击鼠标右键即可标记为雷区,之后左键无法单击继续;
  6. 获胜条件:正确探明所有安全区域才算获胜(仅标注地雷不算胜出)。

那么按照上述需求,应该如何分类呢?每一局无论区域有多大,地雷有多少,都可以将整个界面剥离成两类问题:

  1. 页面的渲染问题;
  2. 页面元素的事件绑定问题;

接下来就围绕这两类问题进行讨论,看看问题分解如何发挥作用。

3 问题分解实战

根据刚才的思路分析,页面渲染和事件绑定存在明显的先后顺序和依赖关系:渲染出页面后才能绑定事件,并且绑定事件还需要知道每个单元格的状态(即地雷数据)。于是可以设计出如下两个子函数:

let currentLv = 0;function start(lv = currentLv) {// init game boardconst mineCells = init(lv);// bind eventsbindEvents(lv, mineCells);
}start(currentLv);

在对这两个任务做进一步分解前,需要先准备好游戏需要的静态页面。

3.1 静态页面与基础样式

这一步包括 HTML 基本结构和 CSS 基础样式。选型时为了更灵活地设置样式,选用了 iconfont + 样式类来实现所有的单元格图案和标记:

图 2:游戏界面用到的主要图标字体(来源:阿里图标库)

【图 2:游戏界面用到的主要图标字体(来源:阿里图标库)】

使用方法:

<!-- 引入样式 -->
<link rel="stylesheet" href="./iconfont.css">
<!-- 设置图标 -->
<span class="mine ms-xxx"></span>

具体图标从阿里图标库选取即可,这里不多赘述。对照最开始给出的页面效果,再上网找出经典扫雷游戏需要的基本效果,于是有了如下两个文件:

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Mine Sweeper Game</title><link prefetch rel="stylesheet" href="assets/font/iconfont.css"><link async rel="stylesheet" href="assets/css/index.css"><link async rel="shortcut icon" href="assets/favicon.png" type="image/x-icon">
</head>
<body><div class="container"><h1>扫雷游戏</h1><section class="level"><span>难度:</span><button data-level="1" class="active">初级</button><button data-level="2">中级</button><button data-level="3">高级</button><button class="restart hidden">重新开始</button></section><section class="stats"><div class="score">地雷数:<span id="mineCount"></span></div><div class="time">已排除:<span id="mineFound"></span></div><section class="game"><table class="gameBoard"></table></section></div><script src="assets/js/index.js" type="module"></script>
</body>
</html>

以及对应的样式文件 index.css

* {margin: 0;padding: 0;box-sizing: border-box;font-family: Verdana, sans-serif;--cell-size: 25px;
}.container {margin-inline: auto;text-align: center;
}h1 {font-size: 1.7rem;margin-block: 1rem;
}.level {padding: .5em 0;&>button {padding: .3em .7em;border: none;border-radius: 5px;background-color: #cdd4d8;&.active {background-color: #0856df;color: #fff;font-weight: bold;}&.hidden {display: none;}}
}.stats {padding: .5em;
}#mineCount, #mineFound {font-weight: 700;color: maroon;
}.gameBoard {border: 6px solid #d4d4d4;outline: 1px solid #808080;margin: 1em auto;background-color: #c0c0c0;
}.cell {border-top: 3px solid #ffffff;border-left: 3px solid #ffffff;border-right: 3px solid #808080;border-bottom: 3px solid #808080;width: var(--cell-size);height: var(--cell-size);line-height: var(--cell-size);font-size: 1rem;cursor: pointer;&.invalid {background-color: violet;}&.mine {background: #c0c0c0;border: 1px solid #e4e4e4;opacity: 1;&.ms-mine {font-size: 1.2em;line-height: var(--cell-size);vertical-align: middle;color: #000;}&.ms-flag {font-size: 1em;line-height: var(--cell-size);vertical-align: middle;color: #f00;}&.ms-flag.correct {background: #6ad654;}}&.fail {background: #bb3d3d;border: 1px solid #808080;opacity: 1;}&.number {border-collapse: collapse;background: #c0c0c0;border: 1px solid #dddddd;font-weight: bold;}&.mc-0 {color: #c0c0c0;}&.mc-1 {color: #0000FF;}&.mc-2 {color: #008000;}&.mc-3 {color: #FF0000;}&.mc-4 {color: #000080;}&.mc-5 {color: #800000;}&.mc-6 {color: #008080;}&.mc-7 {color: #000000;}&.mc-8 {color: #808080;}
}

3.2 页面渲染逻辑

设计好了静态页面,接下来就可以安心拆解 JavaScript 核心逻辑了。

首先是 init(lv) 方法。该方法应该有三个基本任务:

  1. 在页面中心位置加载出扫雷区域;
  2. 页面统计指标(总地雷数、已探明雷数)均设为初始值;
  3. 状态矩阵。

前两个不用多想,第三个任务要费点脑筋。这里绝不能将单元格的状态信息放入页面元素(例如 data-* 属性),否则打开 F12 调试工具就能轻松破解游戏。

因此必须单独维护一个与页面每个单元格绑定的 状态矩阵(即对象数组)。

这样一来,init(lv) 就可以拆分为以下三个板块:

let mineFound = 0;
function init(lv) {// 1. create table elementsconst doms = renderGameBoard(lv);// 2. render stats info$('#mineCount').innerHTML = lv.mine;$('#mineFound').innerHTML = mineFound;// 3. create mine arrayconst mines = generateMineCells(lv, doms);return mines;
}

由于中间那项过于简单,就不必另外创建函数实现了。这里需要再次拆分的是负责加载 DOM 节点的渲染函数 renderGameBoard(lv) 以及对应的状态矩阵生成函数 generateMineCells(lv, doms)

要渲染单元格,既可以使用 div 也可以使用 table 表格,这里我倾向用 table,因为扫雷的整体风格感觉就像一个 Excel,而且单元格什么的也叫习惯了(哈哈)。到时候根据关卡对象 lv 拿到总的行列数与地雷数,就能批量生成扫雷区域了。

比较烧脑的是状态矩阵的数据结构:每个单元格要存放哪些信息呢?这一部分只能慢慢完善:

  1. id:即 ID 编号。该编号值设置不仅要保持统一,更重要的是要明确制定一套转换公式,可以很方便地知道该 ID 值和页面单元格坐标((iRow, jCol))的对应关系(届时这些转换公式都放入单独的 utils.js 模块,以免干扰核心逻辑)。
  2. isMine:即该单元格是否为地雷的定性标记;
  3. mineCount:如果当前单元格不是地雷,则用它表示周围的地雷数,这样进行鼠标操作时就能直接显示,无需临时计算;
  4. flagged:表示当前单元格是否被标为地雷,若已经标记,则该单元格不可再次点击,除非取消标记;

先暂定上述四个基本状态,后面需要增补再说。有了这样的数据结构,整个扫雷游戏的数据流就清楚了:用户通过各种操作触发状态矩阵的改变,状态矩阵再将变化情况更新到扫雷区域:

点击/标记等事件
更新数据
渲染界面
用户页面操作
扫雷状态矩阵
页面扫雷区域

【图 3:扫雷基本数据流向示意图】

这样,init() 的拆分就暂告一个段落了,如下图所示:

flowchart LRA[start] --> B[init]A --> C[bindEvents]B --> D[renderGameBoard]B --> E[renderStatsInfo(无需创建)]B --> F[generateMineCells]

【图 4 初步确定的 init() 函数拆分方案】

3.2 事件绑定逻辑

(未完待续)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76862.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用DeepSeek-Prover-V1.5解决数学问题

DeepSeek-Prover-V1.5-RLRMaxTS是一个结合强化学习和搜索策略的自动定理证明系统。 1. 初等代数&#xff1a;二次方程求解 问题&#xff1a;解方程 x - 5x 6 0 操作步骤&#xff1a; 将问题转换为Coq形式&#xff1a; Theorem quadratic : exists x : Z, x^2 - 5*x 6 0…

3.3 技术框架:LangChain、ReAct、Memory与Tool Integration

随着人工智能技术的飞速发展&#xff0c;智能代理&#xff08;Agent&#xff09;已成为企业实现自动化、智能化和个性化服务的核心工具。在2025年&#xff0c;技术框架如LangChain、ReAct、Memory和Tool Integration在构建高效、灵活的AI代理系统中占据了重要地位。这些框架通过…

STM32F103 单片机(基于 ARM Cortex-M3 内核)的启动过程涉及硬件初始化、固件配置和程序执行流程。

1. 启动模式与地址映射 STM32F103 的启动模式由 BOOT0 和 BOOT1 引脚配置决定&#xff0c;不同的启动模式对应不同的存储器映射&#xff1a; 启动模式 映射地址范围 说明 主 Flash 0x08000000~0x0807FFFF 用户程序存储在 Flash 中&#xff0c;复位后从 Flash 启动&#xff08…

【C语言-选择排序算法】实现对十个数进行排序

目录 前言 一、选择排序算法原理 二、选择排序算法实现对十个数进行排序 三、代码运行示例 四、选择排序算法的时间复杂度和空间复杂度分析 五、选择排序算法的优缺点 六、总结 前言 在计算机科学领域&#xff0c;排序算法是基石般的存在&#xff0c;它们就像是整理杂乱…

配置Intel Realsense D405驱动与ROS包

配置sdk使用 Ubuntu20.04LTS下安装Intel Realsense D435i驱动与ROS包_realsense的驱动包-CSDN博客 中的方法一 之后不通过apt安装包&#xff0c;使用官方的安装步骤直接clone https://github.com/IntelRealSense/realsense-ros/tree/ros1-legacy 从这一步开始 执行完 这一步…

基于SpringBoot的中华诗词文化分享平台-项目分享

基于SpringBoot的中华诗词文化分享平台-项目分享 项目介绍项目摘要管理员功能图会员功能图系统功能图项目预览会员主页面诗词页面发布问题回复评论 最后 项目介绍 使用者&#xff1a;管理员、会员 开发技术&#xff1a;MySQLJavaSpringBootVue 项目摘要 本文旨在设计与实现一…

ProxySQL 性能调优工具推荐

ProxySQL 的性能优化需结合‌实时监控工具‌与‌自动化分析平台‌,以下为常用工具分类与推荐: 一、‌内置诊断工具‌ ProxySQL Admin 接口‌ 通过内置管理表直接分析性能数据: sql Copy Code SELECT * FROM stats_mysql_query_digest; – 高频查询分析(执行次数、平均耗…

unity TEngine学习记录3

上一篇讲了怎么使用te框架&#xff0c;本篇主要学习的是UI&#xff0c;一个游戏百分之70%都是UI的展示效果&#xff0c;现在让我们继续打开te官网找到UI部分继续学习。 ui创建以及加载 我们根据文档首先打开命名规则界面,大家第一次看就知道这个是干啥的&#xff0c;你想使用此…

23种设计模式-创建型模式之单例模式(Java版本)

Java 单例模式&#xff08;Singleton Pattern&#xff09;详解 &#x1f31f; 什么是单例模式&#xff1f; 单例模式确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问它。 &#x1f9e0; 使用场景 配置管理类&#xff08;如读取配置文件&#xff09;日志工具类…

2025能源网络安全大赛CTF --- Crypto wp

文章目录 前言simpleSigninNumberTheory 前言 大半年以来写的第一篇文章&#xff01;&#xff01;&#xff01; simpleSignin 题目&#xff1a; from Crypto.Util.number import * from gmpy2 import * import osflag bxxx p next_prime(bytes_to_long(os.urandom(128))…

加密与解密完全指南,使用Java实现

文章目录 1. 加密基础知识1.1 什么是加密&#xff1f;1.2 加密的历史简介1.2.1 古典加密1.2.2 现代加密的起源 1.3 加密的基本概念1.3.1 密码学中的关键术语1.3.2 加密的基本原则 1.4 加密的分类1.4.1 对称加密&#xff08;Symmetric Encryption&#xff09;1.4.2 非对称加密&a…

十一、数据库day03--SQL语句02

文章目录 一、查询语句1. 基本查询2. 条件查询2.1 ⽐较运算符&逻辑运算符2.2 模糊查询2.3 范围查询2.4 判断空 3. 其他复杂查询3.1 排序3.2 聚合函数3.3 分组3.4 分页查询 二、回顾1. 使⽤ Navicat ⼯具中的命令列2.命令⾏基本操作步骤 提示&#xff1a;以下是本篇文章正文…

Flowable 与 bpmn.io@7.0 完整集成示例 Demo

Flowable 与 bpmn.io7.0 完整集成示例 Demo 下面是一个完整的前后端集成示例&#xff0c;包含前端使用 bpmn.js 7.0 和与 Flowable 后端交互的实现。 1. 后端实现 (Spring Boot Flowable) 1.1 添加依赖 (pom.xml) <dependencies><!-- Spring Boot --><depe…

ROS2 安装详细教程,Ubuntu 22.04.5 LTS 64 位 操作系统

一、完整安装流程&#xff08;推荐&#xff09; 1. 安装依赖工具 sudo apt update && sudo apt install -y software-properties-common curl gnupg2 2. 添加 ROS 2 GPG 密钥 sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /…

STM32 基本GPIO控制

目录 GPIO基础知识 ​编辑IO八种工作模式 固件库实现LED点灯 蜂鸣器 按键基础知识 ​编辑继电器 震动传感器 433M无线模块 GPIO基础知识 GPIO(General-Purpose input/output,通用输入/输出接口) 用于感知外部信号&#xff08;输入模式&#xff09;和控制外部设备&…

14.Chromium指纹浏览器开发教程之WebGL指纹定制

WebGL指纹概述 当在浏览器打开的网页上浏览内容时&#xff0c;看到的大多是平面的、静态的图像和文字。但是有时想要在网页上看到更加生动、立体的图像&#xff0c;如3D游戏、虚拟现实应用等。这时&#xff0c;就需要用到WebGL。 简单来说&#xff0c;WebGL&#xff08;Web G…

C# foreach 循环中获取索引的完整方案

一、手动维护索引变量 ‌实现方式‌&#xff1a; 在循环外部声明索引变量&#xff0c;每次迭代手动递增&#xff1a; int index 0; foreach (var item in collection) { Console.WriteLine($"{index}: {item}"); index; } ‌特点‌&#xff1a; 简单直接&#…

Android 下拉栏中的禁用摄像头和麦克风隐藏

Android 下拉栏中的禁用摄像头和麦克风隐藏 文章目录 Android 下拉栏中的禁用摄像头和麦克风隐藏一、前言二、下拉框中的禁用摄像头和麦克风隐藏实现1、设置支持属性为false2、修改代码 三、其他1、下拉栏中的禁用摄像头和麦克风隐藏小结2、 Android SensorPrivacyService ps&a…

数字后端设计 (四):时钟树综合——让芯片的「心跳」同步到每个角落

—— 试想全城的人要在同一秒按下开关——如果有的表快、有的表慢&#xff0c;结果会乱套&#xff01;时钟树综合就是给芯片内部装一套精准的“广播对时系统”&#xff0c;让所有电路踩着同一个节拍工作。 1. 为什么时钟如此重要&#xff1f; 芯片的「心跳」&#xff1a;时钟信…

华为网路设备学习-19 路由策略

一、 二、 注意&#xff1a; 当该节点匹配模式为permit下时&#xff0c;参考if else 当该节点匹配模式为deny下时&#xff1a; 1、该节点中的apply子语句不会执行。 2、如果满足所有判断&#xff08;if-match&#xff09;条件时&#xff0c;拒绝该节点并跳出&#xff08;即不…