You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
5.5 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. module VM where
  2. import Data.Hashable (hash, Hashable(..))
  3. import qualified Data.List as List
  4. import Data.Set (Set)
  5. import qualified Data.Set as Set
  6. import Datatypes.Board (Board(..))
  7. import qualified Datatypes.Board as Board
  8. import Datatypes.Cell (Cell(..))
  9. import Datatypes.Game (Game(..), Command(..))
  10. import qualified Datatypes.Game as Game
  11. import Datatypes.Unit (Unit(..))
  12. import qualified Datatypes.Unit as Unit
  13. data Notes = OK
  14. | GameOver
  15. | Lock { rowsCleaned :: Int }
  16. | ErrorSamePosition
  17. | ErrorGameEnded
  18. deriving (Show,Eq)
  19. cmdToString :: [Command] -> String
  20. cmdToString (MoveW:cs) = 'p' : cmdToString cs
  21. cmdToString (MoveE:cs) = 'b' : cmdToString cs
  22. cmdToString (MoveSW:cs) = 'a' : cmdToString cs
  23. cmdToString (MoveSE:cs) = 'l' : cmdToString cs
  24. cmdToString (RotateClockwise:cs) = 'd' : cmdToString cs
  25. cmdToString (RotateCounterclockwise:cs) = 'k' : cmdToString cs
  26. cmdToString [] = []
  27. moveScore :: Int -> Int -> Int -> Int
  28. moveScore size lines linesOld = points + lineBonus where
  29. points = size + 100 * ((1 + lines) * lines) `div` 2
  30. lineBonus = if linesOld > 1
  31. then floor (fromIntegral ((linesOld - 1) * points) / 100)
  32. else 0
  33. checkSpawn :: Game -> Game
  34. checkSpawn game@(Game { units = [] }) = game
  35. checkSpawn game@(Game { units = (u:us), board = b }) =
  36. if u `Unit.collidesWith` b
  37. then game { Game.units = [] }
  38. else game
  39. lockUnit :: Game -> Game
  40. lockUnit game = game {
  41. board = newBoard,
  42. units = otherUnits,
  43. visitedUnits = Set.empty,
  44. oldLines = clearedLines,
  45. score = Game.score game + newScore
  46. } where
  47. (currentUnit:otherUnits) = Game.units game
  48. mergedBoard = currentUnit `Unit.mergeWith` (Game.board game)
  49. (newBoard, clearedLines) = Board.clearLines mergedBoard
  50. size = Set.size $ Unit.members currentUnit
  51. newScore = moveScore size clearedLines (Game.oldLines game)
  52. testStep = let unit = Unit (0, 0) (Set.fromList [(2,9)])
  53. board = Board 5 10 (Set.fromList [(0,8),(1,8),(0,9),(1,9),(4,8),(3,9),(4,9)])
  54. in step (Game board [unit] (Set.fromList []) 0 0 []) MoveSW
  55. testStep2 = let unit = Unit (2, 4) (Set.fromList [(0,3),(1,3),(2,3),(3,3),(1,4),(2,4),(3,4), (1,5),(2,5),(2,6)])
  56. board = Board 6 10 (Set.fromList [(0,8),(0,9),(4,6),(3,7),(4,7),(3,8),(4,8),(2,9),(3,9),(4,9),(5,8),(5,9)])
  57. (g1, n1) = step (Game board [unit] (Set.fromList []) 2 0 []) MoveSW
  58. (g2, n2) = step g1 MoveSE
  59. (g3, n3) = step g2 MoveSW
  60. (g4, n4) = step g3 MoveSW
  61. in (g4, n4)
  62. testStep3 = let unit = Unit (0, 0) (Set.fromList [(2,9)])
  63. board = Board 5 10 (Set.fromList [(0,8),(1,8),(0,9),(1,9),(4,8),(3,9),(4,9)])
  64. in step (Game board [unit] (Set.fromList []) 0 0 []) MoveSW
  65. step :: Game -> Command -> (Game, Notes)
  66. step game@(Game { units = [] }) command = (game, ErrorGameEnded)
  67. step game command =
  68. let (unit:otherUnits) = Game.units game
  69. newUnit = applyCommand unit command
  70. board = Game.board game
  71. shouldLock = newUnit `Unit.collidesWith` board || newUnit `Unit.isOutsideOf` board
  72. newVisitedUnits = Set.insert (hash newUnit) (Game.visitedUnits game)
  73. updatedGame = (checkSpawn $ lockUnit game) { history = command:(Game.history game) }
  74. in
  75. if shouldLock
  76. then
  77. if Game.completed updatedGame
  78. then (updatedGame, GameOver)
  79. else (updatedGame, Lock (Game.oldLines updatedGame))
  80. else
  81. if Set.member (hash newUnit) (Game.visitedUnits game)
  82. then (game { units = newUnit:otherUnits }, ErrorSamePosition)
  83. else (game { units = newUnit:otherUnits, visitedUnits = newVisitedUnits, history = command:(Game.history game) }, OK)
  84. applyCommand :: Unit -> Command -> Unit
  85. applyCommand unit MoveW = Unit.map move unit where
  86. move (x, y) = (x - 1, y)
  87. applyCommand unit MoveE = Unit.map move unit where
  88. move (x, y) = (x + 1, y)
  89. applyCommand unit MoveSW = Unit.map move unit where
  90. move (x, y) = (x - ((y + 1) `mod` 2), y + 1)
  91. applyCommand unit MoveSE = Unit.map move unit where
  92. move (x, y) = (x + (y `mod` 2), y + 1)
  93. applyCommand (Unit pivot members) RotateClockwise = Unit pivot (Set.map transform members) where
  94. transform cell = rotateCell pivot cell
  95. applyCommand (Unit pivot members) RotateCounterclockwise = Unit pivot (Set.map transform members) where
  96. transform cell = counterRotateCell pivot cell
  97. rotateCell :: Cell -> Cell -> Cell
  98. rotateCell (px, py) (x, y) = (px - ddiag + (doriz + py `mod` 2) `div` 2, py + doriz) where
  99. (ddiag, doriz) = relativePosition (px, py) (x, y)
  100. counterRotateCell :: Cell -> Cell -> Cell
  101. counterRotateCell pivot = rp . rp . rp . rp . rp where
  102. rp = rotateCell pivot
  103. counterRotateCell2 :: Cell -> Cell -> Cell
  104. counterRotateCell2 (px, py) (x, y) = (px + (ddiag + doriz + py `mod` 2) `div` 2, py + ddiag - doriz) where
  105. (ddiag, doriz) = relativePosition (px, py) (x, y)
  106. relativePosition :: Cell -> Cell -> (Int, Int)
  107. relativePosition (px, py) (x, y) = (diagDir, horizDir) where
  108. diagDir = y - py
  109. horizDir = x - px + (if py `mod` 2 == 0 then diagDir + diagDir `mod` 2 else diagDir - diagDir `mod` 2) `div` 2