this answer is updated when I find new solutions
After several hours of training, I came up with the following code:
{- Simple game loop example. -} import System.IO import System.Timeout inputTimeout = 50000 -- in microseconds initialGameState = 100 type GameState = Int -- change to your own gamestate type here nextGameState :: GameState -> Char -> GameState nextGameState previousGameState inputChar = -- REPLACE THIS FUNCTION WITH YOUR GAME case inputChar of 's' -> previousGameState + 1 'a' -> previousGameState - 1 _ -> previousGameState loop :: GameState -> IO () -- game loop loop gameState = do putStrLn (show gameState) hFlush stdout c <- timeout inputTimeout getChar -- wait for input, with timeout case c of -- no input given Nothing -> do loop gameState -- quit on 'q' Just 'q' -> do putStrLn "quitting" -- input was given Just input -> do loop (nextGameState gameState input) main = do hSetBuffering stdin NoBuffering -- to read char without [enter] hSetBuffering stdout (BlockBuffering (Just 80000)) -- to reduce flickering, you can change the constant hSetEcho stdout False -- turn off writing to console with keyboard loop initialGameState
A few notes:
- This (not quite) game simply writes out a world state, which is just a number, and increases / decreases it with "s" or "a". "q" terminates the program.
- Obviously, this is a very simple solution and will not be used for more serious games. Dips:
- The code does not scan the keyboard state, but reads standard input, which limits input processing. You will not be able to read simultaneous keystrokes, and there will be a repeated delay. You can fix this by writing a script in another language that will handle keyboard input in a more complex way and pass it through a pipe to your program. On Unix-like systems, you can also read the keyboard state from a file in / dev / ...
- You can expect each frame to take about microseconds
inputTimeout
, but not exactly. A very fast entry can theoretically reduce this, computational delays will increase this.
- I'm a beginner at Haskell, so feel free to improve this and post here. I am updating this answer.
updated code
In the previous code, if the nextGameState
function of the game took a considerable time to calculate, the input characters will accumulate in stdin, and the reaction of the program will be delayed. The following code solves this by always reading all the characters from the input and accepting only the last.
{- Simple game loop example, v 2.0. -} import System.IO import Control.Concurrent frameDelay = 10000 -- in microseconds initialGameState = 100 type GameState = Int -- change to your own gamestate type here nextGameState :: GameState -> Char -> GameState nextGameState previousGameState inputChar = -- REPLACE THIS FUNCTION WITH YOUR GAME case inputChar of 's' -> previousGameState + 1 'a' -> previousGameState - 1 _ -> previousGameState getLastChar :: IO Char getLastChar = do isInput <- hWaitForInput stdin 1 -- wait for char for 1 ms if isInput then do c1 <- getChar c2 <- getLastChar if c2 == ' ' then return c1 else return c2 else do return ' ' gameLoop :: GameState -> IO () -- game loop gameLoop gameState = do threadDelay frameDelay putStrLn (show gameState) hFlush stdout c <- getLastChar case c of 'x' -> do putStrLn "quitting" _ -> do gameLoop (nextGameState gameState c) main = do hSetBuffering stdin NoBuffering -- to read char without [enter] hSetBuffering stdout (BlockBuffering (Just 80000)) -- to reduce flickering, you can change the constant hSetEcho stdout False -- turn off writing to console with keyboard gameLoop initialGameState
source share