자, 클론할 게임 중 제일 인기있고 프로그래밍 게임개발의 기본이라고 할 수 있는 테트리스를 클론해볼 시간이다 .
따로 테트리스에 대해서 설명해주지 않아도 다들 알지 않는가!
코드가 길어질예정이니 미리 용어를 정리하고 시작하자
- Board : 10*20 공간으로 되어있고 블록이 떨어져서 채우는 공간이다.
- Box : 보드의 정사각형 하나의 단위를 말한다.
- Piece : 화면 위에서 떨어지며 플레이어가 이 피스를 회전시키거나 위치를 잡을 수 있다.
각 피스는 형태가달고 4개의 상자로 구성된다.- shape : T,S,Z,J,L,I,O 형태가 있다.
- template : 형태 데이터 구조의 리스트이며, 형태를 회전 시켰을 때 만들어질 수 있는 모든 모양을 나타낸다.
S_SHAPE_TEMPLATE 같은 변수에 저장한다.- Landed : 착지~ 하면 다음 피스가 내려온다.
시작하즈앙...
import random, time, pygame, sys
from pygame.locals import *
FPS = 25
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
BOXSIZE = 20
BOARDWIDTH = 10
BOARDHEIGHT = 20
BLANK = '.'
BLANK 상수는 보드 데이터 구조에서 빈칸을 나타낼 떄 사용한다.
MOVESIDEWAYSFREQ = 0.15
MOVEDOWNFREQ = 0.1
플레이어가 한번씩 키를 누르는것이 아니라 계속해서 키를 누르는경우 MOVESIDEWAYSFREQ 값을 설정하여 왼쪽 화살표 키나 오른쪽 화살표키를 계속 누르고 있는 경우 한 번씩 키를 누른 것처럼 한 칸씩 옆으로 바로 이동하도록 한다.
하나는 옆 하나는 아래이당~
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2)
TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5
코드그대로당..
이렇당
# R G B
WHITE = (255, 255, 255)
GRAY = (185, 185, 185)
BLACK = ( 0, 0, 0)
RED = (155, 0, 0)
LIGHTRED = (175, 20, 20)
GREEN = ( 0, 155, 0)
LIGHTGREEN = ( 20, 175, 20)
BLUE = ( 0, 0, 155)
LIGHTBLUE = ( 20, 20, 175)
YELLOW = (155, 155, 0)
LIGHTYELLOW = (175, 175, 20)
BORDERCOLOR = BLUE
BGCOLOR = BLACK
TEXTCOLOR = WHITE
TEXTSHADOWCOLOR = GRAY
COLORS = ( BLUE, GREEN, RED, YELLOW)
LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW)
assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color
각 피스의 색과 하이라이트도 정의 해주었다.
4가지 색은 COLORS 튜플에 저장하고 밝은 색은 LIGHTCOLORS 에 저장한다.
TEMPLATEWIDTH = 5
TEMPLATEHEIGHT = 5
S_SHAPE_TEMPLATE = [['.....',
'.....',
'..OO.',
'.OO..',
'.....'],
['.....',
'..O..',
'..OO.',
'...O.',
'.....']]
Z_SHAPE_TEMPLATE = [['.....',
'.....',
'.OO..',
'..OO.',
'.....'],
['.....',
'..O..',
'.OO..',
'.O...',
'.....']]
I_SHAPE_TEMPLATE = [['..O..',
'..O..',
'..O..',
'..O..',
'.....'],
['.....',
'.....',
'OOOO.',
'.....',
'.....']]
O_SHAPE_TEMPLATE = [['.....',
'.....',
'.OO..',
'.OO..',
'.....']]
J_SHAPE_TEMPLATE = [['.....',
'.O...',
'.OOO.',
'.....',
'.....'],
['.....',
'..OO.',
'..O..',
'..O..',
'.....'],
['.....',
'.....',
'.OOO.',
'...O.',
'.....'],
['.....',
'..O..',
'..O..',
'.OO..',
'.....']]
L_SHAPE_TEMPLATE = [['.....',
'...O.',
'.OOO.',
'.....',
'.....'],
['.....',
'..O..',
'..O..',
'..OO.',
'.....'],
['.....',
'.....',
'.OOO.',
'.O...',
'.....'],
['.....',
'.OO..',
'..O..',
'..O..',
'.....']]
T_SHAPE_TEMPLATE = [['.....',
'..O..',
'.OOO.',
'.....',
'.....'],
['.....',
'..O..',
'..OO.',
'..O..',
'.....'],
['.....',
'.....',
'.OOO.',
'..O..',
'.....'],
['.....',
'..O..',
'.OO..',
'..O..',
'.....']]
.은 빈칸을 나타내고 o는 상자를 나태낸다.
PIECES = {'S': S_SHAPE_TEMPLATE,
'Z': Z_SHAPE_TEMPLATE,
'J': J_SHAPE_TEMPLATE,
'L': L_SHAPE_TEMPLATE,
'I': I_SHAPE_TEMPLATE,
'O': O_SHAPE_TEMPLATE,
'T': T_SHAPE_TEMPLATE}
PIECES 변수는 모든 템플리트를 저장하는 딕셔너리이다.
각 템플리트는 하나의 형태가 회전하면서 가질 수 있는 모든 모양을 가지고 있다!
그러므로 PIECES 변수는 테트로미노가 사용할 수 있는 모든 형태와 회전한 모양을 가지게 된다.
따라서 이 게임이 가지는 모든 형태에 대한 데이터 구조가된다.
def main():
global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT))
BASICFONT = pygame.font.Font('freesansbold.ttf',18)
BIGFONT = pygame.font.Font('freesansbold.ttf',100)
pygame.display.set_caption("Chans Tetromino")
showTextScreen('Tetromino')
기본적인 설정을 해준다.
while True: #game loop
if random.randint(0,1) == 0:
pygame.mixer.music.load('tetrisb.mid')
else:
pygame.mixer.music.load('tetrisc.mid')
pygame.mixer.music.load(-1,0.0)
runGame()
pygame.mixer.music.stop()
showTextScreen('Game Over')
실제 게임을 수행하는 코드는 runGame()에 있다.
main() 함수는 무작위로 노래 틀어주고 게임이 끝나면 게임오버~ 뜨게해준당
def runGame():
#게임 시작 부분에서 변수 설정
board = getBlankBoard()
lastMoveDownTime = time.time()
lastMoveSidewaysTime = time.time()
lastFallTime = time.time()
movingDown = False
movingLeft = False
movingRight = False
score = 0
level, fallFreq = calculateLevelAndFallFreq(score)
fallingPiece = getNewPiece()
nextPiece = getNewPiece()
시작전에 모두 초기화한다.
FallingPiece 는 현재 떨어지고 있는 플레이어가 회전시킬 수 있는 피스로 설정한다.
NextPiece 는 다음번에 떨어 뜨릴 피스이다.
while True: #game loop
if fallingPiece == None:
#떨어지고 있는 피스가 없으면 새 피스가 위에서 떨어지도록 한다.
fallingPiece = nextPiece
nextPiece = getNewPiece()
lastFallTime = time.time #reset lastFallTime
if not isValidPosition(board,fallingPiece):
return # end game
checkForQuit()
for event in pygame.event.get(): #Event loop
if event.tpye == KEYUP:
이벤트 처리 루프는 플레이어가 떨어지는 피스를 회전시키거나 피스를 이동하거나 잠시 게임을 멈출 때 발생하는 이벤트를 처리한다.
if(event.key == K_p):
#게임을 잠시 멈춘다.
DISPLAYSURF.fill(BGCOLOR)
pygame.mixer.music.stop()
showTextScreen('Paused') # 키를 누를 때까지 멈춘다.
pygame.mixer.music.play(-1,0.0)
lastFallTime = time.time()
lastMoveDownTime = time.time()
lastMoveSidewaysTime = time.time()
p를 누르면 게임을 잠깐 멈출 수 있다.
이때 보드를 가려야한다!
그렇지 않으면 플레이어가 생각을 할 수 있기 때문이다!! DISPLAYSURF.fill 을 통해 화면을 가리고 음악을 멈춘다.
플레이어가 키를 누르면 showTextScreen은 반환되어 게임을 재개한다.
elif (event.key == K_LEFT or event.key == K_a):
movingLeft = False
elif (event.key == K_RIGHT or event.key == K_d):
movingRight = False
elif (event.key == K_DOWN or event.key == K_s):
movingDown = False
키에서 손을 땠을때 False 값으로 설정한다!
이 의미는 플레이어가 더 이상 그 방향으로 움직이려고 하지 않음을 의미한다.
elif event.type == KEYDOWN:
# moving the piece sideways
if(event.key == K_LEFT or event.key == K_a) and isValidPosition(board,fallingPiece,adjX=-1):
fallingPiece['x'] -= 1
movingLeft = True
movingRight = False
lastMoveSidewaysTime = time.time()
elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1):
fallingPiece['x'] += 1
movingRight = True
movingLeft = False
lastMoveSidewaysTime = time.time()
adjx 에 -1값을 주면 왼쪽으로 한 칸 이동한 위치에서 피스의 위치가 적절한지를 검사한다.
+1이면 오른쪽 y는 위아래에 대해 !
#회전
elif(event.key == K_UP or event.key == K_w):
fallingPiece['rotation'] = (fallingPiece['rotation']+1) % len(PIECES[fallingPiece['shape']])
회전시킨당
if not isValidPosition(board,fallingPiece):
fallingPiece['rotation'] = (fallingPiece['rotation']-1 % len(PIECES[fallingPiece['shape']]))
만약 회전을 못하는 상황이라면 -1을 해서 원래의 모양으로 돌려놔야한다.
elif(event.key == K_q): #반대방향 회전
fallingPiece['rotation'] = (fallingPiece['rotation']-1)%len(PIECES[fallingPiece['shape']])
if not isValidPosition(board,fallingPiece):
fallingPiece['rotation'] = (fallingPiece['rotation']+1)%len(PIECES[fallingPiece['shape']])
q를 누르면 반대 방향으로 돌린다.
#피스를 빨리 떨어뜨린다
elif(event.key == K_DOWN or event.key == K_s):
movingDown = True
if isValidPosition(board,fallingPiece,adjY=1):
fallingPiece['y']+=1
lastMoveDownTime = time.time()
아래로 누르면 빨리 내려간다~
# 현재 피스를 바닥으로 떨어뜨린다.
elif event.key = K_SPACE:
movingDown = False
movingRight = False
movingLeft = False
for i in range(1,BOARDHEIGHT):
if not isValidPosition(board,fallingPiece,adjY=1):
break
fallingPiece['y']+=i-1
플레이어가 스페이스 키를 치면 현재 피스는 바로 아래로 떨어진다.
우선 프로그램은 아래로 착지하기위해 얼마나 많은 공간이 남았는지 확인해야한다.
isValidPosition 이 False를 반환하면 더 이상 아래로 내려갈수 없고 True를 반환하면 피스가 한 칸 아래로 이동할 수 있음을 의미한다.
# 사용자의 입력에 따라 피스 움직이기
if(movingLeft or movingRight) and time.time()-lastMoveSidewaysTime > MOVESIDEWAYSFREQ:
if movingLeft and isValidPosition(board,fallingPiece,adjX=-1):
fallingPiece['x'] -= 1
elif movingRight and isValidPosition(board,fallingPiece,adjX=1):
fallingPiece['x'] += 1
lastMoveSidewaysTime = time.time()
if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1):
fallingPiece['y'] += 1
lastMoveDownTime = time.time()
#떨어질 시간이 되면 피스를 떨어뜨린다.
if time.time() -lastFallTime>fallFreq:
#피스가 착지했는지 검사한다.
if not isValidPosition(board,fallingPiece,adjY=1):
#떨어지는 피스가 칙지했으면 보드에 둔다.
addToBoard(board,fallingPiece)
score += removeCompleteLines(board)
level, fallFreq = calculateLevelAndFallFreq(score)
fallingPiece = None
else :
#피스가 아직 착지하지 않았으면 피스를 아래로 움직인다.
fallingPiece['y'] += 1
lastFallTime = time.time()
#모두 그리쟈
DISPLAYSURF.fill(BGCOLOR)
drawBoard(board)
drawStatus(score,level)
drawNextPiece(nextPiece)
if fallingPiece != None :
drawPiece(fallingPiece)
pygame.display.update()
FPSCLOCK.tick(FPS)
모두 그린다.
이제 남은 함수들은 다음 포스트에서 정의하자 ...