「プログラミングの美しさ」を見る: 「プログラムを書くのは難しいですが、美しいです。プログラムを上手に書くには、プログラミング言語、データ構造、アルゴリズムなどの基本的な知識が必要です。プログラムを上手に書くと、 、厳密な論理的思考スキルと分析の優れた基礎、およびプログラミング環境とプログラミング ツールに精通していることが必要です。」
数年間コンピューターを勉強した後、プログラミングに夢中になったことはありますか?言い換えれば、自分でゲームを書いてみたことがない人は、プログラミングがあまり好きではないということです。
かつてテトリスが引き起こしたセンセーションと経済的価値は、ゲームの歴史の中で大きな出来事と言えます。シンプルに見えますが、無限に変化し、中毒性があります。私のクラスメートのほとんどは、かつては食べ物やお茶について考えるのをやめたほど夢中になっていたと思います。
ゲームルール
1. 小さな正方形を配置するための平らな仮想会場。その標準サイズは、行幅 10、列高さ 20 で、それぞれの小さな正方形が単位となります。
2. 4 つの小さな正方形で構成される通常のグラフィックのセット。英語ではテトロミノと呼ばれ、中国語では一般に正方形として知られており、それぞれに S、Z、L、J、I、O の文字が付いています。 、T. 形状に名前を付けます。
I: 一度に最大 4 つのレイヤーを削除します
J (左右): 最大 3 つのレイヤーを削除、または 2 つのレイヤーを削除します
L: 最大 3 つのレイヤーを削除、または 2 つのレイヤーを削除します
O: 1 階から 2 階を削除します
S(左右):2層まで、穴が開きやすい
Z(左右):2層まで、穴が開きやすい
T: 2 階まで
ブロックはエリアの上からゆっくりと落ち続けます。プレイヤーはブロックを90度単位で回転させたり、ブロックをグリッド単位で左右に動かしたりして、ブロックの落下を加速させることができます。ブロックがエリアの一番下に移動したり、他のブロックに着地して移動できなくなると、そのブロックはそこで固定され、エリア上に新たなブロックが出現して落下し始めます。エリア内の水平グリッドの列が正方形で完全に埋まると、列が消えてプレイヤーのスコアになります。同時に削除される列が多いほど、スコアインデックスは増加します。
分析と解決策
各ブロックが落ちたら、次のことができます:
1) 適切な方向に回転します
2) 特定の列まで水平に移動します
3) 垂直に下にドロップします
まず、2 次元配列を使用する必要があります。area[18][10] は 18*10 のゲーム領域を表します。このうち、配列の値 0 は空を意味し、値 1 はブロックがあることを意味します。
ブロックは全部で7種類あり、それぞれ4方向あります。 activeBlock[4] を定義します。この配列の値はコンパイル前に事前に計算され、プログラムで直接使用されます。
困難
1) 境界チェック。
//检查左边界,尝试着朝左边移动一个,看是否合法。 function checkLeftBorder(){ for(var i=0; i<activeBlock.length; i++){ if(activeBlock[i].y==0){ return false; } if(!isCellValid(activeBlock[i].x, activeBlock[i].y-1)){ return false; } } return true; } //同理,需要检测右边界和底边界
2) 回転には数学的論理が必要で、ある点を別の点に対して 90 度回転させる問題です。
3) タイミングとモニタリングのキーボード イベント メカニズムにより、ゲームが自動的に実行されます。
//开始 function begin(e){ e.disabled = true; status = 1; tbl = document.getElementById("area"); if(!generateBlock()){ alert("Game over!"); status = 2; return; } paint(); timer = setInterval(moveDown,1000); } document.onkeydown=keyControl;
お手続きの流れ
1) ユーザーは、「スタート」->「アクティブなグラフィックの構築」をクリックし、タイマーを設定します。
//当前活动的方块, 它可以左右下移动, 变型。当它触底后, 将会更新area; var activeBlock; //生产方块形状, 有7种基本形状。 function generateBlock(){ activeBlock = null; activeBlock = new Array(4); //随机产生0-6数组,代表7种形态。 var t = (Math.floor(Math.random()*20)+1)%7; switch(t){ case 0:{ activeBlock[0] = {x:0, y:4}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:0, y:5}; activeBlock[3] = {x:1, y:5}; break; } //省略部分代码.............................. case 6:{ activeBlock[0] = {x:0, y:5}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:1, y:5}; activeBlock[3] = {x:1, y:6}; break; } } //检查刚生产的四个小方格是否可以放在初始化的位置. for(var i=0; i<4; i++){ if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){ return false; } } return true; }
2) 下に移動するたびに、底に到達したかどうかを確認し、底に到達した場合は、その行を削除してみます。
//消行 function deleteLine(){ var lines = 0; for(var i=0; i<18; i++){ var j=0; for(; j<10; j++){ if(area[i][j]==0){ break; } } if(j==10){ lines++; if(i!=0){ for(var k=i-1; k>=0; k--){ area[k+1] = area[k]; } } area[0] = generateBlankLine(); } } return lines; }
3) 終了したら、アクティブなグラフィックを構築し、タイマーを設定します。
レンダリング
最適化予定
1) さまざまな形状のブロックの色を設定します。
アイデア: ブロック作成関数で、activeBlockColor の色を設定します。7 つの異なる形状のブロックには異なる色があります (generateBlock メソッドの変更に加えて、paintarea メソッドも変更する必要があります。これはよく考えられていなかったためです)。最初は行を削除した後、色を統一しながら四角形を描画することを繰り返します。表から n 行削除し、削除せずに四角形の整合性を確保するために上部に n 行追加することを検討できます)。
2) 現在のブロックが落ちた場合、次のブロックを事前に確認できます。
アイデア:generateBlock メソッドを 2 つの部分に分割します。1 つの部分は次のブロックをランダムに試行するために使用され、もう 1 つの部分は描画される現在のブロックをキャッシュするために使用されます。現在のブロックが底に達して固定されると、次のブロックの描画が開始され、再び新しいブロックがランダムに生成されます。繰り返し。
完全な HTML ソース コード:
<!DOCTYPE> <html> <head> <title>Tetris</title> <meta charset="UTF-8"> <style> *{ font-family: "微软雅黑"; } .tetrisContainer{ width: 230px; height: 400px; position: relative; left: 50%; margin-left: -115px; top: 40%; margin-top: -200px; } #area tr td{ width: 20px; height: 20px; border:1px solid #ccc; } </style> </head> <body> <div class = "tetrisContainer"> <input type="button" value="开始游戏" onclick="begin(this);"/> 得分: <span id="score"> 0</span> <table id="area" cellspacing="0" cellpadding="0" border="1" style="border-collapse:collapse"><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></table> </div> </body> <script type="text/javascript" src="script/tetris.js"></script> </html>
完全な tetris.js ソース コード:
/** * JS俄罗斯方块游戏 v 1.0 */ //表示页面中的table, 这个table就是将要显示游戏的主面板 var tbl; //游戏状态 0: 未开始;1 运行; 2 中止; var status = 0; //定时器, 定时器内将做moveDown操作 var timer; //分数 var score = 0; //area是一个18*10的数组,也和页面的table对应。初始时都为0, 如果被占据则为1 var area = new Array(18); for(var i=0;i<18;i++){ area[i] = new Array(10); } for(var i=0;i<18;i++){ for(var j=0; j<10; j++){ area[i][j] = 0; } } //当前活动的方块, 它可以左右下移动, 变型。当它触底后, 将会更新area; var activeBlock; //生产方块形状, 有7种基本形状。 function generateBlock(){ activeBlock = null; activeBlock = new Array(4); //随机产生0-6数组,代表7种形态。 var t = (Math.floor(Math.random()*20)+1)%7; switch(t){ case 0:{ activeBlock[0] = {x:0, y:4}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:0, y:5}; activeBlock[3] = {x:1, y:5}; break; } case 1:{ activeBlock[0] = {x:0, y:3}; activeBlock[1] = {x:0, y:4}; activeBlock[2] = {x:0, y:5}; activeBlock[3] = {x:0, y:6}; break; } case 2:{ activeBlock[0] = {x:0, y:5}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:1, y:5}; activeBlock[3] = {x:2, y:4}; break; } case 3:{ activeBlock[0] = {x:0, y:4}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:1, y:5}; activeBlock[3] = {x:2, y:5}; break; } case 4:{ activeBlock[0] = {x:0, y:4}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:1, y:5}; activeBlock[3] = {x:1, y:6}; break; } case 5:{ activeBlock[0] = {x:0, y:4}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:2, y:4}; activeBlock[3] = {x:2, y:5}; break; } case 6:{ activeBlock[0] = {x:0, y:5}; activeBlock[1] = {x:1, y:4}; activeBlock[2] = {x:1, y:5}; activeBlock[3] = {x:1, y:6}; break; } } //检查刚生产的四个小方格是否可以放在初始化的位置. for(var i=0; i<4; i++){ if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){ return false; } } return true; } //向下移动 function moveDown(){ //检查底边界. if(checkBottomBorder()){ //没有触底, 则擦除当前图形, erase(); //更新当前图形坐标 for(var i=0; i<4; i++){ activeBlock[i].x = activeBlock[i].x + 1; } //重画当前图形 paint(); } //触底, else{ //停止当前的定时器, 也就是停止自动向下移动. clearInterval(timer); //更新area数组. updatearea(); //消行 var lines = deleteLine(); //如果有消行, 则 if(lines!=0){ //更新分数 score = score + lines*10; updateScore(); //擦除整个面板 erasearea(); //重绘面板 paintarea(); } //产生一个新图形并判断是否可以放在最初的位置. if(!generateBlock()){ alert("Game over!"); status = 2; return; } paint(); //定时器, 每隔一秒执行一次moveDown timer = setInterval(moveDown,1000) } } //左移动 function moveLeft(){ if(checkLeftBorder()){ erase(); for(var i=0; i<4; i++){ activeBlock[i].y = activeBlock[i].y - 1; } paint(); } } //右移动 function moveRight(){ if(checkRightBorder()){ erase(); for(var i=0; i<4; i++){ activeBlock[i].y = activeBlock[i].y + 1; } paint(); } } //旋转, 因为旋转之后可能会有方格覆盖已有的方格. //先用一个tmpBlock,把activeBlock的内容都拷贝到tmpBlock, //对tmpBlock尝试旋转, 如果旋转后检测发现没有方格产生冲突,则 //把旋转后的tmpBlock的值给activeBlock. function rotate(){ var tmpBlock = new Array(4); for(var i=0; i<4; i++){ tmpBlock[i] = {x:0, y:0}; } for(var i=0; i<4; i++){ tmpBlock[i].x = activeBlock[i].x; tmpBlock[i].y = activeBlock[i].y; } //先算四个点的中心点,则这四个点围绕中心旋转90度。 var cx = Math.round((tmpBlock[0].x + tmpBlock[1].x + tmpBlock[2].x + tmpBlock[3].x)/4); var cy = Math.round((tmpBlock[0].y + tmpBlock[1].y + tmpBlock[2].y + tmpBlock[3].y)/4); //旋转的主要算法. 可以这样分解来理解。 //先假设围绕源点旋转。然后再加上中心点的坐标。 for(var i=0; i<4; i++){ tmpBlock[i].x = cx+cy-activeBlock[i].y; tmpBlock[i].y = cy-cx+activeBlock[i].x; } //检查旋转后方格是否合法. for(var i=0; i<4; i++){ if(!isCellValid(tmpBlock[i].x,tmpBlock[i].y)){ return; } } //如果合法, 擦除 erase(); //对activeBlock重新赋值. for(var i=0; i<4; i++){ activeBlock[i].x = tmpBlock[i].x; activeBlock[i].y = tmpBlock[i].y; } //重画. paint(); } //检查左边界,尝试着朝左边移动一个,看是否合法。 function checkLeftBorder(){ for(var i=0; i<activeBlock.length; i++){ if(activeBlock[i].y==0){ return false; } if(!isCellValid(activeBlock[i].x, activeBlock[i].y-1)){ return false; } } return true; } //检查右边界,尝试着朝右边移动一个,看是否合法。 function checkRightBorder(){ for(var i=0; i<activeBlock.length; i++){ if(activeBlock[i].y==9){ return false; } if(!isCellValid(activeBlock[i].x, activeBlock[i].y+1)){ return false; } } return true; } //检查底边界,尝试着朝下边移动一个,看是否合法。 function checkBottomBorder(){ for(var i=0; i<activeBlock.length; i++){ if(activeBlock[i].x==17){ return false; } if(!isCellValid(activeBlock[i].x+1, activeBlock[i].y)){ return false; } } return true; } //检查坐标为(x,y)的是否在area种已经存在, 存在说明这个方格不合法。 function isCellValid(x, y){ if(x>17||x<0||y>9||y<0){ return false; } if(area[x][y]==1){ return false; } return true; } //擦除 function erase(){ for(var i=0; i<4; i++){ tbl.rows[activeBlock[i].x].cells[activeBlock[i].y].style.backgroundColor="white"; } } //绘活动图形 function paint(){ for(var i=0; i<4; i++){ tbl.rows[activeBlock[i].x].cells[activeBlock[i].y].style.backgroundColor="#CC3333"; } } //更新area数组 function updatearea(){ for(var i=0; i<4; i++){ area[activeBlock[i].x][activeBlock[i].y]=1; } } //消行 function deleteLine(){ var lines = 0; for(var i=0; i<18; i++){ var j=0; for(; j<10; j++){ if(area[i][j]==0){ break; } } if(j==10){ lines++; if(i!=0){ for(var k=i-1; k>=0; k--){ area[k+1] = area[k]; } } area[0] = generateBlankLine(); } } return lines; } //擦除整个面板 function erasearea(){ for(var i=0; i<18; i++){ for(var j=0; j<10; j++){ tbl.rows[i].cells[j].style.backgroundColor = "white"; } } } //重绘整个面板 function paintarea(){ for(var i=0;i<18;i++){ for(var j=0; j<10; j++){ if(area[i][j]==1){ tbl.rows[i].cells[j].style.backgroundColor = "#CC3333"; } } } } //产生一个空白行. function generateBlankLine(){ var line = new Array(10); for(var i=0; i<10; i++){ line[i] = 0; } return line; } //更新分数 function updateScore(){ document.getElementById("score").innerText=" " + score; } //键盘控制 function keyControl(){ if(status!=1){ return; } var code = event.keyCode; switch(code){ case 37:{ moveLeft(); break; } case 38:{ rotate(); break; } case 39:{ moveRight(); break; } case 40:{ moveDown(); break; } } } //开始 function begin(e){ e.disabled = true; status = 1; tbl = document.getElementById("area"); if(!generateBlock()){ alert("Game over!"); status = 2; return; } paint(); timer = setInterval(moveDown,1000); } document.onkeydown=keyControl;