sudokuing
|
|
@ -0,0 +1,21 @@
|
|||
# pointgaming
|
||||
### 1、项目说明
|
||||
总体思路:扫雷程序项目开发的总体思路如图1.1示意,拟实现交互界面与交互控制、数据结构和扫雷算法等。
|
||||
图1.1 项目开发的总体思路图
|
||||
<img src="figs/overall.png" width=80% /></center>
|
||||
|
||||
|
||||
### 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 项目主要函数调用关系图
|
||||
<img src="figs/relation.png" width=80% /></center>
|
||||
|
||||
|
||||
图3.2 项目输出界面演进图
|
||||
<img src="figs/evolution.png" width=80% /></center>
|
||||
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "app_name",
|
||||
"value": "Sudoku"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
||||
|
|
@ -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",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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. */
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"license": "",
|
||||
"devDependencies": {},
|
||||
"author": "",
|
||||
"name": "entry",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;i<m*3+3;i++){
|
||||
subBlock.push(this.martix[i].slice(n*3,n*3+3)) // 切片每一行的指定列范围
|
||||
}
|
||||
let block_martix=subBlock.flat()// 使用flat()方法将二维数组转换为一维数组
|
||||
let block_set=block_martix.filter(num=>num!=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<number>{
|
||||
let Nums:Set<number>=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<number>=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<m*3+3;i++) {
|
||||
for (let j = n*3; j < n*3+3; j++) {
|
||||
if(row==i&&j==col){
|
||||
continue // 跳过当前位置
|
||||
}
|
||||
set.add(this.martix[i][j])//将所在九宫格出现数字加入集合
|
||||
}
|
||||
}
|
||||
|
||||
// 返回1-9的集合与出现数字集合的差集,即可填数字
|
||||
return this.setDifference(Nums,set)
|
||||
}
|
||||
|
||||
// 集合差集运算
|
||||
setDifference(setA:Set<number>, setB:Set<number>) {
|
||||
// 创建一个新的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
|
||||
}
|
||||
}
|
||||
|
|
@ -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");//黄色方块背景
|
||||
|
|
@ -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<number>[][] // 存储每个位置的笔记数字
|
||||
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<number>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化状态
|
||||
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<number>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数独重置函数
|
||||
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()
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -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%')
|
||||
}
|
||||
}
|
||||
|
|
@ -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) // 绘制每个格子的笔记
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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%')
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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%')
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<void>{
|
||||
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<void>{
|
||||
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<boolean>{
|
||||
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<boolean>{
|
||||
// 遍历数独矩阵
|
||||
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,表示游戏结束
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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%')
|
||||
}
|
||||
}
|
||||
|
|
@ -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<number>) {
|
||||
// 计算小格子的大小和位置偏移
|
||||
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<void> {
|
||||
return new Promise(resolve => setTimeout(resolve,ms))
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "模块描述"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import abilityTest from './Ability.test';
|
||||
|
||||
export default function testsuite() {
|
||||
abilityTest();
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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%')
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"testability/pages/Index"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import localUnitTest from './LocalUnit.test';
|
||||
|
||||
export default function testsuite() {
|
||||
localUnitTest();
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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 */
|
||||
}
|
||||
}
|
||||
|
|
@ -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. */
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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": {}
|
||||
}
|
||||