2026-03-24 15:40:22 +03:00
|
|
|
module Main where
|
|
|
|
|
|
2026-03-28 18:38:00 +03:00
|
|
|
import SDL
|
2026-03-30 00:01:17 +03:00
|
|
|
import qualified SDL.Font as FONT
|
2026-03-28 18:38:00 +03:00
|
|
|
import qualified Data.Text as Text
|
|
|
|
|
import Control.Monad (unless)
|
|
|
|
|
import SDL.Raw (getTicks)
|
|
|
|
|
import Data.Word (Word32)
|
2026-03-30 00:01:17 +03:00
|
|
|
import Foreign.C (CInt)
|
|
|
|
|
|
|
|
|
|
import Snake
|
|
|
|
|
|
|
|
|
|
import Assets
|
|
|
|
|
import Graphics
|
2026-03-24 15:40:22 +03:00
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = do
|
2026-03-28 18:38:00 +03:00
|
|
|
initializeAll
|
2026-03-30 00:01:17 +03:00
|
|
|
FONT.initialize
|
|
|
|
|
window <- createWindow (Text.pack "Haskell Snake") defaultWindow {windowInitialSize = snakeWindowSize}
|
2026-03-28 18:38:00 +03:00
|
|
|
renderer <- createRenderer window (-1) defaultRenderer
|
|
|
|
|
|
2026-03-30 00:01:17 +03:00
|
|
|
assets <- loadAssets renderer
|
2026-03-30 01:32:52 +03:00
|
|
|
appLoop renderer assets (startState, False)
|
2026-03-30 00:01:17 +03:00
|
|
|
freeAssets assets
|
2026-03-28 18:38:00 +03:00
|
|
|
destroyRenderer renderer
|
|
|
|
|
destroyWindow window
|
|
|
|
|
|
|
|
|
|
targetFps :: Word32
|
2026-03-29 21:07:11 +03:00
|
|
|
targetFps = 5
|
2026-03-28 18:38:00 +03:00
|
|
|
|
|
|
|
|
targetFrameMs :: Word32
|
|
|
|
|
targetFrameMs = 1000 `div` targetFps
|
|
|
|
|
|
2026-03-30 00:01:17 +03:00
|
|
|
boardSize :: (Int, Int)
|
|
|
|
|
boardSize = (12, 8)
|
|
|
|
|
|
2026-03-30 01:32:52 +03:00
|
|
|
startState :: Game
|
|
|
|
|
startState = initialState boardSize (2, 2)
|
|
|
|
|
|
2026-03-30 00:01:17 +03:00
|
|
|
boardWindowSize :: (Int, Int) -> V2 CInt
|
|
|
|
|
boardWindowSize (w, h) = V2 (fromIntegral w * tileSize) (fromIntegral h * tileSize)
|
|
|
|
|
|
|
|
|
|
snakeWindowSize :: V2 CInt
|
|
|
|
|
snakeWindowSize = boardWindowSize boardSize
|
|
|
|
|
|
2026-03-30 01:32:52 +03:00
|
|
|
appLoop :: Renderer -> Assets -> (Game, Bool) -> IO ()
|
|
|
|
|
appLoop renderer assets (game, isEnd) = do
|
2026-03-28 18:38:00 +03:00
|
|
|
frameStart <- getTicks
|
|
|
|
|
events <- pollEvents
|
|
|
|
|
let eventIsExitPress event = case eventPayload event of
|
2026-03-30 00:01:17 +03:00
|
|
|
KeyboardEvent keyboardEvent ->
|
|
|
|
|
keyboardEventKeyMotion keyboardEvent == Pressed &&
|
|
|
|
|
keysymKeycode (keyboardEventKeysym keyboardEvent) == KeycodeQ
|
|
|
|
|
WindowClosedEvent _ -> True
|
|
|
|
|
_ -> False
|
2026-03-28 18:38:00 +03:00
|
|
|
exitPressed = any eventIsExitPress events
|
2026-03-30 00:01:17 +03:00
|
|
|
let eventIsRestartPress event = case eventPayload event of
|
|
|
|
|
KeyboardEvent keyboardEvent ->
|
|
|
|
|
keyboardEventKeyMotion keyboardEvent == Pressed &&
|
|
|
|
|
keysymKeycode (keyboardEventKeysym keyboardEvent) == KeycodeR
|
|
|
|
|
_ -> False
|
2026-03-30 01:32:52 +03:00
|
|
|
restartPressed = any eventIsRestartPress events
|
2026-03-30 00:01:17 +03:00
|
|
|
let eventDirectionPress event = case eventPayload event of
|
2026-03-29 18:39:27 +03:00
|
|
|
KeyboardEvent keyboardEvent ->
|
|
|
|
|
if keyboardEventKeyMotion keyboardEvent == Pressed
|
|
|
|
|
then getInputByKeycode $ keysymKeycode (keyboardEventKeysym keyboardEvent)
|
|
|
|
|
else NothingPressed
|
|
|
|
|
_ -> NothingPressed
|
2026-03-30 00:01:17 +03:00
|
|
|
input = foldMap eventDirectionPress events
|
2026-03-30 01:32:52 +03:00
|
|
|
(movementResult, updatedGame) <- if restartPressed then pure (IntoEmpty, startState) else if isEnd then pure (IntoEmpty, game) else runTick game input
|
|
|
|
|
let updatedIsEnd = (isEnd || movementResult == IntoSnake) && not restartPressed
|
|
|
|
|
renderFrame renderer assets (updatedGame, updatedIsEnd)
|
2026-03-29 18:39:27 +03:00
|
|
|
|
|
|
|
|
unless exitPressed $ do
|
2026-03-30 00:01:17 +03:00
|
|
|
frameEnd <- getTicks
|
|
|
|
|
let elapsed = frameEnd - frameStart
|
2026-03-29 18:39:27 +03:00
|
|
|
delay (targetFrameMs - elapsed)
|
2026-03-30 01:32:52 +03:00
|
|
|
appLoop renderer assets (updatedGame, updatedIsEnd)
|
2026-03-28 18:38:00 +03:00
|
|
|
|
2026-03-29 18:39:27 +03:00
|
|
|
getInputByKeycode :: Keycode -> MovementInput
|
|
|
|
|
getInputByKeycode KeycodeW = UpPressed
|
|
|
|
|
getInputByKeycode KeycodeUp = UpPressed
|
|
|
|
|
getInputByKeycode KeycodeD = RightPressed
|
|
|
|
|
getInputByKeycode KeycodeRight = RightPressed
|
|
|
|
|
getInputByKeycode KeycodeS = DownPressed
|
|
|
|
|
getInputByKeycode KeycodeDown = DownPressed
|
|
|
|
|
getInputByKeycode KeycodeA = LeftPressed
|
|
|
|
|
getInputByKeycode KeycodeLeft = LeftPressed
|
|
|
|
|
getInputByKeycode _ = NothingPressed
|