I've just started with Haskell and want to make a simple real-time game without installing additional libraries. I need to write a loop that scans for keyboard input but the game also has to run if there is no input. How do I do this?
this answer is being updated as I find new solutions
After hours of studying 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:
inputTimeout
microseconds, but not exactly. Very fast input could theoretically lower this, computation delays will increase this.In previous code, if the game nextGameState
function took a significant time to compute, the input characters would pile up in stdin and the reaction of the program would be delayed. The following code solves this by always reading all characters from input and taking only the last one.
{- 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With