基于JavaScript的网页版数独游戏

Resurgam

发布日期: 2019-03-08 17:21:06 浏览量: 614
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

数据结构课设

这学期的数据结构课设

用JavaScript写得数独游戏网页

一、 问题描述

设计并实现一个基本的数独游戏。功能包括根据用户选择的级别给出初始盘面数字(级别越低,数字越多),并且能够实时监测并提示冲突;当用户完成后,保存最近十次的成绩(完成一次所需时间)以及最好成绩。

二、 问题分析

数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。

因此,核心问题就是我们需要得到一个含有一定数目空格的九宫格,并且一定存在一种填写方式使得九宫格满足数独的要求。

我们可以先得到一个完整的,满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复的数独表。然后随机得到数独表中空格的位置,在通过可视化生成游戏界面,实现交互功能。

在用户与界面交互过程中,实时监测冲突是否产生,检验提交的答案是否正确,保存最近十次成绩和最好的一次成绩。

三、 算法设计

本游戏为使用JavaScript开发的网页数独游戏,因此以下算法实现为JavaScript实现。

经问题分析可得我们需要解决的几个核心子问题,并设计算法如下:

(一) 传统回溯法生成数独表

网上提供的数独算法多种多样,但绝大部分的核心思想依然是传统回溯法:

通过一个9*9的二维数组sudu表示一个数独九宫格。

首先初始化该数组,值都为0,然后将该九宫格的第一行依次随机填入1-9中的一个数字。接下来,从第二行开始,遍历该数组剩下的元素。

每遍历到一个位置,随机填入1-9中的一个数字,然后判断这个九宫格目前的所有数字是否满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复的要求。如果满足,继续向下一个位置遍历;如果不满足,则重新随机填写该位置。当重复填写了十次之后依然不满足要求,则回溯到上一个位置,重新填写该位置。

直到遍历完所有数组后,我们也就得到了一个完整的数独表。用sudu表示。

Math.floor(Math.random() * 9 + 1)可得到1-9范围内的随机整数。

(二) 随机生成难度级别不同的数独

由(一)得,sudu数组保存着完整的数独表,那么游戏中的有空格的数独表可以用9*9的二维数组curSudu表示,其中空格位置的数字设为0。如果填入了数字,则实时修改curSudu中该位置的值。

通过空格个数的不同来区分数独难度级别,空格个数越多,难度级别越高。设计该游戏难度级别有三个,分别为,初级:15个空格;中级:30个空格;高级:45个空格。

欲实现该需求,则先根据难度随机生成对应数目的空格的位置坐标(i,j),用数组表示为:blanks=[{row: i, column: j},……]。其中0 <= i<= 8, 0 <= j <= 8。通过Math.floor(Math.random() *9)即可生成需要的i,j。curSudu一开始等于sudu,得到blanks后将curSudu对应空格的值设为0。

(三) 用户填写数独时实时判断是否产生冲突

用户每填写完一个数字,都会触发判断冲突事件。由于之前填过的数字都被判断过,所以只需要判断刚刚填入的数字所在的行,列,粗线宫内是否满足条件。

首先保存刚刚填入的数字num,然后对所在的行,列,(3*3)区域,分别进行遍历,假如遍历到的数字等于刚刚填入的数字并且所在的位置不同,则该填入数字在九宫格内产生了冲突;否则没有冲突。

(四) 保存最近十次成绩和最好成绩

设计成绩的数据结构为:

  1. let user = {
  2. username: username,
  3. time: time
  4. }

时间表示成绩高低,时间越短,成绩越好。

每当游戏开始时,将当前时刻距 1970 年 1月 1 日之间的毫秒数保存在全局变量time中。当提交答案,游戏结束时,time = ((new Date().getTime() - time) / 1000 / 60).toFixed(2),得到从开始到结束的时间,以分钟为单位,保留两位小数。

每次提交成绩时,将用户名和成绩保存在user中。然后从数据中通过getRank()得到最近的十次成绩(用长度为10的一维数组ranks表示),通过getMaxGrade()得到最好的一次成绩maxGrade。

将user.time与maxGrade.time进行比较,如果user.time比maxGrade.time更小,则maxGrade=user;然后将user插入ranks的最前面,去掉ranks最后一个元素,将ranks的长度保持在十。然后存入数据中

四、 软件架构

该软件是由JavaScript,HTML,CSS编写的的网页游戏,其中主要逻辑算法由JavaScript完成。而其软件架构为MVC模式

(一) 控制层(Controller)

JavaScript是一门事件驱动型的语言,通过在网页的特定DOM中绑定相应的事件监听,当用户在界面上特定的DOM元素上进行特定操作时就会触发对应的事件监听函数时,对逻辑进行处理。

(二) 视图(View)

该软件的界面由HTML和CSS决定,文件夹中的index.html和style.css会在浏览器的运行下生成对应的DOM树与样式,并在浏览器上渲染出界面。Js可以修改DOM树和样式,从而改变视图。

(三) 模型(Model)

sudu.js和index.js文件中保存着实现处理事件的对应代码。而数据保存在浏览器的localstorage中。因为由于规定对浏览器的限制,浏览器只能读取本地文件,却不能修改本地文件,所以在不使用服务器的情况下,本软件通过浏览器的localstorage来保存修改成绩。

五、 实现与测试

(一)软件实现

A.逻辑处理

  1. 1)生成数独表和判断是否冲突的函数
  2. sudu.js中定义Sudu函数,返回函数:
  3. 回溯法生成数独表:gennerateShudu(),

判断填入元素所在的行,列,(3*3)是否冲突:checkRow(), checkColumn(), checkNine()。

以下为代码:

  1. let Sudu = (function() {
  2. //初始化二维数组
  3. let gennerateArr = function () {
  4. let arr = new Array(9);
  5. for (let i = 0; i < arr.length; i++) {
  6. arr[i] = new Array(9);
  7. arr[i].fill(0, 0, 9);
  8. }
  9. return arr
  10. }
  11. //对二维数组的第一行随机填入不重复的1-9数字
  12. let init = function(firstRow) {
  13. for (let i = 0; i < firstRow.length; i++) {
  14. while (true) {
  15. let rand = Math.floor(Math.random() * 9 + 1);
  16. if (firstRow.indexOf(rand) === -1) {
  17. firstRow[i] = rand;
  18. break;
  19. }
  20. }
  21. }
  22. }
  23. //在生成数独表的过程中,判断填入的数字是否会冲突
  24. let judge = function(row, column, num, sudu) {
  25. //judge row
  26. for (let i = 0; i < column; i++) {
  27. if (sudu[row][i] === num) {
  28. return false;
  29. }
  30. }
  31. //judge column
  32. for (let i = 0; i < row; i++) {
  33. if (sudu[i][column] === num) {
  34. return false;
  35. }
  36. }
  37. //judge local
  38. let count = column % 3 + row % 3 * 3;
  39. while (count--) {
  40. //console.log(row - row % 3 + Math.floor(count / 3), column - column % 3 + count % 3);
  41. if (sudu[row - row % 3 + Math.floor(count / 3)][column - column % 3 + count % 3] === num) {
  42. return false;
  43. }
  44. }
  45. return true;
  46. }
  47. //回溯法生成完整数独表
  48. let gennerateShudu = function() {
  49. let sudu = gennerateArr();
  50. init(sudu[0]);
  51. let filltime = 0;
  52. for (let i = 1; i < 9; i++) {
  53. for (let j = 0; j < 9; j++) {
  54. filltime = 0;
  55. while(filltime < 10) {
  56. let num = Math.floor(Math.random() * 9 + 1);
  57. if (judge(i, j, num, sudu)) {
  58. sudu[i][j] = num;
  59. break;
  60. } else {
  61. filltime++;
  62. }
  63. }
  64. if (filltime >= 10) {
  65. if (j === 0) {
  66. i--;
  67. j = 8;
  68. } else {
  69. j--;
  70. j--;
  71. }
  72. }
  73. }
  74. }
  75. return sudu;
  76. }
  77. let checkRow = function(row, column, num, curSudu) {
  78. for (let i = 0; i < 9; i++) {
  79. if (curSudu[row][i] == 0) {
  80. continue;
  81. }
  82. if (curSudu[row][i] == num && i != column) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. }
  88. let checkColumn = function(row, column, num, curSudu) {
  89. for (let i = 0; i < 9; i++) {
  90. if (curSudu[i][column] == 0) {
  91. continue;
  92. }
  93. if (curSudu[i][column] == num && i != row) {
  94. return false;
  95. }
  96. }
  97. return true;
  98. }
  99. let checkNine = function (row, column, num, curSudu) {
  100. let j = Math.floor(row / 3) * 3;
  101. let k = Math.floor(column / 3) * 3;
  102. // 循环比较
  103. for (let i = 0; i < 8; i++) {
  104. if (curSudu[j + Math.floor(i / 3)][k + i % 3] == 0) {
  105. continue;
  106. }
  107. if (curSudu[j + Math.floor(i / 3)][k + Math.round(i % 3)] == num && row != j + Math.floor(i / 3) && column != k + Math.round(i % 3)) {
  108. return false;
  109. }
  110. }
  111. return true;
  112. }
  113. return {
  114. gennerateShudu: gennerateShudu,
  115. checkRow: checkRow,
  116. checkColumn: checkColumn,
  117. checkNine: checkNine
  118. };
  119. })()

B.游戏的逻辑和界面的处理

  1. let levelBlank = { //难度对应的空格数目
  2. "初级": 15,
  3. "中级": 30,
  4. "高级": 45
  5. }
  6. let sudu; //保存完整数独表的二维数组
  7. let curSudu; //保存当前游戏的数独表的二维数组
  8. let status = 0; //0:准备 1:游戏中
  9. let time; //保存游戏开始的时间或者游戏所用时间
  10. //randomBlank
  11. //params: level 难度级别
  12. //return blanks 所有空格的坐标
  13. let randomBlank = function(level) {
  14. let blankNum = levelBlank[level];
  15. let blanks = [];
  16. for (let i = 0; i < blankNum; i++) {
  17. let blank = {
  18. row: Math.floor(Math.random() * 9),
  19. column: Math.floor(Math.random() * 9)
  20. }
  21. let isIn = false;
  22. for (let j = 0; j < blanks.length; j++) {
  23. if (blank.row === blanks[j].row && blank.column === blanks[j].column) {
  24. isIn = true;
  25. break;
  26. }
  27. }
  28. if (!isIn) {
  29. blanks.push(blank);
  30. } else {
  31. i--;
  32. }
  33. }
  34. return blanks;
  35. }
  36. //showSudu
  37. //params:
  38. //return
  39. //开始游戏,生成完整数独表的二维数组,保存在sudu中,初始化curSudu,渲染数独游戏界面
  40. let showSudu = function () {
  41. let level = document.getElementsByClassName("select")[0].value;
  42. let blankIndexs = randomBlank(level);
  43. try {
  44. sudu = Sudu.gennerateShudu();
  45. status = 1;
  46. curSudu = new Array(9);
  47. for (let i = 0; i < sudu.length; i++) {
  48. curSudu[i] = [];
  49. for (let j = 0; j < sudu[i].length; j++) {
  50. curSudu[i].push(sudu[i][j]);
  51. }
  52. }
  53. let board = document.getElementsByClassName("game")[0];
  54. let inputs = board.children;
  55. for (let i = 0; i < blankIndexs.length; i++) {
  56. let row = blankIndexs[i].row;
  57. let column = blankIndexs[i].column;
  58. curSudu[row][column] = 0;
  59. }
  60. for (let i = 0; i < curSudu.length; i++) {
  61. for (let j = 0; j < curSudu[i].length; j++) {
  62. inputs[j*9+i].id = i + "&" + j;
  63. if (curSudu[i][j] != 0) {
  64. inputs[j*9+i].value = curSudu[i][j];
  65. inputs[j*9+i].readOnly = "readonly";
  66. inputs[j*9+i].style.backgroundColor = "";
  67. } else {
  68. inputs[j*9+i].value = "";
  69. inputs[j*9+i].readOnly = "";
  70. inputs[j*9+i].style.backgroundColor = "white";
  71. inputs[j*9+i].addEventListener("change", handleChange);
  72. }
  73. }
  74. }
  75. time = new Date().getTime();
  76. }
  77. catch (e) {
  78. console.log(e);
  79. }
  80. }
  81. //handleChange
  82. //params: e 空格的值发生改变的事件
  83. //return
  84. //当空格填入的值发生改变时,对值进行监测,检查是否冲突,进行提示
  85. let handleChange = function(e) {
  86. let element = e.currentTarget;
  87. let indexs = element.id.split("&");
  88. curSudu[indexs[0]][indexs[1]] = element.value == '' ? 0 : element.value;
  89. let tip = document.getElementsByClassName("cur_situation")[0].children[0];
  90. if (!(Sudu.checkRow(indexs[0], indexs[1], element.value, curSudu) && Sudu.checkColumn(indexs[0], indexs[1], element.value, curSudu) && Sudu.checkNine(indexs[0], indexs[1], element.value, curSudu))) {
  91. tip.innerHTML = "\n第" + indexs[0] + "行第" + indexs[1] + "列冲突!";
  92. element.style.backgroundColor = "#f34949";
  93. } else {
  94. tip.innerHTML = "";
  95. element.style.backgroundColor = "white";
  96. }
  97. }
  98. //isAnswerRight
  99. //params:
  100. //return boolean 提交的答案是否正确
  101. let isAnswerRight = function() {
  102. for (let i = 0; i < sudu.length; i++) {
  103. for (let j = 0; j < sudu[i].length; j++) {
  104. if (curSudu[i][j] != sudu[i][j]) {
  105. return false;
  106. }
  107. }
  108. }
  109. return true;
  110. }
  111. //isAnswerFull
  112. //params:
  113. //return boolean 提交的数独表是否已经填满
  114. let isAnswerFull = function () {
  115. for (let i = 0; i < curSudu.length; i++) {
  116. for (let j = 0; j < curSudu[i].length; j++) {
  117. if (curSudu[i][j] == 0) {
  118. return false;
  119. }
  120. }
  121. }
  122. return true;
  123. }
  124. //submitAnswer
  125. //params:
  126. //return
  127. //提交答案时的总处理函数
  128. let submitAnswer = function() {
  129. if (status != 1) {
  130. alert("请选择关卡,开始游戏!");
  131. } else if (!isAnswerFull()) {
  132. alert("请填满空格!!");
  133. } else if (isAnswerRight() && status == 1) {
  134. time = ((new Date().getTime() - time) / 1000 / 60).toFixed(2);
  135. let tip = document.getElementsByClassName("cur_situation")[0].children[0];
  136. tip.innerHTML = "请选择关卡,继续游戏!"
  137. status = 0;
  138. showElement("username-box");
  139. let timeLable = document.getElementsByClassName("time")[0];
  140. timeLable.innerHTML = time;
  141. let inputs = document.getElementsByClassName("game")[0].children;
  142. Array.from(inputs).forEach(element => {
  143. element.value = "";
  144. element.style.backgroundColor = "#8ca0ff";
  145. element.readOnly = "readonly";
  146. });
  147. } else if (!isAnswerRight()) {
  148. alert("游戏失败!");
  149. }
  150. }
  151. //showElement
  152. //params: className 需要展示的DOM元素的class样式选择器
  153. //return
  154. //在浏览器界面中展示某个Dom元素
  155. let showElement = function(className) {
  156. document.getElementsByClassName(className)[0].style.display = "block";
  157. document.getElementsByClassName("game")[0].style.opacity = 0.5;
  158. document.getElementsByClassName("info")[0].style.opacity = 0.5;
  159. }
  160. //hideElement
  161. //params: className 需要隐藏的DOM元素的class样式选择器
  162. //return
  163. //在浏览器界面中隐藏某个Dom元素
  164. let hideElement = function(className) {
  165. document.getElementsByClassName(className)[0].style.display = "none";
  166. document.getElementsByClassName("game")[0].style.opacity = 1;
  167. document.getElementsByClassName("info")[0].style.opacity = 1;
  168. }
  169. //submitGrade
  170. //params
  171. //return
  172. //提交成绩
  173. let submitGrade = function() {
  174. storeGrade();
  175. hideElement("username-box");
  176. }
  177. //closeChart
  178. //params
  179. //return
  180. //关闭成绩榜单
  181. let closeChart = function(e) {
  182. hideElement("rank-chart")
  183. }
  184. //openChart
  185. //params
  186. //return
  187. //打开成绩榜单
  188. let openChart = function() {
  189. showElement("rank-chart");
  190. let rankLis = document.getElementsByClassName("rank-li")[0].children;
  191. let ranks = getRank();
  192. for (let i = 0; i < ranks.length; i++) {
  193. rankLis[i].getElementsByClassName("username")[0].innerHTML = ranks[i].username;
  194. rankLis[i].getElementsByClassName("user-time")[0].innerHTML = ranks[i].time;
  195. }
  196. for (let i = ranks.length; i < 10; i++) {
  197. rankLis[i].getElementsByClassName("username")[0].innerHTML = '';
  198. rankLis[i].getElementsByClassName("user-time")[0].innerHTML = '';
  199. }
  200. let maxGrade = getMaxGrade();
  201. document.getElementsByClassName('b-username')[0].innerHTML = maxGrade.username;
  202. document.getElementsByClassName('b-time')[0].innerHTML = maxGrade.time;
  203. }
  204. //getRank
  205. //params
  206. //return ranks 最近的十次成绩
  207. //得到最近的十次成绩
  208. let getRank = function() {
  209. let rankStr = '';
  210. let ranks = [];
  211. if (localStorage.getItem('rank')) {
  212. rankStr = localStorage.getItem('rank');
  213. let rankArr = rankStr.split('||');
  214. for (let i = 0; i < rankArr.length; i++) {
  215. let tmp = rankArr[i].split('&');
  216. ranks.push({
  217. username: tmp[0].split('=')[1],
  218. time: tmp[1].split('=')[1]
  219. });
  220. }
  221. }
  222. return ranks;
  223. }
  224. //getMaxGrade
  225. //params
  226. //return maxGrade
  227. //得到最好的一次成绩
  228. let getMaxGrade = function() {
  229. let maxGradeStr = '';
  230. if (localStorage.getItem('maxGrade')) {
  231. maxGradeStr = localStorage.getItem('maxGrade');
  232. }
  233. let maxGrade = {
  234. username: '',
  235. time: ''
  236. };
  237. if (maxGradeStr) {
  238. let arr = maxGradeStr.split('&');
  239. let user = arr[0].split('=');
  240. let time = arr[1].split('=');
  241. maxGrade.username = user[1];
  242. maxGrade.time = time[1];
  243. }
  244. return maxGrade;
  245. }
  246. //storeRank
  247. //params rank 新保存的成绩
  248. //return
  249. //保存刚刚提交的成绩,得到最好的一次成绩并保存
  250. let storeRank = function(rank) {
  251. let ranks = getRank();
  252. ranks.unshift(rank);
  253. for (let i = 0; i < ranks.length - 10; i++) {
  254. ranks.pop();
  255. }
  256. let rankStr = "";
  257. for (let i = 0; i < ranks.length; i++) {
  258. if (rankStr) {
  259. rankStr += `||username=${ranks[i].username}&time=${ranks[i].time}`
  260. } else {
  261. rankStr = `username=${ranks[i].username}&time=${ranks[i].time}`
  262. }
  263. }
  264. let maxGrade = getMaxGrade();
  265. for (let i = 0; i < ranks.length; i++) {
  266. if (ranks[i].time < maxGrade.time) {
  267. maxGrade = ranks[i];
  268. }
  269. }
  270. let maxGradeStr = `username=${maxGrade.username}&time=${maxGrade.time}`;
  271. localStorage.setItem('maxGrade', maxGradeStr);
  272. localStorage.setItem('rank', rankStr);
  273. }
  274. //storeGrade
  275. //params
  276. //return
  277. //处理得到最终成绩并保存
  278. let storeGrade = function() {
  279. let username = document.getElementsByClassName("username")[0].value;
  280. let user = {
  281. username: username,
  282. time: time
  283. }
  284. storeRank(user);
  285. }

(二)软件测试

1、游戏初始界面

2、选择难度为“初级”后点击‘开始游戏’按钮后游戏开始界面

3、在第1行第2列填入5,产生冲突后右边文本框中提示冲突,空格颜色变红

4、在修改数字为9之后,冲突消失界面

5、填满数字提交答案之前的界面

6、点击‘提交结果’按钮后游戏界面,显示用时3.71min,要求输入用户名

7、填入用户名

8、点击‘排行榜’按钮,打开成绩榜单,显示最好的一次成绩与最近十次成绩,其中最近一次成绩yolanda:3.71min保存在最前面

上传的附件 cloud_download 基于JavaScript的网页版数独游戏.zip ( 403.39kb, 0次下载 )
error_outline 下载需要7点积分

发送私信

每个人最终和自己越长越像

16
文章数
15
评论数
最近文章
eject