一、课程目标

  1. 随机生成降落模型(L型、凸型、Z型、一型、田型)。
  2. 程序中的每一个小块,都可以视为一个块元素。
  3. 活动中的模块都可以左右移动、旋转。
  4. 模块固定到底部后不可再移动,同时生成新的移动模块。
  5. 一行被排列完整之后,该行会消失,同时上方的模块会下落。
  6. 被固定的模块搞到达到界面顶部后,游戏结束。

二、项目开发

  • 阶段一 (制作背景)
    1. 1. 创建一个宽为200px,高为360px的背景容器
    2. 1. 在该容器上创建一个20*20的快元素
    3. 1. 键盘控制该元素进行移动,每次移动20px
    4. 1. 代码见 -> [阶段一开发代码](https://www.yuque.com/u12581613/sr444s/qi7p8l?view=doc_embed)
  • 阶段二 (基础移动)
    1. 1. 将容器进行分割,分割为18行,10列。行高列宽均为20px
    2. 1. 16宫格(44列)为基准,定义L形状模型的4个方块位置
    3. 1. 控制该模型进行移动 -> 也可以说是控制16宫格进行移动
    4. 1. 代码见 -> [阶段二开发代码](https://www.yuque.com/u12581613/sr444s/ka31h0?view=doc_embed)
  • 阶段三 (块元素控制)
    1. 1. 控制模型只能在容器中移动
    2. 1. 当模型触底时,将块元素变成灰色固定在底部,同时生成一个新的模型
    3. 1. 判断块元素与块元素之间的碰撞,分为左右碰撞和底部接触
    4. 1. 代码见 -> [阶段三开发代码](https://www.yuque.com/u12581613/sr444s/pe82gb?view=doc_embed)
  • 阶段四 (铺满下坠)
    1. 1. 判断一行是否铺满
    2. 1. 清理铺满的一行
    3. 1. 并让在其上方的块元素下落
    4. 1. 代码见 -> [阶段四开发代码](https://www.yuque.com/u12581613/sr444s/yebpk3?view=doc_embed)
  • 阶段五 (项目收尾)
    1. 1. 创建多种样式模型,根据随机数生成随机样式,创建自动坠落方法
    2. 1. 判断游戏结束
    3. 1. 美化样式 -> 下面完整代码

三、课程总结

项目难点

  1. 容器分割,构建16宫格的概念
  2. 观察块元素的位置变化,找出其中规律
  3. 判断块元素与块元素之间的碰撞
  4. 判断一行被铺满的情况

老师总结:
这些都是在js基础阶段学过的内容,只不过由于项目经验等原因,可能很多新手都无法写出来。本项目中没有根本上的技术难点,项目中也没有使用未学过的知识,我们学习到的知识是实实在在有用的,而不是为了学而学的东西。
个人总结:
老师说的确实有道理,听这堂课的时候感觉都听得懂,自己做就很难写出来,还不能熟练的融汇贯通,希望以后可以做的更好把。

项目完整代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <style type="text/css">
  9. .container{
  10. width: 200px;
  11. height: 360px;
  12. position: relative;
  13. background: url("images/bg.jpg") no-repeat;
  14. background-size: 200px 360px;
  15. }
  16. .activity_model{
  17. width: 20px;
  18. height: 20px;
  19. background: #a4b7da url("images/move.png") center no-repeat;
  20. background-size: 15px;
  21. border: .3px solid rgb(148, 170, 218);
  22. box-sizing: border-box;
  23. position: absolute;
  24. }
  25. .fixed_model{
  26. width: 20px;
  27. height: 20px;
  28. background: rgb(75, 79, 92) url("images/stop.png") center no-repeat;
  29. background-size: 15px;
  30. border: .3px solid rgb(162, 167, 177);
  31. box-sizing: border-box;
  32. position: absolute;
  33. }
  34. </style>
  35. </head>
  36. <body onload="init();">
  37. <!-- 背景容器 -->
  38. <div id="container" class="container">
  39. <!-- 块元素 -->
  40. </div>
  41. <script>
  42. //创建一个常量控制步长
  43. const STEP = 20;
  44. //定义常量分割容器
  45. const ROW_COUNT = 18,COL_COUNT = 10;
  46. //创建每个模型的数据源
  47. const MODELS = [
  48. //第一个样式模型(L型)
  49. {
  50. 0:{
  51. row:2,
  52. col:0
  53. },
  54. 1:{
  55. row:2,
  56. col:1
  57. },
  58. 2:{
  59. row:2,
  60. col:2
  61. },
  62. 3:{
  63. row:1,
  64. col:2
  65. }
  66. },
  67. //第二个样式模型(凸型)
  68. {
  69. 0:{
  70. row:1,
  71. col:1
  72. },
  73. 1:{
  74. row:0,
  75. col:0
  76. },
  77. 2:{
  78. row:1,
  79. col:0
  80. },
  81. 3:{
  82. row:2,
  83. col:0
  84. }
  85. },
  86. //第三个样式模型(田型)
  87. {
  88. 0:{
  89. row:1,
  90. col:1
  91. },
  92. 1:{
  93. row:2,
  94. col:1
  95. },
  96. 2:{
  97. row:1,
  98. col:2
  99. },
  100. 3:{
  101. row:2,
  102. col:2
  103. }
  104. },
  105. //第四个样式模型(一型)
  106. {
  107. 0:{
  108. row:0,
  109. col:0
  110. },
  111. 1:{
  112. row:0,
  113. col:1
  114. },
  115. 2:{
  116. row:0,
  117. col:2
  118. },
  119. 3:{
  120. row:0,
  121. col:3
  122. }
  123. },
  124. //第四个样式模型(Z型)
  125. {
  126. 0:{
  127. row:1,
  128. col:1
  129. },
  130. 1:{
  131. row:1,
  132. col:2
  133. },
  134. 2:{
  135. row:2,
  136. col:2
  137. },
  138. 3:{
  139. row:2,
  140. col:3
  141. }
  142. },
  143. ];
  144. //创建变量,存放当前使用的模型
  145. var currentModel = {};
  146. //创建变量标记16宫格的位置
  147. var currentX = 0,currentY = 0;
  148. //记录所有块元素的位置
  149. //k=行_列 : v=块元素
  150. var fixedBlocks = {};
  151. //创建定时器
  152. var myInterval = null;
  153. //入口方法
  154. function init(){
  155. createModel();
  156. onKeyDown();
  157. }
  158. //根据模型的数据创建对应的块元素
  159. function createModel(){
  160. //判断游戏是否接受
  161. if (isGameOver()){
  162. gameOver();
  163. return;
  164. }
  165. //确定当前使用哪一个模型,随机数生成
  166. currentModel = MODELS[Math.round(Math.random()*4)];
  167. //重新初始化16宫格的位置
  168. currentX = 0;
  169. currentY = 0;
  170. //生成对应数量的块元素
  171. for (var key in currentModel){
  172. var divEle = document.createElement("div");
  173. divEle.className = "activity_model";
  174. document.querySelector("#container").appendChild(divEle);
  175. }
  176. //定位块元素的位置
  177. locationBlock();
  178. //让当前模型自动下落
  179. autoDown();
  180. }
  181. //根据数据源定位块元素的位置
  182. function locationBlock(){
  183. //判断块元素的越界行为
  184. checkBound();
  185. //1.拿到所有的块元素
  186. var eles = document.querySelectorAll(".activity_model");
  187. for (var i = 0;i<eles.length;i++){
  188. //单个块元素
  189. var activityModelEle = eles[i];
  190. //2.找到每个块元素对应的数据
  191. var blockModel = currentModel[i];
  192. //3.根据每个块元素对应的数据来指定块元素的位置
  193. //每个块元素的位置由两个条件决定:1、16宫格所在的位置。 2、块元素在16宫格中的位置
  194. activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px";
  195. activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px";
  196. }
  197. }
  198. //监听用户键盘事件
  199. function onKeyDown(){
  200. document.onkeydown = function(evnet){
  201. switch(event.keyCode){
  202. case 38:
  203. console.log("上");
  204. rotate();
  205. break;
  206. case 39:
  207. console.log("右");
  208. move(1,0);
  209. break;
  210. case 40:
  211. console.log("下");
  212. move(0,1);
  213. break;
  214. case 37:
  215. console.log("左");
  216. move(-1,0);
  217. break;
  218. }
  219. }
  220. }
  221. //控制块元素移动
  222. function move(x,y){
  223. if (isMeet(currentX + x,currentY+y,currentModel)){
  224. //底部的触碰发生在移动16宫格的时候,并且这次移动是因为Y轴的变化引起的
  225. if (y!=0){
  226. //模型之间底部发生触碰
  227. fixedBottomModel();
  228. }
  229. return;
  230. }
  231. //16宫格在动
  232. currentX += x;
  233. currentY += y;
  234. //根据16宫格来重新定位块元素
  235. locationBlock();
  236. }
  237. //控制模型的旋转
  238. function rotate(){
  239. //克隆一下currentModel,这里利用JSON方法完全复制一个currentModel
  240. var json_obj = JSON.stringify(currentModel);
  241. var cloneCurrentModel = JSON.parse(json_obj);
  242. //算法
  243. //旋转后的行 == 旋转前的列
  244. //旋转后的列 == 3-旋转前的行
  245. //遍历我们当前的 模型数据源
  246. for (var key in cloneCurrentModel){
  247. //块元素数据源
  248. var blockModel = cloneCurrentModel[key];
  249. //实现算法
  250. var temp = blockModel.row;
  251. blockModel.row = blockModel.col;
  252. blockModel.col = 3-temp;
  253. }
  254. //如果旋转之后会发生触碰,那么就不需要进行旋转了
  255. if (isMeet(currentX,currentY,cloneCurrentModel)){
  256. return;
  257. }
  258. //接受这次旋转
  259. currentModel = cloneCurrentModel;
  260. locationBlock();
  261. }
  262. //控制模型只能在容器中移动
  263. function checkBound(){
  264. //定义模型活动的边界
  265. var leftBound = 0,rightBound = COL_COUNT,bottomBound = ROW_COUNT;
  266. //当块元素超出边界,让16宫格向后退一步
  267. for (var key in currentModel){
  268. var blockModel = currentModel[key];
  269. //判断左侧越界
  270. if (blockModel.col + currentX < leftBound) {
  271. currentX++;
  272. }
  273. //判断右侧越界
  274. if (blockModel.col + currentX >= rightBound) {
  275. currentX--;
  276. }
  277. //判断底部越界
  278. if (blockModel.row + currentY >= bottomBound){
  279. currentY--;
  280. //把模型固定在底部
  281. fixedBottomModel();
  282. }
  283. }
  284. }
  285. //把模型固定在底部
  286. function fixedBottomModel() {
  287. //1.改变模型(中块元素)的样式
  288. //2.让模型不可以再进行移动
  289. var activityModelEles = document.querySelectorAll(".activity_model");
  290. for (var i = activityModelEles.length - 1;i >= 0;i--){
  291. //更改每个块元素的类名
  292. //由于类名被更改,我们模型移动依靠的就是修改activity_model类的定位,现在不是原类名,自然就无法移动了
  293. activityModelEles[i].className = "fixed_model";
  294. //记录块元素的位置
  295. var blockModel = currentModel[i];
  296. fixedBlocks[(currentY + blockModel.row) + "_" + (currentX + blockModel.col)] = activityModelEles[i];
  297. }
  298. //判断是否要铺满一行需要清理
  299. isRemoveLine();
  300. //3.创建新的模型
  301. createModel();
  302. }
  303. //判断模型之间的触碰问题
  304. //X,Y表示16宫格<将要>移动到的位置
  305. //model 表示当前数据源<将要>完成的变化
  306. function isMeet(x,y,model){
  307. //所谓模型之间的触碰,在一个固定位置已经存在一个被固定的元素块时,那么活动中的模型不可以再占用该位置
  308. //判断触碰,就是判断活动中模型<将要移动到的位置>是否已经存在被固定的模型(块元素)了
  309. //如果存在返回true,表示将要移动到的位置会发送触碰,反之返回false
  310. for (var k in model) {
  311. var blockModel = model[k];
  312. //该位置是否已经存在块元素?
  313. if (fixedBlocks[(y + blockModel.row) + "_" + (x + blockModel.col)]){
  314. return true;
  315. }
  316. }
  317. return false;
  318. }
  319. //判断一行是否被铺满
  320. function isRemoveLine(){
  321. //在一行中,每一列都有块元素,那么该行就需要被清理了
  322. //遍历所有行中的所有列
  323. //遍历所有行
  324. for (var i = 0;i < ROW_COUNT;i++){
  325. //标记符,假设当前行已经被铺满了
  326. var flag = 1;
  327. //遍历当前行中的所有列
  328. for (var j = 0; j < COL_COUNT; j++){
  329. //如果当前行中有一列没有数据,那就说明没有被铺满
  330. if (!fixedBlocks[i + "_" + j]){
  331. flag = 0;
  332. break;
  333. }
  334. }
  335. if (flag){
  336. //该行已经被铺满了,删除改行
  337. removeLine(i);
  338. }
  339. }
  340. }
  341. //清理被铺满的一行
  342. function removeLine(line){
  343. //遍历该行中的所有列
  344. for (var i = 0; i < COL_COUNT; i++){
  345. //1.删除该行中所有的块元素
  346. document.querySelector("#container").removeChild(fixedBlocks[line + "_" + i]);
  347. //2.删除该行中所有块元素的数据源
  348. fixedBlocks[line + "_" + i] = null;
  349. }
  350. downLine(line);
  351. }
  352. //让被清理行之上的块元素下落
  353. function downLine(line){
  354. //遍历被清理行之上的所有行
  355. for (var i = line - 1; i >= 0; i--){
  356. //该行中的所有列
  357. for (var j = 0; j < COL_COUNT; j++){
  358. //不存在数据跳过一次循环
  359. if (!fixedBlocks[i + "_" + j]) { continue; }
  360. //存在数据
  361. //1.被清理行之上所在块元素数据源所在的行数 + 1
  362. fixedBlocks[(i+1) + "_" + j] = fixedBlocks[i + "_" + j];
  363. //2.让容器的位置下落
  364. fixedBlocks[(i+1) + "_" + j].style.top = (i+1) * STEP + "px";
  365. //3.清理掉之前的块元素
  366. fixedBlocks[i + "_" + j] = null;
  367. }
  368. }
  369. }
  370. //让模型自动下落
  371. function autoDown(){
  372. if (myInterval){
  373. clearInterval(myInterval);
  374. }
  375. myInterval = setInterval(function(){
  376. move(0,1);
  377. },300);
  378. }
  379. //判断游戏结束
  380. function isGameOver(){
  381. for (var i = 0;i < COL_COUNT; i++){
  382. if (fixedBlocks["0_" + i]) { return true; }
  383. }
  384. return false;
  385. }
  386. //结束游戏
  387. function gameOver(){
  388. //1.停止计时器
  389. if(myInterval){
  390. clearInterval(myInterval);
  391. }
  392. //2.弹出对话框
  393. alert("恭喜您,游戏结束啦!");
  394. }
  395. </script>
  396. </body>
  397. </html>