入驻博客园后的第一篇技术博客,这次来聊聊前端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
[ 本次探讨只做技术研究,不用于任何商业目的;代码并非出自网络,许多不成熟的地方请各位多多见谅,虚心请教; ]