1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title>俄罗斯方块</title>
    5. <meta charset="utf-8">
    6. <style type="text/css">
    7. .container{
    8. left: 50px;
    9. height: 360px;
    10. width:200px;
    11. background-color: #000000;
    12. position:relative;
    13. }
    14. .activity_model{
    15. height: 20px;
    16. width: 20px;
    17. top:0px;
    18. left:0px;
    19. border-style:solid;
    20. border-width: 1px;
    21. border-color: #ffffff;
    22. background-color: #0000FE;
    23. position: absolute;
    24. }
    25. .fixed_model{
    26. height: 20px;
    27. width: 20px;
    28. top:0px;
    29. left:0px;
    30. border-style:solid;
    31. border-width: 1px;
    32. border-color: #333333;
    33. background-color: #fefefe;
    34. position: absolute;
    35. }
    36. .score{
    37. height: 100px;
    38. width: 300px;
    39. display: flex;
    40. flex-direction: column;
    41. justify-content: center;
    42. align-items: center;
    43. }
    44. .score_score{
    45. height: 50px;
    46. width: 300px;
    47. display: flex;
    48. flex-direction: row;
    49. justify-content: center;
    50. align-items: center;
    51. }
    52. </style>
    53. <script type="text/javascript" src="http://cdn.bootcss.com/lodash.js/4.16.6/lodash.min.js"></script>
    54. <script type="text/javascript">
    55. //分数
    56. var GAME_SCORE = 0;
    57. //暂停状态
    58. var CANCEL_STATE = false;
    59. //游戏速度
    60. var LEVEL = 1;
    61. //游戏默认得分
    62. var SCORE = 100;
    63. //常数值
    64. const STEP = 20;
    65. const ROW_COUNT = 18;
    66. const COL_COUNT = 10;
    67. //定时器
    68. var mInterval = null;
    69. //模型类
    70. const MODELS = [
    71. //L
    72. {
    73. 0:{
    74. row:2,
    75. col:0
    76. },
    77. 1:{
    78. row:2,
    79. col:1
    80. },
    81. 2:{
    82. row:2,
    83. col:2
    84. },
    85. 3:{
    86. row:1,
    87. col:2
    88. }
    89. },
    90. //凸形
    91. {
    92. 0:{
    93. row:1,
    94. col:1
    95. },
    96. 1:{
    97. row:0,
    98. col:0
    99. },
    100. 2:{
    101. row:1,
    102. col:0
    103. },
    104. 3:{
    105. row:2,
    106. col:0
    107. }
    108. },
    109. //田
    110. {
    111. 0:{
    112. row:1,
    113. col:1
    114. },
    115. 1:{
    116. row:2,
    117. col:1
    118. },
    119. 2:{
    120. row:1,
    121. col:2
    122. },
    123. 3:{
    124. row:2,
    125. col:2
    126. }
    127. },
    128. //一 形
    129. {
    130. 0:{
    131. row:0,
    132. col:0
    133. },
    134. 1:{
    135. row:0,
    136. col:1
    137. },
    138. 2:{
    139. row:0,
    140. col:2
    141. },
    142. 3:{
    143. row:0,
    144. col:3
    145. }
    146. },
    147. //Z形状
    148. {
    149. 0:{
    150. row:1,
    151. col:1
    152. },
    153. 1:{
    154. row:1,
    155. col:2
    156. },
    157. 2:{
    158. row:2,
    159. col:2
    160. },
    161. 3:{
    162. row:2,
    163. col:3
    164. }
    165. }
    166. ]
    167. //当前使用的模型
    168. var currentModel = {}
    169. //16宫格的位置
    170. var currentX = 0;
    171. var currentY = 0;
    172. //所有块元素的位置 行_列:块元素
    173. var fixedBlocks = {}
    174. //初始化
    175. function init() {
    176. // body...
    177. createModel();
    178. onKeyDown();
    179. }
    180. //键盘事件监听
    181. function onKeyDown(){
    182. document.onkeydown = function (event){
    183. //console.log(event.keyCode)
    184. switch(event.keyCode){
    185. case 37:
    186. console.log("左");
    187. move(-1,0)
    188. break;
    189. case 38:
    190. console.log("上");
    191. rotate()
    192. break;
    193. case 39:
    194. console.log("右");
    195. move(1,0)
    196. break;
    197. case 40:
    198. console.log("下");
    199. move(0,1)
    200. break;
    201. }
    202. }
    203. }
    204. //创建所使用的模型
    205. function createModel(){
    206. //先判断游戏是否结束,结束了才能生成新的块元素进行使用
    207. if(isGameOver()){
    208. GameOver();
    209. return ;
    210. }
    211. //初始化16宫格
    212. currentX = 0;
    213. currentY = 0;
    214. //确定使用的模型类型
    215. currentModel = MODELS[_.random(0,MODELS.length - 1)]
    216. //生成对应数量的块元素
    217. for(let key in currentModel){
    218. var divEle = document.createElement("div");
    219. divEle.className = "activity_model"
    220. document.getElementById("container").appendChild(divEle);
    221. }
    222. //将生成的块元素进行定位
    223. localtionBlocks();
    224. //开始下落
    225. autoDown();
    226. }
    227. //根据模型来定位块元素的位置
    228. //完成定位的功能-移动模型就是进行不断的定位
    229. function localtionBlocks(){
    230. //检查一下当前的模型位置是否合理
    231. checkBound();
    232. //拿到所有的块元素
    233. let eles = document.getElementsByClassName("activity_model");
    234. for (var i = 0; i < eles.length; i++) {
    235. //单个块元素
    236. let activityModelEles = eles[i];
    237. //每个块元素对应的数据
    238. let blockmodel = currentModel[i]
    239. //每个块元素的位置由16宫格和自己相对的位置共同确定
    240. activityModelEles.style.top = (blockmodel.row + currentY) * STEP + "px";
    241. activityModelEles.style.left = (blockmodel.col + currentX) * STEP + "px";
    242. }
    243. // 找到每个块元素对应的数据
    244. // 根据数据指定位置
    245. }
    246. //移动
    247. function move(x,y){
    248. // let activity = document.getElementsByClassName("activity_model")[0]
    249. // //需要检查一下有没有越界,再者不能进行向上的移动
    250. // activity.style.top = parseInt(activity.style.top||0) + y*STEP + "px";
    251. // activity.style.left = parseInt(activity.style.left||0) + x*STEP + "px";
    252. // 进行移动的其实是整个模型-16宫格移动
    253. //移动前检查下下一个动作是否会导致无法移动
    254. if(isMeet(currentX + x,currentY+y,currentModel)){
    255. if(y!==0){
    256. fixedBottomModel();
    257. }
    258. return;
    259. }
    260. currentX+=x;
    261. currentY+=y;
    262. //根据16宫格位置定位块元素位置
    263. localtionBlocks();
    264. }
    265. //旋转
    266. function rotate(){
    267. //旋转后的行=旋转前的列
    268. //旋转后的列=3-旋转前的行
    269. //遍历当前使用的模型
    270. //旋转时也检查下每个元素是否会相撞
    271. //利用外置库创建一个新的模型对象
    272. let clonecurrentModel = _.cloneDeep(currentModel);
    273. for(let key in clonecurrentModel){
    274. //获取这个模型中的每一块
    275. let blockmodel = clonecurrentModel[key];
    276. let temp = blockmodel.row;
    277. blockmodel.row = blockmodel.col;
    278. blockmodel.col = 3 - temp;
    279. }
    280. if(isMeet(currentX,currentY,clonecurrentModel)){
    281. return;
    282. }
    283. currentModel = clonecurrentModel;
    284. //console.log(currentModel === clonecurrentModel);
    285. localtionBlocks();
    286. }
    287. //边界检测-控制模型在容器中
    288. function checkBound(){
    289. //定义模型可以活动的边界
    290. let left = 0;
    291. let right = COL_COUNT;
    292. let bottom = ROW_COUNT;
    293. //如果当前的模型超过了边界,所在的16宫格对应的后退一步
    294. for(let key in currentModel){
    295. let blockmodel = currentModel[key];
    296. //左
    297. if((blockmodel.col + currentX) < left){
    298. currentX++;
    299. }
    300. //右
    301. if((blockmodel.col + currentX) >= right){
    302. currentX--;
    303. }
    304. //底部
    305. if((blockmodel.row + currentY) >= bottom){
    306. currentY--;
    307. //然后模型不能动了
    308. fixedBottomModel();
    309. }
    310. }
    311. }
    312. //抵达底部的模型就固定
    313. function fixedBottomModel(){
    314. //改变模型的样式-改颜色
    315. //让模型无法移动-让16宫格也不动
    316. let eles = document.getElementsByClassName("activity_model");
    317. for (var i = eles.length - 1; i >= 0; i--) {
    318. //单个块元素
    319. let activityModelEles = eles[i];
    320. //每个块元素类名
    321. activityModelEles.className = "fixed_model";
    322. let blockmodel = currentModel[i];
    323. //将固定了的块元素进行记录
    324. fixedBlocks[(currentY + blockmodel.row)+"_"+(currentX + blockmodel.col)] = activityModelEles;
    325. }
    326. //固定模型后去判断是否有一行铺满了要进行清理
    327. isRemove();
    328. //创建新模型
    329. createModel();
    330. }
    331. //判断模型之间受否产生了碰撞
    332. function isMeet(x,y,model){
    333. //同一个位置只能有一个块元素
    334. //x,y:16宫格将要抵达的位置
    335. //model:模型元素将要完成的变化:旋转
    336. //查看在当前这个模型的每个位置上是否已经存在了元素
    337. for (var key in model) {
    338. var blockmodel = model[key];
    339. if(fixedBlocks[(y+blockmodel.row)+"_"+(x+blockmodel.col)]){
    340. return true;
    341. }
    342. //这里写了return false自然会出问题,因为第一个没有碰到就返回没碰到本身就不对
    343. //
    344. }
    345. return false
    346. }
    347. function isRemove(){
    348. //判断一行是否已经被铺满了,如果被铺满了就清楚当前行所有的元素
    349. for(let i = 0; i < ROW_COUNT; i++){
    350. let flag = true
    351. for(let j = 0; j < COL_COUNT; j++){
    352. if(!fixedBlocks[i+"_"+j]){
    353. flag = false
    354. break
    355. }
    356. }
    357. //判断当前行是否被铺满
    358. if(flag == true){
    359. //console.log("清除")
    360. removeline(i)
    361. }
    362. }
    363. }
    364. function removeline(line){
    365. for(let i = 0; i < COL_COUNT; i++){
    366. //删除改行的块元素
    367. document.getElementById("container").removeChild(fixedBlocks[line+"_"+i]);
    368. fixedBlocks[line+"_"+i] = null;
    369. }
    370. //清除当前行后,当前后上面的块元素要落到底部
    371. downline(line);
    372. //然后总得分增加
    373. score_count();
    374. }
    375. //清理行之上的元素下落
    376. function downline(line){
    377. for (let i = line - 1; i >= 0; i--) {
    378. for(let j = 0;j < COL_COUNT; j++){
    379. if(!fixedBlocks[i+"_"+j]){
    380. continue;
    381. }
    382. //对于每个清理行之上的元素增大top然后记录块元素的记录也要更改,改变索引名称 A->B new C=A 则C->B 让A->null 就变成了C->B
    383. //元素位置下落
    384. fixedBlocks[(i+1)+"_"+j] = fixedBlocks[i+"_"+j];
    385. //实现元素位置下落
    386. fixedBlocks[(i+1)+"_"+j].style.top = (i+1)*STEP + "px";
    387. //清除原来的块元素
    388. fixedBlocks[i+"_"+j] = null;
    389. }
    390. }
    391. }
    392. //模型自动降落
    393. function autoDown(){
    394. if(mInterval){
    395. clearInterval(mInterval);
    396. }
    397. mInterval = setInterval(function(){
    398. move(0,1);
    399. },1000/LEVEL)
    400. }
    401. //判断游戏结束
    402. function isGameOver(){
    403. //第0行存在块元素就结束了
    404. for(let i = 0; i < COL_COUNT; i++){
    405. if(fixedBlocks["0_"+i]){
    406. return true
    407. }
    408. }
    409. return false
    410. }
    411. //游戏结束
    412. function GameOver(){
    413. if(mInterval){
    414. clearInterval(mInterval);
    415. }
    416. alert("GameOver")
    417. }
    418. //开始游戏
    419. function start(){
    420. createModel();
    421. onKeyDown();
    422. }
    423. //重新开始游戏
    424. function restart(){
    425. //清楚所有的块元素然后继续
    426. //暂停状态变为没有暂停
    427. //计时器取消
    428. //当前的16宫格位置从开始开始
    429. window.location.reload();
    430. }
    431. //暂停游戏
    432. function cancel(){
    433. if(CANCEL_STATE){
    434. //如果暂停了,就继续
    435. autoDown();
    436. CANCEL_STATE = false;
    437. onKeyDown();
    438. }else{
    439. if(mInterval){
    440. clearInterval(mInterval);
    441. }
    442. CANCEL_STATE = true;
    443. //鼠标监听事件取消
    444. document.onkeydown = null;
    445. }
    446. }
    447. //改变游戏的速度
    448. function levelUP(){
    449. if(LEVEL <= 3){
    450. LEVEL++;
    451. autoDown();
    452. onKeyDown();
    453. }
    454. }
    455. function levelDOWN(){
    456. if(LEVEL >= 1){
    457. LEVEL--;
    458. autoDown();
    459. onKeyDown();
    460. }
    461. }
    462. //计算分数
    463. function score_count(){
    464. let leastscore = parseInt(document.getElementById("score").innerText);
    465. //console.log(leastscore);
    466. leastscore += LEVEL * SCORE;
    467. document.getElementById("score").innerText = leastscore;
    468. }
    469. </script>
    470. </head>
    471. <body>
    472. <!-- 背景容器 -->
    473. <div class="container" id="container">
    474. <!-- 块元素 -->
    475. </div>
    476. <div class="score">
    477. <div class="score_score">
    478. <p id="score">0</p>
    479. <p></p>
    480. </div>
    481. <div class="score_control">
    482. <button onclick="start()">开始</button>
    483. <button onclick="restart()">重新</button>
    484. <button onclick="cancel()">暂停</button>
    485. <button onclick="levelUP()">levelUP</button>
    486. <button onclick="levelDOWN()">levelDOWN</button>
    487. </div>
    488. </div>
    489. </body>
    490. </html>