1.创造方块:
1.1使用string和vector去创造对象
# creating assets
tetromino = []
object1 = "..X." \
"..X." \
"..X." \
"..X."
object2 = "..X." \
".XX." \
".X.." \
"...."
object3 = ".X.." \
".XX." \
"..X." \
"...."
object4 = "...." \
".XX." \
".XX." \
"...."
object5 = "..X." \
".XX." \
"..X." \
"...."
object6 = "...." \
".XX." \
"..X." \
"..X."
object7 = "...." \
".XX." \
".X.." \
".X.."
tetromino.append(object1)
tetromino.append(object2)
tetromino.append(object3)
tetromino.append(object4)
tetromino.append(object5)
tetromino.append(object6)
tetromino.append(object7)
2.旋转方块
在俄罗斯方块中,我们总是希望可以将方块旋转。
2.1使用一维向量来表示二维向量
比如有下图这样的二维向量:
可以很轻松的通过x和y两个参数来进行索引(a[x][y])。同样也可以使用一个参数来索引,只要我们给每个位置一个数字作为索引,正如上图所示。例如,我们可以通过i[10]来索引a[2][2]。这里的10可以表示为2*4+2. 即以左上角为坐标原点,y*width+x。
2.2旋转方块
现在旋转方块90°后:
如果我们还是想索引a[2][2]这个位置,如图易得现在需要用i[6]来索引。
通过图片可以轻易的看出,每当横坐标增加1,一维索引的数字就减少4;每当纵坐标增加1,一维索引的数字就增加1。因此旋转90°,索引数字为12-4x+y。
同样的:
旋转180°索引数字为15-4y-x。
旋转270°,索引数字为3-y+4x。
def rotate(px, py, r):
if r == 0:
return py*4+px
elif r == 1:
return 12+py-4*px
elif r == 2:
return 15-4*py-px
elif r == 3:
return 3-py+4*px
3.界面
在本节中,我将构建一个12*18字段。我会用9来表示边缘。
# Field
fieldwidth = 12
fieldheight = 18
pField = [0 for i in range(fieldwidth*fieldheight)]
# print(len(pField))
for x in range(fieldwidth):
for y in range(fieldheight):
# print(y*fieldwidth+x)
pField[y*fieldwidth+x] = 9 if (x == 0 or x == fieldwidth-1 or y == fieldheight-1) else 0
在这个项目中,我使用pygame来实现输出。
1 import pygame
2 # game window
3 pygame.init()
4 window_width = 480
5 window_height = 720
6 window_title = "Tetris"
7
8 screen = pygame.display.set_mode((window_width, window_height))
9 pygame.display.set_caption(window_title)
10
11 running = True
12 while running:
13 for event in pygame.event.get():
14 if event.type == pygame.QUIT:
15 running = False
16
17 # fill in black background
18 screen.fill((0, 0, 0))
19 # implement the game interface and blocks here
20 for x in range(fieldwidth):
21 for y in range(fieldheight):
22 # print(y*fieldwidth+x)
23 if pField[y*fieldwidth+x] == 9:
24 pygame.draw.rect(screen, (255,255,255), (x*40, y*40, 40,
40))
25 else:
26 ...
27 pygame.display.update()
28 pygame.quit()
输出结果如下图:
4.游戏循环
游戏循环是游戏引擎中最重要的部分。这些都是一切发生的顺序。像俄罗斯方块这样的简单游戏并不是大规模的事件驱动应用程序。它包括一些元素,如时间、用户输入、游戏逻辑的日期,然后将其绘制在屏幕上。它一直这样做,直到游戏结束或用户退出为止。
4.1判断位置是否正确
此函数有四个参数:
nTetromino:它代表块的类型
nrotation:表示旋转角度
positionX:表示四阶块的水平坐标
positionY:表示四阶块的垂直坐标
# judge if the position of the piece fit the rule
def doesPieceFit(nTetromino, nrotation, positionX, positionY):
for px in range(4):
for py in range(4):
# get the index
pi = rotate(px, py, nrotation)
# print(pi)
# exit(0)
#get index into field
fi = int((positionY+py)*12+positionX+px)
# print(fi)
# exit(0)
if (positionX+px >= 0) and (positionX+px <= 11):
if (positionY+py >= 0) and (positionY+py <= 18):
if tetromino[nTetromino][pi] == 'X' and ((pField[fi] == 1) or (pField[fi] == 9)):
return False
return True
4.2输入和相应的操作
俄罗斯方块中有四种类型的输入:左移、右移、下移和旋转在程序中,我使用左、右、下和z键来对应四种不同的操作。
1 for event in pygame.event.get():
2 if event.type == pygame.QUIT:
3 gameover = True
4 elif event.type == pygame.KEYDOWN:
5 if event.key == pygame.K_LEFT:
6 bKey = 1
7 elif event.key == pygame.K_RIGHT:
8 bKey = 2
9 elif event.key == pygame.K_DOWN:
10 bKey = 3
11 elif event.key == pygame.K_z:
12 bKey = 4
13 # Game logic ======================================================
14 if bKey == 1 and doesPieceFit(nCurrentPiece, nCurrentRotation,
nCurrentX-1, nCurrentY):
15 nCurrentX = nCurrentX - 1
16 bKey = 0
17
18 if bKey == 2 and doesPieceFit(nCurrentPiece, nCurrentRotation,
nCurrentX+1, nCurrentY):
19 nCurrentX = nCurrentX + 1
20 bKey = 0
21
22 if bKey == 3 and doesPieceFit(nCurrentPiece, nCurrentRotation,
nCurrentX, nCurrentY+1):
23 nCurrentY = nCurrentY + 1
24 bKey = 0
25
26 if bKey == 4 and doesPieceFit(nCurrentPiece, (nCurrentRotation+1)%4,
nCurrentX, nCurrentY):
27 nCurrentRotation += 1
28 nCurrentRotation %= 4
29 bKey = 0
但是,如果使用上述方法,每轮只能输入一个按钮,所以我使用了一个列表来保存每轮的输入。
# input ===========================================================
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameover = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
# 用户按下了左箭头键
# print("左箭头键被按下")
# bKey = 1
bKey.append(1)
elif event.key == pygame.K_RIGHT:
# 用户按下了右箭头键
# print("右箭头键被按下")
# bKey = 2
bKey.append(2)
elif event.key == pygame.K_DOWN:
# 用户按下了下箭头键
# print("下箭头键被按下")
# bKey = 3
bKey.append(3)
elif event.key == pygame.K_z:
# 用户按下了上箭头键
# print("上箭头键被按下")
# bKey = 4
bKey.append(4)
# Game logic ======================================================
if (1 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX-1, nCurrentY):
nCurrentX = nCurrentX - 1
bKey.remove(1)
# bKey=0
if (2 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX+1, nCurrentY):
nCurrentX = nCurrentX + 1
bKey.remove(2)
# bKey=0
if (3 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY+1):
nCurrentY = nCurrentY + 1
bKey.remove(3)
# bKey=0
if (4 in bKey) and doesPieceFit(nCurrentPiece, (nCurrentRotation+1)%4, nCurrentX, nCurrentY):
nCurrentRotation += 1
nCurrentRotation %= 4
bKey.remove(4)
# bKey=0
4.3物块下降
在俄罗斯方块中,方块会周期性地掉落。下落功能可以通过添加时间判断来容易地实现。
fall_time = 0.5
last_fall_time = time.time()
if current_time - last_fall_time >= fall_time:
if doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX,nCurrentY + 1):
nCurrentY += 1
last_fall_time = current_time
当然,我们还需要增加一个判断功能,看看方块是否可以继续下降。如果它不能下降,我们需要完成一系列的事情。
首先,我们需要将当前方块锁定到字段中。然后,检查我们是否有线路。最后,我们选择下一块,并检查它是否合适。
else:
2 # lock the current piece into the field.
3 for px in range(4):
4 for py in range(4):
5 if tetromino[nCurrentPiece][rotate(px, py,
nCurrentRotation)] == ’X’:
6 # print(len(pField))
7 #
print(int((nCurrentY+py)*fieldwidth+nCurrentX+px))
8 pField[int((nCurrentY+py)*fieldwidth+nCurrentX+px)]= 1
9 # check if we get any lines.
10 for py in range(4):
11 if nCurrentY+py<fieldheight-1:
12 bline = True
13 for px in range(1, fieldwidth):
14 if pField[(nCurrentY+py)*fieldwidth+px] == 0:
15 bline = False
16 if bline:
17 # remove the line
18 for px in range(1, fieldwidth-1):
19 pField[(nCurrentY + py) * fieldwidth + px] = 8
20 vline.append(nCurrentY+py)
21 # Finally, we choose next piece
22 nCurrentX = fieldwidth//2
23 nCurrentY = 0
24 nCurrentRotation = 0
25 nCurrentPiece = random.randint(0, 6)
26 # check if piece fits.
27 gameover = not doesPieceFit(nCurrentPiece, nCurrentRotation,nCurrentX, nCurrentY)
1 if len(vline) != 0:
2 pField_copy = copy.deepcopy(pField)
3 for dy in vline:
4 for dx in range(1, fieldwidth-1):
5 pField[dy*fieldwidth+dx] = 0
6 for dy in range(0, vline[0]):
7 for dx in range(1, fieldwidth-1):
8 pField[(dy + len(vline)) * fieldwidth + dx] = 0
9 for dy in range(0, vline[0]):
10 for dx in range(1, fieldwidth-1):
11 if pField_copy[dy*fieldwidth+dx] == 1:
12 pField[(dy+len(vline))*fieldwidth+dx] = 1
13 vline = []
5.附加功能:增加难度和分数显示
很容易可以通过以下代码实现:
1 nPieceCount = 1
2 nscore = 0
3
4 for py in range(4):
5 if nCurrentY+py<fieldheight-1:
6 bline = True
7 for px in range(1, fieldwidth):
8 if pField[(nCurrentY+py)*fieldwidth+px] == 0:
9 bline = False
10 if bline:
11 # remove the line
12 for px in range(1, fieldwidth-1):
13 pField[(nCurrentY + py) * fieldwidth + px] = 8
14 vline.append(nCurrentY+py)
15 # Finally, we choose next piece
16 nCurrentX = fieldwidth//2
17 nCurrentY = 0
18 nCurrentRotation = 0
19 nCurrentPiece = random.randint(0, 6)
20 nPieceCount += 1
21 nscore += 100
22
23 font = pygame.font.Font(None, 36)
24 score_text = font.render("Score: " + str(nscore), True, (255, 135,207))
25 screen.blit(score_text, (10, 10))
全部的代码如下:
import random
import copy
import pygame
import time
# creating assets
tetromino = []
object1 = "..X." \
"..X." \
"..X." \
"..X."
object2 = "..X." \
".XX." \
".X.." \
"...."
object3 = ".X.." \
".XX." \
"..X." \
"...."
object4 = "...." \
".XX." \
".XX." \
"...."
object5 = "..X." \
".XX." \
"..X." \
"...."
object6 = "...." \
".XX." \
"..X." \
"..X."
object7 = "...." \
".XX." \
".X.." \
".X.."
tetromino.append(object1)
tetromino.append(object2)
tetromino.append(object3)
tetromino.append(object4)
tetromino.append(object5)
tetromino.append(object6)
tetromino.append(object7)
# print(tetromino[1])
#
# exit(0)
# rotate
def rotate(px, py, r):
if r == 0:
return py*4+px
elif r == 1:
return 12+py-4*px
elif r == 2:
return 15-4*py-px
elif r == 3:
return 3-py+4*px
# Field
fieldwidth = 12
fieldheight = 18
pField = [0 for i in range(fieldwidth*fieldheight)]
# print(len(pField))
for x in range(fieldwidth):
for y in range(fieldheight):
# print(y*fieldwidth+x)
pField[y*fieldwidth+x] = 9 if (x == 0 or x == fieldwidth-1 or y == fieldheight-1) else 0
# print(pField)
# game window
pygame.init()
window_width = 480
window_height = 720
window_title = "Tetris"
screen = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption(window_title)
# judge if the position of the piece fit the rule
def doesPieceFit(nTetromino, nrotation, positionX, positionY):
for px in range(4):
for py in range(4):
# get the index
pi = rotate(px, py, nrotation)
# print(pi)
# exit(0)
#get index into field
fi = int((positionY+py)*12+positionX+px)
# print(fi)
# exit(0)
if (positionX+px >= 0) and (positionX+px <= 11):
if (positionY+py >= 0) and (positionY+py <= 18):
if tetromino[nTetromino][pi] == 'X' and ((pField[fi] == 1) or (pField[fi] == 9)):
return False
return True
# Game logic stuff======================================================
gameover = False
# which piece is falling
nCurrentPiece = random.randint(0, 6)
# is it rotate
nCurrentRotation = 0
nCurrentX = fieldwidth/2
nCurrentY = 0
bKey = []
clock = pygame.time.Clock()
fps = 60
wait_time = 100
fall_time = 0.5
last_fall_time = time.time() # 初始化最后一次下降时间点
vline = []
nPieceCount = 1
nscore = 0
# Render output ===================================================
while not gameover:
# game timing =====================================================
clock.tick(fps)
current_time = time.time() # 当前时间
# input ===========================================================
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameover = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
# 用户按下了左箭头键
# print("左箭头键被按下")
# bKey = 1
bKey.append(1)
elif event.key == pygame.K_RIGHT:
# 用户按下了右箭头键
# print("右箭头键被按下")
# bKey = 2
bKey.append(2)
elif event.key == pygame.K_DOWN:
# 用户按下了下箭头键
# print("下箭头键被按下")
# bKey = 3
bKey.append(3)
elif event.key == pygame.K_z:
# 用户按下了上箭头键
# print("上箭头键被按下")
# bKey = 4
bKey.append(4)
# Game logic ======================================================
if (1 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX-1, nCurrentY):
nCurrentX = nCurrentX - 1
bKey.remove(1)
# bKey=0
if (2 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX+1, nCurrentY):
nCurrentX = nCurrentX + 1
bKey.remove(2)
# bKey=0
if (3 in bKey) and doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY+1):
nCurrentY = nCurrentY + 1
bKey.remove(3)
# bKey=0
if (4 in bKey) and doesPieceFit(nCurrentPiece, (nCurrentRotation+1)%4, nCurrentX, nCurrentY):
nCurrentRotation += 1
nCurrentRotation %= 4
bKey.remove(4)
# bKey=0
if current_time - last_fall_time >= fall_time:
# 执行方块的下降操作
if doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY + 1):
nCurrentY += 1
last_fall_time = current_time # 更新最后一次下降时间点
else:
# lock the current piece into the field.
for px in range(4):
for py in range(4):
if tetromino[nCurrentPiece][rotate(px, py, nCurrentRotation)] == 'X':
# print(len(pField))
# print(int((nCurrentY+py)*fieldwidth+nCurrentX+px))
pField[int((nCurrentY+py)*fieldwidth+nCurrentX+px)] = 1
# check if we get any lines.
for py in range(4):
if nCurrentY+py<fieldheight-1:
bline = True
for px in range(1, fieldwidth):
if pField[(nCurrentY+py)*fieldwidth+px] == 0:
bline = False
if bline:
# remove the line
for px in range(1, fieldwidth-1):
pField[(nCurrentY + py) * fieldwidth + px] = 8
vline.append(nCurrentY+py)
# Finally, we choose next piece
nCurrentX = fieldwidth//2
nCurrentY = 0
nCurrentRotation = 0
nCurrentPiece = random.randint(0, 6)
nPieceCount += 1
nscore += 100
# check if piece fits.
gameover = not doesPieceFit(nCurrentPiece, nCurrentRotation, nCurrentX, nCurrentY)
# Fill in black background
screen.fill((0, 0, 0))
# Draw field pygame.draw.rect(screen, (255, 0, 0), (block_x, block_y, block_size, block_size))
# pygame.draw.rect(screen, (255, 0, 0), (0, 0, 10, 10))
for x in range(fieldwidth):
for y in range(fieldheight):
# print(y*fieldwidth+x)
if pField[y*fieldwidth+x] == 9:
pygame.draw.rect(screen, (255, 255, 179), (x*40, y*40, 40, 40))
if pField[y*fieldwidth+x] == 1:
pygame.draw.rect(screen, (255, 255, 255), (x*40, y*40, 40, 40))
if pField[y*fieldwidth+x] == 8:
pygame.draw.rect(screen, (255, 209, 224), (x*40, y*40, 40, 40))
# draw current piece
else:
for px in range(4):
for py in range(4):
if tetromino[nCurrentPiece][rotate(px, py, nCurrentRotation)] == 'X':
pygame.draw.rect(screen, (255, 255, 255), ((nCurrentX+px) * 40, (nCurrentY+py) * 40, 40, 40))
if len(vline) != 0:
pField_copy = copy.deepcopy(pField)
nscore += 114514*len(vline)
for dy in vline:
for dx in range(1, fieldwidth-1):
pField[dy*fieldwidth+dx] = 0
for dy in range(0, vline[0]):
for dx in range(1, fieldwidth-1):
pField[(dy + len(vline)) * fieldwidth + dx] = 0
for dy in range(0, vline[0]):
for dx in range(1, fieldwidth-1):
if pField_copy[dy*fieldwidth+dx] == 1:
pField[(dy+len(vline))*fieldwidth+dx] = 1
vline = []
if (nPieceCount%10 == 0) and fall_time>0.2:
fall_time -= 0.01
font = pygame.font.Font(None, 36) # 创建字体对象
score_text = font.render("Score: " + str(nscore), True, (255, 135, 207)) # 渲染分数文本
screen.blit(score_text, (10, 10)) # 在屏幕上绘制分数文本的位置
# pygame.time.delay(wait_time)
pygame.display.update()
pygame.quit()
# print(tetromino)
运行视频:
...
github链接: