最近用css+vue实现了这样一款数独界面,可以说除了侯选数其他的功能还算完善。
实现效果
前端代码
一共三个文件
index.html
<!doctype html>
<html>
<head>
<title>数独</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="index.css">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<div class="container cf" @keydown="move($event)">
<div v-for="Row,index in allnums" class="row">
<div tabindex="-1" v-for="num1,indexSub in Row" :class="{
'Hlight':index==nowPos.x||indexSub==nowPos.y||getBlock(index,indexSub)==nowBlock,
'no':num1==0&&index==nowPos.x&&indexSub==nowPos.y,
'num':num1!=0&&index==nowPos.x&&indexSub==nowPos.y,
'newnums':oldnums[index][indexSub]==0,
'other-num':num1==nowClickNum,
'error':num1==nowNum&&(index == optionPos.x||indexSub == optionPos.y||getBlock(index,indexSub)==nowClickBlock)&&(index != optionPos.x||indexSub != optionPos.y),
'err':num1 != answer[index][indexSub]&&num1!=0
}" @keydown="fillnum($event,index,indexSub)" @click="hitNum(index,indexSub)">
{{num1==0?'':num1}}
</div>
</div>
</div>
<div class="container newcontainer">
<div v-for="index in 9" class="block"></div>
</div>
<div class="header">
<button @click="getSodoku">换一题</button>
<button @click="getAnswer">获取答案</button>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
index.js
new Vue({
el: '#app',
data: {
oldnums: [], //保存初始题目
allnums: [],
answer: [],
nowPos: {}, //当前鼠标移入位置
optionPos: {}, //当前填入数字的位置
nowClickBlock: -1, //当前鼠标点击宫
nowBlock: -1, //当前鼠标移入宫
nowNum: 0, //当前填入的数值
gameover: false, //游戏是否结束
nowClickNum: -1,
clickPos: {
x: 0,
y: 0
}, //鼠标点击位置
},
created() {
this.getSodoku();
},
methods: {
reset() {
this.nowNum = -1;
},
getSodoku() {
axios.get('http://localhost:8888/getsodoku')
.then((res) => {
this.allnums = res.data;
this.answer = JSON.parse(JSON.stringify(this.allnums));
this.oldnums = JSON.parse(JSON.stringify(this.allnums));
var parm = {
sodoku: JSON.stringify(this.allnums),
};
axios.post('http://localhost:8888/getanswer', parm)
.then((res) => {
this.answer = res.data;
this.reset();
});
});
},
getAnswer() {
this.allnums = JSON.parse(JSON.stringify(this.answer));
this.reset();
},
move(e) {
var num = e.key;
if (num == "ArrowUp") {
this.clickPos.x = (this.clickPos.x + 8) % 9;
}
if (num == "ArrowDown") {
this.clickPos.x = (this.clickPos.x + 1) % 9;
}
if (num == "ArrowLeft") {
this.clickPos.y = (this.clickPos.y + 8) % 9;
}
if (num == "ArrowRight") {
this.clickPos.y = (this.clickPos.y + 1) % 9;
}
this.hitNum(this.clickPos.x, this.clickPos.y);
},
hitNum(x, y) {
this.showHlight(x, y);
this.optionPos={
x:x,
y:y
};
this.nowClickBlock = this.getBlock(x,y);
this.nowNum = this.allnums[x][y]==0?-1:this.allnums[x][y];
this.clickPos = {
x: x,
y: y
};
var obj = document.getElementsByClassName("container")[0].children[x].children[y];
obj.focus();
},
showHlight(x, y) {
var num = this.allnums[x][y];
if (num == 0) this.nowClickNum = -1;
else this.nowClickNum = num;
this.nowPos = {
x: x,
y: y,
}
this.nowBlock = this.getBlock(x, y);
},
getBlock(x, y) {
x = parseInt(x / 3);
y = parseInt(y / 3);
return x * 3 + y;
},
checkWin() {
var count = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
count[this.allnums[i][j] - 1]++;
}
}
for (var i = 0; i < 9; i++) {
if (count[i] != 9) return false;
}
return true;
},
fillnum(e, x, y) {
var num = e.key;
if (num == 0) {
this.$set(this.allnums[x], y, num);
this.nowClickNum = -1;
this.nowNum = -1;
}
if (num >= 1 && num <= 9 && this.oldnums[x][y] == 0) {
this.optionPos = {
x: x,
y: y,
}
this.nowClickBlock = this.getBlock(x, y);
this.$set(this.allnums[x], y, num);
this.nowNum = num;
this.nowClickNum = num;
this.gameover = true;
for (var i = 0; i < 9; i++) {
for (var j = 0; j < 9; j++) {
if (this.allnums[i][j] == 0) this.gameover = false;
}
}
if (this.gameover && this.checkWin()) alert("你赢了");
}
}
},
});
index.css
*{
margin:0;
padding:0;
}
.header{
width:450px;
height: 50px;
position: absolute;
left: 50%;
transform: translate(-50%);
}
.header button{
background-color: #4CAF50;
border: none;
color: white;
border-radius: 2%;
width: 121px;
height: 25px;
margin-left: 50px;
margin-right: 50px;
text-align: center;
margin-top: 29px;
}
.container{
width: 450px;
height: 450px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
background-color: rebeccapurple;
}
.row{
width: 100%;
height: 50px;
background-color: #bababa;
float: left;
display: flex;
flex-direction: row;
}
.row div{
width: 49px;
height: 49px;
margin: 0.5px;
text-align: center;
line-height: 49px;
color: rgb(93,94,112);
font-size: 28px;
background-color: rgb(255,255,255);
pointer-events: auto;
}
.block{
width: 148px;
height: 148px;
border: 1px solid rgb(93,94,112);
}
.newcontainer{
background-color: transparent;
display: flex;
flex-direction: row;
flex-wrap: wrap;
pointer-events: none;
}
.Hlight{
background-color: rgb(238,238,238) !important;
color: rgb(93,94,112) !important;
}
.newnums{
color: rgb(123,135,187) !important;
}
.other-num{
background-color: rgb(94,95,113) !important;
color: #fff !important;
}
.no{
background-color: rgb(179,182,187) !important;
outline: none;
}
.num{
background-color: rgb(140,163,57) !important;
color: #fff !important;
}
/* 和新填入数冲突位置的样式 */
.error{
background-color: rgb(238,238,238) !important;
color: red !important;
}
/* 新填入数错误时的样式 */
.err{
background-color: red !important;
color:#fff !important;
}
/* 'err':num1==nowNum&&(index == nowClickPos.x||indexSub == nowClickPos.y||getBlock(index,indexSub)==nowClickBlock)&&(index != nowClickPos.x||indexSub != nowClickPos.y), */
.cf::after{
height: 0px;
display: block;
clear: both;
visibility: hidden;
content: '';
}
后端部分
项目地址
https://gitee.com/yabcd/sodoku.git
用原来的helloworld项目改写的springboot项目,项目名字都没有改,使用DLX算法解题。生成题目采用随机删数,DLX求解,无解则回溯,回溯超过十次终止算法。
项目结构
生成jar包
生成后的jar包在target目录下,使用java -jar jar包名运行项目