implementation module StdGameDef

import	StdString
from	StdFunc			import :: St
from	StdOverloaded	import class zero
from	StdIOBasic		import :: Point2, :: Size, :: IdFun
from	StdMaybe		import :: Maybe
from	StdPictureDef	import :: Colour
import	StdGSt

:: GameAttribute gs
   = ScreenSize Size     // screen resolution, default 320x240
   | ColorDepth Int      // screen color depth, default 8 (256 colors)

:: Game gs
   = { levels        :: [Level (GSt gs)]        // levels
     , quitlevel     :: St (GSt gs) Bool        // True quits the game
     , nextlevel     :: St (GSt gs) Int         // new level if >0 (0 quits)
     , textitems     :: St (GSt gs) [GameText]  // all text items
     }

:: Level state
   = { boundmap      :: !BoundMap               // map of all static bounds in a level
     , initpos       :: !Point2                 // center of screen in boundmap
     , layers        :: ![Layer]                // all layers [back..front]
     , objects       :: ![GameObjectLS state]   // all other objects in the level
     , music         :: !Maybe Music            // background music
     , soundsamples  :: ![SoundSample]          // list of sound samples
     , leveloptions  :: !LevelOptions           // level options
     }

:: LevelOptions
   = { fillbackground  :: !Maybe Colour    // fill the screen before drawing the layers
     , escquit         :: !Bool            // quit the level whenever the Esc key is pressed
     , debugscroll     :: !Bool            // scroll through the level with the arrow keys
     , fadein          :: !Bool            // fade in from black
     , fadeout         :: !Bool            // fade out to black
     }

:: Music
   = { musicfile :: !String     // MIDI file
     , restart   :: !Bool       // restart the music when it ends?
     , continue  :: !Bool       // music continues after end of level
     }

:: SoundSample
   = { soundid      :: !SoundID    // id for the sample (any number)
     , soundfile    :: !String     // WAV file
     , soundbuffers :: !Int        // max times sample can be played together
     }

:: SoundID
   :== Int

:: BoundMap
   = { map       :: ![{#Int}]  // map of all static bounds
     , blocksize :: !Size      // size of the map units (in pixels)
     , objstart  :: !Int       // min. value for objects (lower values are ignored)
     , startobjx :: !Int       // X-distance from screen where objects are initialized
     , startobjy :: !Int       // Y-distance from screen where objects are initialized
     }

:: GameRegion
   :== [Int]

:: Bounds
   :== Int

:: DirectionSet
   = { top    :: !Bool
     , left   :: !Bool
     , bottom :: !Bool
     , right  :: !Bool
     }

:: Layer
   = { bmp       :: !GameBitmap        // bitmap that contains all the tiles
     , layermap  :: !LayerMap          // map of the tiles in the level
     , sequences :: ![TileSequence]    // tiles that change repeatedly
     , movement  :: !Movement          // function to scroll the layer
     }

:: GameTime
   :== Int       // time in frames

:: GameBitmap
   = { bitmapname  :: !String        // bitmap that contains smaller blocks
     , unitsize    :: !Size          // size of these blocks (width, height)
     , dimensions  :: !(!Int,!Int)   // number of blocks (horizontal, vertical)
     , transparent :: !Maybe Point2  // position of a transparent pixel
     }

:: LayerMap
   :== [{#Int}]  // map of block numbers in a bitmap
                 // 0: empty; 1..n: block number; n+1..2n: mirror block;
                 // 2n+1..3n: upsidedown; 3n+1..4n: mirror and upsidedown;
                 // -1..-m: block sequence number
                 // (n = # blocks in gamebitmap; m = # sequences)

:: TileSequence
   :== (!Int, Sequence)   // block sequence number, Sequence

:: Sequence
   :== [(Int, Int)]   // tile number, duration

:: Movement
   :== Point2 GameTime -> Point2   // calculate layer's position from game position

:: SpriteID
   :== Int

:: Sprite
   = { bitmap   :: !GameBitmap   // sprites may have their own bitmap
     , sequence :: !Sequence     // seqence of blocks
     , loop     :: !Bool         // if FALSE, callback animation function
     }

:: GameObjectLS gs
   = E. state: GameObjectLS (GameObject state gs)
:: GameObject state gs
// = E. state:
   = { objectcode :: !ObjectCode    // code for object type (0 AutoInitObject)
     , sprites    :: ![Sprite]      // sprite 1..n
     , init       :: !SubCode Point2 GameTime gs -> GameObjectState state gs
     , done       :: !(GameObjectState state gs)            -> gs
     , move       :: !                                         ObjectFun state gs
     , animation  :: !                                         ObjectFun state gs
     , touchbound :: !DirectionSet MapCode                  -> ObjectFun state gs
     , collide    :: !DirectionSet ObjectCode GameObjectRec -> ObjectFun state gs
     , frametimer :: !                                         ObjectFun state gs
     , keydown    :: !KeyCode                               -> ObjectFun state gs
     , keyup      :: !KeyCode                               -> ObjectFun state gs
     , userevent  :: !EventCode EventPar EventPar           -> ObjectFun state gs
     }
     
:: *GameObjectState state gs
   = { st :: state             // object state
     , or :: GameObjectRec     // object record
     , gs :: gs                // game state
     }

:: ObjectFun state gs
   :== IdFun (GameObjectState state gs)

:: ObjectCode
   :== Int

:: SubCode
   :== Int

:: MapCode
   :== Int

:: KeyCode
   :== Int

:: EventCode
   :== Int

:: EventPar
   :== Int

:: FVXY
   = { fvx  :: !FV
     , fvy  :: !FV
     }

:: FV
   = Factor !Real
   | Value !Real

:: RealXY
   = { rx :: !Real
     , ry :: !Real
     }

:: GameObjectRec
   = { active              :: !Bool            // move and check collisions?
     , subcode             :: !SubCode         // object's sub code
     , size                :: !Size            // the actual size
     , pos                 :: !Point2          // current position
     , offset              :: !Point2          // relative offset for sprite
     , currentsprite       :: !Int             // current animation sequence
     , displayoptions      :: !DisplayOptions  // invisible/mirror etc.
     , ownbounds           :: !Bounds          // bound(s) of the object (bits)
     , bouncebounds        :: !Bounds          // just bounce against these bounds
     , collidebounds       :: !Bounds          // call collide func for these bounds
     , forgetdistance      :: !Point2          // make object inactive at distance
     , framecounter        :: !GameTime        // frame counter
     , layer               :: !LayerPosition   // layer the object moves in front of
     , acceleration        :: !RealXY          // x/y acceleration
     , speed               :: !RealXY          // object's x/y speed
     , bounce              :: !FVXY            // x/y bounce at static bounds * 256
     , maxspeed            :: !RealXY          // x/y maximum speed
     , slowdown            :: !FVXY            // x/y slow down
     , skipmove            :: !Int             // acceleration delay
     , options             :: !ObjectOptions   // object options
     }

:: DisplayOptions
   = { blink               :: !Bool       // object blinks
     , stretch             :: !Bool       // stretch sprite to fit in size
     , mirrorleftright     :: !Bool       // mirror the sprite
     , mirrorupdown        :: !Bool       // draw sprite up side down
     , rotation            :: !Rotation   // rotation
     }

:: Rotation
   = NoRotation | Rotate90 | Rotate180 | Rotate270

:: ObjectOptions
   = { ignorelevelbounds   :: !Bool         // object can move out of the level
     , checkkeyboard       :: !Bool         // generate keydown event for this object
     , allowkeyboardrepeat :: !Bool         // allow pressed key to repeat
     , static              :: !Bool         // object always moves with screen
     , hdirection          :: !HDirection   // horizontal direction of the object
     , vdirection          :: !VDirection   // vertical direction of the object
     , automirrorleftright :: !Bool         // mirror object if horizontal direction changes
     , automirrorupdown    :: !Bool         // mirror object if vertical direction changes
     , freeze              :: !Bool         // no movement at all until framecounter reaches 0
     , removemapcode       :: !Bool         // remove the object completely from the map?
     }

:: HDirection
   = DirLeft | DirRight

:: VDirection
   = DirUp | DirDown

:: LayerPosition
   = InFront | AtLayer Int

:: GameText
   = { format    :: !String        // text to display or formatstring for value
     , value     :: Maybe Int      // value to display
     , position  :: !Point2        // position on screen
     , style     :: !Style         // style to write the text in
     , color     :: Colour         // color for the text
     , shadow    :: Maybe Shadow   // shadow
     , alignment :: TextAlignment  // text alignment
     }

:: TextAlignment
   = { xyfromscreencenter :: !(!Bool,!Bool)   // center position on the screen
     , xycentered         :: !(!Bool,!Bool)   // center text around position
     }

:: Style
   = { fontname :: !String   // any font name
     , fontsize :: !Int      // size of the text
     , bold     :: !Bool     // bold
     , italic   :: !Bool     // italic
     }

:: Shadow
   = { shadowpos   :: !Point2   // relative position of the shadow to the text
     , shadowcolor :: !Colour    // color of the shadow
     }


instance zero RealXY
where
   zero = {rx = 0.0, ry = 0.0}

instance zero TextAlignment
where
   zero = { xyfromscreencenter = (False, False)
          , xycentered = (False, False)
          }
