diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for piyo
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 peus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# Piyo🐤
+
+Haskell game engine like fantasy console.
+Inspired by PICO-8.
+
+![](pictures/piyo.png)
+
+NOTE: UNDER DEVELOPMENT
+
+## Feature
+
+- minimum but simple api
+- pure update funciton
+- japanease bitmap font support
+
+It supports PICO-8 like drawing api.
+
+## Assuming Specification
+
+For now below.
+
+- display: 192x192
+- sprite size: 12x12
+- map size: 16x16
+
+## Requirements
+
+SDL2 needed.
+
+### For OS X
+
+Install middle wares with homebrew.
+```
+brew install sdl2 sdl2_gfx sdl2_image sdl2_mixer sdl2_ttf
+```
+
+### For others
+
+It may work ..!
+
+## Minimal code
+
+```haskell
+import Piyo
+
+instance Game () where
+    draw _ =
+        [ cls Yellow
+        , txt Black "Hello, World！" 48 48
+        ]
+
+main :: IO ()
+main = piyo () Nothing
+```
+
+![](pictures/demo.png)
+
+## Data Flow Schematic
+
+Functions are called in order update, draw, sound every frame.
+
+![](pictures/flow.svg)
+
+## Indexing
+
+Index number starts with 0. Not 1.
+
+## Examples
+
+Sample working code at [Examples](examples)
+
+## Development in the future
+
+- Redesign assets format
+- Enrich sound api
+- Add useful state update functions
+- FPS management
+- Support ore key action
+- Sprite and map editor
+- Export function for distribution
+- etc...
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,25 @@
+module Main where
+
+import           Piyo
+import           System.Environment             ( getArgs )
+import           Data.Maybe                     ( listToMaybe )
+
+
+newtype Arg = Arg { unTag :: Maybe String }
+
+instance Game Arg where
+    draw s =
+        [ cls Yellow
+        , txt Black str 48 48
+        ]
+        where
+            str = case unTag s of
+                    Just arg -> arg
+                    Nothing -> "Hello, World!"
+
+-- print first argument in the game window 
+main :: IO ()
+main = do
+    args <- getArgs
+    let s = Arg $ listToMaybe args
+    piyo s Nothing
diff --git a/assets/fonts/JF-Dot-K12.ttf b/assets/fonts/JF-Dot-K12.ttf
new file mode 100644
# file too large to diff: assets/fonts/JF-Dot-K12.ttf
diff --git a/assets/fonts/JF-Dot-K12B.ttf b/assets/fonts/JF-Dot-K12B.ttf
new file mode 100644
# file too large to diff: assets/fonts/JF-Dot-K12B.ttf
diff --git a/piyo.cabal b/piyo.cabal
new file mode 100644
--- /dev/null
+++ b/piyo.cabal
@@ -0,0 +1,98 @@
+-- This file has been generated from package.yaml by hpack version 0.28.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: c20a5643221412b3bb584ecb66fd486d4cf830267ee205edec582cdab993a592
+
+name:           piyo
+version:        0.1.0.0
+synopsis:       Haskell game engine like fantasy console.
+description:    Please see the README on GitHub at <https://github.com/opyapeus/piyo#readme>
+category:       Game
+homepage:       https://github.com/opyapeus/piyo#readme
+bug-reports:    https://github.com/opyapeus/piyo/issues
+author:         peus
+maintainer:     opyapeus@gmail.com
+copyright:      2018 peus
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+extra-source-files:
+    ChangeLog.md
+    README.md
+data-files:
+    fonts/JF-Dot-K12.ttf
+    fonts/JF-Dot-K12B.ttf
+data-dir:       assets
+
+source-repository head
+  type: git
+  location: https://github.com/opyapeus/piyo
+
+library
+  exposed-modules:
+      Piyo
+      Piyo.Game
+      Piyo.Asset
+      Piyo.Types
+      Piyo.Draw
+      Piyo.Sound
+  other-modules:
+      Piyo.Internal.Common
+      Piyo.Internal.Config
+      Piyo.Internal.Constants
+      Piyo.Internal.InputState
+      Piyo.Internal.Types
+      Paths_piyo
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , extra
+    , sdl2
+    , sdl2-gfx
+    , sdl2-image
+    , sdl2-mixer
+    , sdl2-ttf
+    , text
+  default-language: Haskell2010
+
+executable piyo-exe
+  main-is: Main.hs
+  other-modules:
+      Paths_piyo
+  hs-source-dirs:
+      app
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , extra
+    , piyo
+    , sdl2
+    , sdl2-gfx
+    , sdl2-image
+    , sdl2-mixer
+    , sdl2-ttf
+    , text
+  default-language: Haskell2010
+
+test-suite piyo-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_piyo
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , extra
+    , piyo
+    , sdl2
+    , sdl2-gfx
+    , sdl2-image
+    , sdl2-mixer
+    , sdl2-ttf
+    , text
+  default-language: Haskell2010
diff --git a/src/Piyo.hs b/src/Piyo.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo.hs
@@ -0,0 +1,94 @@
+{-|
+Module      : Piyo
+Description : Haskell game engine like fantasy console.
+Copyright   : (c) peus, 2018
+License     : MIT
+Maintainer  : opyapeus@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+Please see the README on GitHub at <https://github.com/opyapeus/piyo#readme>
+
+Minimal working code is below. 
+
+@
+import Piyo
+
+instance Game () where
+    draw _ =
+        [ cls Yellow
+        , txt Black "Hello, World！" 48 48
+        ]
+
+main :: IO ()
+main = piyo () Nothing
+@
+
+-}
+module Piyo
+  ( piyo
+  , module Piyo.Game
+  , module Piyo.Asset
+  , module Piyo.Types
+  , module Piyo.Draw
+  , module Piyo.Sound
+  )
+where
+
+import           Control.Monad.Extra            ( loopM )
+import qualified SDL
+import           Data.IORef
+import           Data.Foldable                  ( toList )
+import           Piyo.Internal.Common
+import           Piyo.Internal.InputState
+import           Piyo.Internal.Types
+import           Piyo.Game
+import           Piyo.Asset
+import           Piyo.Types
+import           Piyo.Draw
+import           Piyo.Sound
+
+-- | Start up game function.
+-- First argument is state that user can flexibly define.
+-- Second argument is asset (sprite, map, sound).
+piyo :: Game s => s -> Maybe Asset -> IO ()
+piyo initialState mAsset =
+  withSDL
+    $ withWindow
+    $ \w -> withRenderer w $ \r ->
+        withSound snds $ \chnks -> withImage img r $ \mTxtr ->
+          withFont $ \font -> do
+            let ra = RenderAsset r font mTxtr mapData
+
+            -- clear screen
+            cls Black ra
+            -- init inputsRef
+            inputsRef <- newIORef []
+
+            loopM
+              (\s -> do
+                mEvent <- SDL.pollEvent
+                let act = mkAction mEvent
+
+                case act of
+                  KeyInput km -> modifyIORef inputsRef (updateInputs km) -- update inputsRef
+                  _           -> return ()
+                -- read inputsRef
+                inputs <- readIORef inputsRef
+
+                case act of
+                  Quit -> return $ Right ()
+                  _    -> do
+                    let ns       = update inputs s
+                        drawOps  = draw ns
+                        soundOps = sound ns
+                    sequence_ $ drawOps <*> [ra]
+                    sequence_ $ soundOps <*> [chnks]
+                    SDL.present r
+                    return $ Left ns
+              )
+              initialState
+ where
+  img     = mAsset >>= image
+  snds    = toList mAsset >>= sounds
+  mapData = toList mAsset >>= imap
diff --git a/src/Piyo/Asset.hs b/src/Piyo/Asset.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Asset.hs
@@ -0,0 +1,11 @@
+-- | Asset for draw and sound.
+module Piyo.Asset
+    ( defaultAsset
+    )
+where
+
+import           Piyo.Internal.Types
+
+-- | Empty asset.
+defaultAsset :: Asset
+defaultAsset = Asset {sounds = [], image = Nothing, imap = []}
diff --git a/src/Piyo/Draw.hs b/src/Piyo/Draw.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Draw.hs
@@ -0,0 +1,197 @@
+-- | Draw functions.
+-- Last two arguments -> X -> Y is the drawing origin.
+module Piyo.Draw
+    ( cls
+    , dot
+    , line
+    , rect
+    , rectfill
+    , circ
+    , circfill
+    , tri
+    , trifill
+    , spr
+    , sprn
+    , sprc
+    , txt
+    , txtb
+    , mp
+    )
+where
+
+import           Control.Monad.IO.Class         ( MonadIO )
+import           Control.Monad
+import           Foreign.C.Types                ( CInt )
+import           SDL                            ( ($=) )
+import qualified SDL
+import qualified SDL.Primitive
+import qualified SDL.Image
+import qualified SDL.Font
+import           Piyo.Internal.Types
+import           Piyo.Internal.Constants
+import           Data.Word                      ( Word8 )
+import           Data.Text                      ( pack )
+
+
+-- | Clear the screen with given color.
+cls :: MonadIO m => Color -> DrawOp m
+cls c ra = setColor c ra >> SDL.clear (renderer ra)
+
+-- | Draw rectangle with given color.
+-- Origin is left-top.
+rect :: MonadIO m => Color -> Width -> Height -> X -> Y -> DrawOp m
+rect = rectF SDL.Primitive.rectangle
+
+rectfill :: MonadIO m => Color -> Width -> Height -> X -> Y -> DrawOp m
+rectfill = rectF SDL.Primitive.fillRectangle
+
+rectF f c w h x y = \ra -> f (renderer ra) pa pb col
+  where
+    pa  = makePos x y
+    pb  = makePos (x + w) (y + h)
+    col = colorCode c
+
+dot :: MonadIO m => Color -> X -> Y -> DrawOp m
+dot c x y = \ra -> SDL.Primitive.pixel (renderer ra) p col
+  where
+    p   = makePos x y
+    col = colorCode c
+
+line :: MonadIO m => Color -> X -> Y -> X -> Y -> DrawOp m
+line c xa ya xb yb = \ra -> SDL.Primitive.line (renderer ra) pa pb col
+  where
+    pa  = makePos xa ya
+    pb  = makePos xb yb
+    col = colorCode c
+
+-- | Draw circle with given color.
+-- Origin is center.
+circ :: MonadIO m => Color -> Radius -> X -> Y -> DrawOp m
+circ = circF SDL.Primitive.circle
+
+circfill :: MonadIO m => Color -> Radius -> X -> Y -> DrawOp m
+circfill = circF SDL.Primitive.fillCircle
+
+circF f c r x y = \ra -> f (renderer ra) p r' col
+  where
+    p   = makePos x y
+    r'  = fromIntegral r
+    col = colorCode c
+
+tri :: MonadIO m => Color -> X -> Y -> X -> Y -> X -> Y -> DrawOp m
+tri = triF SDL.Primitive.triangle
+
+trifill :: MonadIO m => Color -> X -> Y -> X -> Y -> X -> Y -> DrawOp m
+trifill = triF SDL.Primitive.fillTriangle
+
+triF f c xa ya xb yb xc yc = \ra -> f (renderer ra) pa pb pc col
+  where
+    pa  = makePos xa ya
+    pb  = makePos xb yb
+    pc  = makePos xc yc
+    col = colorCode c
+
+-- | Draw sprite.
+-- Origin is left-top.
+spr :: MonadIO m => IdX -> IdY -> X -> Y -> DrawOp m
+spr xId yId = sprn xId yId 1 1
+
+-- | Draw sprite for large size.
+sprn :: MonadIO m => IdX -> IdY -> CntX -> CntY -> X -> Y -> DrawOp m
+sprn xId yId bw bh = sprc x' y' w h
+  where
+    x' = boxSize * xId
+    y' = boxSize * yId
+    w  = boxSize * bw
+    h  = boxSize * bh
+
+-- | Draw sprite for any size.
+sprc :: MonadIO m => X -> Y -> Width -> Height -> X -> Y -> DrawOp m
+sprc xs ys w h xt yt ra = case mTxtr of
+    Just txtr -> SDL.copy rdr txtr (Just rs) (Just rt)
+    Nothing   -> return ()
+  where
+    mTxtr = mImage ra
+    rdr   = renderer ra
+    rs    = makeRect xs ys w h
+    rt    = makeRect xt yt w h
+
+-- | Draw text with given color.
+-- Font is JF-Dot-K12.
+-- Origin is left-top.
+txt :: MonadIO m => Color -> String -> X -> Y -> DrawOp m
+txt = txtF plain
+
+-- | Draw bold text with given color.
+-- Font is JF-Dot-K12B.
+-- Origin is left-top.
+txtb :: MonadIO m => Color -> String -> X -> Y -> DrawOp m
+txtb = txtF bold
+
+txtF f c t x y ra = do
+    sfc    <- SDL.Font.solid font' col t'
+    txtr   <- SDL.createTextureFromSurface rdr sfc
+    (w, h) <- SDL.Font.size font' t'
+    let rt = makeRect x y w h in SDL.copy rdr txtr Nothing (Just rt)
+    SDL.freeSurface sfc
+    SDL.destroyTexture txtr
+  where
+    t'    = pack t
+    font' = f $ font ra
+    rdr   = renderer ra
+    col   = colorCode c
+
+-- | Draw map (a set of sprites).
+-- Origin is left-top.
+mp :: MonadIO m => IdX -> IdY -> CntX -> CntY -> X -> Y -> DrawOp m
+mp mx my mw mh x y ra = forM_
+    im
+    (\(vrt, il) -> forM_
+        il
+        (\(hr, (nx, ny)) -> do
+            let w  = boxSize
+                h  = boxSize
+                xs = w * nx
+                ys = h * ny
+                xt = x + w * (mx + hr)
+                yt = y + h * (my + vrt)
+            unless (hr > mw || vrt > mh) $ sprc xs ys w h xt yt ra
+        )
+    )
+  where
+    m  = imageMap ra
+    l  = zip [0 ..]
+    im = l $ (map l) m
+
+
+setColor :: MonadIO m => Color -> DrawOp m
+setColor c ra = SDL.rendererDrawColor (renderer ra) $= colorCode c
+
+colorCode :: Color -> SDL.V4 Word8
+colorCode Black      = SDL.V4 0 0 0 maxBound
+colorCode DarkBlue   = SDL.V4 29 43 83 maxBound
+colorCode DarkPurple = SDL.V4 126 37 83 maxBound
+colorCode DarkGreen  = SDL.V4 0 135 81 maxBound
+colorCode Brown      = SDL.V4 171 82 54 maxBound
+colorCode DarkGray   = SDL.V4 95 87 79 maxBound
+colorCode LightGray  = SDL.V4 194 195 199 maxBound
+colorCode White      = SDL.V4 maxBound 241 232 maxBound
+colorCode Red        = SDL.V4 maxBound 0 77 maxBound
+colorCode Orange     = SDL.V4 maxBound 163 0 maxBound
+colorCode Yellow     = SDL.V4 maxBound 236 39 maxBound
+colorCode Green      = SDL.V4 0 228 54 maxBound
+colorCode Blue       = SDL.V4 41 173 maxBound maxBound
+colorCode Indigo     = SDL.V4 131 118 156 maxBound
+colorCode Pink       = SDL.V4 maxBound 119 168 maxBound
+colorCode Peach      = SDL.V4 maxBound 204 170 maxBound
+
+makePos :: Int -> Int -> SDL.V2 CInt
+makePos x y = SDL.V2 x' y' where (x', y') = (fromIntegral x, fromIntegral y)
+
+makeRect :: Int -> Int -> Int -> Int -> SDL.Rectangle CInt
+makeRect x y w h = SDL.Rectangle o z
+  where
+    (x', y', w', h') =
+        (fromIntegral x, fromIntegral y, fromIntegral w, fromIntegral h)
+    o = SDL.P (SDL.V2 x' y')
+    z = SDL.V2 w' h'
diff --git a/src/Piyo/Game.hs b/src/Piyo/Game.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Game.hs
@@ -0,0 +1,28 @@
+-- | Game class.
+-- Functions are called in order of update, draw, sound every frame.
+module Piyo.Game
+    ( Game(..)
+    )
+where
+
+import           Control.Monad.IO.Class         ( MonadIO )
+import           Piyo.Internal.Types
+
+-- | Game class.
+-- s is a state that user can flexibly define.
+class Game s where
+    -- | Update state.
+    -- The state next frame is determined by keyboard input and previous state. 
+    -- There is no side effect in this function.
+    update :: [Input] -> s -> s
+    update _ = id
+
+    -- | Draw.
+    -- Oprations are determined by state.
+    draw :: MonadIO m => s -> [DrawOp m]
+    draw _ = []
+
+    -- | Sound.
+    -- Oprations are determined by state.
+    sound :: MonadIO m => s -> [SoundOp m]
+    sound _ = []
diff --git a/src/Piyo/Internal/Common.hs b/src/Piyo/Internal/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Internal/Common.hs
@@ -0,0 +1,80 @@
+module Piyo.Internal.Common
+    ( withSDL
+    , withWindow
+    , withRenderer
+    , withSound
+    , withImage
+    , withFont
+    )
+where
+
+import           Paths_piyo
+import qualified SDL
+import           SDL                            ( ($=) )
+import qualified SDL.Font
+import qualified SDL.Mixer
+import qualified SDL.Image
+import           Control.Monad                  ( void )
+import           Control.Monad.IO.Class         ( MonadIO )
+import           Data.Traversable               ( forM )
+import           Data.Foldable                  ( forM_ )
+import           Piyo.Internal.Types
+import           Piyo.Internal.Constants
+import           Piyo.Internal.Config
+import           Piyo.Game
+
+
+withSDL :: (MonadIO m) => m a -> m ()
+withSDL op = do
+    SDL.initialize []
+    void op
+    SDL.quit
+
+withWindow :: (MonadIO m) => (SDL.Window -> m a) -> m ()
+withWindow op = do
+    w <- SDL.createWindow title windowConfig
+    SDL.windowMinimumSize w $= SDL.windowInitialSize windowConfig
+    SDL.showWindow w
+    void $ op w
+    SDL.destroyWindow w
+
+withRenderer :: (MonadIO m) => SDL.Window -> (SDL.Renderer -> m a) -> m ()
+withRenderer w op = do
+    r  <- SDL.createRenderer w (-1) rendererConfig
+    ws <- SDL.get $ SDL.windowSize w
+    SDL.rendererLogicalSize r $= Just ws
+    void $ op r
+    SDL.destroyRenderer r
+
+withSound :: [FilePath] -> SoundOp IO -> IO ()
+withSound []     op = void $ op []
+withSound sounds op = SDL.Mixer.withAudio audioConfig chunkSize $ do
+    SDL.Mixer.initialize audioFlugs
+    chnks <- forM sounds SDL.Mixer.load
+    void $ op chnks
+    forM_ chnks SDL.Mixer.free
+    SDL.Mixer.quit
+
+withImage
+    :: MonadIO m
+    => Maybe FilePath
+    -> SDL.Renderer
+    -> (Maybe SDL.Texture -> m ())
+    -> m ()
+withImage (Just path) r op = do
+    txtr <- SDL.Image.loadTexture r path
+    void $ op (Just txtr)
+    SDL.destroyTexture txtr
+withImage Nothing r op = void $ op Nothing
+
+withFont :: (Font -> IO ()) -> IO ()
+withFont op = do
+    SDL.Font.initialize
+    fontPath'  <- Paths_piyo.getDataFileName fontPath
+    fontBPath' <- Paths_piyo.getDataFileName fontBPath
+    fontPlain  <- SDL.Font.load fontPath' fontSize
+    fontBold   <- SDL.Font.load fontBPath' fontSize
+    void $ op $ Font {plain = fontPlain, bold = fontBold}
+    SDL.Font.free fontPlain
+    SDL.Font.free fontBold
+    SDL.Font.quit
diff --git a/src/Piyo/Internal/Config.hs b/src/Piyo/Internal/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Internal/Config.hs
@@ -0,0 +1,42 @@
+module Piyo.Internal.Config
+    ( windowConfig
+    , rendererConfig
+    , audioConfig
+    , audioFlugs
+    )
+where
+
+import qualified SDL
+import qualified SDL.Mixer
+import Piyo.Internal.Constants
+
+
+windowConfig :: SDL.WindowConfig
+windowConfig = SDL.defaultWindow
+    { SDL.windowInitialSize = makeV2 windowSize
+    , SDL.windowResizable   = True
+    }
+    where
+        makeV2 (x, y) = SDL.V2 (fromIntegral x) (fromIntegral y)
+
+rendererConfig :: SDL.RendererConfig
+rendererConfig = SDL.RendererConfig
+    { SDL.rendererType          = SDL.AcceleratedVSyncRenderer
+    , SDL.rendererTargetTexture = False
+    }
+
+audioConfig :: SDL.Mixer.Audio
+audioConfig = SDL.Mixer.Audio
+    { SDL.Mixer.audioFrequency = sampleFrequency
+    , SDL.Mixer.audioFormat    = SDL.Mixer.FormatS16_Sys -- NOTE: output sample format
+    , SDL.Mixer.audioOutput    = SDL.Mixer.Stereo
+    }
+
+audioFlugs :: [SDL.Mixer.InitFlag]
+audioFlugs =
+    [ SDL.Mixer.InitMOD
+    , SDL.Mixer.InitOGG
+    -- , SDL.Mixer.InitMP3
+    -- , SDL.Mixer.InitFLAC
+    ]
+
diff --git a/src/Piyo/Internal/Constants.hs b/src/Piyo/Internal/Constants.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Internal/Constants.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Piyo.Internal.Constants
+    ( windowSize
+    , title
+    , fontPath
+    , fontBPath
+    , fontSize
+    , boxSize
+    , chunkSize
+    , sampleFrequency
+    )
+where
+
+import           Data.Text
+
+
+windowSize :: (Int, Int)
+windowSize = (192, 192)
+
+title :: Text
+title = "PIYO"
+
+fontPath :: FilePath
+fontPath = "fonts/JF-Dot-K12.ttf"
+
+fontBPath :: FilePath
+fontBPath = "fonts/JF-Dot-K12B.ttf"
+
+fontSize :: Int
+fontSize = 12
+
+boxSize :: Int
+boxSize = 12
+
+-- TODO: adjust
+chunkSize :: Int
+chunkSize = 16
+
+sampleFrequency :: Int
+sampleFrequency = 44100
diff --git a/src/Piyo/Internal/InputState.hs b/src/Piyo/Internal/InputState.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Internal/InputState.hs
@@ -0,0 +1,52 @@
+module Piyo.Internal.InputState
+    ( updateInputs
+    , mkAction
+    , extractPayload
+    , payloadToAction
+    , getKey
+    )
+where
+
+import qualified SDL
+import           Data.List
+import           Piyo.Internal.Common
+import           Piyo.Internal.Types
+
+
+updateInputs :: KeyMotion -> [Input] -> [Input]
+updateInputs (Pressed  i) is = union is [i]
+updateInputs (Released i) is = is \\ [i]
+
+mkAction :: Maybe SDL.Event -> Action
+mkAction = maybe Idle (payloadToAction . extractPayload)
+
+extractPayload :: SDL.Event -> SDL.EventPayload
+extractPayload (SDL.Event _ p) = p
+
+payloadToAction :: SDL.EventPayload -> Action
+payloadToAction SDL.QuitEvent         = Quit
+payloadToAction (SDL.KeyboardEvent k) = getKey k
+payloadToAction _                     = Idle
+
+getKey :: SDL.KeyboardEventData -> Action
+getKey (SDL.KeyboardEventData _ SDL.Pressed False keysym) =
+    case SDL.keysymKeycode keysym of
+        SDL.KeycodeEscape -> Quit
+        SDL.KeycodeUp     -> KeyInput (Pressed BtnUp)
+        SDL.KeycodeDown   -> KeyInput (Pressed BtnDown)
+        SDL.KeycodeLeft   -> KeyInput (Pressed BtnLeft)
+        SDL.KeycodeRight  -> KeyInput (Pressed BtnRight)
+        SDL.KeycodeZ      -> KeyInput (Pressed BtnA)
+        SDL.KeycodeX      -> KeyInput (Pressed BtnB)
+        _                 -> Idle
+getKey (SDL.KeyboardEventData _ SDL.Released False keysym) =
+    case SDL.keysymKeycode keysym of
+        SDL.KeycodeEscape -> Quit
+        SDL.KeycodeUp     -> KeyInput (Released BtnUp)
+        SDL.KeycodeDown   -> KeyInput (Released BtnDown)
+        SDL.KeycodeLeft   -> KeyInput (Released BtnLeft)
+        SDL.KeycodeRight  -> KeyInput (Released BtnRight)
+        SDL.KeycodeZ      -> KeyInput (Pressed BtnA)
+        SDL.KeycodeX      -> KeyInput (Pressed BtnB)
+        _                 -> Idle
+getKey _ = Idle
diff --git a/src/Piyo/Internal/Types.hs b/src/Piyo/Internal/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Internal/Types.hs
@@ -0,0 +1,70 @@
+module Piyo.Internal.Types where
+
+import qualified SDL
+import qualified SDL.Font
+import qualified SDL.Mixer
+
+
+data Action = KeyInput KeyMotion | Idle | Quit
+  deriving Show
+
+data KeyMotion
+  = Pressed Input
+  | Released Input
+  deriving Show
+
+-- | Keyboard input patterns.
+data Input = BtnUp | BtnDown | BtnLeft | BtnRight | BtnA | BtnB
+  deriving (Show, Eq)
+
+-- | Color patterns.
+-- Same as PICO-8 color palette.
+data Color
+  = Black | DarkBlue | DarkPurple | DarkGreen
+  | Brown | DarkGray | LightGray | White
+  | Red | Orange | Yellow | Green
+  | Blue | Indigo | Pink | Peach
+  deriving (Show, Eq)
+
+-- | Asset data for draw and sound.
+data Asset = Asset
+  { sounds :: [FilePath]
+  , image :: Maybe FilePath
+  , imap :: ImageMap
+  }
+  deriving Show
+
+-- | 2d nested list.
+-- The value means sprite indices.
+type ImageMap = [[(IdX, IdY)]]
+
+data RenderAsset = RenderAsset
+  { renderer :: SDL.Renderer
+  , font :: Font
+  , mImage :: Maybe SDL.Texture
+  , imageMap :: ImageMap
+  }
+
+data Font = Font
+  { plain :: SDL.Font.Font
+  , bold :: SDL.Font.Font
+  }
+  deriving Show
+
+type DrawOp m = RenderAsset -> m ()
+
+type SoundOp m = [SDL.Mixer.Chunk] -> m ()
+
+-- | Posision value.
+type X = Int
+type Y = Int
+-- | Length.
+type Width = Int
+type Height = Int
+type Radius = Int
+-- | Index number.
+type IdX = Int
+type IdY = Int
+-- | Count number.
+type CntX = Int
+type CntY = Int
diff --git a/src/Piyo/Sound.hs b/src/Piyo/Sound.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Sound.hs
@@ -0,0 +1,40 @@
+-- | Sound functions.
+-- It may be more development.
+module Piyo.Sound
+    ( ply
+    , hlt
+    , hltall
+    )
+where
+
+import           Control.Monad.IO.Class         ( MonadIO )
+import           Control.Monad
+import qualified SDL.Mixer
+import           Piyo.Internal.Types
+import           Piyo.Internal.Config
+
+
+-- | Play sound on given channel.
+-- Specify the sound by given index.
+-- If the channel is already taken, halt previous then play.
+ply :: MonadIO m => SDL.Mixer.Channel -> Int -> SoundOp m
+ply ch n []    = return ()
+ply ch n chnks = do
+    hlt ch chnks
+    forM_ chnk (SDL.Mixer.playOn ch SDL.Mixer.Once)
+    where chnk = sliceOne n chnks
+
+-- | Halt sound on given channel.
+-- If the channel is already halted, do nothing.
+hlt :: MonadIO m => SDL.Mixer.Channel -> SoundOp m
+hlt ch [] = return ()
+hlt ch _  = do
+    isPlaying <- SDL.Mixer.playing ch
+    when isPlaying $ SDL.Mixer.halt ch
+
+-- | Halt sounds on all channels.
+hltall :: MonadIO m => SoundOp m
+hltall = return $ SDL.Mixer.halt SDL.Mixer.AllChannels
+
+sliceOne i xs = take 1 $ drop i xs
+
diff --git a/src/Piyo/Types.hs b/src/Piyo/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Piyo/Types.hs
@@ -0,0 +1,20 @@
+-- | Types.
+-- Type synonyms are used for readability for now.
+module Piyo.Types
+    ( Input(..)
+    , Color(..)
+    , Asset(..)
+    , ImageMap
+    , X
+    , Y
+    , Width
+    , Height
+    , Radius
+    , IdX
+    , IdY
+    , CntX
+    , CntY
+    )
+where
+
+import           Piyo.Internal.Types
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+main :: IO ()
+main = putStrLn "Test suite not yet implemented"
