JavaScript實(shí)現(xiàn)俄羅斯方塊游戲過(guò)程分析及源碼分享
觀摩一下《編程之美》:“程序雖然很難寫(xiě),卻很美妙。要想把程序?qū)懞?,需要?xiě)好一定的基礎(chǔ)知識(shí),包括編程語(yǔ)言、數(shù)據(jù)結(jié)構(gòu)與算法。程序?qū)懙煤?,需要縝密的邏輯思維能力和良好的梳理基礎(chǔ),而且熟悉編程環(huán)境和編程工具。”
學(xué)了幾年的計(jì)算機(jī),你有沒(méi)有愛(ài)上編程。話(huà)說(shuō),沒(méi)有嘗試自己寫(xiě)過(guò)一個(gè)游戲,算不上熱愛(ài)編程。
俄羅斯方塊曾經(jīng)造成的轟動(dòng)與造成的經(jīng)濟(jì)價(jià)值可以說(shuō)是游戲史上的一件大事,它看似簡(jiǎn)單但卻變化無(wú)窮,令人上癮。相信大多數(shù)同學(xué),曾經(jīng)為它癡迷得茶不思飯不想。
游戲規(guī)則
1、一個(gè)用于擺放小型正方形的平面虛擬場(chǎng)地,其標(biāo)準(zhǔn)大?。盒袑挒?0,列高為20,以每個(gè)小正方形為單位。
2、一組由4個(gè)小型正方形組成的規(guī)則圖形,英文稱(chēng)為T(mén)etromino,中文通稱(chēng)為方塊共有7種,分別以S、Z、L、J、I、O、T這7個(gè)字母的形狀來(lái)命名。

I:一次最多消除四層
J(左右):最多消除三層,或消除二層
L:最多消除三層,或消除二層
O:消除一至二層
S(左右):最多二層,容易造成孔洞
Z (左右):最多二層,容易造成孔洞
T:最多二層
方塊會(huì)從區(qū)域上方開(kāi)始緩慢繼續(xù)落下。玩家可以以90度為單位旋轉(zhuǎn)方塊,以格子為單位左右移動(dòng)方塊,讓方塊加速落下。方塊移到區(qū)域最下方或是著地到其他方塊上無(wú)法移動(dòng)時(shí),就會(huì)固定在該處,而新的方塊出現(xiàn)在區(qū)域上方開(kāi)始落下。當(dāng)區(qū)域中某一列橫向格子全部由方塊填滿(mǎn),則該列會(huì)消失并成為玩家的得分。同時(shí)刪除的列數(shù)越多,得分指數(shù)上升。
分析與解法
每塊方塊落下的過(guò)程中,我們可以做:
1)旋轉(zhuǎn)到合適的方向
2)水平移動(dòng)到某一列
3)垂直下落到底部
首先,需要用一個(gè)二維數(shù)組,area[18][10]表示18*10的游戲區(qū)域。其中,數(shù)組中值為0表示空,1表示有方塊。
方塊一共7種,每種有4種方向。定義activeBlock[4],在編譯之前這個(gè)數(shù)組的值預(yù)定算好,在程序中直接使用。
難點(diǎn)
1)邊界檢查。
//檢查左邊界,嘗試著朝左邊移動(dòng)一個(gè),看是否合法。
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;
} //同理,需要檢測(cè)右邊界和底邊界
2)旋轉(zhuǎn), 需要數(shù)理邏輯, 一個(gè)點(diǎn)相對(duì)另外一個(gè)點(diǎn)旋轉(zhuǎn)90度的問(wèn)題。
3)定時(shí)和監(jiān)聽(tīng)鍵盤(pán)事件機(jī)制讓游戲自動(dòng)運(yùn)行下去。
//開(kāi)始
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;
程序過(guò)程
1)用戶(hù)點(diǎn)開(kāi)始->構(gòu)造一個(gè)活動(dòng)圖形, 設(shè)置定時(shí)器。
//當(dāng)前活動(dòng)的方塊, 它可以左右下移動(dòng), 變型。當(dāng)它觸底后, 將會(huì)更新area;
var activeBlock;
//生產(chǎn)方塊形狀, 有7種基本形狀。
function generateBlock(){
activeBlock = null;
activeBlock = new Array(4);
//隨機(jī)產(chǎn)生0-6數(shù)組,代表7種形態(tài)。
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;
}
}
//檢查剛生產(chǎn)的四個(gè)小方格是否可以放在初始化的位置.
for(var i=0; i<4; i++){
if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){
return false;
}
}
return true;
}
2)每次向下移動(dòng)后, 都檢查是否觸底, 如果觸底了, 則嘗試消行。
//消行
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)完了之后再構(gòu)造一個(gè)活動(dòng)圖形, 再設(shè)置定時(shí)器。
效果圖



有待優(yōu)化
1)設(shè)置不同形狀方塊的顏色。
思路:在創(chuàng)建方塊函數(shù)內(nèi),設(shè)定activeBlockColor顏色,七種不同形態(tài)方塊顏色各異(除了修改generateBlock方法之外,還需要修改paintarea方法。因?yàn)橐婚_(kāi)始考慮不周全,消除一行后,重繪方塊的同時(shí)將顏色統(tǒng)一,因此可以考慮移除表格n行,然后在頂部增添n行,以保證沒(méi)消除方塊的完整性)。
2)當(dāng)當(dāng)前方塊下落時(shí),可以提前查看下一個(gè)方塊。
思路:將generateBlock方法拆分成兩部分,一部分用于隨機(jī)嘗試下一個(gè)方塊,一部分用于緩存當(dāng)前所要描繪的方塊。當(dāng)當(dāng)前方塊碰到底部被固定后,下一方塊開(kāi)始描繪,同時(shí)又再次隨機(jī)產(chǎn)生新方塊。如此反復(fù)。
完整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="開(kāi)始游戲" 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
*/
//表示頁(yè)面中的table, 這個(gè)table就是將要顯示游戲的主面板
var tbl;
//游戲狀態(tài) 0: 未開(kāi)始;1 運(yùn)行; 2 中止;
var status = 0;
//定時(shí)器, 定時(shí)器內(nèi)將做moveDown操作
var timer;
//分?jǐn)?shù)
var score = 0;
//area是一個(gè)18*10的數(shù)組,也和頁(yè)面的table對(duì)應(yīng)。初始時(shí)都為0, 如果被占據(jù)則為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;
}
}
//當(dāng)前活動(dòng)的方塊, 它可以左右下移動(dòng), 變型。當(dāng)它觸底后, 將會(huì)更新area;
var activeBlock;
//生產(chǎn)方塊形狀, 有7種基本形狀。
function generateBlock(){
activeBlock = null;
activeBlock = new Array(4);
//隨機(jī)產(chǎn)生0-6數(shù)組,代表7種形態(tài)。
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;
}
}
//檢查剛生產(chǎn)的四個(gè)小方格是否可以放在初始化的位置.
for(var i=0; i<4; i++){
if(!isCellValid(activeBlock[i].x, activeBlock[i].y)){
return false;
}
}
return true;
}
//向下移動(dòng)
function moveDown(){
//檢查底邊界.
if(checkBottomBorder()){
//沒(méi)有觸底, 則擦除當(dāng)前圖形,
erase();
//更新當(dāng)前圖形坐標(biāo)
for(var i=0; i<4; i++){
activeBlock[i].x = activeBlock[i].x + 1;
}
//重畫(huà)當(dāng)前圖形
paint();
}
//觸底,
else{
//停止當(dāng)前的定時(shí)器, 也就是停止自動(dòng)向下移動(dòng).
clearInterval(timer);
//更新area數(shù)組.
updatearea();
//消行
var lines = deleteLine();
//如果有消行, 則
if(lines!=0){
//更新分?jǐn)?shù)
score = score + lines*10;
updateScore();
//擦除整個(gè)面板
erasearea();
//重繪面板
paintarea();
}
//產(chǎn)生一個(gè)新圖形并判斷是否可以放在最初的位置.
if(!generateBlock()){
alert("Game over!");
status = 2;
return;
}
paint();
//定時(shí)器, 每隔一秒執(zhí)行一次moveDown
timer = setInterval(moveDown,1000)
}
}
//左移動(dòng)
function moveLeft(){
if(checkLeftBorder()){
erase();
for(var i=0; i<4; i++){
activeBlock[i].y = activeBlock[i].y - 1;
}
paint();
}
}
//右移動(dòng)
function moveRight(){
if(checkRightBorder()){
erase();
for(var i=0; i<4; i++){
activeBlock[i].y = activeBlock[i].y + 1;
}
paint();
}
}
//旋轉(zhuǎn), 因?yàn)樾D(zhuǎn)之后可能會(huì)有方格覆蓋已有的方格.
//先用一個(gè)tmpBlock,把a(bǔ)ctiveBlock的內(nèi)容都拷貝到tmpBlock,
//對(duì)tmpBlock嘗試旋轉(zhuǎn), 如果旋轉(zhuǎn)后檢測(cè)發(fā)現(xiàn)沒(méi)有方格產(chǎn)生沖突,則
//把旋轉(zhuǎn)后的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;
}
//先算四個(gè)點(diǎn)的中心點(diǎn),則這四個(gè)點(diǎn)圍繞中心旋轉(zhuǎn)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);
//旋轉(zhuǎn)的主要算法. 可以這樣分解來(lái)理解。
//先假設(shè)圍繞源點(diǎn)旋轉(zhuǎn)。然后再加上中心點(diǎn)的坐標(biāo)。
for(var i=0; i<4; i++){
tmpBlock[i].x = cx+cy-activeBlock[i].y;
tmpBlock[i].y = cy-cx+activeBlock[i].x;
}
//檢查旋轉(zhuǎn)后方格是否合法.
for(var i=0; i<4; i++){
if(!isCellValid(tmpBlock[i].x,tmpBlock[i].y)){
return;
}
}
//如果合法, 擦除
erase();
//對(duì)activeBlock重新賦值.
for(var i=0; i<4; i++){
activeBlock[i].x = tmpBlock[i].x;
activeBlock[i].y = tmpBlock[i].y;
}
//重畫(huà).
paint();
}
//檢查左邊界,嘗試著朝左邊移動(dòng)一個(gè),看是否合法。
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;
}
//檢查右邊界,嘗試著朝右邊移動(dòng)一個(gè),看是否合法。
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;
}
//檢查底邊界,嘗試著朝下邊移動(dòng)一個(gè),看是否合法。
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;
}
//檢查坐標(biāo)為(x,y)的是否在area種已經(jīng)存在, 存在說(shuō)明這個(gè)方格不合法。
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";
}
}
//繪活動(dòng)圖形
function paint(){
for(var i=0; i<4; i++){
tbl.rows[activeBlock[i].x].cells[activeBlock[i].y].style.backgroundColor="#CC3333";
}
}
//更新area數(shù)組
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;
}
//擦除整個(gè)面板
function erasearea(){
for(var i=0; i<18; i++){
for(var j=0; j<10; j++){
tbl.rows[i].cells[j].style.backgroundColor = "white";
}
}
}
//重繪整個(gè)面板
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";
}
}
}
}
//產(chǎn)生一個(gè)空白行.
function generateBlankLine(){
var line = new Array(10);
for(var i=0; i<10; i++){
line[i] = 0;
}
return line;
}
//更新分?jǐn)?shù)
function updateScore(){
document.getElementById("score").innerText=" " + score;
}
//鍵盤(pán)控制
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;
}
}
}
//開(kāi)始
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;
- javascript實(shí)現(xiàn)俄羅斯方塊游戲的思路和方法
- JS俄羅斯方塊,包含完整的設(shè)計(jì)理念
- JS 俄羅斯方塊完美注釋版代碼
- JAVASCRIPT代碼編寫(xiě)俄羅斯方塊網(wǎng)頁(yè)版
- 60行js代碼實(shí)現(xiàn)俄羅斯方塊
- 使用JS代碼實(shí)現(xiàn)俄羅斯方塊游戲
- JavaScript實(shí)現(xiàn)簡(jiǎn)潔的俄羅斯方塊完整實(shí)例
- JS+Canvas實(shí)現(xiàn)的俄羅斯方塊游戲完整實(shí)例
- js html5 css俄羅斯方塊游戲再現(xiàn)
- JavaScript canvas實(shí)現(xiàn)俄羅斯方塊游戲
相關(guān)文章
微信小程序修改swiper默認(rèn)指示器樣式的實(shí)例代碼
這篇文章主要介紹了微信小程序修改swiper默認(rèn)指示器樣式的實(shí)例代碼,代碼塊是從微信開(kāi)發(fā)文檔中心復(fù)制的代碼塊,在此基礎(chǔ)上修改官方swiper樣式,需要的朋友可以參考下2018-07-07
JS實(shí)現(xiàn)仿雅虎首頁(yè)快捷登錄入口及導(dǎo)航模塊效果
這篇文章主要介紹了JS實(shí)現(xiàn)仿雅虎首頁(yè)快捷登錄入口及導(dǎo)航模塊效果,涉及JavaScript響應(yīng)鼠標(biāo)事件遍歷頁(yè)面元素的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
JavaScript代碼實(shí)現(xiàn)左右上下自動(dòng)晃動(dòng)自動(dòng)移動(dòng)
最近幾天做了一個(gè)項(xiàng)目,原來(lái)是用css3動(dòng)畫(huà)做的,由于不兼容IE,改成用js做了,特此分享給大家,供大家參考2016-04-04

