纯JS的打字游戏算是JavaScript入门的一个检验吧,我开始做的时候也是各种蒙圈,确实作为前端小白有点不知道该怎么入手,但是学习了那么久的知识,总得磨刀霍霍向猪羊…啊呸,总得实践出真知啊。所以在网上研究了和分析了别人的程序(我想知道实现的思路却全都是代码,大概别人都觉得这个太简单了吧-,-),花了一段时间把代码写出来了,现在来分享一下我的成果~
这部分代码是属于完全的功能的堆砌,没有对JavaScript代码进行对象化处理,属于人家一看就觉得low但是作为初学者的我好理解的类型(ps: 为了美观起见,我还是不是纯JS的代码,有部分样式设计)。关门,放代码:
"en">
"UTF-8">
Typing Game
"stylesheet" type="text/css" href="style.css">
"container">
"tools">
"list">
单词大战
到达底线消灭游戏框中出现的字母
"list">
游戏得分
首局游戏需得分"need">10分
"box" style="height: 640px; width: 760px;">
"panel">得分:"score">0
"gameBox" style="height: 610px; width: 585px;">
"danger">
/* style.css */
* {
margin: 0;
padding: 0;
}
html, body {
/* Box-model */
height: 100%;
/* Typography */
color: #fff;
font-family: helvetica, arial, sans-serif;
/* Visual */
background: -webkit-linear-gradient(#3a3a3a, #c3c3c3);
background: -o-linear-gradient(#3a3a3a, #c3c3c3);
background: -moz-linear-gradient(#3a3a3a, #c3c3c3);
background: linear-gradient(#3a3a3a, #c3c3c3);
}
#container {
/* Positioning */
position: relative;
margin-left: auto;
margin-right: auto;
/* Box-model */
display: block;
width: 80%;
max-width: 1140px;
}
#tools {
/* Positioning */
position: absolute;
top: 32%;
left: 25%;
margin-top: 50px;
z-index: 100;
/* Box-model */
display: block;
padding: 5px;
}
#tools .list {
/* Positioning */
margin-bottom: 35px;
/* Typography */
font-family: "Microsoft YaHei UI", "微软雅黑";
}
#panel {
/* Positioning */
position: absolute;
right: 5%;
top: 5%;
z-index: 100;
/* Box-model */
display: none;
/* Typography */
font-family: "Microsoft YaHei UI", "微软雅黑";
font-weight: 700;
/* Visual */
text-shadow: 2px 2px 3px rgba(255, 255, 255, 0.5);
/* Misc */
}
.btn {
/* Positioning */
z-index: 100;
/* Box-model */
display: inline-block;
width: 80px;
height: 40px;
/* Typography */
font-family: helvetica, Arial, sans-serif;
line-height: 40px;
text-align: center;
/* Visual */
color: #fff;
background-color: #999;
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
-webkit-box-shadow: 0 0 2px 5px rgba(255, 255, 255, 0.5);
-moz-box-shadow: 0 0 2px 5px rgba(255, 255, 255, 0.5);
box-shadow: 0 0 2px 5px rgba(255, 255, 255, 0.5);
/* Misc */
opacity: 0.8;
cursor: pointer;
}
#start {
/* Positioning */
position: absolute;
right: 5%;
top: 34%;
}
#stop {
/* Positioning */
position: absolute;
right: 5%;
top: 48%;
}
#box {
/* Positioning */
position: relative;
top: 50px;
margin-left: auto;
margin-right: auto;
/* Box-model */
display: block;
height: 640px;
width: 760px;
/* Visual */
background-image: url("backdrop.png");
border: 1px solid #888;
/* Misc */
opacity: 1;
}
#gameBox {
/* Positioning */
position: relative;
/* Box-model */
margin-top: 20px;
margin-left: 20px;
}
#danger {
/* Positioning */
position: absolute;
left: 0;
bottom: 30px;
/* Box-model */
width: 100%;
/* Typography */
text-align: center;
/* Visual */
color: #c3c3c3;
border-top: 5px dotted #c3c3c3;
}
/**
* main.js
*/
// 1. 字母下降的速度选择
var getSpeed = {
1: {
speed: 50
},
2: {
speed: 30
},
3: {
speed: 20
},
4: {
speed: 10
},
5: {
speed: 5
}
};
// 2. 随机生成字母
function getRandom() {
// 随机生成一个字母( a-z )的ASCII码
var charCode = 97 + Math.floor(Math.random() * 26);
// 将该ASCII码转化为字母
return String.fromCharCode(charCode);
}
// 获取键盘上按键的值并消除
function game(level, score) {
var gameBox = document.getElementById("gameBox");
var letterArr = [], // 字母对象列表
spanArr = []; // span对象列表
var hit = 0; // 击中个数
var start = function () {
// 使用random可以使每次下落的字母数随机
if (Math.random() > (0.8 - level * 0.01)) {
var letterIn = getRandom();
letterArr.push(letterIn);
spanArr.push(createSpan(letterIn));
window.addEventListener("keyup", keyup);
}
};
// 绑定键盘事件
var keyup = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var keyCode = String.fromCharCode(e.keyCode);
for (var i = 0; i < letterArr.length; i++) {
if (keyCode.toLowerCase() === letterArr[i]) {
clearInterval(spanArr[i].intervalID); // 这是一句不写就后患无穷的代码,不信你可以试试
spanArr[i].parentNode.removeChild(spanArr[i]);
letterArr.splice(i, 1);
spanArr.splice(i, 1);
hit++;
document.getElementById("score").innerHTML = hit;
if (hit >= Number(score) && getSpeed[level + 1] === undefined) {
alert("恭喜你,所有关卡挑战成功!");
location.reload();
return;
} else if (hit >= Number(score)) {
clear();
alert("恭喜你,进入下一关卡!\n下一关卡需要得分:" + ( score + 10 ));
document.getElementById("score").innerHTML = 0;
game(level + 1, score + 10);
}
break;
}
}
};
// 3. 根据随机生成的字母生成标签,插入到gameBox中并显示
var createSpan = function (letter) {
var span = document.createElement("span");
var spanCon = document.createTextNode(letter);
var loc = document.getElementById("gameBox");
var width = parseInt(loc.style.width);
span.appendChild(spanCon);
span.setAttribute("style",
"position:absolute;" +
"top:" + parseInt(loc.offsetTop) + "px;" +
"left:" + Math.random() * width + "px;" +
"display:inline-block;" +
"height:15px;width:15px;" +
"line-height:15px;" +
"text-align:center;" +
"background-color:#888;" +
"border-radius:15px;" +
"box-shadow:0 0 2px 5px rgba(255,255,255,0.5);" +
"opacity:0.8");
loc.appendChild(span);
spanMove(span);
return span;
};
// 4. 获取标签位置始末并下落
var spanMove = function (span) {
// 页面高度
var height = parseInt(document.getElementById("gameBox").style.height);
var top = parseInt(span.style.top);
span.intervalID = window.setInterval(function () {
if (span.parentNode) {
top = top + 1;
if (top <= height - 40) {
span.style.top = top + "px";
} else {
span.style.boxShadow = "0px 0px 2px 5px red";
console.log(span.intervalID);
clearInterval(span.intervalID);
alert("很遗憾,挑战失败,游戏结束!");
location.reload();
}
}
}, getSpeed[level].speed);
};
var clear = function () {
clearInterval(game.timer);
window.removeEventListener("keyup", keyup);
for (var n = spanArr.length - 1; n >= 0; n--) {
console.log(spanArr[n] === null);
if (spanArr[n] !== null) {
spanArr[n].parentNode.removeChild(spanArr[n]);
clearInterval(spanArr[n]);
spanArr[n] = null;
}
}
letterArr = [];
};
game.timer = setInterval(start, 200);
}
// 游戏开始
document.getElementById("start").addEventListener("click", function () {
var start = document.getElementById("start");
start.disabled = true;
start.style.cursor = "not-allowed";
document.getElementById("tools").style.display = "none";
document.getElementById("panel").style.display = "inline-block";
game(1, 10);
});
// 游戏结束
document.getElementById("stop").addEventListener("click", function () {
location.reload();
document.getElementById("start").disabled = false;
document.getElementById("tools").style.display = "block";
document.getElementById("panel").style.display = "none";
});
对象化设计该部分,将功能细分并且抽象,使得代码更易于阅读和修改(ps:大概这部分才是前端愿意看的代码…)。这里只放修改后的JS的部分咯,再关门,再放代码:
/**
* main.js
*/
// 1. letter对象,使用构造函数模式创建对象
/**
* @param id: 时间戳作为每个标签的唯一标识
* @param value: 存储随机生成的字母值
* @param x_pos: 标签的left初始值
* @param y_pos: 标签的top初始值
*/
function Letters(id, value, x_pos, y_pos) {
this.id = id || '';
this.value = String.fromCharCode(value) || 'A';
this.x_pos = x_pos || 0;
this.y_pos = y_pos || 0;
this.speed = Math.random() * 3 + 1;
this.domObj = null;
this.createSpan = function () {
var span = document.createElement("span");
var alpha = document.createTextNode(this.value);
span.appendChild(alpha);
span.setAttribute("id", this.id);
span.setAttribute("style",
"position:absolute;" +
"top: 0px;" +
"left: 0px;" +
"display:inline-block;" +
"height:15px;width:15px;" +
"line-height:15px;" +
"text-align:center;" +
"background-color:#888;" +
"border-radius:15px;" +
"box-shadow:0 0 2px 5px rgba(255,255,255,0.5);" +
"opacity:0.8");
this.domObj = span;
};
// 将标签创建之后赋值给this.domObj,调用此函数时,将this.domObj添加到相应父元素中去
this.attachStage = function (stage) {
stage.appendChild(this.domObj);
};
this.moveTo = function (x_pos, y_pos) {
this.domObj.style.left = x_pos + "px";
this.domObj.style.top = y_pos + "px";
};
this.remove = function () {
var span = document.getElementById(this.id);
if (span.parentNode !== null) {
span.parentNode.removeChild(span);
} else {
console.log("No parent Node! " + span.id);
}
};
this.failed = function () {
var span = document.getElementById(this.id);
span.style.boxShadow = "0px 0px 2px 5px red";
};
}
// 2. Monitor对象
function Monitor(level, score) {
// 存储当前屏幕上的所有元素
var nodes = [];
// 存储最终得分
var final = new Score();
// 逐帧运行
this.runFrame = function () {
if (Math.random() > 0.8) {
this.createAlpha();
}
for (var i = 0; i < nodes.length; i++) {
nodes[i].y_pos = parseInt(nodes[i].y_pos) + nodes[i].speed;
nodes[i].y_pos += "px";
document.getElementById(nodes[i].id).style.top = nodes[i].y_pos;
if (parseInt(nodes[i].y_pos) > 560) {
nodes[i].failed();
alert("很遗憾,挑战失败,游戏结束!");
location.reload();
}
if (level === 5 && final.getCount() >= score) {
alert("恭喜你,完成所有关卡,闯关成功!");
location.reload();
console.log("getCount = " + final.getCount() + " score = " + score + final.getCount() >= score);
} else if (final.getCount() >= score) {
alert("恭喜你,进入下一关卡\n下一关卡需要得分:" + (score + 10));
final.clearScore();
document.getElementById("score").innerHTML = String(0);
this.clear();
level++;
score += 10;
this.Monitor(level, score);
}
}
};
// 生成字母表标签
this.createAlpha = function () {
// 随机生成字母(A - Z)
var code = 65 + Math.floor(Math.random() * 26);
var letter = new Letters(new Date().getTime(), code);
// 将定义的节点添加到盒子中
letter.createSpan();
letter.attachStage(document.getElementById("gameBox"));
var x = Math.ceil(Math.random() * parseInt(document.getElementById("gameBox").style.width));
var y = 0;
letter.moveTo(x, y);
nodes.push(letter);
return letter;
};
// 绑定键盘事件
this.keydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var keyCode = String.fromCharCode(e.keyCode);
for (var i = 0; i < nodes.length; i++) {
if (keyCode === nodes[i].value) {
nodes[i].remove();
nodes.splice(i, 1);
final.incScore();
document.getElementById("score").innerHTML = final.getCount();
break;
}
}
// 跳出循环后,i = monitor.node.length || i = 当前value在node中的下标
// 若i = monitor.node.length, 则表明未找到键盘按下的字母
// i !== 0排除数组只有一个元素且被消除的情况
if (i === nodes.length && i !== 0) {
final.decScore();
document.getElementById("score").innerHTML = final.getCount();
if (final.getCount() === 0) {
alert("很遗憾,您的分数太低了,挑战失败!");
location.reload();
}
}
};
// 清除游戏界面上的多余标签
this.clear = function () {
for (var i = 0; i < nodes.length; i++) {
nodes[i].remove();
}
nodes.splice(0, nodes.length);
clearInterval(this.Monitor.timer);
};
this.refreshFrame = {
1: {
time: 150
},
2: {
time: 100
},
3: {
time: 80
},
4: {
time: 50
},
5: {
time: 30
}
};
window.addEventListener("keydown", this.keydown);
this.Monitor.timer = setInterval(this.runFrame, this.refreshFrame[level].time);
}
// 3. 计分对象
function Score() {
var count = 0;
this.incScore = function () {
count++;
};
this.decScore = function () {
count --;
};
this.getCount = function () {
return count;
};
this.clearScore = function () {
count = 0;
}
}
// 游戏开始
document.getElementById("start").addEventListener("click", function () {
var start = document.getElementById("start");
start.disabled = true;
start.style.cursor = "not-allowed";
document.getElementById("tools").style.display = "none";
document.getElementById("panel").style.display = "inline-block";
Monitor(1, 10);
});
// 游戏结束
document.getElementById("stop").addEventListener("click", function () {
location.reload();
document.getElementById("start").disabled = false;
document.getElementById("tools").style.display = "block";
document.getElementById("panel").style.display = "none";
});
这一部分内容我也按照不同的版本来进行不同的解释好了,按照上面摆放的顺序,首先来说一下第一个版本。
这里我先要说一下,原则上纯JS的打字游戏是没有这么多元素更没有CSS文件的,我只是看到有的游戏做的很好看,然后就突发奇想把自己做的游戏加上了样式,还顺便捞过来一个背景图→_→,不知道算不算侵权,如果算作者记得提醒我我就换一下。哦,还有我的样式只是我在我自己的电脑上看起来比较顺眼,换了电脑如果样式辣眼睛我是不负责任的。
———— (正事专用分隔线←_←) 我们来考虑一个问题,字母游戏的需求是什么:不断生成掉落的字母,然后由用户键盘按下后对应消除游戏界面上的字母,当字母超出界面或者按错按钮时做出对应操作。根据该需求,我们不难得到,字母游戏的主要功能有以下几点: 现在可以开始完成功能了:使用JS自带的Math对象随机生成一个数字,并将该数字转换为字符串中的字符。也就是我们上面的getRandom()函数: 接着我们考虑,游戏需要的内容,有了生成的字母,现在需要一个标签来装这个字母,然后标签装好了字母后还要从顶部向下掉落,所以创建标签函数中需要一个参数,该参数表示传入的字母值,将字母值写入span标签的文本节点并对其设置样式,然后将设置好内容和样式的标签插入到游戏盒子中去,这就是createSpan()函数完成的工作;至于标签下落的工作就不妨交给spanMove()函数,它可以获取页面高度,并对每一个元素设置对应的计时器ID(正式代码千万不要这样写,效率极低!!!另外此处还有一个容易被忽略掉的bug,让我头疼了很久啊,后面仔细说)。 至此,我们完成了1、2两个功能点。接下来我们看第3个功能点,键盘绑定事件(哎呀,我开始以为很难,其实简单到只有几行代码←_←) 我们可以先写一个start函数来运行前面的代码,start函数作为整体游戏的setInterval的功能,对该游戏进行重复的调用,具体代码见start()函数,功能很好理解,随机生成一个字母,将该字母添加到全局变量letterArr[]中,然后调用createSpan()函数生成字母对应的标签元素,再对窗口添加键盘监听事件。那么键盘监听事件做的事情有哪些呢:获取键入信息→判断键入信息是否与自身定义的对象相同→根据判断结果做出不同的响应。具体代码见keyup()函数。 最后,我们来考虑游戏结束的判断。我在设计游戏的时候,设置的一共有5关,每一关过关分数在之前的基础上加10分,一旦元素接触到底线游戏就结束,用户成功闯过5关则挑战成功。此处有三处判断:1. 游戏界面是否有元素触碰到底线(该部分判断在spanMove()函数中);2. 用户在当前关卡是否已经达到过关分数;3. 用户是否全部闯关成功(判断2,3在keyup()函数中)。在这里,我就遇到了由于每个元素都设置了setInterval()而带来的隐患:我在数组中将元素删除了之后,没有清除元素对应的计时器,导致元素从游戏界面上清除了但是过了一段时间会直接弹出“游戏失败”的幽灵事件。解决方案也是只有一行代码,我已经在对应的地方做了标注。 恕我直言,自己写了第二个版本之后,真心觉得第一个版本没眼看…这都是什么鬼,功能各种交缠,说都说不清楚,就是纯粹的功能的堆叠没有一点逻辑!请注意:JavaScript是面向对象的语言!JavaScript是面向对象的语言!!JavaScript是面向对象的语言!!!所以,在设计代码的时候,注意要从面向对象的方式来思考问题。把之前的所有思路打破,来分析一下在打字游戏中有哪些对象: 首先,我们来看第一个对象:我们在HTML文件中已经设置了一个div标签用于确定游戏盒子的大小和方位,之后的操作只需要把生成的字母标签添加到游戏盒子中即可,对象1的功能就到此结束。 其次,我们解释一下字母对象,和字母有关的操作:创建字母,删除字母,改变字母的状态。此处可能会有疑问,字母下落难道不是和字母相关的操作吗?准确的说,字母下落是指挥者控制的行为,字母本身只需要确定它需要下落的初始位置,下落的行为不由字母本身控制,而是应该由指挥者指导,并且下落是所有字母的共同行为,所以如果每个字母逐个下落,反而不如由指挥者控制每一帧中字母的下落来得方便和效率。另外,字母作为对象,它需要接收一系列参数,每个字母应该有一个id,还应该有字母的值,同时,为了确认下落初始位置,还可以设置两个参数x_pos和y_pos来确认方位。对应所需要的方法就有创建字母标签 最后是指挥者对象,指挥者对象首先需要有创建字母数组的功能,能够在屏幕上显示多个字母,对应 不知道读者大大有没有觉得Edition 2整体功能很清晰,从上往下逐个解读也没有任何困难,反正我是觉得写了两个版本之后,深刻的意识到面向对象的好处,就是逻辑清晰,包装良好同时各部分适当耦合,符合最小最大和开放封闭原则,是一个很好的编码方式。在之后的学习中,我也会尽量去按照这种方式去进行编码。 http://www.cnblogs.com/diligenceday/p/5857103.html
HTML文件和CSS样式就不多说了,“开始游戏”按钮控制游戏的开始,游戏中的字母标签均在
Edition 2 代码分析
id="gameBox"
的div元素,它像舞台一样圈定了游戏的范围;createSpan()
,将字母标签添加至盒子attachStage()
,将字母移动到指定的初始位置moveTo()
,移除当前字母标签remove()
,代码中的failed()
方法用于改变字母标签的状态,当字母标签下落至危险区域时,将标签设置为该状态提示用户。createAlpha()
方法;然后指挥者需要能够监控键盘,获取并判断用户键入的值是否与当前屏幕上显示的值相同,对应keydown()
方法;最后还需要一个清除屏幕上所有字母的方法,对应clear()
方法。这里再讨论一下上一个对象中移动的问题,指挥者作为所有字母行为的监控人,它是用来指挥所有字母下落的,这里需要用到计时器的setInterval()
函数来对每一帧的内容进行修改,当执行的频率较快时,用户就不会看到明显的卡顿了。所以setFrame()
函数就用来控制字母表的行为,并对闯关的结果进行判断。参考网址
http://www.mycodes.net/166/7302.htm
(背景图就是上面的代码里“盗”的),这个游戏真的炫,我这个小菜鸡是做不出来