게시판 즐겨찾기
편집
드래그 앤 드롭으로
즐겨찾기 아이콘 위치 수정이 가능합니다.
FastASM으로 제작한 테트리스 전체 소스코드
게시물ID : programmer_16964짧은주소 복사하기
작성자 : 중용자
추천 : 1
조회수 : 1156회
댓글수 : 4개
등록시간 : 2016/04/27 20:30:07
FastASM에 대한 얘기를 간간이 해왔는데 실제 소스코드는 공개한적이 없어서 FastASM으로 제작한 Tetris의 전체 코드를 올립니다.
첫버전이 Class를 지원하지 않은 상태에서 만들어진 이후에 Class와 문법변경에 맞춰 테스트 용도로 계속 변경이 되었기에 그다지 좋다고 보기는 힘들지만 제가 만들어온 FastASM이 어떤 것인지 정도는 파악할 수 있지 않을까 합니다.

아래의 소스에 아이콘등이 포함된 리소스 파일을 합쳐서 컴파일 하면 오유에 예전에 올렸던 테트리스인 10KB 정도의 실행파일이 만들어집니다.
어셈블러, 컴파일러 개발에 관심이 있거나 기술적으로 궁금한 점 있으면 짧게라도 설명드릴테니 편하게 답글 남기기 바랍니다.


FastASMTetris.asm
Compile옵션 및 FastASM의 라이브러리와 전체 코드를 불러오는 일종의 make 역할을 하는 파일입니다.


include "%FastASM%\Init.inc"
!_
  Purpose:  FastASM Tetris
_!

Option WindowsForms
Option Resource On
Option Buffer Memory
;Option Debug On
;Option Error On
;Option DebugProcedure On

!_
  Purpose:  Header
_!
include "%FastASM%\FastASM.inc"
include "frmMain.inc"

!_
  Purpose:  Code
_!
include "%FastASM%\FastASM.asm"
include "frmMain.asm"

Run frmMain

; Buffer Type   File size
;------------------------
; NoBuffer      0000264D
; MemoryBuffer  00002F35
; BitmapBuffer  0000279C

; Compile optimization time
; 1, 0.8 0.7 0.6

frmMain.inc
클래스와 변수들을 정의한 해더파일입니다.
변수들이 전역과 Class가 섞여 있는데 초칭기 FastASM은 클래스를 지원하지 않았었기에 전부 전역으로 변수를 정의했었고 Class 지원을 하면서 Class 테스트를 위한 코드 수정을 하다보니 이런 모양이 됐습니다.

!_
  Purpose:  frmMain
_!

TIMER_ID        = 1     ; 타이머 ID
PADDING_ALL     = 50    ; 상하좌우 여백
BLOCK_SHADOW    = 3     ; 테트리스 블록 테두리 두께

Enum eDoBlock, \    ; 블록 출력 , 이동, 저장 구분 열거자
    Paint, Move, Save, Next
Enum eMove, \       ; 블록 이동 구분 열거자
    Rotate, Left, Right, Down

MATRIX_SPACE    = 7     ; 테트리스 배열 공백
MATRIX_GHOST    = 8     ; 테트리스 배열 고스트
MATRIX_ROWS     = 20    ; 테트리스 배열 높이
MATRIX_COLS     = 10    ; 테트리스 배열 넓이
MATRIX_BLOCK    = 30    ; 테트리스 블록 크기

; 테트리스 배열 좌표
MATRIX_LEFT     = PADDING_ALL
MATRIX_TOP      = PADDING_ALL
MATRIX_RIGHT    = MATRIX_LEFT + MATRIX_COLS * MATRIX_BLOCK
MATRIX_BOTTOM   = MATRIX_TOP + MATRIX_ROWS * MATRIX_BLOCK

BORDER_WIDTH    = 8         ; 테트리스 배열 테두리 두께
COLOR_BORDER    = $667799   ; 테트리스 배열 테두리 

Dim BrushBorder As Long := COLOR_BORDER  ; 테트리스 배열 테두리 브러시

; 테트리스 배열  테두리 좌표
Dim RectMatrix  As RECT :=  MATRIX_LEFT, MATRIX_TOP, MATRIX_RIGHT, MATRIX_BOTTOM
Dim RectBorder  As RECT :=  MATRIX_LEFT - BORDER_WIDTH,     MATRIX_TOP - BORDER_WIDTH, \
                            MATRIX_RIGHT + BORDER_WIDTH,    MATRIX_BOTTOM + BORDER_WIDTH

TETRIS_FONT_SIZE= 30 ; 출력 폰트 크기
SCORE_UNIT      = 10 ; 점수 단위
GOAL_LINE       = 5  ; 레벨당 클리어 라인 시작 개수

; 게임 정보 좌표
INFO_RIGHT      = MATRIX_RIGHT + MATRIX_BLOCK * 5
Dim RectInfo    As RECT := MATRIX_RIGHT + MATRIX_BLOCK, MATRIX_TOP, INFO_RIGHT, MATRIX_BOTTOM

; 0.8초를 시작으로  레벨 12%  속도가 빨라진다. 타이머에서 사용시 4  곱해서 사용한다 .
Dim abLevelSpeed(20) As Byte := 200,176,155,136,120,106,93,82,72,63,56,49,43,38,33,29,26,23,20,18

; 윈도우 클라이언트 크기
CLIENT_WIDTH    = INFO_RIGHT + PADDING_ALL
CLIENT_HEIGHT   = MATRIX_BOTTOM + PADDING_ALL

INIT_BLOCK = 20 ; 초기화면 블록 크기
; 초기화면, TETRIS 글자하나의 크기는 3 * 5
Dim abGameInit(21)(5) As Byte := \
    0,0,0,7,1,1,1,7,0,0,0,7,2,2,7,7,3,7,7,5,5,\
    7,0,7,7,1,7,7,7,7,0,7,7,2,7,2,7,3,7,5,7,7,\
    7,0,7,7,1,1,1,7,7,0,7,7,2,2,7,7,3,7,7,5,7,\
    7,0,7,7,1,7,7,7,7,0,7,7,2,7,2,7,3,7,7,7,5,\
    7,0,7,7,1,1,1,7,7,0,7,7,2,7,2,7,3,7,5,5,7

; 초기화면 그래픽 TETRIS 출력용 좌표
INIT_LEFT   = (CLIENT_WIDTH - (INIT_BLOCK * abGameInit.Cols)) / 2
INIT_TOP    = CLIENT_HEIGHT / 2 - (INIT_BLOCK * abGameInit.Rows)

; 초기화면의 문자열 출력용 좌표
Dim RectInit    As RECT := 0, INIT_TOP-TETRIS_FONT_SIZE*2, CLIENT_WIDTH,INIT_TOP+TETRIS_FONT_SIZE*12

; 블록 종류별 brush, pen        
Dim aBrushBlock(7)  As Long := $EAEA00,$02FBFF,$00CC30,$FF3101,$FF9933,$3366FF,$CC33CC
Dim aBrushBorder(7) As Long := $FDB100,$00B9CE,$239841,$B41F00,$D26900,$0026B4,$8A208A

; 블록 종류별 모양 데이터
Dim awBlock(4)(aBrushBlock.Cols) As Short := \
    0000011001100000b,0000011001100000b,0000011001100000b,0000011001100000b,\   ; O
    0000111100000000b,0010001000100010b,0000111100000000b,0010001000100010b,\   ; I
    0000001101100000b,0010001100010000b,0000001101100000b,0010001100010000b,\   ; S
    0000011000110000b,0001001100100000b,0000011000110000b,0001001100100000b,\   ; Z
    0000011101000000b,0010001000110000b,0001011100000000b,0110001000100000b,\   ; L
    0000011100010000b,0011001000100000b,0100011100000000b,0010001001100000b,\   ; J
    0000011100100000b,0010001100100000b,0010011100000000b,0010011000100000b     ; T


; 파일 크기를 줄이기 위해 문자열을 1바이트 문자열로 정의한다.
Dim sGameOver   As StringA :=   "GAME OVER"
Dim sGamePause  As StringA :=   "PAUSE"
Dim sGameInfo   As StringA :=   "NEXT",13,13,13,13,13,\
                                "LEVEL",13,"%d" ,13,13,\
                                "SCORE",13,"%d" ,13,13,\
                                "GOAL",13,"%d" ,13,13,\
                                "HELP",13,"<F1>"
Dim sGameInit   As StringA :=   "FastASM",13,13,13,13,13,13,13,13,13,13,\
                                "Press enter key to start"
Dim sGameHelpTitle As StringA:= "About"
Dim sGameHelp   As StringA :=   "ENTER",Keys.Tab, "Start",13,\
                                "ESC",Keys.Tab,"Stop" ,13,\
                                "PAUSE",Keys.Tab,"Pause" ,13,\
                                "G",Keys.Tab,"Ghost" ,13,13,\
                                "UP",Keys.Tab,"Rotate" ,13,\
                                "LEFT",Keys.Tab,"Move Left" ,13,\
                                "RIGHT",Keys.Tab,"Move Right" ,13,\
                                "DOWN",Keys.Tab,"Soft Drop" ,13,\
                                "SPACE",Keys.Tab,"Hard Drop" ,13,13,\
                                "http://blog.naver.com/asmpro "

; 블록 드랍 소리
_BLOCK_DROP_INST    = 118
_BLOCK_DROP_NOTE    = 20
_BLOCK_DROP_TIME    = 30

; 레벨업 소리
_BLOCK_LEVEL_INST   = 55
_BLOCK_LEVEL_NOTE   = 50
_BLOCK_LEVEL_TIME   = 1000

;  클리어 소리
_BLOCK_CLEAR_INST   = 127
_BLOCK_CLEAR_NOTE   = 10
_BLOCK_CLEAR_TIME   = 200

; 블록 정보
Class clsBlock
    Dim Type        As Long ; 블록 종류
    Dim NextType    As Long ; 다음 블록 종류
    Dim Rotate      As Long ; 블록 회전 순서
    Dim X           As Long ; 블록 X좌표
    Dim Y           As Long ; 블록 Y좌표
End Class

; 게임 정보
Enum eGame, \
    Init, Run, Pause, Over
Class clsGame
    Dim Level       As Long
    Dim Score       As Long
    Dim GoalLine    As Long
    Dim bStatus     As Byte
End Class

Class frmMain Form
    Dim Block       As clsBlock
    Dim Game        As clsGame
    Dim bGhost      As Byte
    Dim abMatrix(MATRIX_COLS)(MATRIX_ROWS) As Byte  ; 테트리스 배열
   
    Static FontName As String := "Fixedsys"
    Static Name     As String := "FastASMTetris"
    Static Text     As String := "FastASM Tetris"
End Class

frmMain.asm
클래스 함수를 정의한 코드 파일입니다.
초창기 클래스는 함수 상속을 지원하지 않았으나 비용없이 상속이 가능하게 구현한 후 상속받은 함수를 사용하기도 합니다.
코드 부분 또한 클래스를 지원하지 않을 때 제작된 걸 클래스에 맞춰 테스트 용도로 수정한 코드라 객체지향을 제대로 보여주는데는 많이 부족합니다.

!_
  Purpose:  frmMain
_!

Class frmMain
    Sub WindowProc
    Begin
        BEGIN_MSG_MAP
            ON_PAINT
            ON_KEYDOWN
            ON_TIMER
            ON_CREATE
            ON_DESTROY
        END_MSG_MAP
    End Sub

    !_
      Purpose:  생성자
    _!
    Sub Tiny New
    Begin
        Let rMe:WndProc(0)   := frmMain.WindowProc
        Let rMe:psName(0)    := frmMain.Name
        Let rMe:psText(0)    := frmMain.Text

        Let rMe:Style(0)     :=   WS_VISIBLE or \
                                WS_CAPTION or \
                                WS_THICKFRAME or \
                                WS_SYSMENU or \
                                WS_MAXIMIZEBOX or \
                                WS_MINIMIZEBOX or \
                                WS_CLIPCHILDREN or \
                                WS_CLIPSIBLINGS
        Let rMe:BackColor(0)     := Color.Black
        Let rMe:Cursor(0)        := Cursors.Default
        Let rMe:Icon(0)          := IDI_FASTASMTETRIS

        Let  rMe:BitmapFont(0)    := Font.Bitmap.Default
        Let  rMe:BitmapFontSize(0):= 2
        ;Let rMe:psFontName(0)    := frmMain.FontName
        ;Let rMe:FontSize(0)      := TETRIS_FONT_SIZE
        Let rMe:ForeColor(0)     := Color.White
       
        Let rTemp := CW_USEDEFAULT
        Let rMe:Left(0)          := rTemp
        Let rMe:Top(0)           := rTemp

        Let rMe:iClientWidth(0)  := CLIENT_WIDTH
        Let rMe:iClientHeight(0) := CLIENT_HEIGHT

        Let rMe:bBufferMode(0)   := eBufferMode.Fit
        Let rMe:BufferWidth(0)   := CLIENT_WIDTH
        Let rMe:BufferHeight(0)  := CLIENT_HEIGHT
       
        ; Class
        Let rMe:bGhost(0)    := True
    End Sub

    !_
      Purpose:  사용할 자원을 생성한다.
    _!
    Sub frmMain_CREATE
        Dim .rLoop  As Long rbx
    Begin
        ; 미디출력을 준비한다 .
        Midi.Out.Open

if Project.Buffer <> eDoubleBuffer.Memory
        ; brush pen  생성한다 .
        Get BrushBorder(0) := Brush.Solid BrushBorder(0)
        For Each .rLoop In aBrushBlock
            Get aBrushBorder(.rLoop):= Brush.Solid aBrushBorder(.rLoop)
            Get aBrushBlock(.rLoop) := Brush.Solid aBrushBlock(.rLoop)
        Next
end if
    End Sub

    !_
      Purpose:  사용한 자원을 반환하고 종료한다.
    _!
    Sub frmMain_DESTROY
        Dim .rLoop  As Long rbx
    Begin
        ; 미디출력을 종료한다 .
        Midi.Out.Close

if Project.Buffer <> eDoubleBuffer.Memory
        ; 사용한 brush  pen 해제한다.
        Brush.Dispose BrushBorder(0)
        For Each .rLoop In aBrushBlock
            Brush.Dispose   aBrushBorder(.rLoop)
            Brush.Dispose   aBrushBlock(.rLoop)
        Next
end if
        Application.Exit
    End Sub

    !_
      Purpose:  키입력을 처리한다 .
    _!
    Sub frmMain_KEYDOWN, .Key, .KeyData
        Dim .rbKey  As Byte rParam1L
    Begin
        Select .rbKey
            Case Keys.F1        ; 도움말
                MessageBoxA.Show sGameHelp, sGameHelpTitle
            Case Keys.Escape    ; 게임 종료
                If rMe:Game.bStatus(0) = eGame.Init
                    Me OnClose
                    Return
                Else If rMe:Game.bStatus(0) = eGame.Run
                    Timer.Stop TIMER_ID
                End If
                Let rMe:Game.bStatus(0) := eGame.Init
                Me Refresh
            Case Keys.Enter     ; 게임 시작
                Me StartGame
            Case Keys.Pause     ; 게임 정지
                If rMe:Game.bStatus(0) = eGame.Run
                    Let rMe:Game.bStatus(0) := eGame.Pause
                    Timer.Stop TIMER_ID
                Else If rMe:Game.bStatus(0) = eGame.Pause
                    Let rMe:Game.bStatus(0) := eGame.Run
                    Me StartTimer
                End If
                Me Refresh
            Case "G"
                Let rMe:bGhost(0) ^= 1
                Me Refresh
            Case Keys.Up        ; 블록 회전
                Me MoveBlock, eMove.Rotate
            Case Keys.Down      ; 블록 아래
                Me MoveBlock, eMove.Down
            Case Keys.Left      ; 블록 왼쪽
                Me MoveBlock, eMove.Left
            Case Keys.Right     ; 블록 오른쪽
                Me MoveBlock, eMove.Right
            Case Keys.Space     ; 블록 바닥
                If rMe:Game.bStatus(0) = eGame.Run
                    Me DropBlock
                    If rRetL = False Return
                    Me SaveBlock
                End If
        End Select
    End Sub

    !_
      Purpose:  WM_TIMER, 오직 블록을 아래로 내릴 때만 이벤트가 발생한다.
    _!
    Sub frmMain_TIMER, .TimerID
    Begin
        Me MoveBlock, eMove.Down
    End Sub

    !_
      Purpose:  타이머를 실행한다 . 블록이 떨어지는 속도는 레벨당 1 바이트로 저장하기 위해
                밀리세컨드 / 4 단위로 저장되어 있다 . 읽은  4 곱해서 타이머를 실행한다 .
    _!
    Sub StartTimer
    Begin
        Le3 rTemp   := rMe:Game.Level(0) --1; 레벨은 1부터 , 레벨속도 데이터는 0부터 시작하기 때문에
                                            ; 레벨에서 1  뺀후 데이터를 읽는다.
        Let rTemp   <= abLevelSpeed(rTemp)
        Let rTemp   <<= 2
        Timer.Start TIMER_ID, rTemp
    End Sub

    !_
      Purpose:  게임화면을 출력한다.
    _!
    Sub frmMain_PAINT, .e As rbx
    Begin
        ; 배경을 지운다.
        Graphics.Clear .e

        ; 시작화면 또는 실행화면을 출력한다.
        If rMe:Game.bStatus(0) = eGame.Init
            Me PaintMatrix, .e, abGameInit, abGameInit.Cols - 1, abGameInit.Rows - 1, \
                INIT_LEFT, INIT_TOP, INIT_BLOCK
            Graphics.DrawStringA .e, sGameInit, sGameInit.Length, RectInit, DT_CENTER
        Else
            Me PaintGame, .e
        End If
       
        Let rRet := 0
    End Sub

    !_
      Purpose:  실행화면을 출력한다.   
    _!
    Sub PaintGame, .e As rbx
        Dim .rGhost As Long r12
        Dim .sBuffer(sGameInfo.Length + 30) As Char
    Begin
        ; 테트리스 배열 테두리를 그린다.
        Graphics.FillRectangle .e, BrushBorder(0), RectBorder
        Graphics.FillRectangle .e, rMe:hBackColor(0), RectMatrix
       
        ; 테트리스 배열을 그린다 .
        Me PaintMatrix, .e, ByRef rMe:abMatrix(0),frmMain.abMatrix.Cols-1,frmMain.abMatrix.Rows-1,\
            MATRIX_LEFT, MATRIX_TOP, MATRIX_BLOCK

        ; 고스트를 출력한다.
        If rMe:bGhost(0) <> False And rMe:Game.bStatus(0) = eGame.Run
            Let .rGhost := rMe:Block.Y(0)
            Me DropBlock
            Me DoBlock, .e, eDoBlock.Paint, True
            Let rMe:Block.Y(0) := .rGhost
        End If

        Me DoBlock, .e, eDoBlock.Next, False   ; 다음 블록을 출력한다 .
        Me DoBlock, .e, eDoBlock.Paint, False  ; 블록을 출력한다.

        ; 정지 또는 게임오버시 문자열을 출력한다.
        Let rParam2 := 0
        If rMe:Game.bStatus(0) = eGame.Over
            Let rParam2 := sGameOver
            Let rParam3 := sGameOver.Length
        Else If rMe:Game.bStatus(0) = eGame.Pause
            Let rParam2 := sGamePause
            Let rParam3 := sGamePause.Length
        End If
        If rParam2 <> 0
            Graphics.DrawStringA .e, rParam2, rParam3, RectBorder, \
                DT_CENTER or DT_VCENTER or DT_SINGLELINE
        End If

        ; 게임 진행상황을 출력한다.
        String.FormatA ByRef .sBuffer(0), sGameInfo, rMe:Game.Level(0), rMe:Game.Score(0), \
            rMe:Game.GoalLine(0)
        Graphics.DrawStringA .e, ByRef .sBuffer(0), rRet, RectInfo, DT_CENTER
    End Sub

    !_
      Purpose:  게임을 시작한다 .
    _!
    Sub StartGame
    Begin
        If rMe:Game.bStatus(0) <> eGame.Run
            ; 데이터를 초기화한다 .
            Array.Clear rMe:abMatrix, 0, 0, MATRIX_SPACE

            Let rTemp := 0
            Let rMe:Game.Level(0)    := 1
            Let rMe:Game.GoalLine(0) := GOAL_LINE
            Let rMe:Game.Score(0)    := rTemp
            Let rMe:Block.NextType(0):= rTemp
            Let rMe:Game.bStatus(0)   := eGame.Run

            ; 타이머를 실행한다.
            Me StartTimer

            ; 블록을 생성한다.
            Get rMe:Block.NextType(0) := Me CreateBlockType
            Me CreateBlock
        End If
    End Sub

    !_
      Purpose:  배열을 출력한다 .
    _!
    Sub PaintMatrix, .e As r14, .pbMatrix, .Cols, .Rows, .Left, .Top, .Size
        Dim .rIndex As Long rbx
        Dim .rCols  As Long rdi
        Dim .rRows  As Long r13
        Dim .rX     As Long rsi
        Dim .rY     As Long r12
    Begin
        Let .rIndex := .pbMatrix
        Let .rCols  := .Cols
        Let .rRows  := .Rows
        For .rY = 0 To .rRows
            For .rX = 0 To .rCols
                Let rTemp <= byte [.rIndex]
                If rTempL <> MATRIX_SPACE
                    Me PaintSquare, .e, rTemp, .rX, .rY, .Left(0), .Top(0), .Size(0), False
                End If
                Let .rIndex++
            Next
        Next
    End Sub
   
    !_
      Purpose:  블록의 사각형 하나를 출력한다.
    _!
    Sub PaintSquare, .e As r12, .Color, .X, .Y, .Left, .Top, .Size, .IsGhost
        Dim .rLeft  As Long rdi
        Dim .rTop   As Long rsi
        Dim .rColor As Long r13
        Dim .rSize  As Long rbx
    Begin
        If .X >= 0 And .Y >=0
            Let .rColor := .Color
            Let .rSize  := .Size(0)

            Le3 .rLeft  := .X ** .rSize
            Let .rLeft  += .Left(0)
            Le3 .rTop   := .Y ** .rSize
            Let .rTop   += .Top(0)

            Graphics.DrawRectangle .e, aBrushBorder(.rColor), .rLeft, .rTop, \
                ByRef [.rLeft + .rSize], ByRef [.rTop + .rSize]
            Let rTemp := rMe:hBackColor(0)
            If .IsGhost(0) = False Then rTemp := aBrushBlock(.rColor)
            Graphics.DrawRectangle .e, rTemp, ByRef [.rLeft+BLOCK_SHADOW], ByRef [.rTop+BLOCK_SHADOW],\
                ByRef [.rLeft + .rSize - BLOCK_SHADOW], ByRef [.rTop + .rSize - BLOCK_SHADOW]
        End If
    End Sub
   
    !_
      Purpose:  블록 타입을 랜덤하게 생성한다.
    _!
    Sub Tiny CreateBlockType
        Dim .riBlockTypeCount   As Long ecx
        Dim .riBlockType        As Long edx
    Begin
        Random.Int
        Let .riBlockTypeCount   := awBlock.Rows
        div .riBlockTypeCount
        Let rRet    := 0
        Let rRetE   := .riBlockType
    End Sub

    !_
      Purpose:  블록을 생성한다 .
    _!
    Sub CreateBlock
        Dim .rBlockType As Long rcx
    Begin
        ; 같은 블록이 3 연속해서 나오지 않도록 한다.
        Do
            Me CreateBlockType
        Loop Until rRet <> rMe:Block.NextType(0) Or rRet <> rMe:Block.Type(0)
        Let .rBlockType          := rRet
        Let rMe:Block.Type(0)    := rMe:Block.NextType(0)
        Let rMe:Block.NextType(0):= .rBlockType
        Let rMe:Block.Rotate(0)  := 0
        Let rMe:Block.Y(0)       := -1
        Let rMe:Block.X(0)       := (frmMain.abMatrix.Cols - 4) / 2
       
        ; 블록을 생성할 자리가 없으면 게임을 종료한다 .
        Me DoBlock, 0, eDoBlock.Move, False

        If rRetL = False
            Let rMe:Game.bStatus(0) := eGame.Over
            Timer.Stop TIMER_ID
        End If
        Me Refresh
    End Sub
   
    !_
      Purpose:  블록을 출력하거나 이동 가능을 검토하거나 테트리스 배열에 저장한다 .
      Returns:  True- 이동가능, False- 이동불가
    _!
    Sub DoBlock, .e As r13, .Mod, .IsGhost As r15
        Dim .rX             As Long rsi   ; 블록 사각형 X좌표
        Dim .rY             As Long rdi   ; 블록 사각형 Y좌표
        Dim .rBlockType     As Long rcx   ; 블록 타입
        Dim .rBlockTypeL    As Byte cl    ; 블록 타입 ( 테트리스 배열 저장용 1 바이트 변수)
        Dim .rBlockRotate   As Long rdx   ; 블록 회전
        Dim .rwBlock        As Short r14w ; 블록 데이터
        Dim .rBlockCol      As Long r12   ; 블록 모양을 읽어오기 위한 루프
        Dim .rbBlockRow     As Byte bh
        Dim .rbMode         As Byte bl
    Begin       
        Let .rbMode     := CByte .Mod

        If .rbMode = eDoBlock.Next
            Let .rY             := 0
            Let .rBlockType     := rMe:Block.NextType(0)
            Let .rBlockRotate   := 0
        Else
            Let .rY             := rMe:Block.Y(0)
            Let .rBlockType     := rMe:Block.Type(0)
            Let .rBlockRotate   := rMe:Block.Rotate(0)
        End If
        Let .rwBlock := awBlock(.rBlockRotate)(.rBlockType)
       
        For .rbBlockRow = 0 To 3
            Let .rX := rMe:Block.X(0)
            For .rBlockCol = 0 To 3
                Let .rwBlock <<= 1
                If CARRY?
                    Select .rbMode
                        Case eDoBlock.Paint  ; 블록을 출력한다.
                            Me PaintSquare, .e, rMe:Block.Type(0), .rX, .rY, \
                                MATRIX_LEFT, MATRIX_TOP, MATRIX_BLOCK, .IsGhost

                        Case eDoBlock.Next   ; 다음 블록을 출력한다 .
                            ; TODO: 테트리스 타입에 따라 출력하는 위치를 하드코딩으로 조정했다 .
                            ;       블록 모양이 변경되면 수정해야 한다.
                            Let rTemp   := 0
                            Let rTempE  := RectInfo.left(0)
                            If rMe:Block.NextType(0) > 1
                                Let rTempE -= MATRIX_BLOCK / 2
                            End If
                            Me PaintSquare, .e, rMe:Block.NextType(0), .rBlockCol, .rY, \
                                rTemp, MATRIX_TOP + TETRIS_FONT_SIZE, MATRIX_BLOCK, False
                           
                        Case eDoBlock.Move   ; 블록을 이동할  있는지 검토한다.
                            If .rY >= 0
                                If .rX>=frmMain.abMatrix.Cols Or .rX<0 Or .rY>=frmMain.abMatrix.Rows
                                    GoTo .CantMove
                                End If
                                If rMe:abMatrix(.rX)(.rY) <> MATRIX_SPACE GoTo .CantMove
                            End If
                           
                        Case eDoBlock.Save   ; 블록을 테트리스 배열에 저장한다.
                            Let rMe:abMatrix(.rX)(.rY) := .rBlockTypeL
                    End Select 
                End If
                Let .rX++
            Next
            Let .rY++
        Next

        Let rRetL := True
        Return

    .CantMove:
        Let rRetL := False
    End Sub
   
    !_
      Purpose:  블록을 이동시킨다 .
    _!
    Sub MoveBlock, .Direction
        Dim .rbDirection    As Byte bl
        Dim .rBlockX        As Long rdi
        Dim .rBlockY        As Long rsi
        Dim .rBlockRotate   As Long r12
    Begin
        If rMe:Game.bStatus(0) <> eGame.Run Return

        Let .rbDirection    := CByte .Direction
        Let .rBlockX        := rMe:Block.X(0)
        Let .rBlockY        := rMe:Block.Y(0)     
        Let .rBlockRotate   := rMe:Block.Rotate(0)

        Select .rbDirection
            Case eMove.Rotate
                Let rMe:Block.Rotate(0)++
                Let rMe:Block.Rotate(0) %= awBlock.Cols
            Case eMove.Left
                Let rMe:Block.X(0)--
            Case eMove.Right
                Let rMe:Block.X(0)++
            Case eMove.Down
                Let rMe:Block.Y(0)++
        End Select

        Me DoBlock, 0, eDoBlock.Move, False

        If rRetL = False
            Let rMe:Block.X(0)      := .rBlockX
            Let rMe:Block.Y(0)      := .rBlockY
            Let rMe:Block.Rotate(0) := .rBlockRotate

            If .rbDirection = eMove.Down
                Me SaveBlock
            End If
        Else
            Me Refresh
        End If
    End Sub

    !_
      Purpose:  블록을 떨어뜨린다 .
    _!
    Sub DropBlock
    Begin
        ; 블록을 떨어뜨린다 .
        For rMe:Block.Y(0) To frmMain.abMatrix.Rows - 1
            Me DoBlock, 0, eDoBlock.Move, False
            If rRetL = False Exit For
        Next
        Let rMe:Block.Y(0)--

        ; 시작위치부터 블록을 내릴수 없으면 게임을 종료한다.
        Let rRetL := True
        If rMe:Block.Y(0) < 0
            Let rMe:Game.bStatus(0) := eGame.Over
            Timer.Stop TIMER_ID
            Let rRetL := False
        End If
    End Sub
   
    !_
      Purpose:  블록을 저장하고 클리어한다.
    _!
    Sub SaveBlock
        Dim .rIndex     As Long r12
        Dim .rX         As Long rdi
        Dim .rSource    As Long rsi
        Dim .rDest      As Long rdi
        Dim .rScore     As Long rbx
    Begin
        ; 블록을 저장한다.
        Me DoBlock, 0, eDoBlock.Save, False

        ; 완성된 줄을 지운다 .
        ; 블록의 크기는 4칸이므로 블록이 저장된 위치부터 아래로 4 칸까지 검토하고 지운다 .
        Let .rScore := SCORE_UNIT

        Le3 .rIndex := rMe:Block.Y(0) ** frmMain.abMatrix.Cols
        For rMe:Block.Y(0) To frmMain.abMatrix.Rows - 1
            Let rTemp := 0
            For .rX = 0 To frmMain.abMatrix.Cols-1
                If rMe:abMatrix(.rIndex) < MATRIX_SPACE
                    Let rTemp++
                End If
                Let .rIndex++
            Next

            If rTemp = frmMain.abMatrix.Cols
                ; 완성된 줄을 지운다 .
                Let rDest   := ByRef rMe:abMatrix(.rIndex - 1)
                Le3 rSource := rDest -- frmMain.abMatrix.Cols
                Le3 rCount  := rMe:Block.Y(0) ** frmMain.abMatrix.Cols
                std
                rep movsb
                cld

                ;  윗줄을 지운다 .
                Array.Clear rMe:abMatrix, 0, frmMain.abMatrix.Cols, MATRIX_SPACE

                ; ; 점수를 증가시킨다 . 점수 획득은 2 배수로 증가한다.
                Let rMe:Game.Score(0) += .rScore
                Let .rScore *= 2

                ; 레벨마다 할당된 줄을 클리어하면 레벨을 높인다.
                Let rMe:Game.GoalLine(0)--
                If rMe:Game.GoalLine(0) = 0
                    Le3 rMe:Game.GoalLine(0) := rMe:Game.Level(0) ++ GOAL_LINE
                    If rMe:Game.Level(0) < abLevelSpeed.Length
                        Let rMe:Game.Level(0)++
                        Timer.Stop TIMER_ID
                        Me StartTimer
                        Midi.Out.Play _BLOCK_LEVEL_INST, _BLOCK_LEVEL_NOTE, _BLOCK_LEVEL_TIME
                    End If
                End If
            End If
        Next

        If .rScore = SCORE_UNIT
            Midi.Out.Play _BLOCK_DROP_INST, _BLOCK_DROP_NOTE, _BLOCK_DROP_TIME
        Else
            Midi.Out.Play _BLOCK_CLEAR_INST, _BLOCK_CLEAR_NOTE, _BLOCK_CLEAR_TIME
        End If

        ;  블록을 생성한다 .
        Me CreateBlock
    End Sub
End Class
전체 추천리스트 보기
새로운 댓글이 없습니다.
새로운 댓글 확인하기
글쓰기
◀뒤로가기
PC버전
맨위로▲
공지 운영 자료창고 청소년보호