La version mobile du jeu de grille a été développée plus rapidement que prévu.
Aperçu de l'interface de jeu
Difficultés d'adaptation
1. Surveillance coulissante dans quatre directions d'événements tactiles.
Solution: J'ai trouvé une classe d'outils écrite par un grand dieu pour résoudre parfaitement le problème. Merci ici.
2. La largeur et la hauteur de la grille ne sont pas fixes et le pourcentage est considéré comme un pourcentage. La valeur renvoyée par le navigateur de pourcentage supprimera la virgule décimale et l'arrondira, ce qui entraînera un écart de la position de décalage du dictionnaire de comparaison .
Solution: définissez l'unité de mesure et modifiez le décalage de manière synchrone dans le dictionnaire de comparaison. Lors du calcul du score, le jugement tout égal d'origine est remplacé par une valeur d'écart comparative.
3. L'événement tactile est déclenché plusieurs fois.
Solution: définissez la valeur booléenne touchingScreen pour limiter le toucher ne peut être déclenché qu'une seule fois à la fois.
4. Réglage de la mise en page.
La fenêtre d'écran est limitée et la liste des scores et le plateau n'apparaissent plus en même temps. En raison de la limitation de largeur, la grille en damier utilise une largeur en pourcentage au lieu d'une largeur fixe. Pourcentage de largeur, définir une hauteur égale à la largeur nécessite des compétences en CSS. Utilisez le style touch-action: none; sur la grille afin qu'aucun événement tactile n'ait le comportement par défaut, mais l'événement tactile sera toujours déclenché, résolvant ainsi l'incapacité à écouter passivement l'événement preventDefault.
annuler
1. L'affichage de la valeur du score n'est pas centré sur la grille.
2. Le problème du défilement sur les pages de navigation mobile n'a pas été résolu.
3. Lorsque le jeu commence, la petite fille signale une erreur lorsqu'elle se déplace normalement puis revient au point de départ. Emplacement de l'erreur: this.chess [i] .usedScore Message d'erreur: Impossible de lire la propriété'usedScore 'd'undefined.
Code principal index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<title>方格游戏</title>
<link rel="shortcut icon" href="favicon.ico">
<meta name="keywords" content="方格游戏">
<meta name="description" content="方格游戏">
<script src="vue.js"></script>
<style>
body{
width: 100%;
height:100%;
margin:0;
background: #fff;
overflow: hidden;
box-sizing: border-box;
color:#1a2a65;
}
div.container{
width: 100%;
margin-top:80px;
text-align: center;
}
div.container div.chess{
width: 98%;
margin: 0 auto;
box-sizing: border-box;
border:1px solid #ccc;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
div.chess-grid{
width:14.2857%;
height:0;
box-sizing: border-box;
padding-bottom: 14.2857%; /* 让div的高等于宽 */
line-height: 100%;
text-align: center;
color: #fff;
/*应用 CSS 属性 touch-action: none; 这样任何触摸事件都不会产生默认行为,但是 touch 事件照样触发,从而解决无法被动侦听事件preventDefault*/
touch-action: none;
}
/*雪人*/
div.snowman{
width:100%;
padding-bottom: 100%; /* 让div的高等于宽 */
background: url("head.jpg") no-repeat;
background-size: 100% 100%;
cursor: pointer;
}
/*雪人的家*/
div.home{
width:100%;
padding-bottom: 100%; /* 让div的高等于宽 */
background: url("home.jpg") no-repeat;
background-size: 100% 100%;
}
div.container div.score{
width: 98%;
margin: 0 auto;
box-sizing: border-box;
border:1px solid red;
background: #f7f7f7;
text-align: center;
}
div.container div.score .title{
font-size:18px;
background:red;
color:#fff;
padding:10px;
}
/*游戏计时器*/
div.head{
width:100%;
height:64px;
box-sizing: border-box;
text-align: center;
position: fixed;
top: 0;
background: #f7f7f7;
padding:12px;
}
div.head span,div.score span{
font-size: 24px;
font-weight: bold;
color:red;
padding:0 4px
}
div.game_start_box{
display: flex;
justify-content: center;
}
div.game_time_box{
margin-right:20px;
line-height: 38px;
}
div.game_time_box input{
height:30px;
width:40px;
box-sizing: border-box;
}
div.game_start_btn{
width:100px;
height:40px;
line-height: 40px;
box-sizing: border-box;
border:1px solid #ccc;
color: #fff;
cursor: pointer;
background: green;
}
/*控制器*/
div.control{
width:100%;
height:60px;
line-height:60px;
text-align: center;
display: flex;
justify-content: center;
position: fixed;
bottom: 0;
background: #f7f7f7;
}
div.btn{
width:200px;
height:40px;
line-height: 40px;
box-sizing: border-box;
border:1px solid #ccc;
margin:10px;
color: #fff;
cursor: pointer;
}
div.auto{
background: green;
}
div.refresh{
background: red;
}
</style>
</head>
<body>
<div id="root">
<div class="head" v-show="!showControl">
<div v-show="!showTime" class="game_start_box">
<div class="game_time_box">倒计时设置(10 ~ 60s):<input type="number" v-model="userSetGameTime" min="10" max="60"/></div>
<div class="game_start_btn" @click="game_start">开始游戏</div>
</div>
<div v-show="showTime">倒计时<span>{
{gameTime}}</span>秒 </div>
</div>
<div class="container">
<div class="chess" v-show="!showControl">
<div class="chess-grid"><div ref="snowman" class="snowman" :style="moveStyle" title="按键盘方向键移动我哦" @touchend="dealTouchEvent($event)"></div></div>
<div class="chess-grid" v-for="(item,index) in chess" :key="index" :style="{background:item.background}">{
{item.usedScore == 0 ? item.usedScore : item.score}}</div>
<div class="chess-grid"><div class="home"></div></div>
</div>
<div class="score" v-show="showControl">
<div class="title">战绩</div>
<p v-for="(score,index) in gameScores" :key="index">第{
{index+1}}战得分<span>{
{score}}</span>分</p>
</div>
</div>
<div class="control" v-show="showControl">
<div class="btn auto" @click="game_review">本局复盘</div>
<div class="btn refresh" @click="game_update">再来一局</div>
</div>
</div>
<script src="touch.js"></script>
<script>
new Vue({
el: "#root",
data: {
touchingScreen:false,//是否正在触屏 限制 touch 触发频率
showControl:false,//是否显示本局复盘、再来一局
showTime:false,//是否显示倒计时 不显示倒计时的时候会显示开始游戏按钮
is_get_home:false,
gameTime:30,//系统设置的游戏时间 30秒一局游戏
userSetGameTime:30,//用户设置的游戏时间
gameScores:[],//单机多人游戏 游戏复盘,存储历史得分
currentScore:0,//当前一局游戏的得分
moveDown:0,
moveRitht:0,
chess:[],//存储随机的分数和背景色数组
moveStyle:{transform:"translate(0,0)"},//偏移样式
unit:0,//记量单位 移动端 移动的距离不再是固定的
isDictionaryUpdate:false,//字典中一部分数据只用更新一次,因此设置flag,isDictionaryUpdate 是否已经同步更新数据,默认否。
dictionary:[//棋盘各块偏移量对照字典 最后一格为终点格
{"score":0," i":0, "r":1, "d":0},
{"score":0, "i":1, "r":2, "d":0},
{"score":0,"i":2, "r":3, "d":0},
{"score":0,"i":3, "r":4, "d":0},
{"score":0,"i":4, "r":5, "d":0},
{"score":0,"i":5, "r":6, "d":0},
{"score":0,"i":6, "r":0, "d":1},
{"score":0,"i":7, "r":1, "d":1},
{"score":0,"i":8, "r":2, "d":1},
{"score":0,"i":9, "r":3, "d":1},
{"score":0,"i":10, "r":4, "d":1},
{"score":0,"i":11, "r":5, "d":1},
{"score":0,"i":12, "r":6, "d":1},
{"score":0,"i":13, "r":0, "d":2},
{"score":0,"i":14, "r":1, "d":2},
{"score":0,"i":15, "r":2, "d":2},
{"score":0,"i":16, "r":3, "d":2},
{"score":0,"i":17, "r":4, "d":2},
{"score":0,"i":18, "r":5, "d":2},
{"score":0,"i":19, "r":6, "d":2},
{"score":0,"i":20, "r":0, "d":3},
{"score":0,"i":21, "r":1, "d":3},
{"score":0,"i":22, "r":2, "d":3},
{"score":0,"i":23, "r":3, "d":3},
{"score":0,"i":24, "r":4, "d":3},
{"score":0,"i":25, "r":5, "d":3},
{"score":0,"i":26, "r":6, "d":3},
{"score":0,"i":27, "r":0, "d":4},
{"score":0,"i":28, "r":1, "d":4},
{"score":0,"i":29, "r":2, "d":4},
{"score":0,"i":30, "r":3, "d":4},
{"score":0,"i":31, "r":4, "d":4},
{"score":0,"i":32, "r":5, "d":4},
{"score":0,"i":33, "r":6, "d":4},
{"score":0,"i":34, "r":0, "d":5},
{"score":0,"i":35, "r":1, "d":5},
{"score":0,"i":36, "r":2, "d":5},
{"score":0,"i":37, "r":3, "d":5},
{"score":0,"i":38, "r":4, "d":5},
{"score":0,"i":39, "r":5, "d":5},
{"score":0,"i":40, "r":6, "d":5},
{"score":0,"i":41, "r":0, "d":6},
{"score":0,"i":42, "r":1, "d":6},
{"score":0,"i":43, "r":2, "d":6},
{"score":0,"i":44, "r":3, "d":6},
{"score":0,"i":45, "r":4, "d":6},
{"score":0,"i":46, "r":5, "d":6},
{"score":0,"i":47, "r":6, "d":6}
]
},
methods: {
//生成随机分数棋格
createChess:function(){
//7*7方格,掐头去尾,需要生成47个随机数。
var score;
var bgColor;
if(this.isDictionaryUpdate){
for(var i=0;i<47;i++){
// 按奇数偶数对应正负分值
if(i%2 ==0){
//正数 加分
score =Math.round(Math.random()*10)+2;
bgColor="#16a05d";
}else{
//负数 减分
score =-Math.round(Math.random()*6)-1;
bgColor="#e21918";
}
this.chess.push({
"score":score,
"usedScore":100,//随便指定一个现今规则不可能有的一个分数即可
"background":bgColor
});
//同步更新对照字典,存储分值。
this.dictionary[i].score = score;
}
}else{
for(var i=0;i<47;i++){
// 按奇数偶数对应正负分值
if(i%2 ==0){
//正数 加分
score =Math.round(Math.random()*10)+2;
bgColor="#16a05d";
}else{
//负数 减分
score =-Math.round(Math.random()*6)-1;
bgColor="#e21918";
}
this.chess.push({
"score":score,
"usedScore":100,//随便指定一个现今规则不可能有的一个分数即可
"background":bgColor
});
//同步更新对照字典,存储分值。
this.dictionary[i].score = score;
//同步更新对照字典,计算准确坐标偏移量
this.dictionary[i].r = this.dictionary[i].r * this.unit;
this.dictionary[i].d = this.dictionary[i].d * this.unit;
}
this.isDictionaryUpdate=true;
}
},
//处理手机触摸事件
dealTouchEvent:function(e){
e||event;
this.touchingScreen = true;
//使用的时候很简单,只需要像下面这样调用即可 up, right, down, left为四个回调函数,分别处理上下左右的滑动事件
EventUtil.listenTouchDirection(e.target,true,this.upCallback, this.rightCallback, this.downCallback, this.leftCallback);
},
//touch的回调事件
upCallback:function(){
//当游戏倒计时显示时,即游戏还未结束,才能触发键盘事件,开始移动。
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//触屏只触发一次
//向上移动
this.moveDown -=this.unit;
this.moveDown < 0 ? this.moveDown = 0 : this.moveDown;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根据偏移的位置,统计得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
rightCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//触屏只触发一次
//向右移动
this.moveRitht +=this.unit;
//判断界限值 不能超出棋盘活动
this.moveRitht > 6*this.unit ? this.moveRitht = 6*this.unit : this.moveRitht;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根据偏移的位置,统计得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
downCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//触屏只触发一次
//向下移动
this.moveDown +=this.unit;
this.moveDown > 6*this.unit ? this.moveDown = 6*this.unit : this.moveDown;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根据偏移的位置,统计得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
leftCallback:function(){
if(this.showTime && this.touchingScreen){
this.touchingScreen = false;//触屏只触发一次
//向左移动
this.moveRitht -=this.unit;
//判断界限值 不能超出棋盘活动
this.moveRitht < 0 ? this.moveRitht = 0 : this.moveRitht;
this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
//根据偏移的位置,统计得分。
this.countScore(this.moveRitht,this.moveDown);
}
},
//计算得分
countScore:function(r,d){
//遍历偏移量字典,根据当前所在的位置,获取对应的分值。
//偏移量字典(len=48)比棋格(len=47)多了一个终点的位置信息。
if(!(Math.abs(r - this.unit*6) < 7 && Math.abs(d - this.unit*6) < 7)){
//不在家,赋值false 防止回家后再离开的情形
this.is_get_home = false;
for(var i=0;i<48;i++){
//因为浏览器将数值取整后返回,所以,与实际值差值小于1.累积偏移差值小于1*7. Math.abs 取绝对值.
if(Math.abs(r - this.dictionary[i].r) < 7 && Math.abs(d - this.dictionary[i].d) < 7){
//游戏开始时,小女孩正常移动后再回到起点会报这个错误
//undo: this.chess[i].usedScore 有时会报错 Cannot read property 'usedScore' of undefined
if(this.chess[i].usedScore == 100){
this.currentScore += this.dictionary[i].score;
//分数一次性有效 走过的分数变为0.
//为了复盘,不直接改变分数,新分数存储到 usedScore
this.chess[i].usedScore = 0;
}
}
}
}else{
//雪人到家
this.is_get_home = true;
}
},
//计时器,每次时间-1,时间单位秒。
timer:function(){
this.gameTime -= 1
},
//用户点击游戏开始 创建定时器 显示倒计时
game_start:function(){
if(this.gameTime != this.userSetGameTime){
//系统设置的游戏时长和用户设置的游戏时长冲突,则使用用户设置的时长
this.gameTime = this.userSetGameTime;
}
this.showTime=true;
timer1=setInterval(this.timer, 1000);
},
game_review:function(){
this.parameter_reset();
this.resetUsedScore();
},
//恢复棋盘 使用过的分数初始化
resetUsedScore:function(){
var len = 47;
while(len--){
this.chess[len].usedScore = 100;
}
},
//本局重玩,只需要重置参数。
parameter_reset:function(){
this.showControl=false;
this.is_get_home = false;
this.showTime=false;//先不显示倒计时,显示开始游戏按钮。
this.moveRitht=0;
this.moveDown=0;
this.moveStyle.transform = "translate(0,0)";
this.currentScore=0;
},
//页面初始化 游戏重新开始
game_update:function(){
this.parameter_reset();
this.gameScores=[];
this.chess=[];
this.createChess();
}
},
watch:{//监测游戏时间
gameTime(){
if(this.gameTime == 0){
clearInterval(timer1);
this.showControl=true;
this.showTime=false;//倒计时结束,关闭倒计时结果显示
if(this.is_get_home){
//游戏结束:倒计时结束,雪人进入小屋。当前得分计入。
this.gameScores.push(this.currentScore);
}else{
//游戏失败: 倒计时结束,但雪人未进入小屋。本局得分为0。
this.currentScore=0;
this.gameScores.push(0);
}
}
}
},
mounted(){
//计量单位等于小方格的宽或高
//获取的高度值约等于实际值,存在差值。获取的值取了实际值的近似整数。
this.unit = this.$refs.snowman.offsetHeight;
this.game_update();
}
})
</script>
</body>
</html>
L'outil de code référencé touch.js
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener)
element.addEventListener(type, handler, false);
else if (element.attachEvent)
element.attachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
removeHandler: function (element, type, handler) {
if(element.removeEventListener)
element.removeEventListener(type, handler, false);
else if(element.detachEvent)
element.detachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
/**
* 监听触摸的方向
* @param target 要绑定监听的目标元素
* @param isPreventDefault 是否屏蔽掉触摸滑动的默认行为(例如页面的上下滚动,缩放等)
* @param upCallback 向上滑动的监听回调(若不关心,可以不传,或传false)
* @param rightCallback 向右滑动的监听回调(若不关心,可以不传,或传false)
* @param downCallback 向下滑动的监听回调(若不关心,可以不传,或传false)
* @param leftCallback 向左滑动的监听回调(若不关心,可以不传,或传false)
*/
listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
this.addHandler(target, "touchstart", handleTouchEvent);
this.addHandler(target, "touchend", handleTouchEvent);
this.addHandler(target, "touchmove", handleTouchEvent);
var startX;
var startY;
function handleTouchEvent(event) {
switch (event.type){
case "touchstart":
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
break;
case "touchend":
var spanX = event.changedTouches[0].pageX - startX;
var spanY = event.changedTouches[0].pageY - startY;
if(Math.abs(spanX) > Math.abs(spanY)){ //认定为水平方向滑动
if(spanX > 30){ //向右
if(rightCallback)
rightCallback();
} else if(spanX < -30){ //向左
if(leftCallback)
leftCallback();
}
} else { //认定为垂直方向滑动
if(spanY > 30){ //向下
if(downCallback)
downCallback();
} else if (spanY < -30) {//向上
if(upCallback)
upCallback();
}
}
break;
case "touchmove":
//阻止默认行为
if(isPreventDefault)
event.preventDefault();
break;
}
}
}
};