www.gusucode.com > 数独游戏js网页版【极简风格】源码程序 > 数独游戏js网页版【极简风格】/Sudoku/Sudoku/js/Sudoku.js

    /**
 * 数独游戏js版
 * Download by http://www.srcfans.com
 * @author:tiger
 * @email:0463023@163.com
 * @date:2009-3-24
 * @param {}
 *            size
 */
Sudoku = function(size) {
	this.size = size;
	this.version = 'v1.0';
};

Sudoku.prototype = {
	// 记录游戏开始时间
	startTime : 0,
	// 玩家所用的时间
	usedTime : 0,
	// 游戏状态
	gameState : 'init',
	// 计时器
	gameTimer : null,
	// 游戏局面
	layout : [],
	// 答案
	answer : [],
	// 答案的索引
	answerPosition : [],
	// 游戏时的待填局面
	solving : [],
	// 记录玩家填写答案的顺序栈
	solvingStack : [],
	mask : null,
	// 数字选择器
	numberPicker : null,
	// 当前待选择填入数据的单元格
	choosing : {
		row : 0,
		col : 0
	},

	// 初始化
	init : function() {
		this.startTime = new Date().getTime();
		this.usedTime = 0;
		// this.gameState = 'init';

		for (var i = 0; i < this.size; i++) {
			for (var j = 0; j < this.size; j++) {
				this.layout[i * this.size + j] = 0;
				this.solving[i * this.size + j] = 0;
				this.answerPosition[i * this.size + j] = 0;
				for (var h = 0; h < this.size; h++) {
					this.answer[i * this.size * this.size + j * this.size + h] = 0;
				}
			}
		}
	},
	// 取指定行列的答案
	getAnswer : function(row, col) {
		for (var i = 1; i <= this.size; i++) {
			this.answer[row * this.size * this.size + col * this.size + i - 1] = i;// 假定包含所有解
		}
		// 去除已经包含的
		for (var i = 0; i < this.size; i++) {
			if (this.layout[i * this.size + col] != 0) {
				this.answer[row * this.size * this.size + col * this.size
						+ this.layout[i * this.size + col] - 1] = 0;// 去除列中包含的元素
			}
			if (this.layout[row * this.size + i] != 0) {
				this.answer[row * this.size * this.size + col * this.size
						+ this.layout[row * this.size + i] - 1] = 0;// 去除行中包含的元素
			}
		}
		var subnum = Math.floor(Math.sqrt(this.size));
		var x = Math.floor(row / subnum);
		var y = Math.floor(col / subnum);
		for (var i = x * subnum; i < subnum + x * subnum; i++) {
			for (var j = y * subnum; j < subnum + y * subnum; j++) {
				if (this.layout[i * this.size + j] != 0)
					this.answer[row * this.size * this.size + col * this.size
							+ this.layout[i * this.size + j] - 1] = 0;// 去小方格中包含的元素
			}
		}
		this.randomAnswer(row, col);
	},
	// 对指定行列的答案随机排序
	randomAnswer : function(row, col) {
		// 随机调整一下顺序
		var list = [];
		for (var i = 0; i < this.size; i++)
			list.push(this.answer[row * this.size * this.size + col * this.size
					+ i]);
		var rdm = 0, idx = 0;
		while (list.length != 0) {
			rdm = Math.floor(Math.random() * list.length);
			this.answer[row * this.size * this.size + col * this.size + idx] = list[rdm];
			list.splice(rdm, 1);
			idx++;
		}
	},
	// 计算指定行列可用解的数量
	getAnswerCount : function(row, col) {
		var count = 0;
		for (var i = 0; i < this.size; i++)
			if (this.answer[row * this.size * this.size + col * this.size + i] != 0)
				count++;
		return count;
	},
	// 返回指定行列在指定位置的解
	getAnswerNum : function(row, col, ansPos) {
		// 返回指定布局方格中指定位置的解
		var cnt = 0;
		for (var i = 0; i < this.size; i++) {
			// 找到指定位置的解,返回
			if (cnt == ansPos
					&& this.answer[row * this.size * this.size + col
							* this.size + i] != 0)
				return this.answer[row * this.size * this.size + col
						* this.size + i];
			if (this.answer[row * this.size * this.size + col * this.size + i] != 0)
				cnt++;// 是解,调整计数器
		}
		return 0;// 没有找到,逻辑没有问题的话,应该不会出现这个情况
	},
	// 生成游戏局面
	generate : function() {
		this.init(this.size);
		var curRow = 0, curCol = 0;
		while (curRow != this.size) {
			if (this.answerPosition[curRow * this.size + curCol] == 0)
				this.getAnswer(curRow, curCol);// 如果这个位置没有被回溯过,就不用重新计算解空间
			var ansCount = this.getAnswerCount(curRow, curCol);
			if (ansCount == this.answerPosition[curRow * this.size + curCol]
					&& curRow == 0 && curCol == 0)
				break;// 全部回溯完毕
			if (ansCount == 0) {
				this.answerPosition[curRow * this.size + curCol] = 0;// 无可用解,应该就是0
				// alert("无可用解,回溯!");
				if (curCol > 0) {
					curCol--;
				} else if (curCol == 0) {
					curCol = 8;
					curRow--;
				}
				this.layout[curRow * this.size + curCol] = 0;
				continue;
			}
			// 可用解用完
			else if (this.answerPosition[curRow * this.size + curCol] == ansCount) {
				// alert("可用解用完,回溯!");
				this.answerPosition[curRow * this.size + curCol] = 0;
				if (curCol > 0) {
					curCol--;
				} else if (curCol == 0) {
					curCol = 8;
					curRow--;
				}
				this.layout[curRow * this.size + curCol] = 0;
				continue;
			} else {
				// 返回指定格中,第几个解
				this.layout[curRow * this.size + curCol] = this.getAnswerNum(
						curRow, curCol, this.answerPosition[curRow * this.size
								+ curCol]);
				// alert("位置:(" + curRow + ", " + curCol + ")="
				// + layout[curRow][curCol]);
				this.answerPosition[curRow * this.size + curCol]++;
				if (curCol == 8) {
					curCol = 0;
					curRow++;
				} else if (curCol < 8) {
					curCol++;
				}
			}
		}
	},
	get : function(id) {
		return document.getElementById(id);
	},
	// 获得事件Event对象,用于兼容IE和FireFox
	getEvent : function() {
		return window.event || arguments.callee.caller.arguments[0];
	},
	getMousePosition : function(e) {
		var x = e.x || e.pageX;
		var y = e.y || e.pageY;
		return {
			x : x,
			y : y
		};
	},
	// 初始化界面
	initLayout : function() {
		var self = this;
		// 难度选择
		var level = "<input type='radio' name='level' checked>初级<br/>"
				+ "<input type='radio' name='level'>中级<br/>"
				+ "<input type='radio' name='level'>高级<br/>"
				+ "<input type='radio' name='level'>骨灰级<br/><br/>";
		var time = '用时:<span id="timer" style="background:#0d0;width:60;color:red;">00:00:00</span><br/><br/>';
		var s = document.createElement('input');
		s.type = 'button';
		s.value = '开始';
		s.onclick = function() {
			this.value = '重新开始';
			self.start(this.size);
		}
		var p = document.createElement('input');
		p.type = 'button';
		p.setAttribute('id', 'pause');
		p.value = '暂停';
		p.onclick = function() {
			if (self.gameState != 'init') {
				if (p.value == '暂停') {
					p.value = '继续';
					self.gameState = 'pause';
				} else {
					p.value = '暂停';
					self.gameState = 'continue';
				}
				self.pause();
			}
		}

		// 西部面板
		var westPanel = document.createElement('div');
		westPanel.className = 'westPanel';
		westPanel.innerHTML = '<span style="color:red;">游戏选项</span><br/>'
				+ '<font size="4">请选择难度:<br/>' + level + time + '</font>';
		westPanel.appendChild(s);
		westPanel.appendChild(p);
		document.body.appendChild(westPanel);

		// 中央面板
		var mp = document.createElement('div');
		mp.setAttribute('id', 'mp');
		mp.className = 'mainPanel';
		document.body.appendChild(mp);

		this.mask = document.createElement('div');
		this.mask.className = 'mask';
		// this.mask.innerHTML = '<img src="../images/sudoku1.jpg"
		// style="width:405px;height:455px;"></img>';

		// 数字选择器
		this.numberPicker = document.createElement('div');
		this.numberPicker.setAttribute('id', 'numberPicker');
		this.numberPicker.className = 'numberPicker';

		var title = document.createElement('div');
		title.style.cssText = 'position:absolute;left:0;top:0;width:100;'
				+ 'height:18;text-align:left;color:red;';
		title.innerHTML = '请选择答案';
		this.numberPicker.appendChild(title);

		var closeBtn = document.createElement('div');
		closeBtn.style.cssText = 'position:absolute;left:100;top:0;width:20;height:18;'
				+ 'cursor:pointer;cursor:hand;text-align:right;color:red;background:url(images/close.gif) no-repeat;';
		closeBtn.onclick = function() {
			document.body.removeChild(self.numberPicker);
		}
		this.numberPicker.appendChild(closeBtn);
		// this.numberPicker.onmouseout = function() {
		// document.body.removeChild(this);
		// }
		for (var i = 0; i < this.size; i++) {
			var numi = document.createElement('div');
			numi.setAttribute('id', 'picker_' + (i + 1));
			numi.innerHTML = i + 1;
			numi.style.cssText = "position:absolute;text-align:'center';font-size:28;left:"
					+ (i % 3)
					* 40
					+ "px;top:"
					+ ((Math.floor(i / 3)) * 40 + 18)
					+ "px;width:40px;height:40px;border:2px solid #666;cursor:pointer;";

			numi.onclick = function() {
				self.choose(this.id);
				this.style.background = '#0e0';
				document.body.removeChild(self.numberPicker);
			}
			numi.onmouseover = function() {
				this.style.background = '#FF0000';
			}
			numi.onmouseout = function() {
				this.style.background = '#0e0';
			}
			this.numberPicker.appendChild(numi);
		}

		// 游戏局面
		var w = mp.clientWidth / this.size;
		var h = (mp.clientHeight - 50) / this.size;

		for (var i = 0; i < this.size; i++) {
			for (var j = 0; j < this.size; j++) {
				var cell = document.createElement('div');
				cell.setAttribute('id', 'cell_' + i + '_' + j);
				cell.style.cssText = "position:absolute;left:" + j * w
						+ "px;top:" + i * h + "px;width:" + w + "px;height:"
						+ h + "px;border:2px solid #666;";
				var subSize = Math.floor(Math.sqrt(this.size));
				var r = Math.floor(i / subSize);
				var c = Math.floor(j / subSize);
				if (r % 2 == c % 2) {
					cell.style.background = 'pink';
				}
				mp.appendChild(cell);
			}
		}
		this.stopSolving();

		/* 游戏控制按钮 */
		// 完成按钮
		var finish = document.createElement('input');
		finish.type = 'button';
		finish.value = '完成';
		finish.className = 'finish';
		finish.onclick = function() {
			self.checkAnswer();
		}

		// 撤消按钮
		var reset = document.createElement('input');
		reset.type = 'button';
		reset.value = '撤消';
		reset.className = 'reset';
		reset.onclick = function() {
			self.reset();
		}

		// 按钮面板
		var btnPanel = document.createElement('div');
		btnPanel.setAttribute('id', 'btnPanel');
		btnPanel.className = 'btnPanel';
		btnPanel.appendChild(finish);
		btnPanel.appendChild(reset);

		mp.appendChild(btnPanel);
	},
	// el的背景闪烁效果
	twinkle : function(el, oldColor, newColor) {
		var i = 0;
		var t = setInterval(function() {
			if (i < 7) {
				if (i % 2 == 1) {
					el.style.background = newColor;
				} else {
					el.style.background = oldColor;
				}
				i++;
			} else {
				clearInterval(t);
			}
		}, 200);
	},
	// 处理玩家选择的数字
	choose : function(id) {
		var c = id.split('_')[1];
		var t = this.get('cell_' + this.choosing.row + '_' + this.choosing.col);
		var previous = t.innerHTML;
		t.innerHTML = c;
		this.solvingStack
				.push([this.choosing.row, this.choosing.col, previous]);

		this.onSelect(c, this.choosing.row, this.choosing.col);
	},
	// 检查玩家的答案是否正确
	checkAnswer : function() {
		var flag = true;

		for (var i = 0; i < this.size; i++) {
			for (var j = 0; j < this.size; j++) {
				if (this.solving[i * this.size + j] != this.layout[i
						* this.size + j]) {
					flag = false;
					var cell = this.get('cell_' + i + '_' + j);
					this.twinkle(cell, cell.style.background, 'red');
					// alert('请在第' + (i + 1) + '行,' + (j + 1) + '列填上你的答案!');
					break;
				}
			}
			if (!flag)
				break;
		}

		if (flag && this.gameState != 'init') {
			var t = Math.floor((new Date().getTime() - this.startTime) / 1000);
			clearInterval(this.gameTimer);
			var c = confirm('恭喜!你的答案完全正确!\n用时:'
					+ this.changeTimeToString(t + this.usedTime) + '\n重新开始?');
			if (c) {
				this.start();
			} else {
				this.get('timer').innerHTML = '00:00:00';
			}
		}
	},
	// 判断生成的游戏局面是否只有一种答案
	checkUnique : function() {
		var res = [];
		for (var r1 = 0; r1 < this.size - 1; r1++) {
			for (var r2 = r1 + 1; r2 < this.size; r2++) {
				for (var c1 = 0; c1 < this.size - 1; c1++) {
					for (var c2 = c1 + 1; c2 < this.size; c2++) {
						if (this.layout[r1 * this.size + c1] == this.layout[r2
								* this.size + c2]
								&& this.layout[r1 * this.size + c2] == this.layout[r2
										* this.size + c1]) {
							res.push([r1, r2, c1, c2]);
						}
					}
				}
			}
		}
		return res;
	},
	// 开始游戏
	start : function() {
		// 重新生成游戏局面
		this.restart();

		// 除去蒙板
		if (this.gameState == 'init' || this.gameState == 'pause')
			this.continueSolving();

		// 判断当前游戏状态
		if (this.gameState == 'continue' || this.gameState == 'start') {
			clearInterval(this.gameTimer);
		} else {
			this.get('pause').value = '暂停';
		}
		// 修改游戏状态
		this.gameState = 'start';

		var self = this;

		// 开始计时
		this.startTime = new Date().getTime();
		var timer = this.get('timer');
		this.gameTimer = setInterval(function() {
			var time = Math.floor((new Date().getTime() - self.startTime)
					/ 1000);
			timer.innerHTML = self.changeTimeToString(time);
		}, 1000);
	},
	// 重新开局
	restart : function() {
		// 游戏局面生成
		this.generate(this.size);
		// 游戏难度级别
		var checkedIndex = this.getLevel();
		var self = this;

		for (var i = 0; i < this.size; i++) {
			for (var j = 0; j < this.size; j++) {
				var cell = this.get('cell_' + i + '_' + j);
				cell.style.borderColor = '#666';
				cell.innerHTML = '';

				var rdm = Math.floor(Math.random() * 6);
				if (rdm > checkedIndex) {
					cell.innerHTML = this.layout[i * this.size + j];
					this.solving[i * this.size + j] = this.layout[i * this.size
							+ j];
				} else {
					cell.style.borderColor = '#0e0';
					// 获取焦点时,为同行,同列和所在九宫格添加背景色
					cell.onmouseover = function() {
						self.onMouseOver(this.id);
					}
					// 失去焦点时,去除背景色
					cell.onmouseout = function() {
						self.onMouseOut(this.id);
					}
					// 点击时,弹出选择答案窗口
					cell.onclick = function(e) {
						// 存储当前选择的待填单元格
						var rc = this.id.split('_');
						self.choosing.row = Number(rc[1]);
						self.choosing.col = Number(rc[2]);

						var np = self.numberPicker;
						var pos = self.getMousePosition(self.getEvent());
						np.style.left = pos.x;
						np.style.top = pos.y;
						document.body.appendChild(np);
					}
				}
			}
		}

		// 消除可能存在的多解情形
		var isUnique = this.checkUnique();
		for (i = 0; i < isUnique.length; i++) {
			var r1 = isUnique[i][0];
			var r2 = isUnique[i][1];
			var c1 = isUnique[i][2];
			var c2 = isUnique[i][3];
			// 如果多解的四个格子都为空
			if (this.solving[r1 * this.size + c1] == 0
					&& this.solving[r1 * this.size + c2] == 0
					&& this.solving[r2 * this.size + c1] == 0
					&& this.solving[r2 * this.size + c2] == 0) {
				// 四个空中,随机填上一个
				var rdm = Math.floor(Math.random() * 4);
				var r = isUnique[i][Math.floor(rdm / 2)];
				var c = isUnique[i][rdm % 2 + 2];
				var cell = this.get('cell_' + r + '_' + c);
				cell.innerHTML = this.layout[r * this.size + c];
				this.solving[r * this.size + c] = this.layout[r * this.size + c];
			}
		}
	},
	// 当鼠标移入时
	onMouseOver : function(id) {
		var o = id.split('_');
		var i = Number(o[1]);
		var j = Number(o[2]);
		for (var h = 0; h < this.size; h++) {
			if (h != i) {
				this.get('cell_' + h + '_' + j).style.background = '#FFA000';// 所在列变色
			}
			if (h != j) {
				this.get('cell_' + i + '_' + h).style.background = '#FFA000';// 所在行变色
			}
		}
		// 所在的九宫格变色
		var sub = Math.floor(Math.sqrt(this.size));
		var subRow = Math.floor(i / sub);
		var subCol = Math.floor(j / sub);
		for (i = subRow * sub; i < subRow * sub + sub; i++) {
			for (j = subCol * sub; j < subCol * sub + sub; j++) {
				this.get('cell_' + i + '_' + j).style.background = '#FFA000';
			}
		}
	},
	// 当鼠标移出时
	onMouseOut : function(id) {
		// 如果未选择答案就将鼠标移出,则隐藏答案选择窗口
		// var np = this.get('numberPicker');
		// if (np) {
		// document.body.removeChild(np);
		// }

		var o = id.split('_');
		var i = Number(o[1]);
		var j = Number(o[2]);

		var subSize = Math.floor(Math.sqrt(this.size));
		var r = Math.floor(i / subSize);
		var c = Math.floor(j / subSize);
		for (var h = 0; h < this.size; h++) {
			var sh = Math.floor(h / subSize);
			if (h != i) {
				if (sh % 2 == c % 2) {
					this.get('cell_' + h + '_' + j).style.background = 'pink';
				} else {
					this.get('cell_' + h + '_' + j).style.background = 'white';
				}// 所在列颜色恢复
			}
			if (h != j) {
				if (sh % 2 == r % 2) {
					this.get('cell_' + i + '_' + h).style.background = 'pink';
				} else {
					this.get('cell_' + i + '_' + h).style.background = 'white';
				}// 所在行颜色恢复
			}
		}
		// 所在的九宫格变色
		var bgColor = (c % 2 == r % 2) ? 'pink' : 'white';
		var sub = Math.floor(Math.sqrt(this.size));
		var subRow = Math.floor(i / sub);
		var subCol = Math.floor(j / sub);
		for (i = subRow * sub; i < subRow * sub + sub; i++) {
			for (j = subCol * sub; j < subCol * sub + sub; j++) {
				this.get('cell_' + i + '_' + j).style.background = bgColor;
			}
		}
	},
	// 当输入答案时,检查是否有冲突
	onSelect : function(v, i, j) {
		var cp = this.getCheckingPosition(i, j);

		var flag = true;
		for (var h = 0; h < cp.length; h++) {
			var r = cp[h][0];
			var c = cp[h][1];
			if (this.solving[r * this.size + c] == v) {
				// alert('v=' + v + '(i,j)=(' + r + ',' + c + ')')
				flag = false;
				// 有冲突的标记为红色
				this.get('cell_' + r + '_' + c).style.background = 'red';
			}
		}
		// 如果没有冲突
		// if (flag)
		this.solving[i * this.size + j] = v;
	},
	// 取得某格所在行,列,九宫格的所有行列号
	getCheckingPosition : function(i, j) {
		var res = [];
		for (var h = 0; h < this.size; h++) {
			if (h != i)
				res.push([h, j])
			if (h != j)
				res.push([i, h]);
		}
		var sub = Math.floor(Math.sqrt(this.size));
		var subRow = Math.floor(i / sub);
		var subCol = Math.floor(j / sub);
		for (var x = subRow * sub; x < subRow * sub + sub; x++) {
			for (var y = subCol * sub; y < subCol * sub + sub; y++) {
				if (x != i || y != j) {
					res.push([x, y]);
				}
			}
		}

		return res;
	},
	// 游戏暂停或继续
	pause : function() {
		// 当继续时,重新开始计时
		if (this.gameState == 'continue') {
			// 除去蒙板
			this.continueSolving();

			this.startTime = new Date().getTime();
			var self = this;
			this.gameTimer = setInterval(function() {
				var time = Math.floor(self.usedTime
						+ (new Date().getTime() - self.startTime) / 1000);
				timer.innerHTML = self.changeTimeToString(time);
			}, 1000);
		}
		// 暂停时,停止计时,并将已用的时间记录下来
		else {
			clearInterval(this.gameTimer);
			var t = Math.floor((new Date().getTime() - this.startTime) / 1000);
			this.usedTime += t;
			this.stopSolving();
		}
	},
	// 游戏暂停时不能填写答案
	stopSolving : function() {
		document.body.appendChild(this.mask);
	},
	// 游戏继续,除去游戏界面的蒙板
	continueSolving : function() {
		document.body.removeChild(this.mask);
	},
	// 取得游戏难度等级
	getLevel : function() {
		var l = document.getElementsByName('level');
		for (var i = 0; i < l.length; i++) {
			if (l[i].checked) {
				return i + 1;
			}
		}
	},
	// 重新填写答案
	reset : function() {
		// for (var i = 0; i < this.solvingStack.length; i++) {
		if (this.solvingStack.length > 0) {
			var ss = this.solvingStack.pop();
			this.solving[ss[0] * this.size + ss[1]] = ss[2];
			this.get('cell_' + ss[0] + '_' + ss[1]).innerHTML = ss[2];
		}
		// }
	},
	// 将时间间隔转换为时间字符串,如90秒转化为:00:01:30
	changeTimeToString : function(time) {
		var res = '';
		var h = Math.floor(time / 3600);
		if (h < 10) {
			h = '0' + h;
		}
		var m = time % 3600;
		m = Math.floor(m / 60);
		if (m < 10) {
			m = '0' + m;
		}
		var s = time % 60;
		if (s < 10) {
			s = '0' + s;
		}

		res = h + ':' + m + ':' + s;
		return res;
	}
};

window.onload = function() {
	var sudoku = new Sudoku(9);
	sudoku.initLayout();
}