내가 처음으로 Tetris를 쓴 것은 1년여 전이었고, JS를 배운 지 얼마 되지 않았습니다.
js에 대한 이해를 높이고 게임에 대한 사랑을 더하기 위해 다른 사람의 아이디어나 코드를 참고하지 않고 가장 기본적인 js 코드를 사용하여 캔버스 기반으로 Tetris를 작성했습니다.
3학년 여름방학 동안 es6에 대한 이해를 더욱 높이기 위해 클래스의 구문 설탕, 화살표 함수 등을 포함하여 es6의 구문을 개선했습니다. 코드는 400줄이 넘습니다.
이 작은 게임을 만들려면 먼저 H5 캔버스, js 배열 처리, 키보드 이벤트 모니터링 및 처리, 타이머 사용 등에 익숙해져야 합니다. 나머지는 기본적인 논리 처리입니다.
게임의 규칙은 우리 코드의 핵심이자 최우선 사항입니다.
여기서 핵심 로직은 블록이 충돌하는지 여부를 결정하는 것입니다(현재 움직이는 블록이 이미 위치한 블록과 충돌하여 현재 움직이는 블록은 아래로 이동할 수 없습니다. 왜냐하면 우리 블록은 기본적으로 아래로 이동하기 때문입니다. 아래로 이동할 수 없는 경우 아래로 걷는 것으로 간주됩니다. 새로운 블록이 생성되어 초기 위치에서 계속 아래로 이동합니다.
그리고 이 충돌은 블록이 변형될 때에도 적용되어야 합니다. 마찬가지로 변형 프로세스 중에 블록이 다른 위치의 블록과 충돌하는 경우 블록이 성공적으로 변형되는 것을 방지해야 합니다.
코드가 첨부되어 있으며 토론과 수정을 환영합니다.
<!DOCTYPE html><html lang=en><head> <meta charset=UTF-8> <title>es6-테트리스 재구성(캔버스 기반)</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> /** * [magic_xiang의 완전한 Tetris 클래스 디자인] * @param {number} 변 [각 정사각형 변의 길이(px), 기본값 35] * @param {number} width [한 행의 정사각형 수(숫자), 기본값 20] * @param {number} 높이 [열에 포함된 블록 수(기본값 15)] * @param {number} speed [블록 낙하 이동 속도(ms), 기본값 400] */ class tetris{ constructor(side=35, width=20, height=15 , speed=400){ this.side = side // 각 블록의 측면 길이 this.width = width // 행에 포함된 블록 수 this.height = height // 열에 포함된 블록 수 this .speed = 속도 // 블록의 하강 이동 속도 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 좌표를 저장 this. 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(color){ this.paints.beginPath() this.paints.fillStyle = color this.arr_bX.forEach((item, index) => { this.paintfr(item, this.arr_bY[index], 0.9) }) this.paints.closePath() } // 이미 배치된 블록 그리기 drawStaticBlock( ){ this.arr_store_X.forEach((item, index) => { this.paints.beginPath() this.paints.fillStyle = this.arr_store_color[index] this.paintfr(item, this.arr_store_Y[index], 0.9) this.paints.closePath() }) } // 난수를 생성하고 블록 유형 또는 색상을 반환합니다. type createRandom( type){ let temp = this.width/2-1 if (type == 'rBlock'){ //블록형인 경우 this.num_blcok = Math.round(Math.random()*4+1) switch(this.num_blcok){ 사례 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) break } } if (type == 'rColor'){ //if 색상 유형입니다. let num_color = Math.round(Math.random()*8+1) switch(num_color){ 사례 1: this.type_color='#3EF72A' 중단 사례 2: this.type_color='yellow' 중단 사례 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' break 사례 9: this.type_color= '#F4E9E1' break } } } // 블록이 충돌하는지(하부), 변형 중 하한 경계를 넘는지 확인 JudgeCollision_down(){ for(let i = 0 ; i < this.arr_bX.length; i++){ if (this.arr_bY[i] + 1 == this.height) //변형 중 하한 경계를 넘었는지 여부 return false } if (this.arr_store_X.length != 0) { //블록 충돌(하부) 여부 확인 for(let j = 0; j < this.arr_store_X.length; j++ ){ if (this.arr_bX[i] == this.arr_store_X[j]) { if (this.arr_bY[i] + 1 == this.arr_store_Y[j]) { return false } } } } } return true } //블록이 충돌하는지(왼쪽 및 오른쪽) 여부, 변형 시 왼쪽 및 오른쪽 경계를 넘나드는지 여부 판단 JudgeCollision_other(num){ for(let i = 0; i < this.arr_bX.length; i++){ if (num == 1) { //변형 중 오른쪽 경계를 넘었는지 여부 if (this.arr_bX[i] == this.width - 1) return false } if (num == -1) { //변형 중 왼쪽 경계를 넘었는지 여부 if (this.arr_bX[i] == 0) return false } if (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 } } } } } return true } //가속 함수 down_speed_up( ){ let flag_all_down = true flag_all_down = this.judgeCollision_down() if (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{ for(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() } //방향키는 왼쪽 이동 function move(dir_temp){ this.initBackground() if (dir_temp == 1) { //오른쪽 let flag_all_right = true flag_all_right = this.judgeCollision_other(1) if (flag_all_right) { for(let i = 0; i < this.arr_bY.length; i++){ this.arr_bX[i] = this.arr_bX[i]+1 } } } else{ let flag_all_left = true flag_all_left = this.judgeCollision_other(-1) if (flag_all_left) { for(let i=0; i < this.arr_bY.length; i++){ this.arr_bX[i] = this.arr_bX[i]-1 } } } this.drawBlock(this.type_color) this.drawStaticBlock() } //방향 키는 공간 변환 방향 function up_change_direction(num_blcok){ if (num_blcok == 5) { return } let arr_tempX = [] let arr_tempY = [] //변환이 성공할 수 있을지 모르기 때문에 먼저 저장합니다 for(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++ //중심 좌표를 추출하고 현재 센터가 우선합니다 let ax_temp = this.arr_bX[0] let 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, 도끼_온도+1) this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp) break } } if (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) break } } if (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) break } } if (num_blcok == 4) { 스위치(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) break } } if (! (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) for(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(){ //저수준 블록 삭제 let arr_row=[] let line_num if (this.arr_store_X.length != 0) { for(let j = this.height-1; j >= 0; j--) { for (let i = 0; i < this.arr_store_color.length; i++){ if (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) } } } if (arr_row.length == this.width ) { //성적 등급 계산 this.grade++ document.getElementById('text').innerHTML = '현재 성적:'+this.grade for(let 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) } //상위 블록이 한 칸 아래로 떨어지도록 합니다. for(let 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 } } } //게임 종료 판단 gameover() { for (let i=0; i < this.arr_store_X.length; i++){ if (this.arr_store_Y[i] == 0) {clearInterval(this.ident) this.over = true } } } } let tetrisObj = new tetris() tetrisObj.gameStart() //방향 키 함수 document.onkeydown = (e) => { if (tetrisObj.over) return switch(e.keyCode){ case 40: // 방향이 아래쪽입니다 tetrisObj.down_speed_up() break case 32: // 방향을 변경할 공간 tetrisObj.initBackground() // 맵을 다시 그립니다 tetrisObj.up_change_direction(tetrisObj.num_blcok) tetrisObj.drawBlock(tetrisObj.type_color) break case 37: // 방향은 왼쪽입니다 tetrisObj.initBackground() tetrisObj .이동 (-1) tetrisObj.drawBlock(tetrisObj.type_color) break 사례 39: // 방향이 오른쪽입니다 tetrisObj.initBackground() tetrisObj.move(1) tetrisObj.drawBlock(tetrisObj.type_color) break } } </script></body></ HTML >
위 내용은 이 기사의 전체 내용입니다. 모든 분들의 학습에 도움이 되기를 바랍니다. 또한 모든 분들이 VeVb Wulin Network를 지지해 주시길 바랍니다.