Copyright | © 2017-2019 Francesco Ariis |
---|---|
License | GPLv3 (see LICENSE file) |
Maintainer | Francesco Ariis <fa-ml@ariis.it> |
Stability | provisional |
Portability | portable |
Safe Haskell | None |
Language | Haskell2010 |
Machinery and utilities for 2D terminal games.
New? Start from simpleGame
.
Synopsis
- type TPS = Integer
- type FPS = Integer
- data Event
- data GEnv = GEnv {
- eTermDims :: Dimensions
- eFPS :: FPS
- data Game s = Game {
- gTPS :: TPS
- gInitState :: s
- gLogicFunction :: GEnv -> s -> Event -> s
- gDrawFunction :: GEnv -> s -> Plane
- gQuitFunction :: s -> Bool
- simpleGame :: forall s. (Width, Height) -> TPS -> s -> (s -> Event -> s) -> (s -> Plane) -> (s -> Bool) -> Game s
- playGame :: Game s -> IO ()
- data Timed a
- creaTimer :: a -> a -> Integer -> Timed a
- creaBoolTimer :: Integer -> Timed Bool
- creaTimerLoop :: a -> a -> Integer -> Timed a
- creaBoolTimerLoop :: Integer -> Timed Bool
- type Animation = Timed Plane
- creaAnimation :: [(Integer, Plane)] -> Animation
- creaLoopAnimation :: [(Integer, Plane)] -> Animation
- tick :: Timed a -> Timed a
- ticks :: Integer -> Timed a -> Timed a
- reset :: Timed a -> Timed a
- lapse :: Timed a -> Timed a
- fetchFrame :: Timed a -> a
- isExpired :: Timed a -> Bool
- data StdGen
- getStdGen :: MonadIO m => m StdGen
- mkStdGen :: Int -> StdGen
- getRandom :: UniformRange a => (a, a) -> StdGen -> (a, StdGen)
- pickRandom :: [a] -> StdGen -> (a, StdGen)
- class UniformRange a
- data Plane
- type Dimensions = (Width, Height)
- type Coords = (Row, Column)
- type Row = Int
- type Column = Int
- type Width = Int
- type Height = Int
- blankPlane :: Width -> Height -> Plane
- stringPlane :: String -> Plane
- stringPlaneTrans :: Char -> String -> Plane
- makeTransparent :: Char -> Plane -> Plane
- makeOpaque :: Plane -> Plane
- planePaper :: Plane -> String
- planeSize :: Plane -> Dimensions
- type Draw = Plane -> Plane
- (%) :: Coords -> Plane -> Draw
- (&) :: a -> (a -> b) -> b
- (#) :: Plane -> Draw -> Plane
- subPlane :: Plane -> Coords -> Coords -> Plane
- mergePlanes :: Plane -> [(Coords, Plane)] -> Plane
- cell :: Char -> Plane
- word :: String -> Plane
- box :: Width -> Height -> Char -> Plane
- data Color
- data ColorIntensity
- color :: Color -> ColorIntensity -> Plane -> Plane
- bold :: Plane -> Plane
- invert :: Plane -> Plane
- (%^>) :: Coords -> Plane -> Draw
- (%.<) :: Coords -> Plane -> Draw
- (%.>) :: Coords -> Plane -> Draw
- textBox :: Width -> Height -> String -> Plane
- textBoxLiquid :: Width -> String -> Plane
- textBoxHyphen :: Hyphenator -> Width -> Height -> String -> Plane
- textBoxHyphenLiquid :: Hyphenator -> Width -> String -> Plane
- data Hyphenator
- english_GB :: Hyphenator
- english_US :: Hyphenator
- esperanto :: Hyphenator
- french :: Hyphenator
- german_1996 :: Hyphenator
- italian :: Hyphenator
- spanish :: Hyphenator
- (|||) :: Plane -> Plane -> Plane
- (===) :: Plane -> Plane -> Plane
- (***) :: Plane -> Plane -> Plane
- hcat :: [Plane] -> Plane
- vcat :: [Plane] -> Plane
- testGame :: Game s -> [Event] -> s
- setupGame :: Game s -> [Event] -> Game s
- recordGame :: Game s -> FilePath -> IO ()
- readRecord :: FilePath -> IO [Event]
- narrateGame :: Game s -> [Event] -> IO s
- playGameS :: Game s -> IO s
- displaySize :: IO Dimensions
- errorPress :: IO a -> IO a
- data ATGException = CannotGetDisplaySize
Running
The number of Tick
s fed each second to the logic function;
constant on every machine. Frames per second might be lower
(depending on drawing function onerousness, terminal refresh rate,
etc.).
The number of frames blit to terminal per second. Frames might be
dropped, but game speed will remain constant. Check balls
(cabal run -f examples balls
) to see how to display FPS.
Instances
Eq Event Source # | |
Show Event Source # | |
Generic Event Source # | |
Arbitrary Event Source # | |
Serialize Event Source # | |
type Rep Event Source # | |
Defined in Terminal.Game.Layer.Object.Interface type Rep Event = D1 ('MetaData "Event" "Terminal.Game.Layer.Object.Interface" "ansi-terminal-game-1.6.0.0-LrNIatC1bVNHZu8SjfSvjS" 'False) (C1 ('MetaCons "Tick" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "KeyPress" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Char))) |
Game environment with current terminal dimensions and current display rate.
GEnv | |
|
General way to create a Game
. This allows you more control by
exposing GEnv
(allows you to e.g. adapt to screen resizes, blit
FPS). If you fancy simple, sensible defaults, check simpleGame
.
Game | |
|
:: forall s. (Width, Height) | Gamescreen dimensions, in columns and rows. Asks the player to resize their terminal if it is too small. |
-> TPS | Ticks per second. Since the 2D “char canvas” is coarse, you do not need high values (e.g. 13 TPS is enough for action games). |
-> s | Initial state of the game. |
-> (s -> Event -> s) | Simple logic function. |
-> (s -> Plane) | Simple draw function. |
-> (s -> Bool) | “Should I quit?” function. |
-> Game s |
Simplest way to create a game. The two most important parameters
are the function dealing with logic and the drawing one. Check alone
(you can compile it with cabal run -f examples alone
) to see a basic
game in action. If you want more control, look at Game
.
playGame :: Game s -> IO () Source #
Entry point for the game execution, should be called in main
.
You must compile your programs with -threaded
; if you do not do
this the game will crash at start-up. Just add:
ghc-options: -threaded
in your .cabal
file and you will be fine!
Throws DisplayTooSmall
if game widht/height cannot be accomodated
by terminal.
Game logic
Some convenient function dealing with
Timers (Timed
) and Animation
s.
Usage of these is not mandatory: Game
is
parametrised over any state s
, you are free
to implement game logic as you prefer.
Timers/Animation
Timers
A timed resource is a timer which, at any given moment, points to a specific item (like an animation).
Example:
timer = creaTimedRes (Times 1 Elapse) [(2, "a "), (1, "b "), (2, "c ")] test t | isExpired t = putStrLn "Fine." | otherwise = do putStr (fetchFrame t) test (tick t) -- λ> test timer -- a a b c c Fine.
Instances
creaTimer :: a -> a -> Integer -> Timed a #
A simple off/on timer expiring in fixed number of ticks.
Example:
timer = creaTimer Nothing (Just "Over!") 4 test t | isExpired t = print (fetchFrame t) | otherwise = do print (fetchFrame t) test (tick t) -- λ> test timer -- Nothing -- Nothing -- Nothing -- Nothing -- Just "Over"!
creaTimerLoop :: a -> a -> Integer -> Timed a #
A looped version of creaTimer
.
creaBoolTimerLoop :: Integer -> Timed Bool #
Shorthand for:
.creaTimerLoop
False True i
Animations
T/A interface
fetchFrame :: Timed a -> a #
Fetches the current resource of the timer.
isExpired :: Timed a -> Bool #
Checks wheter the timer is expired (an expired timer will not
respond to tick
).
Random numbers
The standard pseudo-random number generator.
Instances
Eq StdGen | |
Show StdGen | |
NFData StdGen | |
Defined in System.Random.Internal | |
RandomGen StdGen | |
Defined in System.Random.Internal next :: StdGen -> (Int, StdGen) # genWord8 :: StdGen -> (Word8, StdGen) # genWord16 :: StdGen -> (Word16, StdGen) # genWord32 :: StdGen -> (Word32, StdGen) # genWord64 :: StdGen -> (Word64, StdGen) # genWord32R :: Word32 -> StdGen -> (Word32, StdGen) # genWord64R :: Word64 -> StdGen -> (Word64, StdGen) # genShortByteString :: Int -> StdGen -> (ShortByteString, StdGen) # |
getStdGen :: MonadIO m => m StdGen #
Gets the global pseudo-random number generator. Extracts the contents of
globalStdGen
Since: random-1.0.0
getRandom :: UniformRange a => (a, a) -> StdGen -> (a, StdGen) Source #
Simple, pure pseudo-random generator.
pickRandom :: [a] -> StdGen -> (a, StdGen) Source #
Picks at random from list.
class UniformRange a #
The class of types for which a uniformly distributed value can be drawn from a range.
Since: random-1.2.0
Instances
Drawing
To get to the gist of drawing, check the
documentation for %
.
Blitting on screen is double-buffered and diff'd (at each frame, only cells with changed character will be redrawn).
Plane
A two-dimensional surface (Row, Column) where to blit stuff.
type Dimensions = (Width, Height) Source #
Size of a surface.
stringPlane :: String -> Plane Source #
stringPlaneTrans :: Char -> String -> Plane Source #
Same as stringPlane
, but with transparent Char
.
makeTransparent :: Char -> Plane -> Plane Source #
Adds transparency to a plane, matching a given character
makeOpaque :: Plane -> Plane Source #
Changes every transparent cell in the Plane
to an opaque ' '
character.
planePaper :: Plane -> String Source #
A String (n
divided and ended) representing the Plane
. Useful
for debugging/testing purposes.
planeSize :: Plane -> Dimensions Source #
Dimensions or a plane.
Draw
subPlane :: Plane -> Coords -> Coords -> Plane Source #
Cut out a plane by top-left and bottom-right coordinates.
word :: String -> Plane Source #
1xn
Plane
with a word in it. If you need to import multiline
ASCII art, check stringPlane
and stringPlaneTrans
.
ANSI's eight standard colors. They come in two intensities, which are
controlled by ColorIntensity
. Many terminals allow the colors of the
standard palette to be customised, so that, for example,
setSGR [ SetColor Foreground Vivid Green ]
may not result in bright green
characters.
data ColorIntensity #
ANSI's standard colors come in two intensities
Instances
Alternative origins
Placing a plane is sometimes more convenient if the coordinates origin
is a corner other than top-left (e.g. «Paste this plane one row from
bottom-left corner»). These combinators — meant to be used instead of %
— allow you to do so. Example:
prova :: Plane prova = let rect = box 6 3 '.' letters = word "ab" in rect & (1, 1) %.> letters -- start from bottom-right -- λ> putStr (planePaper prova) -- ...... -- ...... -- ....ab
(%.<) :: Coords -> Plane -> Draw infixl 4 Source #
Pastes a plane onto another (origin: bottom-left).
(%.>) :: Coords -> Plane -> Draw infixl 4 Source #
Pastes a plane onto another (origin: bottom-right).
Text boxes
textBoxHyphen :: Hyphenator -> Width -> Height -> String -> Plane Source #
As textBox
, but hypenated. Example:
(normal textbox) (hyphenated textbox) Rimasi un po’ a meditare nel buio Rimasi un po’ a meditare nel buio velato appena dal barlume azzurrino velato appena dal barlume azzurrino del fornello a gas, su cui del fornello a gas, su cui sobbol- sobbolliva quieta la pentola. liva quieta la pentola.
Notice how in the right box «sobbolliva» is broken in two. This can be useful and aesthetically pleasing when textboxes are narrow.
textBoxHyphenLiquid :: Hyphenator -> Width -> String -> Plane Source #
As textBoxLiquid
, but hypenated.
data Hyphenator #
A Hyphenator
is combination of an alphabet normalization scheme, a set of Patterns
, a set of Exceptions
to those patterns
and a number of characters at each end to skip hyphenating.
Eurocentric convenience reexports. Check Text.Hyphenation.Language for more languages.
>>>
hyphenate english_GB "supercalifragilisticexpialadocious"
["su","per","cal","i","fra","gil","istic","ex","pi","alado","cious"]
favors UK hyphenation
>>>
hyphenate english_US "supercalifragilisticexpialadocious"
["su","per","cal","ifrag","ilis","tic","ex","pi","al","ado","cious"]
favors US hyphenation
esperanto :: Hyphenator #
Hyphenators for a wide array of languages.
french :: Hyphenator #
>>>
hyphenate french "anticonstitutionnellement"
["an","ti","cons","ti","tu","tion","nel","le","ment"]
Hyphenators for a wide array of languages.
italian :: Hyphenator #
Hyphenators for a wide array of languages.
spanish :: Hyphenator #
Hyphenators for a wide array of languages.
Declarative drawing
Testing
testGame :: Game s -> [Event] -> s Source #
Tests a game in a pure environment. You can
supply the Event
s yourself or use recordGame
to obtain them.
recordGame :: Game s -> FilePath -> IO () Source #
Play as in playGame
and write the session to file
. Useful to
produce input for testGame
and narrateGame
. Session will be
recorded even if an exception happens while playing.
Utility
displaySize :: IO Dimensions Source #
Usable terminal display size (on Win32 console the last line is
set aside for input). Throws CannotGetDisplaySize
on error.
errorPress :: IO a -> IO a Source #
Wraps an IO
computation so that any ATGException
or error
gets
displayed along with a <press any key to quit>
prompt.
Some terminals shut-down immediately upon program end; adding
errorPress
to playGame
makes it easier to beta-test games on those
terminals.
data ATGException Source #
ATGException
s are thrown synchronously for easier catching.
Instances
Show ATGException Source # | |
Defined in Terminal.Game.Layer.Object.Interface showsPrec :: Int -> ATGException -> ShowS # show :: ATGException -> String # showList :: [ATGException] -> ShowS # | |
Exception ATGException Source # | |
Defined in Terminal.Game.Layer.Object.Interface |
Cross platform
Good practices for cross-compatibility:
- choose game dimensions of no more than 24 rows and 80 columns. This ensures compatibility with the trickiest terminals (i.e. Win32 console);
- use ASCII characters only. Again this is for Win32 console compatibility, until this GHC bug gets fixed;
- employ colour sparingly: as some users will play your game in a light-background terminal and some in a dark one, choose only colours that go well with either (blue, red, etc.);
- some terminals/multiplexers (i.e. tmux) do not make a distinction between vivid/dull; do not base your game mechanics on that difference.