用html+js写一个简单的游戏

入驻博客园后的第一篇技术博客,这次来聊聊前端js技术应用。

 

最近接触一款手机游戏:PopStar(消灭星星),第一次在手机上玩就被深深吸引了。玩了几局后发现,想要一路过关斩将,就要想方设法得到更高的分数,所以要么一次消灭更多的星星,要么每一局剩下更少的星星(10个以下有奖励分数,挺可观的)(具体信息上网找攻略,这里不做详述)。

到此,技术宅有了一个自己写PopStar的计划,想到就开始动手 —— 这次选择html页面+js来实现。

GO ...

 

首先,需要知道这款游戏的各种规则,最基本的就是,消灭x个星星,得到(x2 * 5)分数,最后剩下星星数和得分:10(0)、9(380)、8(720)、7(1020) ... 1(1980)、0(2000)[神!星星眼仰望]

好了,首先需要有一个界面,这里就直接参照PopStar的游戏界面,简化了一番,用html写出:

  1 <style type="text/css">
  2 body {
  3     margin: 0;
  4     padding: 0;
  5     font-size: 14px;
  6     font-family: Arial;
  7 }
  8 
  9 .container {
 10     width: 600px;
 11     margin: 10px auto;
 12     border: 1px solid #aaaaaa;
 13     padding: 10px;
 14 }
 15 
 16 .label {
 17     display: inline-block;
 18     padding: 2px 9px;
 19     background-color: rgba(58, 135, 173, 0.75);
 20     color: #ffffff;
 21     text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
 22     white-space: nowrap;
 23     vertical-align: baseline;
 24     -webkit-border-radius: 3px;
 25     -moz-border-radius: 3px;
 26     border-radius: 3px;
 27 }
 28 
 29 .row:after,
 30 .box:after {
 31     display: table;
 32     content: "";
 33     line-height: 0;
 34     clear: both;
 35 }
 36 
 37 .stage,
 38 .difficulty,
 39 .target {
 40     float: left;
 41     width: 33.33333333%;
 42     padding: 5px 0 15px;
 43 }
 44 
 45 .score {
 46     text-align: center;
 47 }
 48 
 49 .message {
 50     height: 30px;
 51     line-height: 30px;
 52     margin: 15px 0 20px;
 53     text-align: center;
 54 }
 55 
 56     .message .selected {
 57         font-size: 10px;
 58         color: #999999;
 59     }
 60 
 61     .message .encourage {
 62         font-size: 16px;
 63         font-weight: bold;
 64     }
 65 
 66 .box {
 67     position: relative;
 68 }
 69 
 70     .box div {
 71         /*position: absolute;*/
 72         cursor: pointer;
 73         -webkit-border-radius: 3px;
 74         -moz-border-radius: 3px;
 75         border-radius: 3px;
 76 
 77         /* for text */
 78         float: left;
 79         width: 58px;
 80         height: 58px;
 81         margin: 1px;
 82     }
 83 
 84 .star-red {
 85     background-color: rgba(255, 0, 0, 0.5);
 86 }
 87 
 88 .star-green {
 89     background-color: rgba(0, 255, 0, 0.5);
 90 }
 91 
 92 .star-blue {
 93     background-color: rgba(0, 0, 255, 0.5);
 94 }
 95 
 96 .star-yellow {
 97     background-color: rgba(255, 255, 0, 0.5);
 98 }
 99 
100 .star-lightblue {
101     background-color: rgba(0, 255, 255, 0.5);
102 }
103 </style>
104 
105 <div class="container">
106     <div class="row">
107         <div class="stage">
108             级数
109             <label id="stage" class="label">1</label>
110         </div>
111         <div class="difficulty">
112             难度
113             <label id="difficulty" class="label">1</label>
114         </div>
115         <div class="target">
116             目标分数
117             <label id="target" class="label">1</label>
118         </div>
119     </div>
120     <div class="score">
121         分数
122         <label id="score" class="label">1</label>
123     </div>
124     <div class="message">
125         <label id="message_selected" class="selected"></label>
126         <label id="message_encourage" class="encourage"></label>
127         <label id="message_failed" class="encourage"></label>
128     </div>
129 
130     <div id="box" class="box">
131         <!-- 100个div标签 -->
132     </div>
133 </div>

这里仅聊聊实现逻辑,所以只是用代码简单的设置了5种颜色的星星,也可以用图片做得再漂亮一点。

 

有了界面接下来开始用js来操作。

引入jquery.js文件(比较懒,直接在jq官网down了最新版的,没去比较更新了什么内容),接着设置几个值:

 1 var maps = {
 2     xItemCount: 10, // 横排个数
 3     yItemCount: 10, // 竖排个数
 4     itemMargin: 1,
 5     difficulty: 5,  // 难度
 6     braveScore: { value: 125, message: 'Brave!' },   // 超过125分,鼓励
 7     failedMessage: 'Sorry, you are failed!'
 8 }
 9 var starClass = [
10     'star-red',
11     'star-blue',
12     'star-yellow',
13     'star-green',
14     'star-lightblue'
15 ];
16 
17 var Stage = 0, Target, Score = 0;
18 var $selecteditems = [];
19 
20 var $box = $('#box');
21 $box.css({ height: $box[0].clientWidth });
22 var boxWidth = $box[0].clientWidth;
23 var itemWidth = boxWidth / maps.xItemCount - maps.itemMargin * 2;

(因为css里只设置了5种星星样式,所以最高难度只到5)

 

接着,需要1个Array的自定义方法exists,用来判断星星Item是否在选择列表中;还需要3个计算分数的方法:_getTarget(获取目标级别的目标分数,原版中目标分数并非以一个相同的数字递增,这里为了方便就采取这种计算方法)、_getScore(根据选择、消灭的星星个数计算得分)、_getAdditionalScore(每局最后计算奖励分数)

 1 Array.prototype.exists = function ($item) {
 2     var isIn = false;
 3     this.forEach(function ($i) {
 4         if ($i.attr('id') === $item.attr('id')) { isIn = true; }
 5     });
 6     return isIn;
 7 };
 8 
 9 
10 /* 获取目标分数 */
11 var _getTarget = function (stage) {
12     if (stage === 1) { return 1000; }
13     else if (stage === 2) { return 2500; }
14     else { return (stage - 1) * 2500 }
15 };
16 
17 /* 计算得分 */
18 var _getScore = function () {
19     return $selecteditems.length * $selecteditems.length * 5;
20 }
21 
22 /* 计算奖励分数 */
23 var _getAdditionalScore = function (count) {
24     if (count >= 10) return 0;
25     else return _getAdditionalScore(count + 1) + (380 - (9 - count) * 40);
26     /* 利用高中数列的计算方法可以得出正确的公式,偷个小懒直接用最简单递归计算得分 */
27 }

 

然后,需要一个自动生成星星的方法:

随机获取一个大于等于0,小于maps.difficulty的整数

var val = Math.floor(Math.random() * maps.difficulty);

该数值作为每颗星星的值,并获取对应的星星样式(starClass),接着计算星星位置(top、left),这里排列方式为:

从右下角开始,往上堆入一列,再往右堆入第2列 ... ...

 1 var _newStage = function () {
 2     $('#stage').html(++Stage);
 3     Target = _getTarget(Stage);
 4     $('#target').html(Target);
 5 
 6     for (var x = 0; x < maps.xItemCount; ++x) {
 7         for (var y = 0; y < maps.yItemCount; ++y) {
 8             var val = Math.floor(Math.random() * maps.difficulty);
 9             var top = boxWidth - (itemWidth + maps.itemMargin * 2) * (y + 1);
10             var left = (itemWidth + maps.itemMargin * 2) * x;
11             $box.append(
12                 $('<div>', { id: x + '_' + y, 'class': starClass[val], css: { top: top, left: left,  itemWidth, height: itemWidth } })
13                     .data({ x: x, y: y, value: val })
14                     .click(function () {
15                         var $item = $(this);
16 
17                         if ($selecteditems.length) {
18                             if ($selecteditems.exists($item)) {
19                                 // 点击的方块在选中的方块列表里面
20                                 _clearSelectItems();
21                                 return;
22                             } else {
23                                 // 点击的方块不在选中的方块列表里面
24                                 _clearItemStatus();
25                             }
26                         }
27 
28                         _selectItems($item);
29                         _setSelectedItemsStatus();
30                     })
31             );
32         }
33     }
34 }

 

试试效果!

 

当点击某一颗星星时,有3种情况:

1、原本什么都么有选择($selecteditems.length == 0)

则选择与之相邻(上下左右)的,数值一样的星星。若是数量为1,则取消该次选择;若数量大于等于2,则全部显示选择,并计算出可得分数。

这里通过当前星星Item的ID,依次检查左、右、上、下4颗星星的数值,用递归的方法,一层层寻找,最终取得所有相连且数值相同的星星Item,保存在$selecteditems中。

 1 /* 寻找相邻方块 */
 2 var _selectItems = function ($item) {
 3     $selecteditems.push($item);
 4 
 5     var x = $item.data('x');
 6     var y = $item.data('y');
 7     var val = $item.data('value');
 8 
 9     if (x - 1 >= 0) {
10         var $newItem = $('#' + (x - 1) + '_' + y, $box);
11         if ($newItem.length && $newItem.data('value') === val && !$selecteditems.exists($newItem)) {
12             _selectItems($newItem);
13         }
14     }
15     if (x + 1 < maps.xItemCount) {
16         var $newItem = $('#' + (x + 1) + '_' + y, $box);
17         if ($newItem.length && $newItem.data('value') === val && !$selecteditems.exists($newItem)) {
18             _selectItems($newItem);
19         }
20     }
21     if (y - 1 >= 0) {
22         var $newItem = $('#' + x + '_' + (y - 1), $box);
23         if ($newItem.length && $newItem.data('value') === val && !$selecteditems.exists($newItem)) {
24             _selectItems($newItem);
25         }
26     }
27     if (y + 1 < maps.yItemCount) {
28         var $newItem = $('#' + x + '_' + (y + 1), $box);
29         if ($newItem.length && $newItem.data('value') === val && !$selecteditems.exists($newItem)) {
30             _selectItems($newItem);
31         }
32     }
33 }
34 
35 /* 设置选中方块状态 */
36 var _setSelectedItemsStatus = function () {
37     if ($selecteditems.length <= 1) {
38         $selecteditems = [];
39         $('#message_selected').empty();
40     } else {
41         $.each($selecteditems, function () {
42             this.css({  itemWidth - 4, height: itemWidth - 4, border: '2px solid rgba(0, 0, 0, 0.6)' });
43         });
44 
45         $('#message_selected').html('个数:' + $selecteditems.length + ',分数:' + _getScore());
46         $('#message_encourage').empty();
47     }
48 }

 

2、原本已有选择($selecteditems.length >= 2),但本次点击的星星不在已选择的星星列表里

则清空已选择的星星状态、列表,并依照1重新检查、计算、选择。

1 /* 清除方块状态 */
2 var _clearItemStatus = function () {
3     $box.children().css({  itemWidth, height: itemWidth, border: 'none' });
4     $selecteditems = [];
5 }

 

3、原本已有选择($selecteditems.length >= 2),且本次点击的星星在已选择的星星列表里

则需要删除已选择的星星,并重新排列剩余的星星。

 1 /* 移除选中方块 */
 2 var _clearSelectItems = function () {
 3     var score = _getScore();
 4     Score += score;
 5     $('#score').html(Score);
 6     $('#message_selected').empty();
 7 
 8     if (score >= maps.braveScore.value) {
 9         $('#message_encourage').html(maps.braveScore.message);
10 
11         setTimeout(function () {
12             $('#message_encourage').empty()
13         }, 2000);
14     }
15 
16     $selecteditems.forEach(function ($i) {
17         $i.fadeOut(0, function () {
18             $i.remove();
19         });
20     });
21     $selecteditems = [];
22 
23     var setX = 0, setY = 0;
24     for (var x = 0; x < maps.xItemCount; ++x) {
25         for (var y = 0; y < maps.yItemCount;) {
26             var $item = $('#' + x + '_' + y, $box);
27 
28             if ($item.length) {
29                 if (x != setX || y != setY) {
30                     var top = boxWidth - (itemWidth + maps.itemMargin * 2) * (setY + 1);
31                     var left = (itemWidth + maps.itemMargin * 2) * setX;
32                     $item
33                         .attr({ id: setX + '_' + setY })
34                         .data({ x: setX, y: setY })
35                         .animate({ top: top, left: left }, 300);
36                 }
37 
38                 ++setY;
39                 ++y;
40                 if (setY >= maps.yItemCount || y >= maps.yItemCount) {
41                     ++setX;
42                     setY = 0;
43                 }
44             } else {
45                 ++y;
46                 if (y >= maps.yItemCount) {
47                     if (setY > 0) {
48                         ++setX;
49                     }
50                     setY = 0;
51                 }
52             }
53         }
54     }
55 
56     _checkSingleItems();
57 }

 

已经有效果了!(自己先得瑟一下 :D 玩多一会)

 

最后,在消灭了选择的星星后,需要判断剩余的星星是否是单独的。若不是单独的,则不做任何操作;若是,则需要计算奖励分为多少,算总分后,若不足目标分数,显示失败,否则,清除所有剩余星星,并重新开始下一级别。

 1 /* 检查单个的方块 */
 2 var _checkSingleItems = function () {
 3     var $items = $box.children();
 4     var isSingle = true;
 5     for (var i = 0, length = $items.length; i < length; ++i) {
 6         _selectItems($($items[i]));
 7         var count = $selecteditems.length;
 8         $selecteditems = [];
 9 
10         if (count > 1) {
11             isSingle = false;
12             break;
13         }
14     }
15 
16     if (isSingle) {
17         setTimeout(function () {
18             var additionalScore = _getAdditionalScore($items.length);
19             Score += additionalScore;
20             $('#score').html(Score);
21 
22             if (additionalScore) {
23                 $('#message_encourage').html('Additional Score ' + additionalScore);
24             }
25 
26             if (Score < Target) {
27                 $('#message_encourage').empty();
28                 $('#message_failed').html(maps.failedMessage);
29             } else {
30                 setTimeout(function () {
31                     $('#message_encourage').empty()
32                 }, 2000);
33 
34                 if ($items.length) {
35                     $items.fadeOut(300, function () {
36                         $items.remove();
37                         _newStage();
38                     });
39                 } else {
40                     _newStage();
41                 }
42             }
43         }, 500);
44     }
45 }

 

完成!

基本可以玩了,当然,没有经过系统的测试,肯定会存在许多bug。

(截至博主发布时,已无意点到一个bug了  T-T  不过这里只是技术逻辑的讨论,不再做深层的检查修复)

 

附上下载路径:https://files.cnblogs.com/SugarLSG/PopStar.zip

 


[ 本次探讨只做技术研究,不用于任何商业目的;代码并非出自网络,许多不成熟的地方请各位多多见谅,虚心请教; ]

原文地址:https://www.cnblogs.com/SugarLSG/p/3134210.html