试玩地址,请使用手机打开
代码放在了github
杀人游戏流程
2-1:冷启动界面
2-2:版本选择(其实只有一个版本)
2-3:设置玩家人数
3:身份查看页
4-1:法官查看页
4-2:显示天数
4-3:杀人页
4-4:黑夜解密
4-5:投票页
4-6:结果页
设置玩家人数,可满足6-18人游戏。设置完成之后,依次查看自己的身份,不要泄露给他人。然后法官查看身份,宣布开始游戏。
晚上,杀手杀人。白天公布被杀玩家,被杀的玩家发言,全体讨论后投票。
当水民全部死亡,杀手获得胜利,反之杀手全部死亡,水民获胜。
篇幅有限,这里我只说一下实现的最主要的地方
1. localStorage
关于localStorage与Cookie的与别:
Cookie主要用来保存登陆信息,大小被限制在4KB左右
localStorage即本地储存,是HTML5标准新加入的技术。大小通常为5M左右,兼容IE8及以上浏览器
用法:
- 写入:
localStorage.str = "Hello world!"
或者localStorage.setItem("str","Hello World!")
- 读取:
var nStr = localStorage.str;
或者var nStr = localStorage.getItem("str");
- localStorage是一个对象,所以有长度,可遍历
localStorage注意事项:
使用localStorage储存在本地均为字符串,比如1 2
| localStorage.num = 3; console.log(typeof localStorage.num); //string
|
所以取本地储存中的值的时候,需要类型转换
1 2
| var num = JSON.parse(localStorage.num); console.log(typeof num); //number
|
对数组和对象要在储存前将其转换为JSON字符串才能保证取出来的还是原来的类型
1 2 3 4
| var arr = [1,2,3]; localStorage.arr = JSON.stringify(arr); //转换为JSON字符串 var nStr = JSON.parse(localStorage.arr); console.log(typeof nStr); //object
|
2. DOM操作
一些基本的DOM操作,比如这里向HTML追加元素
采用的方法就是分别对杀手人数和水民人数进行遍历,进行以下操作
1 2 3 4
| var parent = document.getElementById("xx"); //找到父元素 var span = document.creatElement("span");//穿件标签 span.innerHTML = "xxxxx";//标签内容 parent.appendChild(span);//向父元素追加
|
还有比如更改节点属性,在判断是否胜利的时候,如果达到胜利条件的话,就要更改a
标签的href
属性
1
| element.setAttribute(name,value);
|
3. 计时器
主要思路是:
在每天开始的时候,使用new Date()
获取当前的时间,然后存到localStorage里面,在每天结束的时候,再获取一个当前时间,并把localStorage里的时间取出来,相减,得到的就是这一天用的时间。
在每天开始时的代码
1 2 3 4
| btn.addEventListener("click",function () { var time = new Date(); localStorage.time = time; })
|
每天结束时的代码
1 2 3 4 5
| btn.addEventListener("click",function () { var time = new Date() - new Date(localStorage.time); var name = "time" + (deadPlayer.length / 2);//根据死亡了几个玩家来判断是第几天,分配localStorage的名字 localStorage.setItem(name,time); })
|
结果就是这样的
还有一点
我们是在每天结束的时候相减得到该天的时间的,那如果这天只过了一半就获胜了呢?最后一天的时间就没了,所以在达到胜利条件的时候我们需要再储存一次最后一天的耗时
1 2 3 4 5 6 7 8
| if(aliveWaters === 0 || aliveKillers === 0) { //修复计时BUG var lastTime = new Date() - new Date(localStorage.time); localStorage.lastTime = lastTime; btn.parentNode.setAttribute("href", "task4-6.html"); } else { btn.parentNode.setAttribute("href", "task4-4.html") }
|
如果活着的杀手或者活着的水民人数为0,就满足获胜条件,就立刻获取时间然后相减,因为不知道是第几天,所以就干脆命名为lastTime
我们得到了每天的时间,但是是存在本地储存里的,我们要取出来,之前说localStorage
又长度,可遍历,这里就要对localStorage
进行遍历。通过localStorage.getItem(name)来获取对应的值
因为我们在localStorage里存了很多东西,比如活着的人数,死亡玩家的数组等等,我们只想要name
为time*
这种格式的,所以我们验证name
前4个字符为name
的取出来存到数组里待用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| //获取储存在本地的时间 var timeArr = []; for(var i = 0; i < localStorage.length; i++) { if(localStorage.key(i).substring(0,4) === "time") { var getValue = localStorage.getItem(localStorage.key(i)); timeArr.push(Math.floor(parseInt(getValue) / 1000)); } } //因为存的还有time这个值,前面讲了它是用来获取当时的时间的,它的前4位也满足,所以也给取出来了,但是它是没用的,所以给他删除 timeArr.shift(); //修复计时bug var lastTime = parseInt(localStorage.lastTime); timeArr.push(Math.floor(lastTime / 1000));
|
然后关于时间的格式,经过上面的处理,我们得到了单位为秒的数组,但是我们不能直接把秒输出到HMTL里,我们应该输出为xx小时xx分钟xx秒
这样的格式,还牵扯到补0的问题。
1 2 3 4 5 6 7 8 9
| //把秒转化为标准时间 function time(s) { var hours = Math.floor(s / 3600); var minutes = Math.floor(s % 3600 / 60); var seconds = Math.floor(s % 3600 % 60); return (hours > 0 ? hours + "小时" + (minutes < 10 ? "0" : "") : "") + (minutes > 0 ? minutes + "分钟" + (seconds < 10 && seconds > 0 ? "0" : ""): "") + (seconds > 0 ? seconds + "秒" : ""); }
|
关于return
那一大块,其实就是一个条件操作符,相当于if(){}else{}
比如比较大小,如果num1>num2就把num1赋值给结果,否则就赋值num2。
1
| var max = (num1 > num2) ? num1 : num2;
|
4. 点击事件
onclick 与 addEventListener的区别:
addEventListener
是DOM2级事件处理程序,支持IE9以上,一个事件可以绑定多个函数,默认是false,冒泡阶段触发
onclick
是DOM0级事件处理程序,冒泡阶段触发
比如一个使方框变色的函数可以这样写
1 2 3 4 5
| for(var i = 0; i < liNum.length; i++) { liNum[i].onclick = function () { this.style.borderColor = "red";//注意这里要用this而不能用liNum[i],涉及到闭包,后面会讲 } }
|
为各个方框添加了点击事件之后,点击变色之后,再点击另一个,问题出现了。第二个变色了,但是第一个并没有变回去。我们的本意并不是这样的。。
解决办法就是,先定义一个临时变量,让它指向一个没有用的DOM节点(不指定节点的话接下来会报错)
1 2 3 4 5 6
| var temp = document.getElementById("fix"); btn.addEventListener("click",function(){ temp.style.borderColor = "#fff"; temp = this; temp.style.borderColor = "red"; })
|
比如第一次我们点击了第一个li,此时temp
等于id为fix的元素,使它边框变为白色,然后把第一个li赋值给temp
,然后使temp
(也就是第一个li)边框变红,然后第二次点击的时候,此时temp
是等于第一个li的,我们会先把上次点击的li(也就是第一个)变回原来的颜色,然后把这次点击的li赋值给temp
,继续变色。
这就完成了每次变色之前把上次点击的恢复颜色的效果,其实就是用一个中间值记录上次点击的数据
接下来讲刚刚说的闭包
比如我们有这样一个DOM结构
1 2 3 4 5 6 7 8 9
| <div id="div1"></div> <div id="div2"></div> <div id="div3"></div> <div id="div4"></div> <div id="div5"></div> <div id="div6"></div> <div id="div7"></div> <div id="div8"></div> <div id="div9"></div>
|
和这样一段js代码
1 2 3 4 5 6 7
| var count = document.getElementsByTagName("div"); for(var i = 0; i < count.length; i++) { count[i].onclick = function () { console.log(i); } }
|
结果并不是想象中那样每次点击输出对应的 i 。而是无论点击哪个,都会输出 9 。可以自己试一下
那为什么是这样呢。主要就是闭包+js没有块级作用域。每次迭代,就会形成一个闭包,onclick
函数会有一个对 i 值的引用,但是,由于没有块作用域,所以这个i是全局作用域中的,也就是这几个函数所引用的i是在同一个作用域中,当然也就只有一个值
那么怎么解决呢? 有两种方法
第一种:使用ES的let
语法
1 2 3 4 5
| for(let i = 0; i < count.length; i++) { count[i].onclick = function () { console.log(i); } }
|
因为let
会产生一个块级作用域,每次迭代都会产生一个块级作用域,里面有一个i,每个块级作用域互不影响。这样每次调用点击函数,就会访问对应作用域中的i,就好了
当然如果不想用ES6,我们还有第二种方法
第二种:使用立即执行函数
1 2 3 4 5 6 7 8
| for(var i = 0; i < count.length; i++) { (function (i) { count[i].onclick = function(){ console.log(i); } })(i) }
|
既然原因是没有块级作用域,那我们就手动模拟。每次迭代都会有一个立即执行函数,立即执行函数会创建一个函数作用域,我们把i作为参数传递进去,这样每个作用域也会保存一个i值
点击事件的最后
验证玩家身份和储存死亡玩家
杀手不能杀死杀死,不能杀死(投票给)已经死亡的玩家,这就需要我们在点击事件添加验证
我们在点击时,可以通过节点来获取到玩家身份
1 2 3 4 5 6 7 8
| var deadNum = parseInt(this.lastChild.firstChild.nodeValue); if(deadArr.indexOf(deadNum) >= 0) { alert("该玩家已死") } else if(this.firstChild.firstChild.nodeValue === "杀手") { alert("杀手不能杀死杀手!") } else { ... }
|
记录死亡玩家:
点击确定按钮之后,该玩家被杀死,记录被杀的玩家编号,并push进死亡玩家数组。
同时检查死亡玩家的身份,将相应(杀手或水民)的人数 - 1 。同时判断是否满足胜利条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| btn.addEventListener("click",function () { if(JSON.parse(localStorage.deadPlayerNum) === 0) { alert("请选择一名玩家"); } else { //点击按钮将死亡玩家存入数组 deadArr.push(JSON.parse(localStorage.deadPlayerNum)); localStorage.deadPlayerArr = JSON.stringify(deadArr); //输赢判断 switch (newPlayer[parseInt(localStorage.deadPlayerNum) - 1]) { case "杀手": aliveKillers = parseInt(localStorage.aliveKillers); aliveKillers--; localStorage.aliveKillers = aliveKillers; break; case "水民": aliveWaters = parseInt(localStorage.aliveWaters); aliveWaters--; localStorage.aliveWaters = aliveWaters; break; } if(aliveWaters === 0 || aliveKillers === 0) { //修复计时BUG var lastTime = new Date() - new Date(localStorage.time); localStorage.lastTime = lastTime; btn.parentNode.setAttribute("href", "task4-6.html"); } else { btn.parentNode.setAttribute("href", "task4-4.html") } } });
|
大致就是这么多,没有从头开始讲所以可能不太好懂,但是一个完整的游戏也不可能把代码全部拿出来,感兴趣的可以看我的github或者与我讨论。
大概就是这么多,如有纰漏,请指正