[pygame] 테트로미노 - 테트리스 클론 게임 -2

서희찬·2021년 4월 19일
0
post-thumbnail

이제 ! 나머지 함수들을 정의해주자

makeTextObjs() - 텍스트를 만드는 단축 함수

def makeTextObjs(text,font,color):
    surf = font.render(text,True,color)
    return surf, surf.get_rect()

파라미터로 객체를 받아 render method를 호출 한 후 Surface와 Rect객체를 반환한다.
이를 통해 필요할때마다 객체를 생성해야하는 수고를 덜 수 있다 !

terminate() 함수

def terminate():
    pygame.quit()
    sys.exit()

checkForKeyPress()에서 키 눌림 이벤트가 발생했는지 기다리기

def checkForKeyPress():
    # KeyUp ㅇㅣ벤트가 발생했는지 이벤트 큐를 찾는다.
    # 키다운 이벤트를 찾아서 이벤트 큐에서 제거한다.
    checkForQuit()

    for event in pygame.event.get([KEYDOWN,KEYUP]):
        if event.type == KEYDOWN:
            continue
        return event.key
    return None 

만약 QUIT이벤트가 없으면 이벤트 큐에서 모든 KEYUP/DOWN 이벤트를 가져온다.
그리고 !
KEYDOWN 이벤트는 모두 무시한다.
KEYUP 이벤트가 없으면 함수는 None을 반환한다.

showTextScreen() - 보통의 텍스트 스크린 함수

def showTextScreen(text):
    # 이 함수는 플레이어가 키를 누를때 까지 화면 중간에 커다란 글씨를 그려서 보여준다.

    # 텍스트의 그림자 효과를 그린다.
    titleSurf, titleRect = makeTextObjs(text,BIGFONT,TEXTSHADOWCOLOR)
    titleRect.center = (int(WINDOWWIDTH/2),int(WINDOWHEIGHT/2))
    DISPLAYSURF.blit(titleSurf,titleRect)

    # drawing text
    titleSurf,titleRect = makeTextObjs(text,BIGFONT,TEXTCOLOR)
    titleRect.center = (int(WINDOWWIDTH/2)-3,int(WINDOWHEIGHT/2)-3)
    DISPLAYSURF.blit(titleSurf,titleRect)

    # drawing "Press a Key to Play"
    pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play',BASICFONT,TEXTCOLOR)
    pressKeyRect.center = (int(WINDOWWIDTH/2),int(WINDOWHEIGHT)+100)
    DISPLAYSURF.blit(pressKeySurf,pressKeyRect)

글자를 그려준다!
그냥 Surf,Rect를 makeTextObjs함수로 부터 받아서 그려준다.

    while checkForKeyPress() == None:
        pygame.display.update()
        FPSCLOCK.tick()

플레이어가 키를 누를때까지 화면을 업데이트 한다.

checkForQuit() 함수

def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event) 

간단한 함수이므로 설명은 생략하겠다.

calculateLevelAndFallFreq() 함수

def calculateLevelAndFallFreq(score):
    #점수에 따라 플레이어의 레벨을 계산해서 반환한다.
    #그리고 피스가 얼마나 빨리 떨어져야하는지 계산한다.
    level = int(score/10) + 1
    fallFreq = 0.27 - (level * 0.02)
    return level,fallFreq

중요한 함수이다!
바로 플레이어의 점수에따라 레벨을 올리고 떨어지는 속도를 조정해주는 함수이기때문이다.
int는 반내림 하므로 int(score/10)+1을 해준것이다.
이렇게 하지 않는다면 첫 레벨은 0 이 되게 된다.

그리고 레벨에따라 속도를 fallFreq를 줄여준다!
그런데... 레벨이 14가 되면 떨어지는 속도가 음수이게된다.
이때 버그가 발생하지 않고 현재의 시간이 fallFreq보다 큰지 확인하고 이를 통해 그 직전 양수의 값에서 더빨라지지않는 상태에서 레벨이 올라간다!
즉 속도가 더 이상 빨라지지않는다!

getNewPiece() 함수로 새 피스 만들기

def getNewPiece():
    # 무작위로 회전시키고 무작위 색깔을 가진 새 피스를 반환한다.
    shape = random.choice(list(PIECES.keys()))
    newPiece ={
        'shape':shape,
        'rotation':random.randint(0,len(PIECES[shape])-1),
        'x':int(BOARDWIDTH/2) - int(TEMPLATEWIDTH/2),
        'y':-2, # 보드의 위쪽에서 시작한다(좌표는 0 보다 작다)
        'color':random.randint(0,len(COLORS)-1)
    }
    return newPiece

무작위로 피스를 하나 만든다.
그리고 딕셔너리를 만들어서 newPiece를 만든후 반환한다.

보드 데이터 구조에 피스 추가하기

def addToBoard(board,piece):
    # 피스의 위치, 형태, 회전 여부에 따라 보드를 채운다.
    for x in range(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            if PIECES[piece['shape']][piecep['rotation']][y][x] != BLANK:
                board[x+piece['x']][y+piece['y']] = piece['color']

이전의 피스가 착지한 보드 공간을 계속 기록해 나가는 데이터 이다.
피스의 데이터 구조를 받아서 board 데이터 구조에 상자들을 추가한다.
피스가 바닥이나 다른 피스 위에 완전히 착지하고 나면 데이터 구조를 갱신한다.

중첩for문으로 피스 데이터 구조의 모든 공간을 살펴본 다음, 상자를 찾으면 이를 보드에 추가한다.

새 보드 데이터 구조 만들기

def getBlankBoard():
    #비어 있는 새 보드 데이터 구조를 만들어 반환한다.
    board = []
    for i in range(BOARDWIDTH):
        board.append([BLANK]*BOARDHEIGHT)
    return board 

보드에서 사용하는 데이터 구조는 값을 가지고 있는 리스트의 리스트 이다.

isOnBoard(), isValidPosition() 함수

def isOnBoard(x,y):
    return x>=0 and < BOARDWIDTH and y<BOARDHEIGHT

매우 간단한 함수이다.
파라미터로 넘겨준 x,y좌표가 보드에 있는지 검사한다.

def isValidPosition(board,piece,adjX=0,adjY=0):
    #피스가 보드에 있고 충동하지 않았으면 True 를 반환한다.
    for x in ragne(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            isAboveBoard = y + piece['y'] + adjY<0
            if isAboveBoard or PIECES[piece['shape']][piece['rotation']][y][x] == BLANK:
                continue

board,piece 데이터 구조를 받앙서 피스를 구성하는 모든 상자들이 보드에 위치하고 보드의 다른 상자와 겹치지 않으면 True를 반환한다. 피스의 X,Y좌표를 받아서 피스의 데이터 구조 내에서 좌표를 더해 보고 결정한다.

피스의 좌표계를 피스의 왼쪽 상단좌표를 더해서 보드 좌표계로 변환하여 피스의 각 상자들이 보드에서 어떤 좌표에 위치하는지 알 수 있게 된다.

이제 이 피스가 다른 상자들과 겹치는지 알아보기 위해 for문을 돌린다

            if not isOnBoard(x+piece['x']+adjX,y+piece['y']+adjY):
                return False
            if board[x+piece['x']+adjX][y+piece['y']+adjY] != BLANK:
                retrun False
    return True 

for문을 다 돌고나서 False가 될 이유가 없다면 True를 반환한다.

완전한 한 줄이 되었는지 검사해서 없애기

def isCompleteLine(board,y):
    #상자로 채워진 줄에 빈칸이 없으면 True를 반환한다.
    for x in range(BOARDWIDTH):
        if board[x][y] == BLANK:
            return False 
    return True 

y파라미터로 지정한 가로 행에 대한 단순한 검사를 진행하여 모든 상자가 채워졌을때True 아니면 False를 반환한다.

def removeCompleteLines(board):
    #보드에서 완전하게 완성된 줄을 제거한다. 그리고 다른 줄을 모두 아래로 내리고 몇 개의 행이 완성되었는지 반환한다.
    numLinesRemoved = 0
    y = BOARDHEIGHT - 1 #보드 맨 아랫부분의 y부터 시작한다.
    while y >=0:

넘겨준 보드 데이터 구조에서 완성된 줄을 모두 찾아낸 다음 그 줄을 없애고 그 줄 위에 있었던 상자들을 다시 아래로 한 칸씩 내린다.
그리고 몇개의 라인이 없어졌는지 반환하여 score에 더해준다.

while문을 통하여 검사한다!

        if isCompleteLine(board,y):
            #행을 한 줄 없애고 상자를 모두 한 줄 아래로 내린다.
            for pullDownY in range(y,0,-1):
                for x in range(BOARDWIDTH):
                    board[x][pullDownY] = board[x][pullDownY-1]
            #맨 위의 줄을 빈칸으로 설정한다.
            for x in range(BOARDWIDTH):
                board[x][0] = BLANK
            numLinesRemoved +=1
            #반복문에서 처음에 y는 그대로인데 한 칸 아래로 내린 줄 또한 완성된 줄이라면 그 줄도 없애야한다
        else :
            y -=1 #한 줄씩 위로 올라가면서 검사한다.
    return numLinesRemoved

y로 넘겨준 특정 행이 완성됐다면 트루를 반환한다.
그러면 지워준 위의 다른 줄들을 모두 복사해서 한 칸씩 아래로 내려줘야한다.
이렇게 한줄 위의 줄을 아래로 복사하며 지우다가 제일 위의 줄을 한칸 아래로 복사한 후 이 줄 위의 빈칸으로 채운다!

이렇게 하는 이유는 2개의 완성된 줄이 있으면 두 번째 줄도 한 칸 내려온 다음 없애야 하기 때문이다!

게시판 좌표계를 픽셀 좌표계로 전환하기

def convertToPixelCoords(boxx,boxy):
    #주어진 보드 좌표계를 픽셀 좌표계로 변환한다.
    return(XMARGIN +(boxx*BOXSIZE)),(TOPMARGIN+(boxy*BOXSIZE))

보드나 화면 어딘가에 상자 그리기

def drawBox(boxx,boxy,color,pixelx=None,pixely=None):
    # 보드 좌표계 x,y 좌표에 상자 하나를 그린다 (테트로미노는 4개의 상자로 구성된다)
    #만약 pixelx,pixely 값을 주면 픽셀 좌표계에 상자를 그린다
    if color == BLANK:
        return
    if pixelx == None and pixely == None :
        pixelx,pixely = convertToPixelCoords(boxx,boxy)
    pygame.draw.rect(DISPLAYSURF,COLORS[color],(pixelx+1,pixely+1,BOXSIZE-1,BOXSIZE-1))
    pygame.draw.rect(DISPLAYSURF,LIGHTCOLORS[color],(pixelx+1,pixely+1,BOXSIZE - 4,BOXSIZE-4)) 

스크린에 모두 그리기

def drawBoard(board):
    #보드 주면에 테두리를 그린다.
    pygame.draw.rect(DISPLAYSURF,BORDERCOLOR,(XMARGIN-3,TOPMARGIN-7,(BOARDWIDTH*BOXSIZE)+8,(BOARDHEIGHT*BOXSIZE)+8),5)

    # 보드의 배경색을 채운다.
    pygame.draw.rect(DISPLAYSURF,BGCOLOR,(XMARGIN,TOPMARGIN,BOXSIZE*BOARDWIDTH,BOXSIZE*BOARDHEIGHT))
    #보드의 각 상자를 그린다.
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            drawBoard(x,y,board[x][y])

점수와 현재 레벨 텍스트 그리기

def drawStatus(score,level):
    #스코어 텍스트를 그린다.
    scoreSurf = BASICFONT.render("Score : %s" %score,True,TEXTCOLOR)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (WINDOWWIDTH - 150, 20)
    DISPLAYSURF.blit(scoreSurf,scoreRect)

    #draw level text 
    levelSurf = BASICFONT.render("Level : %s" %level,True,TEXTCOLOR)
    levelRect = levelSurf.get_rect()
    levelRect.topleft = (WINDOWWIDTH - 150,50)
    DISPLAYSURF.blit(levelSurf,levelRect)

그냥 그린다.

보드나 화면 어딘가에 피스 그리기

def drawPiece(piece,pixelx=None,pixely=None):
    shapeToDraw = PIECES[piece['shape']][piece['rotation']]
    if pixelx == None and pixely == None :
        #특정값으로 지정되지 않았으면 피스 데이터 구조에 저장된 위치를 사용한다.
        pixelx,pixely = convertToPixelCoords(piece['x'],piece['y'])

    # 피스를 구성하는 각 상자들을 그린다.
    for x in range(TEMPLATEWIDTH):
        for y in range(TEMPLATEHEIGHT):
            if shapeToDraw[y][x] != BLANK:
                drawBox(None,None,piece['color'],pixelx+(x*BOXSIZE),pixely+(y*BOXSIZE))

떨어지는 피스나 다음에 나올 피스를 그릴때 사용한다. 피스데이터 구조는 모든~것을가지고 있으므로 피스 데이터 구조만 가져오면된다.

'다음' 피스 그리기

def drawNextPiece(piece):
    nextSurf = BASICFONT.render("NEXT : ",True,TEXTCOLOR)
    nextRect = nextSurf.get_rect()
    nextRect.topleft = (WINDOWWIDTH-120,80)
    DISPLAYSURF.blit(nextSurf,nextRect)
    drawPiece(piece,pixelx=WINDOWWIDTH - 120, pixely=100)

if __name__ == '__main__':
    main()

이제 다음 포스트에서 전체코드와 플레이를 보여주겠다.

profile
부족한 실력을 엉덩이 힘으로 채워나가는 개발자 서희찬입니다 :)

0개의 댓글