diff --git a/sudukuing/README.md b/sudukuing/README.md
new file mode 100644
index 0000000..ddbd81e
--- /dev/null
+++ b/sudukuing/README.md
@@ -0,0 +1,21 @@
+# pointgaming
+### 1、项目说明
+总体思路:扫雷程序项目开发的总体思路如图1.1示意,拟实现交互界面与交互控制、数据结构和扫雷算法等。
+ 图1.1 项目开发的总体思路图
+
+
+
+### 2、开发环境说明
+(1)系统版本:OpenHarmony 4.0 Release
+(2)SDK版本:API 10
+(3)Model类型:Stage
+(4)DevEco Studio版本:Deveco Studio 4.0 release
+(5)硬件环境:Unionpi Whale、RK3566开发板
+(6)预览器参数:(1)DeviceType:default;(2)Resolution:560-1280;(3)DPI:240;其余参数自定。
+### 3、项目主要函数调用关系图与项目输出界面演进图
+ 图3.1 项目主要函数调用关系图
+
+
+
+ 图3.2 项目输出界面演进图
+
\ No newline at end of file
diff --git a/sudukuing/figs/evolution.png b/sudukuing/figs/evolution.png
new file mode 100644
index 0000000..732bfbf
Binary files /dev/null and b/sudukuing/figs/evolution.png differ
diff --git a/sudukuing/figs/overall.png b/sudukuing/figs/overall.png
new file mode 100644
index 0000000..214b181
Binary files /dev/null and b/sudukuing/figs/overall.png differ
diff --git a/sudukuing/figs/relation.png b/sudukuing/figs/relation.png
new file mode 100644
index 0000000..8441f6a
Binary files /dev/null and b/sudukuing/figs/relation.png differ
diff --git a/sudukuing/sudoku/.gitignore b/sudukuing/sudoku/.gitignore
new file mode 100644
index 0000000..fbabf77
--- /dev/null
+++ b/sudukuing/sudoku/.gitignore
@@ -0,0 +1,11 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
\ No newline at end of file
diff --git a/sudukuing/sudoku/AppScope/app.json5 b/sudukuing/sudoku/AppScope/app.json5
new file mode 100644
index 0000000..8f8eb49
--- /dev/null
+++ b/sudukuing/sudoku/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "com.example.sudoku",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
diff --git a/sudukuing/sudoku/AppScope/resources/base/element/string.json b/sudukuing/sudoku/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000..b124805
--- /dev/null
+++ b/sudukuing/sudoku/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "Sudoku"
+ }
+ ]
+}
diff --git a/sudukuing/sudoku/AppScope/resources/base/media/app_icon.png b/sudukuing/sudoku/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000..cd45acc
Binary files /dev/null and b/sudukuing/sudoku/AppScope/resources/base/media/app_icon.png differ
diff --git a/sudukuing/sudoku/build-profile.json5 b/sudukuing/sudoku/build-profile.json5
new file mode 100644
index 0000000..cc9434a
--- /dev/null
+++ b/sudukuing/sudoku/build-profile.json5
@@ -0,0 +1,36 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compileSdkVersion": 10,
+ "compatibleSdkVersion": 9,
+ "runtimeOS": "OpenHarmony",
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/.gitignore b/sudukuing/sudoku/entry/.gitignore
new file mode 100644
index 0000000..e2713a2
--- /dev/null
+++ b/sudukuing/sudoku/entry/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/build-profile.json5 b/sudukuing/sudoku/entry/build-profile.json5
new file mode 100644
index 0000000..197e83c
--- /dev/null
+++ b/sudukuing/sudoku/entry/build-profile.json5
@@ -0,0 +1,31 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ "arkOptions": {
+ // "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
+ }
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": true,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/hvigorfile.ts b/sudukuing/sudoku/entry/hvigorfile.ts
new file mode 100644
index 0000000..c6edcd9
--- /dev/null
+++ b/sudukuing/sudoku/entry/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/sudukuing/sudoku/entry/obfuscation-rules.txt b/sudukuing/sudoku/entry/obfuscation-rules.txt
new file mode 100644
index 0000000..985b2ae
--- /dev/null
+++ b/sudukuing/sudoku/entry/obfuscation-rules.txt
@@ -0,0 +1,18 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/oh-package.json5 b/sudukuing/sudoku/entry/oh-package.json5
new file mode 100644
index 0000000..225946c
--- /dev/null
+++ b/sudukuing/sudoku/entry/oh-package.json5
@@ -0,0 +1,10 @@
+{
+ "license": "",
+ "devDependencies": {},
+ "author": "",
+ "name": "entry",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "version": "1.0.0",
+ "dependencies": {}
+}
diff --git a/sudukuing/sudoku/entry/src/main/ets/classes/Mytimer.ets b/sudukuing/sudoku/entry/src/main/ets/classes/Mytimer.ets
new file mode 100644
index 0000000..3f81eee
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/classes/Mytimer.ets
@@ -0,0 +1,54 @@
+export default class MyTimer {
+ private elapsed: number;
+ private timer: number | null;
+ private starttime: number;
+
+ constructor() {
+ this.elapsed = 0;
+ this.timer = null;
+ this.starttime = Date.now();
+ this.start();
+ }
+
+ start() { // 时间开始
+ this.starttime = Date.now() - this.elapsed * 1000;
+ if (this.timer === null) {
+ this.update();
+ }
+ }
+
+ update() { // 实现时间每秒的更新
+ const now = Date.now();
+ this.elapsed = Math.floor((now - this.starttime) / 1000);
+ if (this.timer !== null) {
+ clearTimeout(this.timer);
+ }
+ this.timer = setTimeout((): void => this.update(), 1000);
+ }
+
+
+
+ getMinutes(): number {
+ return Math.floor(this.elapsed / 60);
+ }
+
+ getSeconds(): number {
+ return this.elapsed % 60;
+ }
+
+ restart() { // 时间重置
+ this.elapsed = 0;
+ this.starttime = Date.now();
+ if (this.timer !== null) {
+ clearTimeout(this.timer);
+ }
+ this.update();
+ }
+
+ stop() { // 时间停止
+ if (this.timer !== null) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+ }
+}
diff --git a/sudukuing/sudoku/entry/src/main/ets/classes/SudoKu.ets b/sudukuing/sudoku/entry/src/main/ets/classes/SudoKu.ets
new file mode 100644
index 0000000..86bc6b3
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/classes/SudoKu.ets
@@ -0,0 +1,190 @@
+// 数独核心类
+export default class SudoKu{
+ martix:number[][] // 数独矩阵
+
+ // 构造函数
+ constructor() {
+ // 初始化9x9的数独矩阵
+ this.martix=new Array(9)
+
+ for(let i=0;i<9;i++){
+ this.martix[i]=new Array(9)
+ for(let j=0;j<9;j++){
+ this.martix[i][j]=0 // 初始化为0表示空格
+ }
+ }
+ }
+
+ // 初始化矩阵(随机填充数字,用于测试)
+ init(){
+ for(let i=0;i<9;i++){
+ for(let j=0;j<9;j++){
+ this.martix[i][j]=Math.floor(Math.random() * (9 - 1 + 1))+ 1
+ }
+ }
+ }
+
+ // 计算整个数组是否符合数独要求
+ judge(){
+ for(let i=0;i<9;i++){
+ for(let j=0;j<9;j++){
+ if(this.martix[i][j]==0){
+ continue // 跳过空格
+ }
+ let m=Math.floor(i/3) // 计算所在3x3宫格的行索引
+ let n=Math.floor(j/3) // 计算所在3x3宫格的列索引
+
+ // 检查行是否有重复数字
+ let row_martix=this.martix[i]
+ let row_set=row_martix.filter(num=>num!=0)
+ // 检查列是否有重复数字
+ let col_martix=this.martix[j]
+ let col_set=col_martix.filter(num=>num!=0)
+
+ // 检查3x3宫格是否有重复数字
+ let subBlock:number[][]=[]
+ for(let i=m*3;inum!=0)
+
+ // 统计0的数目
+ let rowNum0=row_martix.filter(num=>num==0).length
+ // 若集合里的数字个数(不包括0)加上0的个数小于9,说明有非0的数字重复
+ if(row_martix.length!=row_set.length+rowNum0){
+ return false
+ }
+
+ let colNum0=col_martix.filter(num=>num==0).length
+ if(col_martix.length!=col_set.length+colNum0){
+ return false
+ }
+
+ let blockNum0=block_martix.filter(num=>num==0).length
+ if(block_martix.length!=block_set.length+blockNum0){
+ return false
+ }
+ }
+ }
+ return true // 所有检查通过,符合数独规则
+ }
+
+ // 获取一个格子里可能可以填的值
+ getPossible(row:number,col:number): Set{
+ let Nums:Set=new Set([1,2,3,4,5,6,7,8,9]) // 1-9的完整集合
+ let m=Math.floor(row/3) // 计算所在3x3宫格的行索引
+ let n=Math.floor(col/3) // 计算所在3x3宫格的列索引
+ let set:Set=new Set() // 创建集合用于存入出现的数字
+
+ // 收集所在列出现的数字
+ for(let i=0;i<9;i++){
+ if(i==row){
+ continue // 跳过当前位置
+ }
+ set.add(this.martix[i][col])// 将所在列出现数字加入集合
+ }
+
+ // 收集所在行出现的数字
+ for(let j=0;j<9;j++){
+ if(j==col){
+ continue // 跳过当前位置
+ }
+ set.add(this.martix[row][j])// 将所在行出现数字加入集合
+ }
+
+ // 收集所在3x3宫格出现的数字
+ for(let i=m*3;i, setB:Set) {
+ // 创建一个新的Set对象,包含setA中但不在setB中的元素
+ let difference = new Set(setA);
+ for (let elem of setB) {
+ difference.delete(elem);
+ }
+ return difference;
+ }
+
+ // 数独求解算法(深度优先搜索)
+ solve(martix:number[][]):boolean{
+ // 遍历数独矩阵
+ for(let i =0;i<9;i++){
+ let row = (i+5)%9 // 行索引偏移,提高求解效率
+ for(let j =0;j<9;j++){
+ let col = (j+4)%9 // 列索引偏移,提高求解效率
+ // 如果当前位置为空格
+ if(martix[row][col]==0){
+ let possible = this.getPossible(row,col)//获取所有可能的数字
+ // 尝试每个可能的数字
+ for(let value of possible){
+ martix[row][col] = value//将可能的数字填入
+ // 递归求解,如果找到解则返回true
+ if(this.solve(martix)){
+ return true
+ }
+ // 如果当前填入的数字会导致后面无解则依然填入0表示空白待填
+ martix[row][col]=0
+ }
+ return false // 所有可能数字都尝试过但无解
+ }
+ }
+ }
+ return true // 所有格子都已填满,求解完成
+ }
+
+ // 初始化数独矩阵
+ InitMartix(){
+ let flag = false // 初始化一个标志,用于判断数独是否可以解决
+ // 使用循环来确保生成的数独矩阵是可解的
+ while(!flag){
+ // 将整个数独矩阵初始化为0
+ for(let i = 0;i<9;i++){
+ for(let j =0;j<9;j++){
+ this.martix[i][j]=0
+ }
+ }
+
+ // 随机在三个对角位置填入1-9的随机数(提供初始条件)
+ this.martix[0][0]=Math.floor(Math.random()*9)+1
+ this.martix[3][3]=Math.floor(Math.random()*9)+1
+ this.martix[6][6]=Math.floor(Math.random()*9)+1
+
+ flag = this.solve(this.martix) // 检查当前矩阵是否可解
+
+ // 随机清除一些数字以生成数独谜题
+ for (let i = 0; i < 9; i++) {
+ for (let j = 0; j < 9; j++) {
+ let ran = Math.floor(Math.random() * 151) - 50
+ if (ran < 0) {
+ this.martix[i][j] = 0;
+ }
+ }
+ }
+ }
+ }
+
+ // 计算空格数量
+ countEmptyCells():number{
+ let count=0
+ for(let i = 0;i<9;i++){
+ for(let j=0;j<9;j++){
+ if(this.martix[i][j]==0){
+ count++
+ }
+ }
+ }
+ return count
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/common/constants/CommonConstants.ets b/sudukuing/sudoku/entry/src/main/ets/common/constants/CommonConstants.ets
new file mode 100644
index 0000000..aaa5cec
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/common/constants/CommonConstants.ets
@@ -0,0 +1,26 @@
+// 公共常量定义文件
+
+// 棋盘行列数(注释掉的9行9列是标准数独,当前使用27可能是为了适配特定显示需求)
+export const boardNum: number = 27; //行列数
+// export const boardNum: number = 9; //行列数
+
+// 格子大小定义
+export const miniGridSize: number = 12; // 小格子大小
+export const gridSize: number = 36; // 格子大小
+
+// 偏移值大小
+export const offSet:number=5
+
+// Canvas渲染上下文设置
+export const settings: RenderingContextSettings = new RenderingContextSettings(true);
+// 主要Canvas渲染上下文
+export const context: CanvasRenderingContext2D = new CanvasRenderingContext2D(settings);
+
+// 第二个Canvas渲染上下文设置
+export const settings2: RenderingContextSettings = new RenderingContextSettings(true);
+// 用于绘制数独数字的Canvas渲染上下文
+export const context2: CanvasRenderingContext2D = new CanvasRenderingContext2D(settings2);
+
+// 图片资源定义
+export const imgWhiteBlock:ImageBitmap = new ImageBitmap("common/pictures/icon_block_white.png");//白色方块背景
+export const imgYellowBlock:ImageBitmap = new ImageBitmap("common/pictures/icon_block_yellow.png");//黄色方块背景
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/common/constants/globalState.ets b/sudukuing/sudoku/entry/src/main/ets/common/constants/globalState.ets
new file mode 100644
index 0000000..2a7d48e
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/common/constants/globalState.ets
@@ -0,0 +1,136 @@
+import SudoKu from '../../classes/SudoKu'
+import MyTimer from '../../classes/Mytimer'
+
+// 定义展示消息接口
+interface ShowMsg {
+ no: number // 序号
+ row: number // 行索引
+ col: number // 列索引
+ selectNum: number // 选择的数字
+}
+
+// 定义撤销状态接口
+interface UndoState {
+ martix: number[][] // 数独矩阵
+ step: ShowMsg[] // 步骤记录
+}
+
+// 全局状态管理类
+class GlobalState {
+ public sudokuInstance: SudoKu = new SudoKu() // 数独实例
+ public myTimer: MyTimer = new MyTimer() // 计时器实例
+ public showArr: ShowMsg[] = [] // 展示数组
+ public showArrItem: ShowMsg = { no: 1, row: 0, col: 0, selectNum: 0 } // 展示项
+ private subscribers: (() => void)[] = [] // 订阅者数组
+ public selectedNum: number = 0 // 选中的数字
+ public initMatrix: number[][] = [] // 存储初始化数独数据
+ private undoStack: UndoState[] = [] // 存储步骤信息用于撤销
+ public stepCount: number = 0 // 步骤计数
+ public undoflag:boolean = false // 标记撤回模式
+ public ifsave:boolean = false // 切换笔记模式
+ public ifnote:boolean = false // 提示记录标志
+ public ifrecord:boolean = false // 一键笔记模式标志
+ public noteMatrix:Set[][] // 存储每个位置的笔记数字
+ public waittime:number = 500 // 设置睡眠时间
+ public emptycell:number=0 // 计算空格数
+ public GameOver:boolean = false // 游戏结束标志
+ public iswork:boolean = false // 判断自动求解状态
+
+ // 构造函数
+ constructor() {
+ this.initState() // 初始化状态
+ // 初始化笔记矩阵
+ this.noteMatrix=[]
+ for(let i = 0;i < 9;i++){
+ this.noteMatrix[i] = []
+ for(let j = 0;j < 9;j++){
+ this.noteMatrix[i][j] = new Set()
+ }
+ }
+ }
+
+ // 初始化状态
+ public initState() {
+ this.iswork = false // 重置自动求解标志
+ this.GameOver = false // 重置游戏结束标志
+ this.sudokuInstance.InitMartix() // 刷新数独
+ this.emptycell = this.sudokuInstance.countEmptyCells() // 重新计算空格数,保证难度计算的正确
+ this.initMatrix = JSON.parse(JSON.stringify(this.sudokuInstance.martix)) // 深拷贝
+ this.showArr = [] // 清空存储展示数据
+ this.showArrItem = { no: 0, row: 0, col: 0, selectNum: 0 }
+ this.selectedNum = -1
+ this.myTimer = new MyTimer() // 刷新计时器
+ this.stepCount = 0
+ this.undoStack = []
+ this.notify()//通知更新
+ // 刷新笔记矩阵
+ this.noteMatrix=[]
+ for(let i = 0;i < 9;i++){
+ this.noteMatrix[i] = []
+ for(let j = 0;j < 9;j++){
+ this.noteMatrix[i][j] = new Set()
+ }
+ }
+ }
+
+ // 数独重置函数
+ public reset() {
+ this.initState()
+ }
+
+ // 订阅事件
+ public subscribe(callback: () => void) {
+ this.subscribers.push(callback)
+ }
+
+ // 通知事件
+ public notify() {
+ console.log("GlobalState notified:", this.sudokuInstance.martix);
+ for (const subscriber of this.subscribers) {
+ subscriber()
+ }
+ }
+
+ // 记录当前状态用于撤销功能
+ public recordState() {
+ const martix: number[][] = JSON.parse(JSON.stringify(this.sudokuInstance.martix)); // 深拷贝以防止引用问题
+ const step: ShowMsg[] = JSON.parse(JSON.stringify(this.showArr))//深拷贝
+ const undo: UndoState = { martix, step }
+ this.undoStack.push(undo)
+ }
+
+ // 撤回方法
+ public undoAction() {
+ // 如果撤销栈中有多个状态
+ if (this.undoStack.length > 1) {
+ this.undoStack.pop() // 移除当前状态
+ const lastState = this.undoStack[this.undoStack.length - 1] // 回到上一步的状态
+ this.sudokuInstance.martix = lastState.martix
+ this.showArr = lastState.step
+ console.log("Undo action performed, restored state:", lastState)
+ }
+ // 如果撤销栈中只有一个状态(初始状态)
+ else if (this.undoStack.length === 1) {
+ this.undoStack.pop()// 移除当前状态
+ this.sudokuInstance.martix = JSON.parse(JSON.stringify(this.initMatrix))// 恢复到初始状态
+ this.showArr = []
+ console.log("Undo action performed, restored to initial state")
+ }
+ this.undoflag = true
+ this.notify()
+ }
+
+ // 添加笔记数字
+ addNoteNumber(row:number,col:number,num:number){
+ this.noteMatrix[row][col].add(num)
+ }
+
+ // 清除一个格子的全部笔记
+ clearNotes(row:number,col:number){
+ this.noteMatrix[row][col].clear()
+ }
+
+}
+
+// 导出全局状态实例
+export const globalState = new GlobalState()
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_white.png b/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_white.png
new file mode 100644
index 0000000..376365b
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_white.png differ
diff --git a/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_yellow.png b/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_yellow.png
new file mode 100644
index 0000000..d442929
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/ets/common/pictures/icon_block_yellow.png differ
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/Header.ets b/sudukuing/sudoku/entry/src/main/ets/components/Header.ets
new file mode 100644
index 0000000..4922ce6
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/Header.ets
@@ -0,0 +1,58 @@
+import SetDialog from './SetDialog'
+import SolveDialog from './SolveDialog'
+
+// 头部功能组件
+@Component
+export default struct Header {
+
+ // 弹窗控制器
+ SetDialogController: CustomDialogController = new CustomDialogController({
+ builder: SetDialog(),
+ gridCount: 4
+ })
+
+ SolveDialogController: CustomDialogController = new CustomDialogController({
+ builder: SolveDialog(),
+ gridCount: 4
+ })
+
+ build() {
+ Row({space:5}) {
+ // 游戏设置按钮
+ Text('游戏设置')
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .margin({left:5,right:5})
+ .onClick(()=>{
+ this.SetDialogController.open() // 打开设置对话框
+ })
+
+ // 自动求解按钮
+ Text('自动求解')
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .margin({right:5})
+ .onClick(()=>{
+ this.SolveDialogController.open() // 打开求解对话框
+ })
+
+ // 空白占位符,用于布局调整
+ Blank()
+ .width('60%')
+
+ // Logo图片
+ Image($r('app.media.icon_logo'))
+ .objectFit(ImageFit.Contain)
+ .width(20)
+
+ // 标题文本
+ Text('数独')
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ }
+ .padding({left:5,right:5})
+ .justifyContent(FlexAlign.Start)
+ .width('100%')
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/Main.ets b/sudukuing/sudoku/entry/src/main/ets/components/Main.ets
new file mode 100644
index 0000000..76ea900
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/Main.ets
@@ -0,0 +1,195 @@
+import { globalState } from '../common/constants/globalState'
+import * as CommonConstants from '../common/constants/CommonConstants'
+import { drawLine, drawBlockBoard, drawGrayBackground, drawSudoku ,drawNote,fillnoteMatrix,clearnoteMatrix} from '../utils/util'
+import { checkWin } from './solvefunc'
+
+// 定义展示消息接口
+interface ShowMsg {
+ no: number // 序号
+ row: number // 行索引
+ col: number // 列索引
+ selectNum: number // 选择的数字
+}
+
+// 棋盘以及数独的绘制逻辑组件
+@Component
+export default struct Main {
+ @Link showArr: ShowMsg[] // 链接父组件的展示数组
+ private arrIndex: number = 1 // 数组索引
+ @Link showArrItem: ShowMsg // 链接父组件的展示项
+ private playX: number = 0 // 玩家点击的X坐标
+ private playY: number = 0 // 玩家点击的Y坐标
+ public selectedRow: number = -1 // 选中的行索引
+ public selectedCol: number = -1 // 选中的列索引
+
+ // 组件即将出现时的生命周期回调
+ aboutToAppear() {
+ // 订阅全局状态变化事件
+ globalState.subscribe(() => {
+ this.updateCanvas() // 更新画布,使得每次操作数独都能正确展示
+ const selectedNumber = globalState.selectedNum;
+
+ // 撤回操作则重置row和num,避免选择位置的影响
+ if(globalState.undoflag){
+ this.resetslection()
+ globalState.undoflag=false
+ this.drawNotes()
+ }
+
+ // 一键笔记模式
+ if(globalState.ifrecord){
+ clearnoteMatrix() // 先清空笔记矩阵
+ fillnoteMatrix() // 填满笔记,实现一键笔记
+ this.drawNotes()
+ globalState.ifrecord=false
+ }
+
+ // 判断是否点击数字输入
+ if (this.selectedRow !== -1 && this.selectedCol !== -1 && selectedNumber > 0) {
+ // 判断是否是笔记模式
+ if(globalState.ifsave){
+ globalState.addNoteNumber(this.selectedRow,this.selectedCol,selectedNumber) // 添加笔记数字
+ this.drawNotes()
+ }
+
+ // 正常输入模式则更新数独展示数字
+ if(this.selectedRow !== -1 && this.selectedCol !== -1&&globalState.ifsave===false){
+ this.drawNumber(this.selectedRow, this.selectedCol, selectedNumber) // 绘制数字
+ globalState.clearNotes(this.selectedRow,this.selectedCol) // 清除该位置的笔记
+ this.drawNotes()
+ }
+ }
+
+ // 擦除模式
+ if (this.selectedRow !== -1 && this.selectedCol !== -1 && selectedNumber === 0 && !globalState.iswork) {
+ this.eraseNumber(this.selectedRow, this.selectedCol) // 擦除数字
+ // 如果该位置有笔记则清除
+ if(globalState.noteMatrix[this.selectedRow][this.selectedCol]){
+ globalState.clearNotes(this.selectedRow,this.selectedCol)
+ this.drawNotes()
+ }
+ }
+
+ // 提示模式
+ if( globalState.ifnote){
+ // 获取可能的数字并添加到笔记中
+ for(const num of globalState.sudokuInstance.getPossible(this.selectedRow,this.selectedCol)){
+ globalState.addNoteNumber(this.selectedRow,this.selectedCol,num)
+ }
+ this.drawNotes()
+ globalState.ifnote = false
+ }
+ })
+ }
+
+ // 更新画布
+ updateCanvas() {
+ const context2 = CommonConstants.context2
+ context2.clearRect(0, 0, context2.width, context2.height) // 清除画布
+ drawBlockBoard() // 绘制区块边框
+ drawLine() // 绘制线条
+ drawSudoku(globalState.sudokuInstance.martix, context2) // 绘制数独数字
+ drawGrayBackground(this.selectedRow,this.selectedCol,globalState.sudokuInstance.martix) // 绘制选中格子的灰色背景
+ this.updateShowArr() // 更新展示数组
+ console.log("Canvas updated with matrix:", globalState.sudokuInstance.martix)
+ }
+
+ // 重设选中行和列,使得撤回操作不受到选择位置的显示影响
+ resetslection(){
+ this.selectedRow=-1
+ this.selectedCol=-1
+ }
+
+ build() {
+ Column() {
+ Stack() {
+ // 绘制棋盘背景
+ Canvas(CommonConstants.context)
+ .width(CommonConstants.miniGridSize * CommonConstants.boardNum + CommonConstants.miniGridSize)
+ .height(CommonConstants.miniGridSize * CommonConstants.boardNum + CommonConstants.miniGridSize)
+ .onReady(() => {
+ drawBlockBoard() // 绘制区块边框
+ drawLine() // 绘制线条
+ })
+
+ // 实时绘制数独
+ Canvas(CommonConstants.context2)
+ .width(CommonConstants.miniGridSize * CommonConstants.boardNum + CommonConstants.miniGridSize)
+ .height(CommonConstants.miniGridSize * CommonConstants.boardNum + CommonConstants.miniGridSize)
+ .onReady(() => {
+ this.updateCanvas()
+ })
+ .onTouch((event) => {
+ // 处理触摸事件
+ if (event.type === TouchType.Up) {
+ this.handleTouch(event.touches[0].x, event.touches[0].y)
+ }
+ })
+ }
+ }
+ .alignItems(HorizontalAlign.Center)
+ .justifyContent(FlexAlign.Center)
+ .height('100%')
+ .width('100%')
+ }
+
+ // 处理棋盘格子的点击事件
+ handleTouch(x: number, y: number) {
+ // 计算点击位置对应的格子坐标
+ this.playX = x - CommonConstants.miniGridSize / 2
+ this.playY = y - CommonConstants.miniGridSize / 2
+ this.selectedRow = Math.floor(this.playY / CommonConstants.gridSize)
+ this.selectedCol = Math.floor(this.playX / CommonConstants.gridSize)
+
+ // 确保点击位置在有效范围内
+ if (this.selectedRow >= 0 && this.selectedRow < 9 && this.selectedCol >= 0 && this.selectedCol < 9) {
+ // 只有初始为空的格子才能被编辑
+ if (globalState.initMatrix[this.selectedRow][this.selectedCol] === 0) {
+ drawGrayBackground(this.selectedRow, this.selectedCol, globalState.sudokuInstance.martix) // 绘制选中格子背景
+ this.drawNotes() // 绘制笔记
+ }
+ }
+ }
+
+ // 绘制数字
+ drawNumber(row: number, col: number, num: number) {
+ globalState.sudokuInstance.martix[row][col] = num // 更新数独矩阵
+ this.updateCanvas() // 更新画布
+ // 添加到展示数组
+ const newShowArrItem: ShowMsg = { no: this.arrIndex, row: row, col: col, selectNum: num }
+ globalState.showArr = globalState.showArr.concat(newShowArrItem)
+ globalState.recordState() // 记录当前状态用于撤销
+ this.updateShowArr()
+ this.arrIndex++
+ globalState.stepCount++; // 增加步骤计数
+ checkWin() // 检查是否游戏胜利
+ }
+
+ // 擦除数字
+ eraseNumber(row: number, col: number) {
+ globalState.sudokuInstance.martix[row][col] = 0 // 将该格子上的数字重新赋值为0
+ this.updateCanvas() // 更新画布,重画数独
+ // 从展示数组中移除
+ globalState.showArr = globalState.showArr.filter(item => !(item.row === row && item.col === col))
+ globalState.recordState() // 记录当前状态用于撤销
+ globalState.stepCount++ // 增加步骤计数
+ }
+
+ // 更新展示数组
+ updateShowArr() {
+ this.showArr = globalState.showArr.slice()
+ // 更新数组索引
+ this.arrIndex = this.showArr.length > 0 ? this.showArr[this.showArr.length - 1].no + 1 : 1
+ }
+
+ // 绘制笔记
+ drawNotes(){
+ // 遍历所有格子绘制笔记
+ for(let row = 0;row<9;row++){
+ for(let col=0;col<9;col++){
+ const notes = globalState.noteMatrix[row][col]
+ drawNote(row,col,notes) // 绘制每个格子的笔记
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/SetDialog.ets b/sudukuing/sudoku/entry/src/main/ets/components/SetDialog.ets
new file mode 100644
index 0000000..c904bc6
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/SetDialog.ets
@@ -0,0 +1,52 @@
+import { globalState } from '../common/constants/globalState'
+
+// 设置对话框组件
+@CustomDialog
+export default struct SetDialog {
+ controller: CustomDialogController // 对话框控制器
+
+ build() {
+ Column() {
+ Column({ space: 10 }) {
+ // 重新开始按钮
+ Text('重新开始')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.initState() // 初始化游戏状态
+ globalState.showArr = []; // 清空步骤记录
+ globalState.selectedNum = -1; // 重置选中的数字
+ globalState.notify(); // 通知状态更新
+ console.log("Game reset.");
+ this.controller.close(); // 关闭对话框
+ })
+
+ // 暂停计时按钮
+ Text('暂停计时')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.myTimer.stop() // 停止计时器
+ this.controller.close() // 关闭对话框
+ })
+
+ // 载入题目按钮
+ Text('载入题目')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.reset() // 重置游戏
+ globalState.notify() // 通知状态更新
+ this.controller.close() // 关闭对话框
+ })
+ }
+ }
+ .backgroundColor('#1E90FF') // 设置背景颜色
+ .justifyContent(FlexAlign.Center)
+ .width(400)
+ .height(120)
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/ShowContent.ets b/sudukuing/sudoku/entry/src/main/ets/components/ShowContent.ets
new file mode 100644
index 0000000..43e5afb
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/ShowContent.ets
@@ -0,0 +1,149 @@
+import { globalState } from '../common/constants/globalState'
+
+// 定义展示消息接口
+interface ShowMsg {
+ no: number // 序号
+ row: number // 行索引
+ col: number // 列索引
+ selectNum: number // 选择的数字
+}
+
+// 内容展示组件
+@Component
+export default struct ShowContent {
+ @Link ifSave: boolean // 链接父组件的笔记模式标志
+ @Link showArrItem: ShowMsg // 链接父组件的展示项
+ @Link showArr: ShowMsg[] // 链接父组件的展示数
+ @State private timerText: string = '' // 计时器文本
+ @State private difficultyText: string = '' // 难度文本
+ @State private gameOver: boolean = false // 游戏结束标志
+ private timerInterval: number | null = null // 计时器ID
+
+ // 组件即将出现时的生命周期回调
+ aboutToAppear() {
+ this.updateData() // 更新数据
+ this.startTimer() // 启动计时器
+ // 订阅全局状态变化事件
+ globalState.subscribe(() => {
+ this.updateData()
+ })
+ }
+
+ // 组件即将消失时的生命周期回调
+ aboutToDisappear() {
+ // 清除计时器避免内存泄漏
+ if (this.timerInterval !== null) {
+ clearInterval(this.timerInterval)
+ }
+ }
+
+ // 启动计时器
+ startTimer() {
+ // 实现时间的每隔一秒更新,实现实时更新显示效果
+ if (this.timerInterval === null) {
+ this.timerInterval = setInterval(() => {
+ this.updateData() // 每秒更新一次数据
+ }, 1000)
+ }
+ }
+
+ // 更新数据函数
+ updateData() {
+ // 更新计时器文本
+ this.timerText = `运行时间: ${globalState.myTimer.getMinutes()} 分 ${globalState.myTimer.getSeconds()} 秒`
+ // 更新难度文本
+ this.difficultyText = `难度: ${(globalState.emptycell / 81).toFixed(2)}`
+ // 更新游戏结束标志
+ this.gameOver = globalState.GameOver
+ }
+
+ build() {
+ Column({ space: 5 }) {
+ // 游戏没结束,正常绘制内容展示界面
+ if (!this.gameOver) {
+ // 显示运行时间
+ Text(this.timerText)
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .margin({ right: 5 })
+
+ // 显示难度等级
+ Text(this.difficultyText)
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .margin({ right: 5 })
+
+ // 显示笔记模式状态
+ Text('False时保存笔记,当前笔记:' + this.ifSave)
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .fontColor(Color.Red)
+ .margin({ right: 5 })
+
+ // 显示步骤记录标题
+ Text('步骤记录:')
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .margin({ right: 5 })
+
+ // 步骤记录列表
+ Scroll() {
+ List({ space: 2 }) {
+ ForEach(this.showArr, (item: ShowMsg) => {
+ ListItem() {
+ // 显示每个步骤的详细信息
+ Text(`[${item.no}, ( ${item.row + 1}, ${item.col + 1}), ${item.selectNum}]`)
+ .width("100%")
+ .height("100%")
+ .fontSize(14)
+ .textAlign(TextAlign.Start)
+ .backgroundColor(Color.White)
+ }.width("100%").height(16)
+ })
+ }
+ }
+ .padding({ left: 10, top: 2 })
+ .border({ width: 1 })
+ .width('100%')
+ .height('60%')
+ .backgroundColor(Color.White)
+ }
+ // 如果游戏已结束,则绘制结束界面
+ else {
+ Column({ space: 5 }) {
+ // 游戏胜利提示
+ Text('游戏结束,恭喜通过!')
+ .fontSize(25)
+ .fontWeight(FontWeight.Bold)
+ .fontColor(Color.Red)
+ .margin({ right: 5 ,bottom:10})
+
+ // 显示最终用时
+ Text(this.timerText)
+ .fontSize(20)
+ .fontWeight(FontWeight.Bold)
+ .margin({ right: 5,bottom:10 })
+
+ // 重新开始按钮
+ Button('重新开始')
+ .fontSize(15)
+ .fontWeight(FontWeight.Bold)
+ .backgroundColor(Color.Blue)
+ .onClick(() => {
+ globalState.reset() // 重置游戏
+ })
+ }
+ .alignItems(HorizontalAlign.Center)
+ .justifyContent(FlexAlign.Center)
+ .width('100%')
+ .height('100%')
+ .backgroundColor(Color.White)
+ }
+ }
+ .padding({ left: 10, top: 10, right: 10 })
+ .alignItems(HorizontalAlign.Start)
+ .justifyContent(FlexAlign.Start)
+ .width('100%')
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/SolveDialog.ets b/sudukuing/sudoku/entry/src/main/ets/components/SolveDialog.ets
new file mode 100644
index 0000000..86cc52f
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/SolveDialog.ets
@@ -0,0 +1,86 @@
+// 求解对话框组件
+import { globalState } from '../common/constants/globalState'
+import {autosolve,bettersolve} from './solvefunc'
+
+@CustomDialog
+export default struct SolveDialog {
+ controller: CustomDialogController // 对话框控制器
+ @State solveChoose:boolean=false // 求解选项状态
+
+ build(){
+ Column(){
+ Column({ space: 10 }) {
+ // 如果未选择求解速度设置
+ if(!this.solveChoose){
+ // 顺序求解按钮
+ Text('自动求解-顺序求解')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.iswork = true // 设置自动求解状态
+ autosolve() // 调用顺序求解函数
+ this.controller.close() // 关闭对话框
+ })
+
+ // 优化求解按钮
+ Text('自动求解-优化')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.iswork = true // 设置自动求解状态
+ bettersolve() // 调用优化求解函数
+ this.controller.close() // 关闭对话框
+ })
+
+ // 求解速度选择按钮
+ Text('求解速度选择')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ this.solveChoose=true // 切换到速度选择界面
+ })
+ }else{
+ // 低速求解选项
+ Text('低速')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.waittime = 1000 // 设置等待时间为1000ms
+ !this.solveChoose // 切换回主界面
+ this.controller.close() // 关闭对话框
+ })
+
+ // 中速求解选项
+ Text('中速')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.waittime = 500 // 设置等待时间为500ms
+ !this.solveChoose // 切换回主界面
+ this.controller.close() // 关闭对话框
+ })
+
+ // 高速求解选项
+ Text('高速')
+ .fontColor(Color.White)
+ .fontWeight(500)
+ .fontSize(20)
+ .onClick(() => {
+ globalState.waittime = 200 // 设置等待时间为200ms
+ !this.solveChoose // 切换回主界面
+ this.controller.close() // 关闭对话框
+ })
+ }
+ }
+ }
+ .backgroundColor('#1E90FF') // 设置背景颜色
+ .justifyContent(FlexAlign.Center)
+ .width(400)
+ .height(120)
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/ToolBar.ets b/sudukuing/sudoku/entry/src/main/ets/components/ToolBar.ets
new file mode 100644
index 0000000..35368b8
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/ToolBar.ets
@@ -0,0 +1,167 @@
+import { globalState } from '../common/constants/globalState'
+
+// 样式扩展,便于复用 - 工具按钮样式
+@Extend(Text) function toolStyle(){
+ .fontSize(16)
+ .width(60)
+ .height(30)
+ .textAlign(TextAlign.Center)
+ .fontWeight(FontWeight.Bold)
+ .borderWidth(2)
+ .backgroundColor(Color.White)
+}
+
+// 样式扩展,便于复用 - 数字按钮样式
+@Extend(Text) function numberStyle(){
+ .fontSize(16)
+ .width(35)
+ .height(35)
+ .textAlign(TextAlign.Center)
+ .fontWeight(FontWeight.Bold)
+ .borderWidth(2)
+ .backgroundColor(Color.White)
+}
+
+// 功能栏组件
+@Component
+export default struct ToolBar {
+ @Link ifSave: boolean // 链接父组件的笔记模式标志
+
+ build() {
+ Column() {
+ // 第一行功能按钮
+ Row() {
+ // 撤回按钮
+ Text('撤回')
+ .toolStyle()
+ .onClick(() => {
+ // 只有步骤计数大于0时才能撤回
+ if(globalState.stepCount>0){
+ globalState.undoAction() // 调用撤回函数
+ globalState.stepCount--
+ }
+ })
+
+ // 擦除按钮
+ Text('擦除')
+ .toolStyle()
+ .onClick(() => {
+ globalState.selectedNum = 0 // 设置选中数字为0表示擦除
+ globalState.notify() // 通知各组件更新
+ console.log("Erase button clicked.")
+ })
+
+ // 笔记按钮
+ Text('笔记')
+ .toolStyle()
+ .onClick(() => {
+ this.ifSave = !this.ifSave // 切换笔记模式标志
+ globalState.ifsave = !globalState.ifsave // 同步更新全局状态
+ })
+
+ // 一键笔记按钮
+ Text('一键笔记')
+ .toolStyle()
+ .fontSize(14)
+ .onClick(() => {
+ globalState.ifrecord = true // 设置一键笔记标志
+ globalState.notify() // 通知各组件更新
+ })
+
+ // 提示按钮
+ Text('提示')
+ .toolStyle()
+ .onClick(() => {
+ globalState.selectedNum=0 // 清除选中数字
+ globalState.ifnote = true // 设置提示标志
+ globalState.notify() // 通知各组件更新
+ })
+ }
+ .justifyContent(FlexAlign.SpaceAround)
+ .width('100%')
+ .height('50%')
+
+ // 第二行数字按钮
+ Row() {
+ // 数字1按钮
+ Text('1')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 1 // 设置选中数字为1
+ globalState.notify() // 通知各订阅组件刷新,从而实现实时更新效果
+ })
+
+ // 数字2按钮
+ Text('2')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 2 // 设置选中数字为2
+ globalState.notify()
+ })
+
+ // 数字3按钮
+ Text('3')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 3 // 设置选中数字为3
+ globalState.notify()
+ })
+
+ // 数字4按钮
+ Text('4')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 4 // 设置选中数字为4
+ globalState.notify()
+ })
+
+ // 数字5按钮
+ Text('5')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 5 // 设置选中数字为5
+ globalState.notify()
+ })
+
+ // 数字6按钮
+ Text('6')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 6 // 设置选中数字为6
+ globalState.notify()
+ })
+
+ // 数字7按钮
+ Text('7')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 7 // 设置选中数字为7
+ globalState.notify()
+ })
+
+ // 数字8按钮
+ Text('8')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 8 // 设置选中数字为8
+ globalState.notify()
+ })
+
+ // 数字9按钮
+ Text('9')
+ .numberStyle()
+ .onClick(() => {
+ globalState.selectedNum = 9 // 设置选中数字为9
+ globalState.notify()
+ })
+ }
+ .justifyContent(FlexAlign.SpaceEvenly)
+ .width('100%')
+ .height('50%')
+ }
+ .justifyContent(FlexAlign.Start)
+ .width('100%')
+ .height('100%')
+ }
+
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/components/solvefunc.ets b/sudukuing/sudoku/entry/src/main/ets/components/solvefunc.ets
new file mode 100644
index 0000000..65cee9b
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/components/solvefunc.ets
@@ -0,0 +1,183 @@
+import {globalState} from '../common/constants/globalState'
+import {sleep,drawGrayBackground} from '../utils/util'
+
+// 定义展示消息接口
+interface ShowMsg {
+ no: number // 序号
+ row: number // 行索引
+ col: number // 列索引
+ selectNum: number // 选择的数字
+}
+
+// 异步函数,用于自动解决数独谜题
+export async function autosolve():Promise{
+ const STEP:ShowMsg[] = [] // 用于存储解决步骤的数组
+ // 重置数独为初始状态
+ globalState.sudokuInstance.martix = JSON.parse(JSON.stringify(globalState.initMatrix))
+ globalState.selectedNum = 0 // 重置选中数字
+ globalState.notify() // 通知状态更新
+ const success =await autosolveHelper(globalState.sudokuInstance.martix,STEP)
+ if(success){ // 如果求解成功
+ globalState.showArr = STEP // 更新显示数组为求解步骤
+ globalState.notify()
+ globalState.myTimer.stop()
+ }
+}
+
+// 更优的自动求解函数
+export async function bettersolve():Promise{
+ const STEP:ShowMsg[] = [] // 用于存储解决步骤的数组
+ // 重置数独为初始状态
+ globalState.sudokuInstance.martix = JSON.parse(JSON.stringify(globalState.initMatrix))
+ globalState.selectedNum = 0 // 重置选中数字
+ globalState.notify() // 通知状态更新
+ globalState.notify() // 再次通知状态更新
+ const success = await bettersolveHelper(globalState.sudokuInstance.martix,STEP)
+ if(success){ // 如果求解成功
+ globalState.showArr = STEP // 更新显示数组为求解步骤
+ globalState.notify()
+ globalState.myTimer.stop()
+ }
+
+}
+
+// 辅助函数,使用优化的回溯算法解决数独
+async function bettersolveHelper(martrix:number[][],STEP:ShowMsg[]):Promise{
+ let flag = false // 标志位,用于标识是否有唯一解的格子
+
+ // 第一阶段:处理所有只有一个可能值的格子(唯一候选数法)
+ for(let row = 0;row < 9;row++){
+ for(let col = 0;col < 9; col++){
+ if(martrix[row][col] === 0){ // 如果当前格子为空
+ // 获取该位置所有可能的数字
+ const possible = Array.from(globalState.sudokuInstance.getPossible(row,col))
+ // 如果只有一个可能值
+ if(possible.length === 1){
+ flag = true // 设置标志,表示找到了唯一解的格子
+ const value:number = possible[0] // 获取唯一可能值
+ martrix[row][col]=value // 填入唯一可能值
+ const seq = STEP.length + 1 // 计算当前步骤序号
+ // 创建步骤对象
+ const step:ShowMsg = {no:seq,row:row,col:col,selectNum:value}
+ STEP.push(step) // 将步骤对象添加到步骤数组中
+ globalState.notify() // 通知状态更新
+ // 绘制当前步骤
+ await drawstep(row,col,value,martrix,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,实现动画效果
+ // 递归调用,继续求解
+ if(await bettersolveHelper(martrix,STEP)){
+ return true // 如果找到解决方案,返回true
+ }
+ // 回溯,将当前格子重置为空
+ martrix[row][col] = 0
+ // 绘制回溯步骤
+ await drawstep(row,col,0,martrix,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,实现动画效果
+ }
+ }
+ }
+ }
+
+ // 第二阶段:处理所有可能值的格子(回溯法)
+ if(!flag){ // 如果没有唯一解的格子
+ for(let row = 0;row < 9;row++){
+ for(let col = 0;col < 9;col++){
+ if(martrix[row][col]===0){ // 如果当前格子为空
+ // 获取该位置所有可能的数字
+ const possible = Array.from(globalState.sudokuInstance.getPossible(row,col))
+ // 尝试每个可能值
+ for(const value of possible){
+ martrix[row][col] = value // 尝试填入每个可能值
+ const seq = STEP.length+1
+ // 创建步骤对象
+ const step:ShowMsg = {no:seq,row:row,col:col,selectNum:value}
+ STEP.push(step) // 将步骤对象添加到步骤数组中
+ // 绘制当前步骤
+ await drawstep(row,col,value,martrix,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,实现动画效果
+ // 递归调用,继续求解
+ if(await bettersolveHelper(martrix,STEP)){
+ return true // 如果找到解决方案,返回true
+ }
+ // 回溯,将当前格子重置为空
+ martrix[row][col] = 0
+ // 绘制回溯步骤
+ await drawstep(row,col,0,martrix,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,实现动画效果
+ }
+ return false // 如果没有找到合适的值,返回false
+ }
+ }
+ }
+ }
+ return true // 如果所有格子都填满,返回true,表示求解成功
+}
+
+// 辅助函数,使用回溯算法解决数独
+async function autosolveHelper(u_M:number[][],STEP:ShowMsg[]):Promise{
+ // 遍历数独矩阵
+ for(let row = 0;row < 9;row++){
+ for(let col = 0; col < 9;col++){
+ if(u_M[row][col] === 0){ // 如果当前格子为空
+ // 获取该位置所有可能的数字
+ const possible = globalState.sudokuInstance.getPossible(row,col)
+ // 尝试每个可能值
+ for(const value of possible){
+ u_M[row][col] = value // 尝试填入一个可能值
+ const seq = STEP.length+1 // 计算当前步骤序号
+ // 创建步骤对象
+ const step:ShowMsg = {no:seq,row:row,col:col,selectNum:value}
+ STEP.push(step) // 将步骤对象添加到步骤数组中
+ globalState.notify() // 通知更新状态
+ drawGrayBackground(row,col,u_M) // 绘制选中格子背景
+ // 绘制当前步骤
+ await drawstep(row,col,value,u_M,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,起到选择速度的效果
+ // 递归调用,继续求解
+ if(await autosolveHelper(u_M,STEP)){
+ return true // 如果找到解决方案,返回true
+ }
+ // 回溯,将当前格子重置为空
+ u_M[row][col] = 0
+ drawGrayBackground(row,col,u_M) // 绘制选中格子背景
+ // 绘制回溯步骤
+ await drawstep(row,col,0,u_M,STEP)
+ await sleep(globalState.waittime) // 等待一段时间,实现动画效果
+ }
+ return false // 如果没有找到合适的值,返回false
+ }
+ }
+ }
+ return true // 如果所有格子都填满,返回true,表示求解成功
+}
+
+// 绘制步骤函数
+async function drawstep(row:number,col:number,value:number,u_M:number[][],STEP:ShowMsg[]){
+ globalState.sudokuInstance.martix = u_M // 更新数独矩阵
+ globalState.showArr = STEP // 更新显示数组
+ globalState.notify() // 通知状态更新
+ drawGrayBackground(row,col,u_M) // 绘制选中格子背景
+ checkWin() // 检查是否游戏胜利
+}
+
+// 检查游戏是否胜利
+export function checkWin(){
+ let count = 0 // 计数器,用于统计空格数量
+ const matrix = globalState.sudokuInstance.martix // 获取当前数独矩阵
+
+ // 遍历数独矩阵统计空格数量
+ for(let i = 0;i < 9;i++){
+ for(let j = 0;j < 9;j++){
+ if(matrix[i][j]===0){ // 若存在空格
+ count++
+ }
+ }
+ }
+
+ // 如果没有空格且数独符合规则,则游戏胜利
+ if(count===0 && globalState.sudokuInstance.judge()){
+ globalState.myTimer.stop() // 若胜利则将计时器暂停
+ globalState.showArr = [] // 将展示数据赋空
+ globalState.GameOver = true // Gameover标志为true,表示游戏结束
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/entryability/EntryAbility.ets b/sudukuing/sudoku/entry/src/main/ets/entryability/EntryAbility.ets
new file mode 100644
index 0000000..1c7aa63
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/entryability/EntryAbility.ets
@@ -0,0 +1,43 @@
+import AbilityConstant from '@ohos.app.ability.AbilityConstant';
+import hilog from '@ohos.hilog';
+import UIAbility from '@ohos.app.ability.UIAbility';
+import Want from '@ohos.app.ability.Want';
+import window from '@ohos.window';
+
+export default class EntryAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
+ }
+
+ onDestroy(): void {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage): void {
+ // Main window is created, set main page for this ability
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
+
+ windowStage.loadContent('pages/Index', (err, data) => {
+ if (err.code) {
+ hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
+ });
+ }
+
+ onWindowStageDestroy(): void {
+ // Main window is destroyed, release UI related resources
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
+ }
+
+ onForeground(): void {
+ // Ability has brought to foreground
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
+ }
+
+ onBackground(): void {
+ // Ability has back to background
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
+ }
+}
diff --git a/sudukuing/sudoku/entry/src/main/ets/pages/Index.ets b/sudukuing/sudoku/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000..35ac9ec
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,93 @@
+// 导入数独类和各个组件
+import SudoKu from "../classes/SudoKu"
+import Header from '../components/Header'
+import Main from '../components/Main'
+import ToolBar from '../components/ToolBar'
+import ShowContent from '../components/ShowContent'
+import { globalState } from '../common/constants/globalState'
+
+// 定义展示消息接口
+interface ShowMsg {
+ no: number // 序号
+ row: number // 行索引
+ col: number // 列索引
+ selectNum: number // 选择的数字
+}
+
+// 应用入口组件
+@Entry
+@Component
+struct Index {
+ // 状态变量定义
+ @State sudoku: SudoKu = globalState.sudokuInstance // 全局数独变量
+ @State showArr: ShowMsg[] = globalState.showArr // 展示数据的数组
+ @State showArrItem: ShowMsg = { no: 1, row: 0, col: 0, selectNum: 0 } // 每次展示的数据
+ @State ifSave: boolean = false // 判断是否是笔记模式
+
+ // 组件即将出现时的生命周期回调
+ aboutToAppear() {
+ this.initState()
+ // 订阅全局状态变化事件,当状态更新时同步数据
+ globalState.subscribe(() => {
+ this.updateStateFromGlobal()
+ })
+ }
+
+ // 初始化状态
+ initState() {
+ globalState.reset(); // 重置全局状态
+ this.updateStateFromGlobal() // 从全局状态更新本地状态
+ }
+
+ // 从全局状态更新本地状态
+ updateStateFromGlobal() {
+ this.sudoku = globalState.sudokuInstance
+ this.showArr = globalState.showArr.slice() // 复制数组避免引用问题
+
+ // 手动复制对象属性确保数据同步
+ this.showArrItem = {
+ no: globalState.showArrItem.no,
+ row: globalState.showArrItem.row,
+ col: globalState.showArrItem.col,
+ selectNum: globalState.showArrItem.selectNum
+ };
+ }
+
+ // 构建UI界面
+ build() {
+ Column() {
+ // 头部菜单栏
+ Header()
+ .width('100%')
+ .height('5%')
+ .backgroundColor(Color.White)
+
+ Row() {
+ // 左侧画布区域
+ Main({ showArr: this.showArr, showArrItem: this.showArrItem })
+ .width('50%')
+ .height('100%')
+
+ // 右侧内容展示与功能栏区域
+ Column() {
+ // 右侧内容展示区域
+ ShowContent({ ifSave: this.ifSave, showArr: this.showArr, showArrItem: this.showArrItem })
+ .width('100%')
+ .height('75%')
+
+ // 功能工具栏
+ ToolBar({ ifSave: this.ifSave })
+ .width('100%')
+ .height('25%')
+ }
+ .width('50%')
+ .height('100%')
+ }
+ .width('100%')
+ .height('95%')
+ }
+ .backgroundColor("#f5970d") // 设置背景颜色
+ .width('100%')
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/ets/utils/util.ets b/sudukuing/sudoku/entry/src/main/ets/utils/util.ets
new file mode 100644
index 0000000..da3072f
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/ets/utils/util.ets
@@ -0,0 +1,221 @@
+import * as CommonConstants from '../common/constants/CommonConstants'
+import { context2 } from '../common/constants/CommonConstants';
+import { globalState } from '../common/constants/globalState';
+
+// 绘制棋盘线条
+export function drawLine() {
+ // 设置线条样式
+ CommonConstants.context2.strokeStyle = "#f5970d"; //线条颜色
+ CommonConstants.context2.lineWidth = 1; //线条宽
+
+ // 绘制横线和竖线
+ for (let i = 0; i <= 9; i++) {
+ // 绘制横线
+ CommonConstants.context2.beginPath();
+ CommonConstants.context2.moveTo(CommonConstants.miniGridSize / 2, i * CommonConstants.gridSize + CommonConstants.miniGridSize / 2);
+ CommonConstants.context2.lineTo(CommonConstants.gridSize * 9 + CommonConstants.miniGridSize / 2, i * CommonConstants.gridSize + CommonConstants.miniGridSize / 2);
+ CommonConstants.context2.stroke();
+
+ // 绘制竖线
+ CommonConstants.context2.beginPath();
+ CommonConstants.context2.moveTo(i * CommonConstants.gridSize + CommonConstants.miniGridSize / 2, CommonConstants.miniGridSize / 2);
+ CommonConstants.context2.lineTo(i * CommonConstants.gridSize + CommonConstants.miniGridSize / 2, CommonConstants.gridSize * 9 + CommonConstants.miniGridSize / 2);
+ CommonConstants.context2.stroke();
+ }
+}
+
+// 绘制白色方块
+export function drawWhiteBlock(row:number, col:number){
+ const X = col * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+ const Y = row * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+ CommonConstants.context.drawImage(CommonConstants.imgWhiteBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+}
+
+// 绘制橙色方块
+export function drawOrangeBlock(row:number, col:number){
+ const X = col * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+ const Y = row * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+ CommonConstants.context.drawImage(CommonConstants.imgWhiteBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+}
+
+// 绘制区块棋盘背景
+export function drawBlockBoard() {
+ // 设置线条样式
+ CommonConstants.context.strokeStyle = '#00aacc'; //线条颜色
+ CommonConstants.context.lineWidth = 1; //线条宽
+
+ // 绘制9x9的棋盘格子,不同区域使用不同颜色背景
+ for (let i = 0; i <9; i++) {
+ for(let j=0;j<9;j++){
+ let X = i * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+ let Y = j * CommonConstants.gridSize+CommonConstants.miniGridSize/2
+
+ // 为特定区域设置黄色背景
+ if(i>=0&&i<=2&&j>=3&&j<=5){
+ CommonConstants.context.drawImage(CommonConstants.imgYellowBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+ }else if(i>=3&&i<=5&&j>=0&&j<=2){
+ CommonConstants.context.drawImage(CommonConstants.imgYellowBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+ }else if(i>=3&&i<=5&&j>=6&&j<=8){
+ CommonConstants.context.drawImage(CommonConstants.imgYellowBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+ }else if(i>=6&&i<=8&&j>=3&&j<=5){
+ CommonConstants.context.drawImage(CommonConstants.imgYellowBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+ }else{
+ // 其他区域使用白色背景
+ CommonConstants.context.drawImage(CommonConstants.imgWhiteBlock, X, Y,CommonConstants.gridSize,CommonConstants.gridSize);
+ }
+
+ }
+ }
+}
+
+// 记录上一次点击的行列位置
+let previousRow: number | null = null
+let previousCol: number | null = null
+
+// 绘制选中格子所在行列的灰色背景
+export function drawGrayBackground(row: number, col: number, matrix: number[][]) {
+ const context = CommonConstants.context2
+ const gridSize = CommonConstants.gridSize
+
+ // 恢复前一个点击的格子所在行列的背景颜色
+ if (previousRow !== null && previousCol !== null) {
+ // 清除之前选中行和列的背景
+ for (let i = 0; i < 9; i++) {
+ context.clearRect(i * gridSize + CommonConstants.miniGridSize / 2,
+ previousRow * gridSize + CommonConstants.miniGridSize / 2, gridSize, gridSize)
+ context.clearRect(previousCol * gridSize + CommonConstants.miniGridSize / 2,
+ i * gridSize + CommonConstants.miniGridSize / 2, gridSize, gridSize)
+ }
+ }
+
+ // 绘制当前点击的格子所在行和列的背景颜色为灰色
+ context.fillStyle = '#ffd4cfcf'
+ for (let i = 0; i < 9; i++) {
+ // 绘制选中行的背景
+ context.fillRect(i * gridSize + CommonConstants.miniGridSize / 2, row * gridSize + CommonConstants.miniGridSize / 2,
+ gridSize, gridSize)
+ // 绘制选中列的背景
+ context.fillRect(col * gridSize + CommonConstants.miniGridSize / 2, i * gridSize + CommonConstants.miniGridSize / 2,
+ gridSize, gridSize)
+ }
+
+ // 重新绘制数字
+ drawSudoku(matrix,context2) // 确保调用 `drawSudoku` 函数来重新绘制数字
+ drawLine() // 绘制线条
+ previousRow = row
+ previousCol = col
+}
+
+// 绘制数独数字
+export function drawSudoku(matrix: number[][], context: CanvasRenderingContext2D) {
+ const gridSize = CommonConstants.gridSize
+ const fontSize = gridSize * 1.5
+ context.font = `${fontSize}px Arial`
+ context.textAlign = 'center'
+ context.textBaseline = 'middle'
+
+ // 遍历数独矩阵绘制数字
+ for (let i = 0; i < 9; i++) {
+ for (let j = 0; j < 9; j++) {
+ const number = matrix[i][j]
+ if (number !== 0) {
+ let color = 'black' // 默认黑色填入(初始数字)
+ // 如果是玩家填入的数字
+ if (globalState.initMatrix[i][j] === 0) {
+ // 检查数字是否符合数独规则
+ const isValid = checkValidity(matrix, i, j, number)
+ // 根据是否符合规则选择颜色:蓝色表示有效,红色表示无效
+ color = isValid ? 'blue' : 'red'
+ }
+ context.fillStyle = color
+ // 在格子中心绘制数字
+ context.fillText(number.toString(), j * gridSize + gridSize / 2, i * gridSize + gridSize / 2)
+ }
+ }
+ }
+}
+
+// 检查数字在数独中的有效性
+function checkValidity(matrix: number[][], row: number, col: number, num: number): boolean {
+ // 检查行是否有重复数字
+ for (let j = 0; j < 9; j++) {
+ if (j !== col && matrix[row][j] === num) {
+ return false
+ }
+ }
+ // 检查列是否有重复数字
+ for (let i = 0; i < 9; i++) {
+ if (i !== row && matrix[i][col] === num) {
+ return false
+ }
+ }
+ // 检查3x3宫格是否有重复数字
+ const startRow = Math.floor(row / 3) * 3
+ const startCol = Math.floor(col / 3) * 3
+ for (let i = startRow; i < startRow + 3; i++) {
+ for (let j = startCol; j < startCol + 3; j++) {
+ if ((i !== row || j !== col) && matrix[i][j] === num) {
+ return false
+ }
+ }
+ }
+ return true // 所有检查通过,数字有效
+}
+
+// 绘制笔记数字
+export function drawNote(row: number, col: number, note:Set) {
+ // 计算小格子的大小和位置偏移
+ const miniCellSize = CommonConstants.gridSize / 3;
+ const xOffset = col * CommonConstants.gridSize + CommonConstants.miniGridSize / 2;
+ const yOffset = row * CommonConstants.gridSize + CommonConstants.miniGridSize / 2;
+
+ // 设置笔记数字的字体样式
+ context2.font = '35px Arial';
+ context2.textAlign = 'center';
+ context2.textBaseline = 'middle';
+ context2.fillStyle = 'blue';
+
+ // 遍历笔记数字集合,绘制每个数字
+ for(const num of note){
+ // 计算数字在格子中的位置
+ const x = xOffset + ((num - 1) % 3) * miniCellSize
+ const y = yOffset + Math.floor((num - 1) / 3) * miniCellSize
+ // 在指定位置绘制数字
+ context2.fillText(num.toString(), x + miniCellSize / 2, y + miniCellSize / 2)
+ }
+}
+
+// 填充笔记矩阵
+export function fillnoteMatrix(){
+ // 遍历所有格子
+ for(let row=0;row<9;row++){
+ for(let col=0;col<9;col++){
+ // 如果格子为空
+ if(globalState.sudokuInstance.martix[row][col]===0){
+ // 获取该位置所有可能的数字并添加到笔记中
+ for(const num of globalState.sudokuInstance.getPossible(row,col)){
+ globalState.addNoteNumber(row,col,num)
+ }
+ }
+ }
+ }
+}
+
+// 清除笔记矩阵
+export function clearnoteMatrix(){
+ // 遍历所有格子
+ for(let row=0;row<9;row++){
+ for(let col=0;col<9;col++){
+ // 如果是初始为空的格子
+ if(globalState.initMatrix[row][col]===0){
+ // 清除该位置的笔记
+ globalState.clearNotes(row,col)
+ }
+ }
+ }
+}
+
+// 异步函数sleep,用于休眠
+export async function sleep(ms:number):Promise {
+ return new Promise(resolve => setTimeout(resolve,ms))
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/module.json5 b/sudukuing/sudoku/entry/src/main/module.json5
new file mode 100644
index 0000000..f2ff2c7
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/module.json5
@@ -0,0 +1,37 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ets",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/element/color.json b/sudukuing/sudoku/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000..3c71296
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/element/string.json b/sudukuing/sudoku/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000..f945955
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/media/icon.png b/sudukuing/sudoku/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000..cd45acc
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/resources/base/media/icon.png differ
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo.png b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo.png
new file mode 100644
index 0000000..468ee55
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo.png differ
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo2.png b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo2.png
new file mode 100644
index 0000000..28a3d49
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo2.png differ
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo3.png b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo3.png
new file mode 100644
index 0000000..fe3db55
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/resources/base/media/icon_logo3.png differ
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/media/startIcon.png b/sudukuing/sudoku/entry/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000..366f764
Binary files /dev/null and b/sudukuing/sudoku/entry/src/main/resources/base/media/startIcon.png differ
diff --git a/sudukuing/sudoku/entry/src/main/resources/base/profile/main_pages.json b/sudukuing/sudoku/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000..1898d94
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/Index"
+ ]
+}
diff --git a/sudukuing/sudoku/entry/src/main/resources/en_US/element/string.json b/sudukuing/sudoku/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000..f945955
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/main/resources/zh_CN/element/string.json b/sudukuing/sudoku/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000..597ecf9
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "模块描述"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/ets/test/Ability.test.ets b/sudukuing/sudoku/entry/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000..8aa3749
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,35 @@
+import hilog from '@ohos.hilog';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', () => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ })
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ })
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ })
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ })
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ })
+ })
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/ets/test/List.test.ets b/sudukuing/sudoku/entry/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000..794c7dc
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,5 @@
+import abilityTest from './Ability.test';
+
+export default function testsuite() {
+ abilityTest();
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/ets/testability/TestAbility.ets b/sudukuing/sudoku/entry/src/ohosTest/ets/testability/TestAbility.ets
new file mode 100644
index 0000000..9484761
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/ets/testability/TestAbility.ets
@@ -0,0 +1,50 @@
+import UIAbility from '@ohos.app.ability.UIAbility';
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+import hilog from '@ohos.hilog';
+import { Hypium } from '@ohos/hypium';
+import testsuite from '../test/List.test';
+import window from '@ohos.window';
+import Want from '@ohos.app.ability.Want';
+import AbilityConstant from '@ohos.app.ability.AbilityConstant';
+
+export default class TestAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
+ hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
+ hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
+ let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator;
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
+ let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs;
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments();
+ hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
+ Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite);
+ }
+
+ onDestroy() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage) {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
+ windowStage.loadContent('testability/pages/Index', (err, data) => {
+ if (err.code) {
+ hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',
+ JSON.stringify(data) ?? '');
+ });
+ }
+
+ onWindowStageDestroy() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
+ }
+
+ onForeground() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
+ }
+
+ onBackground() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/ets/testability/pages/Index.ets b/sudukuing/sudoku/entry/src/ohosTest/ets/testability/pages/Index.ets
new file mode 100644
index 0000000..423b427
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/ets/testability/pages/Index.ets
@@ -0,0 +1,17 @@
+@Entry
+@Component
+struct Index {
+ @State message: string = 'Hello World';
+
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/sudukuing/sudoku/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets
new file mode 100644
index 0000000..917d27a
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets
@@ -0,0 +1,47 @@
+import hilog from '@ohos.hilog';
+import TestRunner from '@ohos.application.testRunner';
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+import Want from '@ohos.app.ability.Want';
+
+let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined
+let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined
+
+async function onAbilityCreateCallback() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
+}
+
+async function addAbilityMonitorCallback(err : Error) {
+ hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
+ }
+
+ async onRun() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ const bundleName = abilityDelegatorArguments.bundleName;
+ const testAbilityName = 'TestAbility';
+ let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = {
+ abilityName: testAbilityName,
+ onAbilityCreate: onAbilityCreateCallback,
+ };
+ abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
+ const want: Want = {
+ bundleName: bundleName,
+ abilityName: testAbilityName
+ };
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
+ abilityDelegator.startAbility(want, (err, data) => {
+ hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? '');
+ hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? '');
+ })
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/module.json5 b/sudukuing/sudoku/entry/src/ohosTest/module.json5
new file mode 100644
index 0000000..4fc9701
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/module.json5
@@ -0,0 +1,37 @@
+{
+ "module": {
+ "name": "entry_test",
+ "type": "feature",
+ "description": "$string:module_test_desc",
+ "mainElement": "TestAbility",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:test_pages",
+ "abilities": [
+ {
+ "name": "TestAbility",
+ "srcEntry": "./ets/testability/TestAbility.ets",
+ "description": "$string:TestAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:TestAbility_label",
+ "exported": true,
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "skills": [
+ {
+ "actions": [
+ "action.system.home"
+ ],
+ "entities": [
+ "entity.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/color.json b/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/color.json
new file mode 100644
index 0000000..3c71296
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/string.json b/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/string.json
new file mode 100644
index 0000000..65d8fa5
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_test_desc",
+ "value": "test ability description"
+ },
+ {
+ "name": "TestAbility_desc",
+ "value": "the test ability"
+ },
+ {
+ "name": "TestAbility_label",
+ "value": "test label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/ohosTest/resources/base/media/icon.png b/sudukuing/sudoku/entry/src/ohosTest/resources/base/media/icon.png
new file mode 100644
index 0000000..ce307a8
Binary files /dev/null and b/sudukuing/sudoku/entry/src/ohosTest/resources/base/media/icon.png differ
diff --git a/sudukuing/sudoku/entry/src/ohosTest/resources/base/profile/test_pages.json b/sudukuing/sudoku/entry/src/ohosTest/resources/base/profile/test_pages.json
new file mode 100644
index 0000000..b7e7343
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/ohosTest/resources/base/profile/test_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "testability/pages/Index"
+ ]
+}
diff --git a/sudukuing/sudoku/entry/src/test/List.test.ets b/sudukuing/sudoku/entry/src/test/List.test.ets
new file mode 100644
index 0000000..bb5b5c3
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/test/List.test.ets
@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+ localUnitTest();
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/entry/src/test/LocalUnit.test.ets b/sudukuing/sudoku/entry/src/test/LocalUnit.test.ets
new file mode 100644
index 0000000..ed22d4d
--- /dev/null
+++ b/sudukuing/sudoku/entry/src/test/LocalUnit.test.ets
@@ -0,0 +1,33 @@
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+ describe('localUnitTest',() => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ });
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ });
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ });
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ });
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ });
+ });
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/hvigor/hvigor-config.json5 b/sudukuing/sudoku/hvigor/hvigor-config.json5
new file mode 100644
index 0000000..61c774a
--- /dev/null
+++ b/sudukuing/sudoku/hvigor/hvigor-config.json5
@@ -0,0 +1,17 @@
+{
+ "modelVersion": "5.1.1",
+ "dependencies": {
+ },
+ "execution": {
+ // "daemon": true, /* Enable daemon compilation. Default: true */
+ // "incremental": true, /* Enable incremental compilation. Default: true */
+ // "parallel": true, /* Enable parallel compilation. Default: true */
+ // "typeCheck": false, /* Enable typeCheck. Default: false */
+ },
+ "logging": {
+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
+ },
+ "debugging": {
+ // "stacktrace": false /* Disable stacktrace compilation. Default: false */
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/hvigorfile.ts b/sudukuing/sudoku/hvigorfile.ts
new file mode 100644
index 0000000..f3cb9f1
--- /dev/null
+++ b/sudukuing/sudoku/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/sudukuing/sudoku/oh-package-lock.json5 b/sudukuing/sudoku/oh-package-lock.json5
new file mode 100644
index 0000000..7487ffe
--- /dev/null
+++ b/sudukuing/sudoku/oh-package-lock.json5
@@ -0,0 +1,21 @@
+{
+ "meta": {
+ "stableOrder": true,
+ "enableUnifiedLockfile": false
+ },
+ "lockfileVersion": 3,
+ "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+ "specifiers": {
+ "@ohos/hypium@1.0.11": "@ohos/hypium@1.0.11"
+ },
+ "packages": {
+ "@ohos/hypium@1.0.11": {
+ "name": "",
+ "version": "1.0.11",
+ "integrity": "sha512-KawcLnv43C3QIYv1UbDnKCFX3MohtDxGuFvzlUxT/qf2DBilR56Ws6zrj90LdH6PjloJQwOPESuBQIHBACAK7w==",
+ "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.11.tgz",
+ "shasum": "fa799d273fa7d921701578c5e7084849354a4af0",
+ "registryType": "ohpm"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sudukuing/sudoku/oh-package.json5 b/sudukuing/sudoku/oh-package.json5
new file mode 100644
index 0000000..8fae540
--- /dev/null
+++ b/sudukuing/sudoku/oh-package.json5
@@ -0,0 +1,13 @@
+{
+ "modelVersion": "5.1.1",
+ "license": "",
+ "devDependencies": {
+ "@ohos/hypium": "1.0.11"
+ },
+ "author": "",
+ "name": "sudoku",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "version": "1.0.0",
+ "dependencies": {}
+}
\ No newline at end of file