前几天在刷算法的时候,遇到了这么一个题目——36. 有效的数独 - 力扣(LeetCode),做完之后就有了一个想法:这不就可以当做数独游戏的判断条件(当然还必须判断数独矩阵是否被填满)吗?以前写个人博客的时候还想着写一下小游戏,正好也有挺久没接触vue3了,就当练练手写了这个小游戏。
预览:数独小游戏(手机和电脑都能玩)
首先我们需要解决的是数独初始化问题,我的想法是:先生成一个随机的完整的数独,再挖几个空
随机生成一个完整的数独:
我最开始的想法是:先建一个9 * 9的二维数组arr,arr的每一行都是1到9,然后再将每一行随机打乱(就相当于洗牌嘛),就硬去碰运气嘛,我就不信了。结果就是不行,我跑了将近2分钟,都没一个是符合数独的要求的,我还跑了5次,应该不是运气的原因。
既然运气不行就想别的办法呗,然后我又想到一个,二维数组每一项就是一个数组,我再将每个田字格也当成一个数组,每随机填一个数前就用indexof判断是否有重复的,有重复的就再换个数嘛。其实这还是碰运气,因为我没去管竖的是否满足不重复的条件,我想着如果要去检测竖的是否满足要求(感觉会很复杂),那我还不如去碰运气。结果还是不行。然后我想着既然竖的不好判断,那我就按竖的填数字不就好了嘛!
完成完整数独的大概代码:
function randNine() {
return [1, 2, 3, 4, 5, 6, 7, 8, 9].sort(() => Math.random() - 0.5)
}
function makeMatrix(n) {
return Array.from(Array(n), () => [])
}
//如果当前这个数字不符合要求,就将这个数字及以后的移一下
function moveArrItem(arr, start) {
const first = arr[start]
for (let i = start; i < 8; i++) {
arr[i] = arr[i + 1]
}
arr[8] = first
}
function makeSudoku() {
while (1) {
let x =-1
const digits = makeMatrix(9)
let randNineArr = randNine()
for (let i = 0; i < 9; i++) {
digits[i].push(randNineArr[i])
}
let box = makeMatrix(3)
box[0] = [...randNineArr.slice(0, 3)]
box[1] = [...randNineArr.slice(3, 6)]
box[2] = [...randNineArr.slice(6)]
let flag = 0
for (let i = 1; i < 9; i++) {
randNineArr = randNine()
//每三列,就初始化九宫格
if (i % 3 === 0) {
box = makeMatrix(3)
}
let count = 0
for (let j = 0; j < 9; j++) {
//判断横向和九宫格里是否重复
if (box[Math.floor(j / 3)].indexOf(randNineArr[j]) === -1 && digits[j].indexOf(randNineArr[j]) === -1) {
x = box[Math.floor(j / 3)].indexOf(randNineArr[j])
digits[j].push(randNineArr[j])
box[Math.floor(j / 3)].push(randNineArr[j])
count = 0
} else {
count++
moveArrItem(randNineArr, j)
j--
if (count > 8 - j) {
flag = 1
break
}
}
}
if (flag) break
}
if (!flag) {
return digits
}
}
}
那之后也就简单很多了,无非是调一调样式了
布局还是用flex布局(其实最开始是用的grid,但我不是特别熟练,遇到一些问题后就直接用永远滴神--flex了)
还有就是挖空了,我是随机选出48个坐标(当然你可以改一下,我一直疑惑,到底是空越多越难还是越少越难?),将其在数独上的数字改为-1,然后再html中判断(v-if)是否为-1,如果是-1就不显示
HTML部分:
<template>
<div class="box1">
<div class="box_grid">
<div class="row" v-for="(item1, index1) in arr2" :key="item1">
<div
class="row_item_outer"
v-for="(item2, index2) in item1"
:key="item2"
>
<div
:class="{
row_item_inner_active: index1 + '' + index2 === divActive,
}"
class="row_item_inner"
@click="divClick(index1, index2)"
>
<template v-if="item2 !== -1">
<template v-if="unChanged.indexOf(index1 + '' + index2) === -1">
<span>{{ item2 }}</span>
</template>
<template v-else
><span
style="color: blue; font-size: 18px; font-weight: bolder"
>{{ item2 }}</span
></template
>
</template>
</div>
</div>
</div>
<div class="numberBtns">
<button v-for="item in 9" :key="item" @click="btnClick(item)">
{{ item }}
</button>
</div>
<div class="gameSetBtns">
<button @click="restart()">重新开始</button>
<button @click="reset()">复原</button>
<el-button @click="finish()">完成</el-button>
</div>
</div>
</div>
</template>
ts部分:
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { Action } from "element-plus";
class Sudoku {
digits: number[][];
unChanged: string[];
unChangedCount: number;
constructor() {
this.digits = this.makeMatrix(9);
this.unChangedCount = 48;//挖掉48个空
this.unChanged = [];//挖掉的空
}
makeSudoku(): boolean {
const temp = this.makeMatrix(9);
let randNineArr = this.randNine();
for (let i = 0; i < 9; i++) {
temp[i].push(randNineArr[i]);
}
let box = this.makeMatrix(3);
box[0] = [...randNineArr.slice(0, 3)];
box[1] = [...randNineArr.slice(3, 6)];
box[2] = [...randNineArr.slice(6)];
for (let i = 1; i < 9; i++) {
randNineArr = this.randNine();
if (i % 3 === 0) {
box = this.makeMatrix(3);
}
let count = 0;
for (let j = 0; j < 9; j++) {
if (
box[Math.floor(j / 3)].indexOf(randNineArr[j]) === -1 &&
temp[j].indexOf(randNineArr[j]) === -1
) {
temp[j].push(randNineArr[j]);
box[Math.floor(j / 3)].push(randNineArr[j]);
count = 0;
} else {
count++;
this.moveArrItem(randNineArr, j);
j--;
if (count > 8 - j) {
return false;
}
}
}
}
this.digits = temp;
return true;
}
randNine() {
return [1, 2, 3, 4, 5, 6, 7, 8, 9].sort(() => Math.random() - 0.5);
}
makeMatrix(n: number): number[][] {
return Array.from(Array(n), (): number[] => []);
}
moveArrItem(arr: number[], start: number) {
const first = arr[start];
for (let i = start; i < 8; i++) {
arr[i] = arr[i + 1];
}
arr[8] = first;
}
shuffle() {
this.unChanged = [];
while (this.unChanged.length < this.unChangedCount) {
const x = Math.floor(Math.random() * 9);
const y = Math.floor(Math.random() * 9);
if (this.unChanged.indexOf(x + "" + y) !== -1) {
continue;
} else {
this.unChanged.push(`${x}${y}`);
this.digits[x][y] = -1;
}
}
}
}
const indexs = ref([12, 12]);
const sudoku = new Sudoku();
while (!sudoku.makeSudoku()) {}
sudoku.shuffle();
const arr2 = ref(copy(sudoku.digits));
const divActive = ref("");
const unChanged = ref([...sudoku.unChanged]);
function btnClick(x: number) {
arr2.value[indexs.value[0]][indexs.value[1]] = x;
}
function divClick(index1: number, index2: number) {
if (sudoku.unChanged.indexOf(index1 + "" + index2) !== -1) {
divActive.value = index1 + "" + index2;
indexs.value = [];
indexs.value.push(index1, index2);
}
}
function restart() {
while (!sudoku.makeSudoku()) {}
sudoku.shuffle();
unChanged.value = [...sudoku.unChanged];
arr2.value = copy(sudoku.digits);
}
function reset() {
arr2.value = copy(sudoku.digits);
}
function isValidSudoku(board: number[][]): boolean {
const col = {};
const box = {};
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
const x = board[i][j];
const boxIndex = Math.floor(i / 3) * 3 + Math.floor(j / 3);
if (col[j + "" + x] || box[boxIndex + "" + x]) {
return false;
}
col[j + "" + x] = 1;
box[boxIndex + "" + x] = 1;
}
}
return true;
}
function copy(arr: number[][]): number[][] {
const re: number[][] = [];
for (let i = 0; i < arr.length; i++) {
const temp = arr[i].concat();
re.push(temp);
}
return re;
}
function finish() {
for (const item of arr2.value) {
for (const x of item) {
if (x === -1) {
ElMessageBox.alert("还未完成", "Title", {
confirmButtonText: "OK",
callback: (action: Action) => {
ElMessage({
type: "info",
message: `action: ${action}`,
});
},
});
return;
}
}
}
if (isValidSudoku(arr2.value)) {
ElMessageBox.alert("You Win!!!", "Title", {
// if you want to disable its autofocus
// autofocus: false,
confirmButtonText: "OK",
callback: (action: Action) => {
ElMessage({
type: "info",
message: `action: ${action}`,
});
},
});
} else {
ElMessageBox.alert("失败", "Title", {
// if you want to disable its autofocus
// autofocus: false,
confirmButtonText: "OK",
callback: (action: Action) => {
ElMessage({
type: "info",
message: `action: ${action}`,
});
},
});
}
}
</script>
css部分:
<style scoped lang="less">
.box1 {
padding: 20px;
.box_grid {
.row {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
.row_item_outer {
width: 30px;
height: 30px;
border: 0.2px solid rgba(0, 0, 0, 0.2);
}
.row_item_inner {
width: 27px;
height: 27px;
}
.row_item_inner_active {
background-color: yellow;
}
.row_item_outer:first-child {
border-left: 2px solid rgba(0, 0, 0, 1);
}
.row_item_outer:nth-child(3n) {
border-right: 2px solid rgba(0, 0, 0, 1);
}
}
.row:nth-child(1) {
border-top: 2px solid rgba(0, 0, 0, 1);
div {
border-top: none;
}
}
.row:nth-child(3n) {
border-bottom: 2px solid rgba(0, 0, 0, 1);
div {
border-bottom: none;
}
}
.numberBtns {
margin-top: 15px;
button {
width: 30px;
height: 30px;
}
}
.gameSetBtns {
margin-top: 15px;
button {
margin-right: 15px;
}
}
}
}
</style>