系统城装机大师 - 固镇县祥瑞电脑科技销售部宣传站!

当前位置:首页 > 网页制作 > html5 > 详细页面

Html5原创俄罗斯方块(基于canvas)

时间:2019-12-05来源:系统城作者:电脑系统城

第一次写俄罗斯方块的时候已经是1年多前了,也是我刚刚学js不久。

为了加强对js的理解又加上对游戏的爱好,于是在没有参考他人的思路和代码下,自己用最基本的js代码写出了基于canvas的俄罗斯方块。

在大三的暑假,我又用了es6的语法进行了改进,包含了class的语法糖、箭头函数等,进一步增加自己对es6的理解,代码有400+行

想要做这个小游戏,必须先熟悉H5的canvas,js对数组的处理,键盘事件监听和处理,定时器的使用等,其他的就是基本的逻辑处理了。

游戏的规则就是核心,也是我们代码的重中之重

这里的逻辑核心是需要判断方块是否碰撞(当前运动的方块和已经定位好的方块有碰撞以致于当前的运动的方块不能在向下走,因为我们的方块默认是向下走的,如果不能向下走,是视为已经定位好的方块,然后在生成一个新的方块从初始位置继续往下走)。

而且这个碰撞还需要应用在方块变形的时候,同样地,如果方块在变形的过程中和其他定位好的方块进行碰撞,则我们应该阻止这个方块进行变形成功,

附上代码,欢迎讨论和指正


 
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>es6-重构俄罗斯方块(基于canvas)</title>
  6. <style type="text/css">
  7. #tetris{ margin: 10px 250px;}
  8. </style>
  9. </head>
  10. <body>
  11. <canvas width="700" height="525" id="tetris"></canvas>
  12. <div id="text" style='color: red;font-size: 30px;'>当前分数:0</div>
  13. <script type="text/javascript">
  14. /**
  15. * [一个完整的俄罗斯方块类 design by magic_xiang]
  16. * @param {number} side [每个方块边长(px),默认35]
  17. * @param {number} width [一行包含的方块数(个),默认20]
  18. * @param {number} height [一列包含的方块数(个),默认15]
  19. * @param {number} speed [方块下落移动速度(ms),默认400]
  20. */
  21. class tetris{
  22. constructor(side=35, width=20, height=15, speed=400){
  23. this.side = side // 每个方块边长
  24. this.width = width // 一行包含的方块数
  25. this.height = height // 一列包含的方块数
  26. this.speed = speed // 方块下落移动速度
  27. this.num_blcok // 当前方块类型的数字变量
  28. this.type_color // 当前颜色类型的字符串变量
  29. this.ident // setInterval的标识
  30. this.direction = 1 // 方块方向,初始化为1,默认状态
  31. this.grade = 0 // 用来计算分数
  32. this.over = false // 游戏是否结束
  33. this.arr_bX = [] // 存放当前方块的X坐标
  34. this.arr_bY = [] // 存放当前方块的Y坐标
  35. this.arr_store_X = [] // 存放到达底部所有方块的X坐标
  36. this.arr_store_Y = [] // 存放到达底部所有方块的Y坐标
  37. this.arr_store_color = [] // 存放到达底部所有方块的颜色
  38. this.paints = document.getElementById('tetris').getContext('2d')
  39. //获取画笔
  40. self = this
  41. }
  42.  
  43. // 封装paints方法,让代码更简洁
  44. paintfr(x, y, scale=1){
  45. this.paints.fillRect(x*this.side, y*this.side, scale*this.side, scale*this.side)
  46. }
  47.  
  48. // 游戏开始
  49. gameStart(){
  50. this.init()
  51. this.run()
  52. }
  53.  
  54. // 初始化工作
  55. init(){
  56. this.initBackground()
  57. this.initBlock()
  58. }
  59.  
  60. // 方块自动下落
  61. run(){
  62. this.ident = setInterval("self.down_speed_up()", this.speed)
  63. }
  64.  
  65. // 初始化地图
  66. initBackground(){
  67. this.paints.beginPath()
  68. this.paints.fillStyle='#000000' //地图填充颜色为黑色
  69. for(let i = 0; i < this.height; i++){
  70. for(let j = 0; j < this.width; j++){
  71. this.paintfr(j, i)
  72. }
  73. }
  74. this.paints.closePath()
  75. }
  76.  
  77. // 初始化方块的位置和颜色
  78. initBlock(){
  79. this.paints.beginPath()
  80. this.createRandom('rColor') //生成颜色字符串,
  81. this.paints.fillStyle = this.type_color
  82. this.createRandom('rBlock') //生成方块类型数字
  83. this.arr_bX.forEach((item, index) => {
  84. this.paintfr(item, this.arr_bY[index], 0.9)
  85. })
  86. this.paints.closePath()
  87. }
  88.  
  89. // 利用数组画方块
  90. drawBlock(color){
  91. this.paints.beginPath()
  92. this.paints.fillStyle = color
  93. this.arr_bX.forEach((item, index) => {
  94. this.paintfr(item, this.arr_bY[index], 0.9)
  95. })
  96. this.paints.closePath()
  97. }
  98.  
  99. // 画已经在定位好的方块
  100. drawStaticBlock(){
  101. this.arr_store_X.forEach((item, index) => {
  102. this.paints.beginPath()
  103. this.paints.fillStyle = this.arr_store_color[index]
  104. this.paintfr(item, this.arr_store_Y[index], 0.9)
  105. this.paints.closePath()
  106. })
  107. }
  108.  
  109. // 生成随机数返回方块类型或颜色类型
  110. createRandom(type){
  111. let temp = this.width/2-1
  112.  
  113. if (type == 'rBlock'){ //如果是方块类型
  114. this.num_blcok = Math.round(Math.random()*4+1)
  115. switch(this.num_blcok){
  116. case 1:
  117. this.arr_bX.push(temp,temp-1,temp,temp+1)
  118. this.arr_bY.push(0,1,1,1)
  119. break
  120. case 2:
  121. this.arr_bX.push(temp,temp-1,temp-1,temp+1)
  122. this.arr_bY.push(1,0,1,1)
  123. break
  124. case 3:
  125. this.arr_bX.push(temp,temp-1,temp+1,temp+2)
  126. this.arr_bY.push(0,0,0,0)
  127. break
  128. case 4:
  129. this.arr_bX.push(temp,temp-1,temp,temp+1)
  130. this.arr_bY.push(0,0,1,1)
  131. break
  132. case 5:
  133. this.arr_bX.push(temp,temp+1,temp,temp+1)
  134. this.arr_bY.push(0,0,1,1)
  135. break
  136. }
  137. }
  138. if (type == 'rColor'){ //如果是颜色类型
  139. let num_color = Math.round(Math.random()*8+1)
  140.  
  141. switch(num_color){
  142. case 1:
  143. this.type_color='#3EF72A'
  144. break
  145. case 2:
  146. this.type_color='yellow'
  147. break
  148. case 3:
  149. this.type_color='#2FE0BF'
  150. break
  151. case 4:
  152. this.type_color='red'
  153. break
  154. case 5:
  155. this.type_color='gray'
  156. break
  157. case 6:
  158. this.type_color='#C932C6'
  159. break
  160. case 7:
  161. this.type_color= '#FC751B'
  162. break
  163. case 8:
  164. this.type_color= '#6E6EDD'
  165. break
  166. case 9:
  167. this.type_color= '#F4E9E1'
  168. break
  169. }
  170. }
  171. }
  172.  
  173. // 判断方块之间是否碰撞(下),以及变形时是否越过下边界
  174. judgeCollision_down(){
  175. for(let i = 0; i < this.arr_bX.length; i++){
  176. if (this.arr_bY[i] + 1 == this.height){ //变形时是否越过下边界
  177. return false
  178. }
  179. if (this.arr_store_X.length != 0) { //判断方块之间是否碰撞(下)
  180. for(let j = 0; j < this.arr_store_X.length; j++){
  181. if (this.arr_bX[i] == this.arr_store_X[j]) {
  182. if (this.arr_bY[i] + 1 == this.arr_store_Y[j]) {
  183. return false
  184. }
  185. }
  186.  
  187. }
  188. }
  189. }
  190. return true
  191. }
  192.  
  193. //判断方块之间是否碰撞(左右),以及变形时是否越过左右边界
  194. judgeCollision_other(num){
  195. for(let i = 0; i < this.arr_bX.length; i++){
  196. if (num == 1) { //变形时是否越过右边界
  197. if (this.arr_bX[i] == this.width - 1)
  198. return false
  199. }
  200. if (num == -1) { //变形时是否越过左边界
  201. if (this.arr_bX[i] == 0)
  202. return false
  203. }
  204. if (this.arr_store_X.length != 0) { //判断方块之间是否碰撞(左右)
  205. for(let j = 0; j < this.arr_store_X.length; j++){
  206. if (this.arr_bY[i] == this.arr_store_Y[j]) {
  207. if (this.arr_bX[i] + num == this.arr_store_X[j]) {
  208. return false
  209. }
  210. }
  211. }
  212. }
  213. }
  214. return true;
  215. }
  216.  
  217.  
  218. //方向键为下的加速函数
  219. down_speed_up(){
  220. let flag_all_down = true
  221. flag_all_down = this.judgeCollision_down()
  222.  
  223. if (flag_all_down) {
  224. this.initBackground()
  225. for(let i = 0; i < this.arr_bY.length; i++){
  226. this.arr_bY[i] = this.arr_bY[i] + 1
  227. }
  228. }
  229. else{
  230. for(let i=0; i < this.arr_bX.length; i++){
  231. this.arr_store_X.push(this.arr_bX[i])
  232. this.arr_store_Y.push(this.arr_bY[i])
  233. this.arr_store_color.push(this.type_color)
  234. }
  235.  
  236. this.arr_bX.splice(0,this.arr_bX.length)
  237. this.arr_bY.splice(0,this.arr_bY.length)
  238. this.initBlock()
  239. }
  240. this.clearUnderBlock()
  241. this.drawBlock(this.type_color)
  242. this.drawStaticBlock()
  243. this.gameover()
  244. }
  245.  
  246. //方向键为左右的左移动函数
  247. move(dir_temp){
  248. this.initBackground()
  249.  
  250. if (dir_temp == 1) { //右
  251. let flag_all_right = true
  252. flag_all_right = this.judgeCollision_other(1)
  253.  
  254. if (flag_all_right) {
  255. for(let i = 0; i < this.arr_bY.length; i++){
  256. this.arr_bX[i] = this.arr_bX[i]+1
  257. }
  258. }
  259. }
  260. else{
  261. let flag_all_left = true
  262. flag_all_left = this.judgeCollision_other(-1)
  263.  
  264. if (flag_all_left) {
  265. for(let i=0; i < this.arr_bY.length; i++){
  266. this.arr_bX[i] = this.arr_bX[i]-1
  267. }
  268. }
  269. }
  270. this.drawBlock(this.type_color)
  271. this.drawStaticBlock()
  272. }
  273.  
  274. //方向键为空格的变换方向函数
  275. up_change_direction(num_blcok){
  276. if (num_blcok == 5) {
  277. return
  278. }
  279.  
  280. let arr_tempX = []
  281. let arr_tempY = []
  282. //因为不知道是否能够变形成功,所以先存储起来
  283. for(let i = 0;i < this.arr_bX.length; i++){
  284. arr_tempX.push(this.arr_bX[i])
  285. arr_tempY.push(this.arr_bY[i])
  286. }
  287. this.direction++
  288. //将中心坐标提取出来,变形都以当前中心为准
  289. let ax_temp = this.arr_bX[0]
  290. let ay_temp = this.arr_bY[0]
  291.  
  292. this.arr_bX.splice(0, this.arr_bX.length) //将数组清空
  293. this.arr_bY.splice(0, this.arr_bY.length)
  294.  
  295. if (num_blcok == 1) {
  296.  
  297. switch(this.direction%4){
  298. case 1:
  299. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
  300. this.arr_bY.push(ay_temp,ay_temp+1,ay_temp+1,ay_temp+1)
  301. break
  302. case 2:
  303. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp)
  304. this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp+1)
  305. break
  306. case 3:
  307. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
  308. this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp)
  309. break
  310. case 0:
  311. this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
  312. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp)
  313. break
  314. }
  315. }
  316. if (num_blcok == 2) {
  317.  
  318. switch(this.direction%4){
  319. case 1:
  320. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp-1,ax_temp+1)
  321. this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp)
  322. break
  323. case 2:
  324. this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp-1)
  325. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+1)
  326. break
  327. case 3:
  328. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+1)
  329. this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp+1)
  330. break
  331. case 0:
  332. this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
  333. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp-1)
  334. break
  335. }
  336. }
  337. if (num_blcok == 3) {
  338.  
  339. switch(this.direction%4){
  340. case 1:
  341. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
  342. this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
  343. break
  344. case 2:
  345. this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
  346. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
  347. break
  348. case 3:
  349. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
  350. this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
  351. break
  352. case 0:
  353. this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
  354. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
  355. break
  356. }
  357. }
  358. if (num_blcok == 4) {
  359.  
  360. switch(this.direction%4){
  361. case 1:
  362. this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
  363. this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp+1)
  364. break
  365. case 2:
  366. this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
  367. this.arr_bY.push(ay_temp,ay_temp+1,ay_temp,ay_temp-1)
  368. break
  369. case 3:
  370. this.arr_bX.push(ax_temp,ax_temp,ax_temp-1,ax_temp+1)
  371. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp-1)
  372. break
  373. case 0:
  374. this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
  375. this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp+1)
  376. break
  377. }
  378. }
  379.  
  380. if (! (this.judgeCollision_other(-1) && this.judgeCollision_down() &&this.judgeCollision_other(1) )) { //如果变形不成功则执行下面代码
  381. this.arr_bX.splice(0, this.arr_bX.length)
  382. this.arr_bY.splice(0, this.arr_bY.length)
  383. for(let i=0; i< arr_tempX.length; i++){
  384. this.arr_bX.push(arr_tempX[i])
  385. this.arr_bY.push(arr_tempY[i])
  386. }
  387. }
  388. this.drawStaticBlock()
  389. }
  390.  
  391. //一行满了清空方块,上面方块Y坐标+1
  392. clearUnderBlock(){
  393. //删除低层方块
  394. let arr_row=[]
  395. let line_num
  396. if (this.arr_store_X.length != 0) {
  397. for(let j = this.height-1; j >= 0; j--){
  398. for(let i = 0; i < this.arr_store_color.length; i++){
  399. if (this.arr_store_Y[i] == j) {
  400. arr_row.push(i)
  401. }
  402. }
  403. if (arr_row.length == this.width) {
  404. line_num = j
  405. break
  406. }else{
  407. arr_row.splice(0, arr_row.length)
  408. }
  409. }
  410. }
  411.  
  412. if (arr_row.length == this.width) {
  413. //计算成绩grade
  414. this.grade++
  415.  
  416. document.getElementById('text').innerHTML = '当前成绩:'+this.grade
  417. for(let i = 0; i < arr_row.length; i++){
  418. this.arr_store_X.splice(arr_row[i]-i, 1)
  419. this.arr_store_Y.splice(arr_row[i]-i, 1)
  420. this.arr_store_color.splice(arr_row[i]-i, 1)
  421. }
  422.  
  423. //让上面的方块往下掉一格
  424. for(let i = 0; i < this.arr_store_color.length; i++){
  425. if (this.arr_store_Y[i] < line_num) {
  426. this.arr_store_Y[i] = this.arr_store_Y[i]+1
  427. }
  428. }
  429. }
  430. }
  431.  
  432. //判断游戏结束
  433. gameover(){
  434. for(let i=0; i < this.arr_store_X.length; i++){
  435. if (this.arr_store_Y[i] == 0) {
  436. clearInterval(this.ident)
  437. this.over = true
  438. }
  439. }
  440. }
  441. }
  442.  
  443. let tetrisObj = new tetris()
  444. tetrisObj.gameStart()
  445.  
  446. //方向键功能函数
  447. document.onkeydown = (e) => {
  448. if (tetrisObj.over)
  449. return
  450.  
  451. switch(e.keyCode){
  452. case 40: // 方向为下
  453. tetrisObj.down_speed_up()
  454. break
  455. case 32: // 空格换方向
  456. tetrisObj.initBackground() //重画地图
  457. tetrisObj.up_change_direction(tetrisObj.num_blcok)
  458. tetrisObj.drawBlock(tetrisObj.type_color)
  459. break
  460. case 37: // 方向为左
  461. tetrisObj.initBackground()
  462. tetrisObj.move(-1)
  463. tetrisObj.drawBlock(tetrisObj.type_color)
  464. break
  465. case 39: // 方向为右
  466. tetrisObj.initBackground()
  467. tetrisObj.move(1)
  468. tetrisObj.drawBlock(tetrisObj.type_color)
  469. break
  470. }
  471. }
  472.  
  473. </script>
  474. </body>
  475. </html>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

分享到:

相关信息

系统教程栏目

栏目热门教程

人气教程排行

站长推荐

热门系统下载