본문 바로가기
AutoHotKey

테트리스

by 닝구르 2014. 9. 20.

스크립트에 대한 설명:



ahk포럼에서 가져온 테트리스 게임입니다.
스크립트를 전체적으로 이해를 못할지라도 한줄 한줄 천천히 보다 보면 나중에 반드시 하나라도 얻는게 있을겁니다.

<--------------------------------------------- AHK 스크립트 내용 --------------------------------------------->

;  ManyTetris 0.17
;  Customizable Pocket Tetris
;  Danny Ben Shitrit (aka Icarus) 2008
;
;-------------------------------------------------------------------------------
NameString    := "ManyTetris"
VersionString := "0.17"
#SingleInstance Force

Gosub Init
Gosub Main

Return
;-------------------------------------------------------------------------------
; MAINS
;-------------------------------------------------------------------------------
Init:
  SetWorkingDir %A_scriptDir%
  IniFile = %NameString%.ini
 
  OnMessage(0x06,  "WM_ACTIVATE")     ; Capture gui focus

  ColorString := "Options_OnColor,Options_OffColor,Options_GuiColor,Options_FontColor"
  IniHelper( "Read", IniFile, "GUI", ColorString . ",Options_Inverse" )
  IniHelper( "Read", IniFile, "Sound", "Options_EnableSound" )
  IniHelper( "Read", IniFile, "Recent", "Options_LastGame,Options_WinX,Options_WinY" )
  Score_MaxScore := Score_GetMaxScore( Options_LastGame )
 
  If( Options_Inverse ) {
    Loop Parse, ColorString, `,
      %A_LoopField% := InverseColor( %A_LoopField% )
  }
 
  Game_PushDownSpeed := 20
  Game_IsPaused := false
 
  ; Arrange games drop down list
  Loop games*.txt
  {
    SplitPath A_LoopFilename,,,,OutNameNoExt

    If( A_Index = 1 and Not FileExist( "games" . Options_LastGame . ".txt" ) )
      Options_LastGame := OutnameNoExt

    If( A_Index <> 1 )
      Gui_GamesDdl .= "|"

    Gui_GamesDdl .= OutNameNoExt
  }
 
  Game_ReadGameConfig( Options_LastGame )
 
  Loop %Game_Piece0% {
    PieceID := A_Index
    StringSplit PiecePart, Game_Piece%A_Index%, %A_Space%
    Game_PieceStates%PieceID% := PiecePart0
    Loop %PiecePart0% {  
      StateID := A_Index
      Game_Piece%PieceID%_State%StateID% := PiecePart%A_Index%
    }
  }

  Active_DropSpeed := Game_DropSpeed
 
  Gosub Game_BuildGui
Return

Main:
  Game_PiecesDropped := 0
  Active_PlaceNewPiece()
  SetTimer Game_ProgressGame, -%Active_DropSpeed%
Return

GuiEscape:
GuiClose:
  Gosub SaveSettings
  ExitApp
Return

;-------------------------------------------------------------------------------
; GAME
;-------------------------------------------------------------------------------
Game_ReadGameConfig( file ) {
  Local FileString, ThisLine

  FileRead FileString, games\%file%.txt
 
  Game_Piece0 := 1
 
  Loop Parse, FileString, `n,`r
  {
    ThisLine = %A_LoopField%
   
    If( SubStr( ThisLine, 1, 1 ) = "#" ) {
      StringTrimLeft, ThisLine, ThisLine, 1
      StringSplit Param, ThisLine, %A_Space%
      Game_%Param1% := Param%Param0%
      Continue
    }
   
    If( ThisLine = "" or SubStr( ThisLine,1,1 ) = ";" )
      Continue
     
    If( ThisLine = "*" ) {
      StringTrimRight Game_Piece%Game_Piece0%, Game_Piece%Game_Piece0%, 1
      Game_Piece0++
    }
    Else If( ThisLine = "=" ) {
      StringTrimRight Game_Piece%Game_Piece0%, Game_Piece%Game_Piece0%, 1
      Game_Piece%Game_Piece0% .= " "
    }
    Else {
      Game_Piece%Game_Piece0% .= ThisLine . "|"
    }
  }
 
  ; Set some defaults, in case the game configuration file does not contain
  ; all the elements.
  ; Mainly for backwards compatibility with older game templates.
  If( Game_LineScore = "" )
  Game_LineScore := 10
  If( Game_PieceScore = "" )
  Game_PieceScore := 2
  If( Game_PausePenalty = "" )
  Game_PausePenalty := 5
 
}

Game_BuildGui:
  Gui Margin,2,2
  Gui Color, %Options_GuiColor%,%Options_GuiColor%
  Gui Font, c%Options_FontColor%
 
  W := Game_Cols * ( Game_BoxSize+1 ) - 83
  Gui Add, DDL, -TabStop w%W% vGui_GamesDdl, %Gui_GamesDdl%
  GuiControl ChooseString, Gui_GamesDdl, %Options_LastGame%
 
  Gui Add, Button, 0x8000 x+2 yp w80 hp+1 -wrap vGui_StartButton gGame_RestartGame, Start
 
  W := Game_Cols * ( Game_BoxSize+1 ) - 1
 
  Gui Font, s12 Bold
  Gui Add, Text, x2 y+2 w%W% h21 vGui_Score Center Border, 0
  Game_UpdateScore( 0 )
  Game_UpdateScore( "Top: " . Score_MaxScore, true )
  Gui Font, s9 Norm
 
  X := 2
  Y := 48
  W := Game_BoxSize
  Loop %Game_Rows% {
    i := A_Index
    Loop %Game_Cols% {
      j := A_Index
      Gui Add, ListView, x%X% y%Y% w%W% h%W% vGuiBox_%i%_%j% -TabStop -E0X200 0x4000 +Background%Options_OffColor%
      DataBox_%i%_%j% := 0
      X += W + 1
    }
    Y += W + 1
    X := 2
  }
 
  GuiControl Focus, Gui_StartButton
  Gui Show, x%Options_WinX% y%Options_WinY%,%NameString% %VersionString%

Return

Game_RestartGame:
  Gui Submit, NoHide
  Options_LastGame := Gui_GamesDdl
  Gosub Reload
Return

Game_ProgressGame:
  Game_ProgressGame()
Return

Game_ProgressGame() {
  Global
 
  ; Drops a piece by one row if possible
  ; Else, remove the completed lines if any
  ; And create a new piece on top
  If( Not Active_DropPiece() ) {
  Game_SetNewDropSpeed()
     
    If( Active_PrevDropSpeed )
      Active_PrevDropSpeed := Active_DropSpeed
    Active_CheckCompletedLines()
    Active_PlaceNewPiece()   

    SetTimer Game_ProgressGame, -%Active_DropSpeed%
  }
}

Game_SetNewDropSpeed() {
 Global

 If( Game_DropSpeedPoints )
  Active_DropSpeed := Game_DropSpeed - ( Trim( Game_Score/Game_DropSpeedPoints )*Game_DropSpeedIncrease )
 Else
  Active_DropSpeed := Game_DropSpeed - ( Game_PiecesDropped*Game_DropSpeedIncrease )
 
 If( Active_DropSpeed < Game_MaxDropSpeed )
  Active_DropSpeed := Game_MaxDropSpeed
  
 ;Tooltip % Active_DropSpeed  ; DEBUG SPEED
}

Game_DataToGui( fromRow, toRow ) {
  ; Reads a range of rows in the data grid, and reflect it in the GUI
  ;_____________________________________________________________________________
  Local Row, Color
 
  Loop %toRow% {
    Row := fromRow + A_Index - 1
    Loop %Game_Cols% {
      Col := A_Index
      Color := DataBox_%Row%_%Col% ? Options_OnColor : Options_OffColor
      GuiControl ,+Background%Color%, GuiBox_%Row%_%Col%   
    }
  }
}

Game_GameOver() {
  ; Called when we can longer place new pieces
  ;_____________________________________________________________________________
  Local Row, Col
 
  SetTimer Game_ProgressGame, Off
 
  Critical
  SoundPlay( "Over" )
  Loop %Game_Rows% {
    Active_RemoveRow( Game_Rows )
    Game_DataToGui( 1, Game_Rows )
    Sleep 50
  }
  Critical Off
  Game_SaveHighScore()
  Sleep 2000 
  Game_UpdateScore( 0 )

  Active_DropSpeed := Game_DropSpeed 
  Gosub Main
}

Game_UpdateScore( score, displayonly=false ) {
  Global Game_Score

  If( score = 0 ) {
    Game_Score := 0
    GuiControl,,Gui_Score, 0
  }

  Else If( not displayOnly ) {
    Game_Score += score
    GuiControl,,Gui_Score, % Game_Score
  }
  Else {
    GuiControl,,Gui_Score, % score
  }
 
}

Game_SaveHighScore() {
  Global
 
  If( Game_Score > Score_MaxScore ) {
    Score_MaxScore := Game_Score
    Score_SetMaxScore( Options_LastGame, Score_MaxScore )
  }
}

;-------------------------------------------------------------------------------
; ACTIVE
;-------------------------------------------------------------------------------
Active_PlaceNewPiece() {
  ; Gets a random piece and places it at the top of the board
  ; Ends the game if it cannot place that piece
  ;_____________________________________________________________________________
  Local Col, Row, Failed, PieceRows
 
  Random Active_Piece, 1, %Game_Piece0%
  Game_PiecesDropped++
  Active_PieceStates := Game_PieceStates%Active_Piece%
  Active_PieceState := 1
  Active_PieceString := Game_Piece%Active_Piece%_State1
 
  StringSplit PieceRow, Game_Piece%Active_Piece%_State1, |
  Active_BaseRow := 1
  Active_PieceCols := StrLen( PieceRow1 )
  Active_BaseCol := Round( 1 + (Game_Cols/2) - (Active_PieceCols/2 ) )
 
  ; Update the data grid
  Failed := false
  Col := Active_BaseCol
  Row := Active_BaseRow
  Loop Parse, Active_PieceString
  {
    If( A_LoopField = "+" ) {
      If( DataBox_%Row%_%Col% <> 1 )
        DataBox_%Row%_%Col% := 2     
      Else
        Failed := true       
    }
    Else If( A_LoopField = "|" ) {
      Col := Active_BaseCol-1
      Row++
    }
    Col++
  }
 
  Active_PieceRows := Row
 
  ; Update the GUI
  If( Failed ) {
    Game_DataToGui( 1, Active_PieceRows )
    Game_GameOver()
  }
  Else {
    SoundPlay( "Piece" )
    Game_DataToGui( 1, Active_PieceRows )
  }

  Return true
}

Active_PieceCanMove( fromRow, toRow, rowOffset, colOffset ) {
  ; Gets a range of rows to check and row+col offsets and returns true if the
  ; piece (that was found in the range of rows) can move to the specified
  ; offsets.
  ;_____________________________________________________________________________
  Local Result, OldRow, OldCol, NewRow, NewCol

  Result := true
  Loop %Active_PieceRows% {
    OldRow := Active_BaseRow + Active_PieceRows - A_Index
    NewRow := OldRow+rowOffset
    Loop %Game_Cols% {
      OldCol := A_Index
      NewCol := OldCol+ColOffset
      If( ( DataBox_%OldRow%_%OldCol% = 2 ) and ( DataBox_%NewRow%_%NewCol% = 1 or NewRow > Game_Rows or NewCol < 1 or NewCol > Game_Cols ) ) {
        Result := false
        Break
      }
    }
  }
 
  Return Result
}

Active_LockPiece( fromRow, toRow ) {
  ; Locks a piece in place, in the data grid only (not GUI)
  ; It will replace all the 2 values in the specified row range to 1
  ;_____________________________________________________________________________
  Local Row, Col
 
  Loop %toRow% {
    Row := fromRow + A_Index - 1
    Loop %Game_Cols% {
      Col := A_Index
      If( DataBox_%Row%_%Col% = 2 )
        DataBox_%Row%_%Col% := 1
    }
  }
}

Active_DropPiece() {
  ; Drops the piece by one line
  ; If it is unable to, it will lock the piece in place (convert 2s to 1s)
  ;_____________________________________________________________________________
  Local OldRow, NewRow
 
  ; If cant drop piece anymore, change active piece to static and return false
  If( Not Active_PieceCanMove( Active_BaseRow, Active_BaseRow + Active_PieceRows, 1, 0 ) ) {
    Active_LockPiece( Active_BaseRow, Active_BaseRow+Active_PieceRows-1 )
    Return false
  }
 
  Critical
  Loop %Active_PieceRows% {
    OldRow := Active_BaseRow + Active_PieceRows - A_Index
    NewRow := OldRow+1
    Loop %Game_Cols% {
      Col := A_Index
      If( DataBox_%OldRow%_%Col% = 2 ) {
        DataBox_%OldRow%_%Col% := 0
        DataBox_%NewRow%_%Col% = 2
      }
    }
  }
  If( Active_DropSpeed > Game_PushDownSpeed )
    SoundPlay( "Step" )
  Game_DataToGui( Active_BaseRow, Active_PieceRows+1 )
  Active_BaseRow++
 
  SetTimer Game_ProgressGame, -%Active_DropSpeed%
  Critical Off
 
  Return true
}

Active_MovePiece( dir ) {
  ; Moves a piece one block to the right (1) or left (-1)
  ; Returns false on failure
  ;_____________________________________________________________________________
  Local OldCol, NewCol
 
  ; If cant move piece anymore, return false
  If( Not Active_PieceCanMove( Active_BaseRow, Active_BaseRow + Active_PieceRows, 0, dir ) ) {
    Return false
  }
 
  Critical
  Loop %Active_PieceRows% {
    Row := Active_BaseRow + A_Index - 1
    Loop %Game_Cols% {
      OldCol := dir < 0 ? A_Index : Game_Cols - A_Index + 1
      NewCol := OldCol + dir
      If( DataBox_%Row%_%OldCol% = 2 ) {
        DataBox_%Row%_%OldCol% := 0
        DataBox_%Row%_%NewCol% := 2
      }
    }
  }
  Active_BaseCol += dir
  Game_DataToGui( Active_BaseRow, Active_PieceRows+1 )
  Critical Off
  Return true
}

Active_PieceCanRotate( fromState, toState ) {
  ; Returns true if the active piece can rotate
  ;_____________________________________________________________________________
  Local Result, Row, Col

  Result := true

  ; Check GUI edges
  If( Active_BaseCol < 1 or Active_BaseCol+Active_PieceCols-1 > Game_Cols )
    Result := false
   
  ; Check collision with other pieces
  Loop %Active_PieceRows% {
    If( not Result )
      Break
    Row := Active_BaseRow+A_Index-1
    Loop %Active_PieceCols% {
      Col := Active_BaseCol+A_Index-1
      If( DataBox_%Row%_%Col% = 1 ) {
        Result := false
        Break
      }
    }
  }
 
 
  Return Result
}


Active_RotatePiece() {
  ; Rotates a piece
  ;_____________________________________________________________________________
  Local Col, Row, NewPieceState
 
  NewPieceState := Active_PieceState < Active_PieceStates ? Active_PieceState+1 : 1
 
  ; If cant move piece anymore, return false
  If( Not Active_PieceCanRotate( Active_PieceState, NewPieceState ) ) {
    Return false
  }
  Else {
    Critical
    Active_PieceState := NewPieceState
    Active_PieceString := Game_Piece%Active_Piece%_State%Active_PieceState%
   
    Row := Active_BaseRow
    Col := Active_BaseCol  
    Loop Parse, Active_PieceString
    {
      If( A_LoopField = "+" )
        DataBox_%Row%_%Col% := 2   
      Else If( A_LoopField = "-" )
        DataBox_%Row%_%Col% := 0
      Else If( A_LoopField = "|" ) {
        Col := Active_BaseCol-1
        Row++
      }
      Col++
    }
    Critical Off
  }
 
  Game_DataToGui( Active_BaseRow, Active_PieceRows+1 )
  Return true
}

Active_CheckCompletedLines() {
 ; Check for completed lines and update the score (even if there are no
 ; completed lines)
  Local Row, FullRow 
 
  Critical
  ; Identify rows for removal
  Loop %Game_Rows% {
    FullRow := true
    Row := Game_Rows-A_Index+1
    Loop %Game_Cols% {
      Col := A_Index
      If( DataBox_%Row%_%Col% <> 1 ) {
        FullRow := false
        Continue
      }
    }
    If( FullRow ) {
      Active_ScoreFactor++
      Active_CycleScore += ( Active_ScoreFactor * Game_LineScore )
      Active_RemoveRow( Row )
      Active_CheckCompletedLines()
      Game_DataToGui( 1, Game_Rows )
      Return     
    }
  }
 
  If( Active_CycleScore ) {
    Gui Submit, NoHide
    SoundPlay( "Line", true )
    Game_UpdateScore( Active_CycleScore )
  }
  Else {
    Gui Submit, NoHide
    Game_UpdateScore( Game_PieceScore )
  }
  Active_ScoreFactor := 0
  Active_CycleScore := 0
 
  Critical Off
}

Active_RemoveRow( targetRow ) {
  Local Col, Row, HigherRow
 
  Critical
  Loop % targetRow-1 {
    Row := targetRow-A_Index+1
    HigherRow := Row-1
   
    Loop %Game_Cols% {
      Col := A_Index
      DataBox_%Row%_%Col% := DataBox_%HigherRow%_%Col%
    }
  }
 
  ; Reset first row to zeros
  Loop %Game_Cols% {
    Col := A_Index
    DataBox_1_%Col% := 0
  }
  Critical Off
}

Active_DropKeyReleased:
  ; Called by timer whenever the Down key is pressed.
  ; Will restore original drop speed when key is released.
  If( not GetKeyState( "Down", "P" ) ) {
    SetTimer Active_DropKeyReleased, Off
    Active_DropSpeed := Active_PrevDropSpeed
    Active_PrevDropSpeed := 0
  }
Return

;-------------------------------------------------------------------------------
; SCORE
;-------------------------------------------------------------------------------
Score_GetMaxScore( game ) {
  Global IniFile
 
  NiceGameName := RegExReplace( game, "[W]" )
  IniRead Result, %IniFile%, Score, %NiceGameName%, 0
  Return Result
}

Score_SetMaxScore( game, score ) {
  Global IniFile
 
  NiceGameName := RegExReplace( game, "[W]" )
  IniWrite %score%, %IniFile%, Score, %NiceGameName%
}

;-------------------------------------------------------------------------------
; DEBUG
;-------------------------------------------------------------------------------
Debug_ShowGrid() {
  Local Row, Col, Result
 
  Result := ""
  Loop %Game_Rows% {
    Row := A_Index
    Loop %Game_Cols% {
      Col := A_Index
      Result .= DataBox_%Row%_%Col% . " "
    }
    Result .= "`n"
  }
  Msgbox % Result
}

;-------------------------------------------------------------------------------
; MISC
;-------------------------------------------------------------------------------
IniHelper( op, file, section, keys ) {
  ; Reads or writes specific keys from an INI section, and sets the values as
  ; global variables
  ;_____________________________________________________________________________
  Global
 
  StringSplit iniKey, keys, `,
 
  Loop %iniKey0% {
    If( op = "Read" ) {
      IniKey := % iniKey%A_Index%
      IniRead %IniKey%, %file%, %section%, %IniKey%
    }
    Else If( op = "Write" ) {
      IniValue := % iniKey%A_Index%
      IniValue := %IniValue%
      IniKey   := iniKey%A_Index%
      IniWrite %IniValue%, %file%, %section%, %IniKey%
    }
  }
}

InverseColor( color ) {
  SetFormat Integer, H
  Result := ""
  Loop Parse, Color
  {
    Result .= 0xF - A_LoopField
    StringReplace Result, Result, 0x
  }
   
  SetFormat Integer, D
  Return Result
}

SoundPlay( sound, wait=false ) {
  Global Options_EnableSound
  If( Not Options_EnableSound )
    Return
  Wait := wait ? "WAIT" : ""
  SoundPlay sounds\%sound%.wav, %WAIT%
}

Trim( num ) {
 StringSplit NumElement, num, .
 Return %NumElement1%
}

SaveSettings:
  Gui +LastFound
  WinGetPos Options_WinX, Options_WinY
  IniHelper( "Write", IniFile, "Recent", "Options_LastGame,Options_WinX,Options_WinY" )
  Game_SaveHighScore()
Return

Reload:
  Gosub SaveSettings
  Reload
Return

WM_ACTIVATE() {
  Global NameString, Game_IsPaused
 
  IfWinActive %NameString% ahk_Class AutoHotkeyGUI
  {
    If( Game_IsPaused )
      Gosub p
  }
  Else If( !Game_IsPaused )
    Gosub p
}


;-------------------------------------------------------------------------------
; HOTKEYS
;-------------------------------------------------------------------------------
#IfWinActive ManyTetris ahk_Class AutoHotkeyGUI
Right::
  If( Game_IsPaused )
    Gosub p
  Active_MovePiece( 1 )
Return

Left::
  If( Game_IsPaused )
    Gosub p
  Active_MovePiece( -1 )
Return

Up::
  If( Game_IsPaused )
    Gosub p
  Active_RotatePiece()
Return 

Down::
  If( Game_IsPaused )
    Gosub p
   
  ; If key is already pushed, do nothing (the Active_DropKeyReleased is working)
  If( Active_PrevDropSpeed )   
    Return
   
  SetTimer Game_ProgressGame, Off
 
  Active_PrevDropSpeed := Active_DropSpeed
  Active_DropSpeed := Game_PushDownSpeed 
  SetTimer Active_DropKeyReleased, 20
  Gosub Game_ProgressGame
Return 

p::
  Game_IsPaused := !Game_IsPaused
  If( Game_IsPaused )
    SetTimer Game_ProgressGame, Off
  Else {
    If( Game_Score > 0 )
      Game_Score -= Game_PausePenalty
    SetTimer Game_ProgressGame, -%Active_DropSpeed%
  }
Return