ครั้งแรกที่ฉันเขียน Tetris นั้นนานกว่าหนึ่งปีแล้ว และฉันเพิ่งเรียนรู้ js ได้ไม่นานมานี้
เพื่อเสริมสร้างความเข้าใจเกี่ยวกับ js และเพิ่มความรักในเกม ฉันเขียน Tetris บนพื้นฐานของ Canvas โดยใช้โค้ด js พื้นฐานที่สุดโดยไม่อ้างอิงถึงแนวคิดและโค้ดของผู้อื่น
ในช่วงปิดเทอมฤดูร้อนของปีแรกของฉัน ฉันใช้ไวยากรณ์ของ es6 เพื่อปรับปรุงมัน รวมถึงวากยสัมพันธ์ของคลาส ฟังก์ชันลูกศร ฯลฯ เพื่อเพิ่มความเข้าใจเกี่ยวกับ es6 ของฉันเพิ่มเติม โค้ดมีมากกว่า 400 บรรทัด
หากคุณต้องการสร้างเกมเล็กๆ นี้ คุณต้องคุ้นเคยกับ H5 canvas, การประมวลผลอาร์เรย์ js, การตรวจสอบและประมวลผลเหตุการณ์คีย์บอร์ด, การใช้ตัวจับเวลา ฯลฯ ส่วนที่เหลือเป็นการประมวลผลลอจิกพื้นฐาน
กฎของเกมคือหัวใจหลักและมีความสำคัญสูงสุดในโค้ดของเรา
ตรรกะหลักที่นี่คือการกำหนดว่าบล็อกชนกันหรือไม่ (บล็อกที่กำลังเคลื่อนที่อยู่ชนกับบล็อกที่อยู่ในตำแหน่งแล้ว ดังนั้นบล็อกที่กำลังเคลื่อนที่อยู่ในปัจจุบันจึงไม่สามารถเลื่อนลงได้ เนื่องจากบล็อกของเราจะเลื่อนลงตามค่าเริ่มต้น หากไม่สามารถเลื่อนลงได้ ถือว่าเดินลง เป็นบล็อกที่ถูกวางตำแหน่งแล้วบล็อกใหม่จะถูกสร้างขึ้นและเลื่อนลงจากตำแหน่งเริ่มต้นต่อไป)
และการชนกันนี้จำเป็นต้องใช้เมื่อบล็อกถูกเปลี่ยนรูปด้วย ในทำนองเดียวกัน หากบล็อกชนกับบล็อกที่อยู่ในตำแหน่งอื่นในระหว่างกระบวนการเปลี่ยนรูป เราควรป้องกันไม่ให้บล็อกเปลี่ยนรูปได้สำเร็จ
แนบรหัสมาด้วย ยินดีต้อนรับการอภิปรายและการแก้ไข
<!DOCTYPE html><html lang=en><head> <meta charset=UTF-8> <title>es6- การสร้าง Tetris ใหม่ (อิงจากแคนวาส)</title> <style type=text/css> #tetris{ ระยะขอบ: 10px 250px;} </style></head><body> <canvas width=700 height=525 id=tetris></canvas> <div id=text style='color: red;font-size: 30px;'>คะแนนปัจจุบัน: 0</div> <script type=text/javascript> /** * [การออกแบบคลาส Tetris ที่สมบูรณ์โดย magic_xiang] * @param ด้าน {number} [ความยาวของแต่ละด้านสี่เหลี่ยมจัตุรัส (px) ค่าเริ่มต้น 35] * @param ความกว้าง {number} [จำนวนสี่เหลี่ยมในแถว (หมายเลข) ค่าเริ่มต้น 20] * @param ความสูง {number} [จำนวนบล็อกที่รวมอยู่ในคอลัมน์ (ค่าเริ่มต้น 15)] * @param {number} ความเร็ว [บล็อกความเร็วในการเคลื่อนที่ที่ตกลงมา (ms) ค่าเริ่มต้น 400] */ class tetris{ Constructor(side=35, width=20, height=15 , speed=400){ this.side = side // ความยาวด้านข้างของแต่ละบล็อก this.width = width // จำนวนบล็อกที่อยู่ในแถว this.height = height // จำนวนบล็อกที่มีอยู่ในคอลัมน์นี้ .ความเร็ว = ความเร็ว // ความเร็วในการเคลื่อนที่ลดลงของบล็อก this.num_blcok // ตัวแปรตัวเลขของบล็อกปัจจุบันประเภท this.type_color // ตัวแปรสตริงของประเภทสีปัจจุบัน this.ident // ตัวระบุของ setInterval this.direction = 1 // ทิศทางของบล็อก เริ่มต้นเป็น 1 สถานะเริ่มต้น this.grade = 0 // ใช้ในการคำนวณคะแนน this.over = false // ไม่ว่าเกมจะจบลง this.arr_bX = [] // เก็บพิกัด X ของบล็อกปัจจุบัน this.arr_bY = [] // เก็บพิกัด Y ของบล็อกปัจจุบัน this.arr_store_X = [] // เก็บพิกัด X ของบล็อกทั้งหมดที่ไปถึงจุดต่ำสุด this.arr_store_Y = [] // เก็บพิกัด Y ของบล็อกทั้งหมดที่มาถึงจุดต่ำสุดนี้ arr_store_color = [] // เก็บบล็อกทั้งหมดที่ถึงด้านล่าง สีของสี่เหลี่ยม this.paints = document.getElementById('tetris').getContext('2d') //รับแปรง self = this } // สรุปวิธีการทาสีเพื่อทำให้โค้ดกระชับมากขึ้น paintfr(x, y, scale=1){ this.paints.fillRect(x*this.side, y*this.side, scale*this.side, scale*this.side ) } // เกมเริ่ม gameStart(){ this.init() this.run() } // งานการเริ่มต้น init(){ this.initBackground() this.initBlock() } // บล็อกจะตกโดยอัตโนมัติ run(){ this.ident = setInterval(self.down_speed_up(), this.speed) } // เตรียมใช้งานแผนที่ initBackground(){ this.paints.beginPath() this.paints.fillStyle='#000000 ' / /สีเติมแผนที่เป็นสีดำ for(let i = 0; i < this.height; i++){ for(let j = 0; j < this.width; j++){ this.paintfr(j, i) } } this.paints.closePath() } // เริ่มต้นตำแหน่งและสีของบล็อก initBlock(){ this.paints.beginPath() this.createRandom('rColor') // สร้างสตริงสี this.paints.fillStyle = this.type_color this.createRandom('rBlock') //สร้างหมายเลขประเภทบล็อก this.arr_bX.forEach((item, index) => { this.paintfr(item, this.arr_bY[index], 0.9) }) this.paints.closePath() } // ใช้อาร์เรย์เพื่อ วาดบล็อก DrawBlock (สี) { this.paints.beginPath () this.paints.fillStyle = สี this.arr_bX.forEach((item, index) => { this.paintfr(item, this.arr_bY[index], 0.9) }) this.paints.closePath() } // วาดบล็อกที่มีตำแหน่งอยู่แล้ว DrawStaticBlock( ){ this.arr_store_X.forEach ((รายการ, ดัชนี) => { this.paints.beginPath () this.paints.fillStyle = this.arr_store_color[index] this.paintfr(item, this.arr_store_Y[index], 0.9) this.paints.closePath() }) } // สร้างตัวเลขสุ่มและส่งคืนประเภทบล็อกหรือสี พิมพ์ createRandom( ประเภท){ ให้ temp = this.width/2-1 if (type == 'rBlock'){ //หากเป็นประเภทบล็อก this.num_blcok = Math.round(Math.random()*4+1) switch(this.num_blcok){ case 1: this.arr_bX.push(temp,temp-1,temp, temp+ 1) this.arr_bY.push(0,1,1,1) ตัวแบ่งกรณีที่ 2: this.arr_bX.push(temp,temp-1,temp-1,temp+1) this.arr_bY.push(1,0,1,1) กรณีที่ 3: this.arr_bX.push(temp,temp-1,temp+1,temp+2) this.arr_bY.push(0,0,0, 0) กรณีที่ 4: this.arr_bX.push(temp,temp-1,temp,temp+1) this.arr_bY.push(0,0,1,1) ตัวแบ่ง 5: this.arr_bX.push(temp,temp+1,temp,temp+1) this.arr_bY.push(0,0,1,1) แบ่ง } } if (type == 'rColor'){ // ถ้า เป็นประเภทสี la num_color = Math.round(Math.random()*8+1) switch(num_color){ case 1: this.type_color='#3EF72A' ตัวแบ่ง 2: this.type_color='green' กรณีที่ 3: this.type_color='#2FE0BF' กรณีที่ 4: this.type_color='red' กรณีที่ 5: this.type_color='gray' กรณีที่ 6: this.type_color ='#C932C6' กรณีที่ 7: this.type_color= '#FC751B' กรณีที่ 8: this.type_color= '#6E6EDD' ตัวแบ่งกรณีที่ 9: this.type_color= '#F4E9E1' ตัวแบ่ง } } } // พิจารณาว่าบล็อกชนกัน (ด้านล่าง) หรือไม่ และพวกมันข้ามขอบเขตล่างในระหว่างการเปลี่ยนรูปตัดสิน Collision_down(){ for(let i = 0 ; i < this.arr_bX.length; i++){ ถ้า (this.arr_bY[i] + 1 == this.height){ // ไม่ว่าขอบเขตล่างจะถูกข้ามระหว่างการเปลี่ยนรูปหรือไม่คืนค่า false } if (this.arr_store_X.length != 0) { // ตรวจสอบว่าบล็อกชนกัน (ด้านล่าง) for(let j = 0; j < this.arr_store_X.length; j++ ){ ถ้า (this.arr_bX[i] == this.arr_store_X[j]) { ถ้า (this.arr_bY[i] + 1 == this.arr_store_Y[j]) { return false } } } } } return true } // ตัดสินว่าบล็อกชนกัน (ซ้ายและขวา) หรือไม่ และไม่ว่าจะข้ามขอบเขตด้านซ้ายและขวาในระหว่างการเปลี่ยนรูปตัดสิน Collision_other(num){ for(let i = 0; i < this.arr_bX.length; i++){ if (num == 1) { // ไม่ว่าจะข้ามขอบเขตด้านขวาระหว่างการเปลี่ยนรูปหรือไม่ ถ้า (this.arr_bX[i] == this.width - 1) คืนค่าเท็จ } if (num == -1) { //ไม่ว่าจะข้ามขอบเขตด้านซ้ายระหว่างการเปลี่ยนรูปหรือไม่ถ้า (this.arr_bX[i] == 0) คืนค่าเท็จ } ถ้า (this.arr_store_X.length != 0) { / /ตรวจสอบว่าบล็อกชนกัน (ซ้ายและขวา) for(let j = 0; j < this.arr_store_X.length; j++){ if (this.arr_bY[i] == this.arr_store_Y[j]) { if (this.arr_bX[i] + num == this.arr_store_X[j]) { return false } } } } } คืนค่าจริง; } //ฟังก์ชันการเร่งความเร็ว down_speed_up( ){ ให้ flag_all_down = true flag_all_down = this.judgeCollision_down() ถ้า (flag_all_down) { this.initBackground() for(let i = 0; i < this.arr_bY.length; i++){ this.arr_bY[i] = this.arr_bY[i] + 1 } } else{ สำหรับ(let i=0; i < this.arr_bX.length; i++){ this.arr_store_X.push(this.arr_bX[i]) this.arr_store_Y.push(this.arr_bY[i]) this.arr_store_color.push(this.type_color) } this.arr_bX.splice(0,this.arr_bX.length) this.arr_bY.splice(0,this.arr_bY. ความยาว) this.initBlock() } this.clearUnderBlock() this.drawBlock(this.type_color) this.drawStaticBlock() this.gameover() } // ปุ่มทิศทางคือฟังก์ชันการเคลื่อนที่ไปทางซ้าย ย้าย (dir_temp){ this.initBackground() if (dir_temp == 1) { //Right ให้ flag_all_right = true flag_all_right = this.judgeCollision_other (1) ถ้า (flag_all_right) { สำหรับ (ให้ i = 0; i < this.arr_bY.length; i++){ this.arr_bX[i] = this.arr_bX[i]+1 } } } else{ ให้ flag_all_left = true flag_all_left = this.judgeCollision_other(-1) if (flag_all_left) { สำหรับ(ให้ i=0; i < this.arr_bY.length; i++){ this.arr_bX[i] = this.arr_bX[i]-1 } } } this.drawBlock(this.type_color) this.drawStaticBlock() } // ปุ่มทิศทางคือฟังก์ชันทิศทางการเปลี่ยนแปลงช่องว่าง up_change_direction(num_blcok){ if (num_blcok == 5) { return } la arr_tempX = [] la arr_tempY = [] //เพราะไม่รู้ว่าการแปลงจะสำเร็จหรือเปล่า เลยเก็บไว้ก่อนเพื่อ(let i = 0;i < this.arr_bX.length; i++){ arr_tempX.push(this.arr_bX[i]) arr_tempY.push(this.arr_bY[i]) } this.direction++ //แยกพิกัดกึ่งกลางและทำให้เสียรูปโดย จุดศูนย์กลางปัจจุบันจะมีผลเหนือกว่า ให้ ax_temp = this.arr_bX[0] ให้ ay_temp = this.arr_bY[0] this.arr_bX.splice (0, this.arr_bX.length) // ล้างอาร์เรย์ this.arr_bY.splice (0, this.arr_bY.length) if (num_blcok == 1) { switch (this.direction%4) { กรณีที่ 1: this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp+1,ay_temp+1,ay_temp+1) แบ่งกรณีที่ 2: this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp) this.arr_bY.push(ay_temp,ay_temp, ay_temp-1,ay_temp+1) แบ่งกรณีที่ 3: this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp) ตัวแบ่งกรณี 0: this.arr_bX.push(ax_temp,ax_temp,ax_temp, ax_temp+1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp) แบ่ง } } ถ้า (num_blcok == 2) { สวิตช์(this.direction%4){ กรณีที่ 1: this.arr_bX.push(ax_temp,ax_temp -1,ax_temp-1,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp) กรณีที่ 2: this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp-1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+ 1 ,ay_temp+1) แบ่งกรณีที่ 3: this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp+1) ตัวแบ่งกรณี 0: this.arr_bX.push(ax_temp,ax_temp, ax_temp,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp-1) แบ่ง } } ถ้า (num_blcok == 3) { สวิตช์(this.direction%4){ กรณีที่ 1: this.arr_bX.push(ax_temp ,ax_temp-1,ax_temp+1,ax_temp+2) this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp) กรณีที่ 2: this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+ 2 ) แตกกรณีที่ 3: this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2) this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp) ตัวแบ่งกรณี 0: this.arr_bX.push(ax_temp,ax_temp,ax_temp, ax_temp) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2) แบ่ง } } ถ้า (num_blcok == 4) { switch(this.direction%4){ กรณีที่ 1: this.arr_bX.push(ax_temp ,ax_temp-1,ax_temp,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp+1) กรณีที่ 2: this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp+ 1 ,ay_temp,ay_temp-1) แบ่งกรณีที่ 3: this.arr_bX.push(ax_temp,ax_temp,ax_temp-1,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp-1) ตัวแบ่งกรณี 0: this.arr_bX.push(ax_temp,ax_temp, ax_temp+1,ax_temp+1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp+1) แบ่ง } } ถ้า (! (this.judgeCollision_other(-1) && this.judgeCollision_down() && this.judgeCollision_other(1) )) { // หากการแปลงไม่สำเร็จ ให้รันโค้ดต่อไปนี้ this.arr_bX.splice(0, this.arr_bX.length) this.arr_bY.splice(0, this.arr_bY.length) สำหรับ (let i=0; i< arr_tempX.length; i++){ this.arr_bX.push(arr_tempX[i]) this.arr_bY.push(arr_tempY[i] ]) } } this.drawStaticBlock() } //เมื่อแถวเต็ม ให้ล้างบล็อก และพิกัด Y ของบล็อกด้านบนคือ +1 clearUnderBlock(){ //ลบบล็อกระดับต่ำ ให้ arr_row=[] ให้ line_num if (this.arr_store_X.length != 0) { for(let j = this.height-1; j >= 0; j--) { สำหรับ (ให้ i = 0; i < this.arr_store_color.length; i++){ ถ้า (this.arr_store_Y[i] == j) { arr_row.push(i) } } if (arr_row.length == this.width) { line_num = j break }else{ arr_row.splice(0, arr_row.length) } } } ถ้า (arr_row.length == this.width ) { //คำนวณเกรด เกรด this.grade++ document.getElementById('text').innerHTML = 'เกรดปัจจุบัน:'+this.grade สำหรับ(ให้ i = 0; i < arr_row.length; i++){ this.arr_store_X.splice(arr_row[i]-i, 1) this.arr_store_Y.splice(arr_row[i]-i, 1) this.arr_store_color splice(arr_row[i]-i, 1) } //ให้บล็อกด้านบนลดลงหนึ่งช่องว่างสำหรับ(ให้ i = 0; i < this.arr_store_color.length; i++){ if (this.arr_store_Y[i] < line_num) { this.arr_store_Y[i] = this.arr_store_Y[i]+1 } } } //ตัดสินการจบเกม () { สำหรับ (ให้ i=0; i < this.arr_store_X.length; i++){ ถ้า (this.arr_store_Y[i] == 0) { clearInterval(this.ident) this.over = true } } } } ให้ tetrisObj = new tetris() tetrisObj.gameStart() // ฟังก์ชันคีย์ทิศทาง document.onkeydown = (e) => { if (tetrisObj.over) return switch (e.keyCode) { กรณีที่ 40: // ทิศทางลดลง tetrisObj.down_speed_up () ตัวแบ่ง 32: // Space เพื่อเปลี่ยนทิศทาง tetrisObj.initBackground() // วาดแผนที่ใหม่ tetrisObj.up_change_direction(tetrisObj.num_blcok) tetrisObj.drawBlock(tetrisObj.type_color) ตัวแบ่งกรณีที่ 37: // ทิศทางยังคงอยู่ tetrisObj.initBackground() tetrisObj .ย้าย (-1) tetrisObj.drawBlock(tetrisObj.type_color) ตัวแบ่ง 39: // ทิศทางถูกต้อง tetrisObj.initBackground() tetrisObj.move(1) tetrisObj.drawBlock(tetrisObj.type_color) ตัวแบ่ง } } </script></body></ html >
ข้างต้นคือเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการศึกษาของทุกคน ฉันหวังว่าทุกคนจะสนับสนุน VeVb Wulin Network