packages feed

FunGEn 1.0.1 → 1.1

raw patch · 12 files changed

+1208/−273 lines, 12 filesdep ~FunGEndep ~basebinary-addedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: FunGEn, base

API changes (from Hackage documentation)

- Graphics.UI.Fungen: Position :: ~GLint -> ~GLint -> Position
+ Graphics.UI.Fungen: Position :: !GLint -> !GLint -> Position
- Graphics.UI.Fungen: addObjectsToGroup :: [(GameObject s)] -> String -> IOGame t s u v ()
+ Graphics.UI.Fungen: addObjectsToGroup :: [GameObject s] -> String -> IOGame t s u v ()
- Graphics.UI.Fungen: addObjectsToManager :: [(GameObject t)] -> String -> [(ObjectManager t)] -> [(ObjectManager t)]
+ Graphics.UI.Fungen: addObjectsToManager :: [GameObject t] -> String -> [ObjectManager t] -> [ObjectManager t]
- Graphics.UI.Fungen: addObjectsToNewGroup :: [(GameObject s)] -> String -> IOGame t s u v ()
+ Graphics.UI.Fungen: addObjectsToNewGroup :: [GameObject s] -> String -> IOGame t s u v ()
- Graphics.UI.Fungen: createGame :: GameMap v -> [(ObjectManager s)] -> WindowConfig -> u -> t -> FilePictureList -> IO (Game t s u v)
+ Graphics.UI.Fungen: createGame :: GameMap v -> [ObjectManager s] -> WindowConfig -> u -> t -> FilePictureList -> IO (Game t s u v)
- Graphics.UI.Fungen: data BitmapFont :: *
+ Graphics.UI.Fungen: data BitmapFont
- Graphics.UI.Fungen: data Key :: *
+ Graphics.UI.Fungen: data Key
- Graphics.UI.Fungen: data Modifiers :: *
+ Graphics.UI.Fungen: data Modifiers
- Graphics.UI.Fungen: data MouseButton :: *
+ Graphics.UI.Fungen: data MouseButton
- Graphics.UI.Fungen: data Position :: *
+ Graphics.UI.Fungen: data Position
- Graphics.UI.Fungen: data SpecialKey :: *
+ Graphics.UI.Fungen: data SpecialKey
- Graphics.UI.Fungen: destroyGameObject :: String -> String -> [(ObjectManager t)] -> [(ObjectManager t)]
+ Graphics.UI.Fungen: destroyGameObject :: String -> String -> [ObjectManager t] -> [ObjectManager t]
- Graphics.UI.Fungen: destroyObjects :: [(GameObject s)] -> IOGame t s u v ()
+ Graphics.UI.Fungen: destroyObjects :: [GameObject s] -> IOGame t s u v ()
- Graphics.UI.Fungen: drawGameObjects :: [(ObjectManager t)] -> QuadricPrimitive -> [TextureObject] -> IO ()
+ Graphics.UI.Fungen: drawGameObjects :: [ObjectManager t] -> QuadricPrimitive -> [TextureObject] -> IO ()
- Graphics.UI.Fungen: findObjectFromId :: GameObject t -> [(ObjectManager t)] -> GameObject t
+ Graphics.UI.Fungen: findObjectFromId :: GameObject t -> [ObjectManager t] -> GameObject t
- Graphics.UI.Fungen: funInit :: WindowConfig -> GameMap v -> [(ObjectManager s)] -> u -> t -> [InputBinding t s u v] -> IOGame t s u v () -> RefreshType -> FilePictureList -> IO ()
+ Graphics.UI.Fungen: funInit :: WindowConfig -> GameMap v -> [ObjectManager s] -> u -> t -> [InputBinding t s u v] -> IOGame t s u v () -> RefreshType -> FilePictureList -> IO ()
- Graphics.UI.Fungen: getObjectManagerObjects :: ObjectManager t -> [(GameObject t)]
+ Graphics.UI.Fungen: getObjectManagerObjects :: ObjectManager t -> [GameObject t]
- Graphics.UI.Fungen: getObjectManagers :: IOGame t s u v [(ObjectManager s)]
+ Graphics.UI.Fungen: getObjectManagers :: IOGame t s u v [ObjectManager s]
- Graphics.UI.Fungen: getObjectsFromGroup :: String -> IOGame t s u v [(GameObject s)]
+ Graphics.UI.Fungen: getObjectsFromGroup :: String -> IOGame t s u v [GameObject s]
- Graphics.UI.Fungen: moveGameObjects :: [(ObjectManager t)] -> [(ObjectManager t)]
+ Graphics.UI.Fungen: moveGameObjects :: [ObjectManager t] -> [ObjectManager t]
- Graphics.UI.Fungen: multiMap :: [(GameMap t)] -> Int -> GameMap t
+ Graphics.UI.Fungen: multiMap :: [GameMap t] -> Int -> GameMap t
- Graphics.UI.Fungen: objectGroup :: String -> [(GameObject t)] -> (ObjectManager t)
+ Graphics.UI.Fungen: objectGroup :: String -> [GameObject t] -> ObjectManager t
- Graphics.UI.Fungen: objectListObjectCollision :: [(GameObject s)] -> GameObject s -> IOGame t s u v Bool
+ Graphics.UI.Fungen: objectListObjectCollision :: [GameObject s] -> GameObject s -> IOGame t s u v Bool
- Graphics.UI.Fungen: objectListObjectFutureCollision :: [(GameObject s)] -> GameObject s -> IOGame t s u v Bool
+ Graphics.UI.Fungen: objectListObjectFutureCollision :: [GameObject s] -> GameObject s -> IOGame t s u v Bool
- Graphics.UI.Fungen: pointsObjectListCollision :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> [(GameObject s)] -> IOGame t s u v Bool
+ Graphics.UI.Fungen: pointsObjectListCollision :: GLdouble -> GLdouble -> GLdouble -> GLdouble -> [GameObject s] -> IOGame t s u v Bool
- Graphics.UI.Fungen: searchObjectManager :: String -> [(ObjectManager t)] -> ObjectManager t
+ Graphics.UI.Fungen: searchObjectManager :: String -> [ObjectManager t] -> ObjectManager t
- Graphics.UI.Fungen: setObjectManagers :: [(ObjectManager s)] -> IOGame t s u v ()
+ Graphics.UI.Fungen: setObjectManagers :: [ObjectManager s] -> IOGame t s u v ()
- Graphics.UI.Fungen: type Tile t = (Int, Bool, Float, t) index of picture, possibility to move, cost to move, additional params
+ Graphics.UI.Fungen: type Tile t = (Int, Bool, Float, t) " index of picture, possibility to move, cost to move, additional params "
- Graphics.UI.Fungen: unless :: (Monad m) => Bool -> m () -> m ()
+ Graphics.UI.Fungen: unless :: Monad m => Bool -> m () -> m ()
- Graphics.UI.Fungen: updateObject :: (GameObject t -> GameObject t) -> Integer -> String -> [(ObjectManager t)] -> [(ObjectManager t)]
+ Graphics.UI.Fungen: updateObject :: (GameObject t -> GameObject t) -> Integer -> String -> [ObjectManager t] -> [ObjectManager t]
- Graphics.UI.Fungen: when :: (Monad m) => Bool -> m () -> m ()
+ Graphics.UI.Fungen: when :: Monad m => Bool -> m () -> m ()
- Graphics.UI.GLUT.Input: data Key :: *
+ Graphics.UI.GLUT.Input: data Key

Files

− CHANGES
@@ -1,76 +0,0 @@-1.0.1 2016/8/18--* add stack config for GHC 8-* fix building with current stackage/OpenGL/GLUT-* fix examples' wrong dependency--1.0 2015/9/21--* support GHC 7.10-* support stack-* repo moved to haskell-game organisation--0.4.6.1 2014/10/9--* update changelog--0.4.6 2014/10/9--* move to github, enable issue tracker--0.4.5 2014/10/8--* add executables to run examples (fungen-hello, fungen-pong, fungen-worms)--0.4.4 2014/10/7--* add missing files to cabal file (Samuel Gélineau)-* support & require latest OpenGL 2.9-* make IOGame also a Functor and Applicative, for ghc 7.10--0.4.3 2014/4/5--* set upper bound on OpenGL to avoid build failure with OpenGL 2.9--0.4.2 2013/08/07--* add q as quit key to examples-* fix buggy input when holding down keys on windows--0.4.1 2013/08/06--* reorganised and exposed more haddocks--0.4 2013/08/05--* a new hakyll-based website, incorporating the old site-* tested with GHC 7.6-* input handlers now receive mouse position and modifier state-  (inspired by Pradeep Kumar).  See fungentest.hs for examples.-* more haddocks--0.3 2011/02/13, Simon Michael--* updated for GHC 6.12 & base 4-* module names simplified-* beginning of haddockification-* docs moved into repo-* published to darcsden, hackage, wiki, haskell-cafe, #haskell-game, reddit--0.1-hackage 2008/09/17, Miloslav Raus--* first hackage release-* updated for GHC 6.10-* cabalised-* Tested under Win32 & Linux/Intel. Known glitches: Flickering under linux-  (at least on my shitty laptop). Weird pong paddle behavior under Win32.--0.1-ghc6.8 2008/02/26, Simon Michael--* updated for GHC 6.8-* slight tweaks to examples-* public darcs repo--0.1 2002, Andre Furtado--* first public release
+ CHANGES.md view
@@ -0,0 +1,82 @@+## 1.1 2020-10-18++* drop custom website, update readme+* set up CI testing+* update for GHC 8.8, 8.10++## 1.0.1 2016-08-18++* add stack config for GHC 8+* fix building with current stackage/OpenGL/GLUT+* fix examples' wrong dependency++## 1.0 2015-09-21++* support GHC 7.10+* support stack+* repo moved to haskell-game organisation++## 0.4.6.1 2014-10-09++* update changelog++## 0.4.6 2014-10-09++* move to github, enable issue tracker++## 0.4.5 2014-10-08++* add executables to run examples (fungen-hello, fungen-pong, fungen-worms)++## 0.4.4 2014-10-07++* add missing files to cabal file (Samuel Gélineau)+* support & require latest OpenGL 2.9+* make IOGame also a Functor and Applicative, for ghc 7.10++## 0.4.3 2014-04-05++* set upper bound on OpenGL to avoid build failure with OpenGL 2.9++## 0.4.2 2013-08-07++* add q as quit key to examples+* fix buggy input when holding down keys on windows++## 0.4.1 2013-08-06++* reorganised and exposed more haddocks++## 0.4 2013-08-05++* a new hakyll-based website, incorporating the old site+* tested with GHC 7.6+* input handlers now receive mouse position and modifier state+  (inspired by Pradeep Kumar).  See fungentest.hs for examples.+* more haddocks++## 0.3 2011-02-13, Simon Michael++* updated for GHC 6.12 & base 4+* module names simplified+* beginning of haddockification+* docs moved into repo+* published to darcsden, hackage, wiki, haskell-cafe, #haskell-game, reddit++## 0.1-hackage 2008-09-17, Miloslav Raus++* first hackage release+* updated for GHC 6.10+* cabalised+* Tested under Win32 & Linux/Intel. Known glitches: Flickering under linux+  (at least on my shitty laptop). Weird pong paddle behavior under Win32.++## 0.1-ghc6.8 2008-02-26, Simon Michael++* updated for GHC 6.8+* slight tweaks to examples+* public darcs repo++## 0.1 2002, Andre Furtado++* first public release
FunGEn.cabal view
@@ -1,13 +1,6 @@-name:               FunGEn
-version:            1.0.1
-copyright:          (C) 2002 Andre Furtado <awbf@cin.ufpe.br>
-license:            BSD3
-license-file:       LICENSE
-author:             Andre Furtado <awbf@cin.ufpe.br>
-maintainer:         Simon Michael <simon@joyful.com>
-homepage:           http://joyful.com/fungen
-category:           Game
-synopsis:           A lightweight, cross-platform, OpenGL/GLUT-based game engine.
+name:           FunGEn
+version:        1.1
+synopsis:       A lightweight, cross-platform, OpenGL/GLUT-based game engine.
 description:
     FunGEn (Functional Game Engine) is a BSD-licensed, cross-platform,
     OpenGL/GLUT-based, non-FRP game engine/framework written in
@@ -36,71 +29,121 @@ 
     This package receives only basic maintenance (see home page).
 
-stability:          beta
-cabal-version:      >= 1.8
-build-type:         Simple
-tested-with:        GHC==7.10.3, GHC==8.0
-extra-source-files: 
-                    README.md,
-                    CHANGES
+category:       Game
+stability:      beta
+homepage:       https://github.com/haskell-game/fungen
+bug-reports:    https://github.com/haskell-game/fungen/issues
+author:         Andre Furtado <awbf@cin.ufpe.br>
+maintainer:     Simon Michael <simon@joyful.com>
+copyright:      (C) 2002 Andre Furtado <awbf@cin.ufpe.br>
+license:        BSD3
+license-file:   LICENSE
+tested-with:    GHC==8.8.4, GHC==8.10.2
+build-type:     Simple
+cabal-version:  >= 1.10
+
+extra-source-files:
+    README.md
+    CHANGES.md
+    TUTORIAL.md
+    site/brief.gif
+    site/example.html
+    site/logo.gif
+    site/pong.png
+    site/tex.bmp
+    site/worms.png
+
 data-files:
-                    examples/pong/*.bmp,
-                    examples/worms/*.bmp
+    examples/pong/tex.bmp
+    examples/worms/border1.bmp
+    examples/worms/border2.bmp
+    examples/worms/border3.bmp
+    examples/worms/congratulations.bmp
+    examples/worms/food.bmp
+    examples/worms/free1.bmp
+    examples/worms/free2.bmp
+    examples/worms/free3.bmp
+    examples/worms/gameover.bmp
+    examples/worms/heade.bmp
+    examples/worms/headn.bmp
+    examples/worms/heads.bmp
+    examples/worms/headw.bmp
+    examples/worms/level1.bmp
+    examples/worms/level2.bmp
+    examples/worms/level3.bmp
+    examples/worms/segment.bmp
 
 source-repository head
-  type:     git
+  type: git
   location: https://github.com/haskell-game/fungen
 
 library
-  ghc-options:      -W
-  exposed-modules:  
-                    Graphics.UI.Fungen,
-                    Graphics.UI.Fungen.Display,
-                    Graphics.UI.Fungen.Game,
-                    Graphics.UI.Fungen.Init,
-                    Graphics.UI.Fungen.Input,
-                    Graphics.UI.Fungen.Loader,
-                    Graphics.UI.Fungen.Map,
-                    Graphics.UI.Fungen.Objects,
-                    Graphics.UI.Fungen.Text,
-                    Graphics.UI.Fungen.Timer,
-                    Graphics.UI.Fungen.Types,
-                    Graphics.UI.Fungen.Util,
-                    Graphics.UI.GLUT.Input
-
+  ghc-options: -W
   build-depends:
-                    base == 4.*
-                   ,base-compat
-                   ,OpenGL
-                   ,GLUT
-                   ,random
+      GLUT
+    , OpenGL
+    , base ==4.*
+    , base-compat
+    , random
+  exposed-modules:
+      Graphics.UI.Fungen
+      Graphics.UI.Fungen.Display
+      Graphics.UI.Fungen.Game
+      Graphics.UI.Fungen.Init
+      Graphics.UI.Fungen.Input
+      Graphics.UI.Fungen.Loader
+      Graphics.UI.Fungen.Map
+      Graphics.UI.Fungen.Objects
+      Graphics.UI.Fungen.Text
+      Graphics.UI.Fungen.Timer
+      Graphics.UI.Fungen.Types
+      Graphics.UI.Fungen.Util
+      Graphics.UI.GLUT.Input
+  other-modules:
+      Paths_FunGEn
+  default-language: Haskell2010
 
 executable fungen-hello
+  main-is: hello.hs
+  hs-source-dirs:
+      examples
   ghc-options: -W
-  hs-source-dirs: examples
-  main-is:          hello.hs
-  build-depends:    FunGEn == 1.0.*
-                   ,base
-                   ,OpenGL
-                   ,GLUT
-                   ,random
+  build-depends:
+      FunGEn ==1.1.*
+    , GLUT
+    , OpenGL
+    , base
+    , random
+  other-modules:
+      Paths_FunGEn
+  default-language: Haskell2010
 
 executable fungen-pong
+  main-is: pong/pong.hs
+  hs-source-dirs:
+      examples
   ghc-options: -W
-  hs-source-dirs: examples
-  main-is:          pong/pong.hs
-  build-depends:    FunGEn == 1.0.*
-                   ,base
-                   ,OpenGL
-                   ,GLUT
-                   ,random
+  build-depends:
+      FunGEn ==1.1.*
+    , GLUT
+    , OpenGL
+    , base
+    , random
+  other-modules:
+      Paths_FunGEn
+  default-language: Haskell2010
 
 executable fungen-worms
+  main-is: worms/worms.hs
+  hs-source-dirs:
+      examples
   ghc-options: -W
-  hs-source-dirs: examples
-  main-is:          worms/worms.hs
-  build-depends:    FunGEn == 1.0.*
-                   ,base
-                   ,OpenGL
-                   ,GLUT
-                   ,random
+  build-depends:
+      FunGEn ==1.1.*
+    , GLUT
+    , OpenGL
+    , base
+    , random
+  other-modules:
+      Paths_FunGEn
+  default-language: Haskell2010
Graphics/UI/Fungen/Game.hs view
@@ -167,6 +167,9 @@   (>>=) = bindST
   return = unitST
 
+instance MonadFail (IOGame t s u v) where
+  fail s = liftIOtoIOGame (fail s)
+
 runIOGame :: IOGame t s u v a -> Game t s u v -> IO (Game t s u v,a)  -- (a,Game t s u v) the state tuple
 runIOGame (IOG f) g = f g
 
README.md view
@@ -1,34 +1,8 @@-<style>-/*-body {-    background-color:black;-    color:white;-}-*/-.a {-    font-weight:bold;-    color:red;-    font-size:200%;-}-.b {-    font-weight:normal;-    /* color:#bbb; */-    color:black;-    font-size:smaller;-}-</style>--<div style="float:right; margin:3em 0 1em 1em;">-<a href="https://github.com/simonmichael/fungen/blob/master/examples/hello.hs#L1"><img border=0 src="/site/logo.gif" title="Click to see the hello world example" style="margin-top:2em;"></a>-<br>-<a href="https://github.com/simonmichael/fungen/blob/master/examples/pong/pong.hs#L1"><img border=0 src="/site/pong.png" title="Click to see the pong example" style="margin-top:2em;"></a>-<br>-<a href="https://github.com/simonmichael/fungen/blob/master/examples/worms/worms.hs#L1"><img border=0 src="/site/worms.png" title="Click to see the worms (snake) example" style="margin-top:1em;"></a>-</div>+# **FunGEn** (**Fun**ctional **G**ame **En**gine) -# <span class="a">Fun<span class="b">ctional</span> G<span class="b">ame</span> En<span class="b">gine</span></span>+<a href="https://github.com/haskell-game/fungen/blob/master/examples/hello.hs#L1"><img border=0 src="site/logo.gif" title="Click to see the hello world example" style="margin-top:2em;"></a> -FunGEn (Functional Game Engine) is a BSD-licensed, cross-platform,+FunGEn is a BSD-licensed, cross-platform, OpenGL/GLUT-based, non-FRP game engine/framework written in Haskell. Created by Andre Furtado in 2002, it's the oldest Haskell game engine, and with very few dependencies and two example games,@@ -46,131 +20,134 @@ * Some debugging and game performance evaluation facilities <!-- * Sound support (windows only, not in current release) --> -[Simon Michael](http://joyful.com) provides basic maintenance for+[Simon Michael](http://joyful.com) provides infrequent basic maintenance for this package. If you'd like to take it over, contact me (`sm` on the #haskell-game IRC channel). -**Home:**      <http://joyful.com/fungen> \-**Hackage:**   <http://hackage.haskell.org/package/FunGEn> \-**Code:**      <https://github.com/haskell-game/fungen> \-\-**Docs:**\-&nbsp; [Changelog](http://hackage.haskell.org/package/FunGEn/changelog) \-&nbsp; [API docs](https://hackage.haskell.org/package/FunGEn/docs/Graphics-UI-Fungen.html) \-&nbsp; Andre's original [pong tutorial](site/example.html) \-&nbsp; Haskell wiki [Game_Development](http://www.haskell.org/haskellwiki/Game_Development) \-&nbsp; Haskell wiki [OpenGL tutorial](http://www.haskell.org/haskellwiki/OpenGLTutorial1) \-<!-- Updated [pong tutorial](TUTORIAL.html) \ -->-<!-- the [old site](http://www.cin.ufpe.br/~haskell/fungen) \ -->-\-**Community:**\-&nbsp; [#haskell-game](http://ircbrowse.net/day/haskell-game/today/recent) IRC channel-       ([join](http://webchat.freenode.net/?channels=haskell-game)) \-&nbsp; [FunGEn questions](http://stackoverflow.com/search?tab=newest&q=fungen) on Stack Overflow \-&nbsp; [haskellgamedev](http://www.reddit.com/r/haskellgamedev) reddit \-&nbsp; [haskell-cafe](http://www.haskell.org/haskellwiki/Mailing_lists) mail list \-+<a href="https://github.com/haskell-game/fungen/blob/master/examples/pong/pong.hs#L1"><img border=0 src="site/pong.png" title="Click to see the pong example" style="margin-top:2em;"></a>+<a href="https://github.com/haskell-game/fungen/blob/master/examples/worms/worms.hs#L1"><img border=0 src="site/worms.png" title="Click to see the worms (snake) example" style="margin-top:1em;"></a> -## Getting started+## Install+### Latest released:+([changes](http://hackage.haskell.org/package/FunGEn/changelog)) ```-$ cabal update-$ [cabal sandbox init]    # if needed, to avoid dependency problems-$ cabal install FunGEn    # & make sure ~/.cabal/bin or ./.cabal-sandbox/bin or windows equiv. are in your PATH+$ stack install FunGEn  # or cabal+```+Make sure `~/.local/bin` (stack) or `~/.cabal/bin` (cabal) or the windows equivalent are in your shell PATH, then run the example games:+``` $ fungen-hello $ fungen-pong $ fungen-worms ```-or+### Latest unreleased:+([changes](https://github.com/haskell-game/fungen/blob/master/CHANGES.md#L1)) ``` $ git clone http://github.com/haskell-game/fungen $ cd fungen-$ stack install-$ fungen-hello-$ fungen-pong-$ fungen-worms+$ stack build  # or cabal+$ stack exec fungen-hello+$ stack exec fungen-pong+$ stack exec fungen-worms `````` ++## Docs++- https://github.com/haskell-game/fungen#readme+- Andre's tutorial, [original](https://htmlpreview.github.io/?https://github.com/haskell-game/fungen/blob/master/site/example.html) or [reformatted](TUTORIAL.md#fungen-pong-tutorial) version+- [Haddock API docs](https://hackage.haskell.org/package/FunGEn/docs/Graphics-UI-Fungen.html)+- [Changelog](CHANGES.md)+- [FunGEn package](http://hackage.haskell.org/package/FunGEn) on Hackage+- [FunGEn questions](http://stackoverflow.com/search?tab=newest&q=fungen) on Stack Overflow+- [Andre's old site](http://www.cin.ufpe.br/~haskell/fungen)+- [#haskell-game](http://webchat.freenode.net/?channels=haskell-game) IRC channel+  <!-- ([log](http://ircbrowse.tomsmeding.com/day/haskell-game/today/recent)) -->+- [/r/haskellgamedev](http://www.reddit.com/r/haskellgamedev) reddit+- [haskell-cafe](http://www.haskell.org/haskellwiki/Mailing_lists) mail list+- Haskell wiki: [Game_Development](http://www.haskell.org/haskellwiki/Game_Development)+- Haskell wiki: [OpenGL tutorial](http://www.haskell.org/haskellwiki/OpenGLTutorial1)+++ ## History -Andre's 2002 site included this Q & A:+Excerpts from Andre's website in 2002: -> **What is a game engine?**-> -> A game engine can be considered as a library that provides game facilities-> to a game programmer. When using a game engine, the programmer must-> specify when the game events happen, rather than how they are-> implemented. A same functionality may have its implementation varying from-> platform to platform, in the case the engine is platform-independent. The-> main advantage of a game engine is that it can be reused to the-> development of many different kind of games, in an automated way, saving a-> lot of programming time.-> -> **Why Haskell?**-> -> We believe that Haskell is a great language to develop games, because of-> its high level of abstraction and the generation of a more concise,-> elegant and shorter code. This is great for code maintenance and-> understanding. Combining the power of Haskell with the facilities provided-> by game engines seems a promising project. You can find more info on-> Haskell in its official site.-> -> **What is HOpenGL?**-> -> HOpenGL stands for Haskell Open Graphics Library. Actually, it is a-> binding to one of the most famous graphics libraries around the world-> (OpenGL) and its auxiliary toolkit (GLUT). In other words, it makes-> possible to call OpenGL/GLUT routines (which were written in the C-> language) when programming in Haskell. You can find more info on HOpenGL-> in my HOpenGL Tutorial site, or in its official site.+**What is a game engine?** -and this:+A game engine can be considered as a library that provides game facilities+to a game programmer. When using a game engine, the programmer must+specify when the game events happen, rather than how they are+implemented. A same functionality may have its implementation varying from+platform to platform, in the case the engine is platform-independent. The+main advantage of a game engine is that it can be reused to the+development of many different kind of games, in an automated way, saving a+lot of programming time. -> Current Status: Some feedback indicated that the first version of FunGEn was not as "functional" as it was desired: some game issues were still being dealt through an imperative fashion. This way, the authors of this project decided to change the game engine philosophy: programmers should describe a game as a set of "specifications" rather than defining its behavior imperatively. One plausible alternative for accomplishing this task-> is porting the Clean Game Library (CGL) to Haskell, adding some FunGEn specific features. Hence, this is the actual status of the FunGEn project: it is being rebuilt in order to provide game programming mechanisms following the CGL-> concepts. This really demands some time, but the authors expect a new version to be released soon.-> -> ... PLEASE NOTE: this is the very first version of FunGEn, and it was released just to get some feedback from game programmers. You are strongly invited to <A HREF="mailto:awbf@cin.ufpe.br">tell</A> your game programming experiences with FunGEn, helping us to release a definitive, stable version). Ok, after this disclaimer, please fell yourself free to take a quick tour in the site; it contains a lot of useful information for those who are really interested in trying a new game programming experience. Nice coding...+**Why Haskell?** -and this todo list:+We believe that Haskell is a great language to develop games, because of+its high level of abstraction and the generation of a more concise,+elegant and shorter code. This is great for code maintenance and+understanding. Combining the power of Haskell with the facilities provided+by game engines seems a promising project. You can find more info on+Haskell in its official site. -> Here you have a list of some upcoming FunGEn features, and some other-> desired features (but with no implementation prevision yet).-> -> - Support map scrolling (coming soon);-> - Support mouse input management (coming soon);-> - Make a polygon map definition avaiable (coming soon);-> - Make sound avaible to non-Win32 platforms;-> - Create, if possible, some operators to avoid the excessive (x <- ...) syntax;-> - Support auto-animated objects;-> - Create a GLUT independent font support (or perhaps extend it);-> - Improve the installation process;-> - Upgrade FunGEn to be both a 2D (bidimensional) and 2D 1/2 (bi and a half dimensional) engine;-> - Create a map editor/generator (possibly in other language, or using the brand new Haskell GUI...);-> - Take courage to start thinking about the 3D world...-> -> Would you like to suggest a feature? Feel free to do it. Would you like to-> implement a feature? Please do it! Keep in touch.+**What is HOpenGL?** -and these credits:+HOpenGL stands for Haskell Open Graphics Library. Actually, it is a+binding to one of the most famous graphics libraries around the world+(OpenGL) and its auxiliary toolkit (GLUT). In other words, it makes+possible to call OpenGL/GLUT routines (which were written in the C+language) when programming in Haskell. You can find more info on HOpenGL+in my HOpenGL Tutorial site, or in its official site. -> FunGEn was created by Andre Furtado, Computation Science graduation-> student at the Informatics Center (CIn) of the Federal University of-> Pernambuco (UFPE), as part of a Scientific Iniciation (PIBIC/CNPq)-> research project (Creating a Game Platform Using Haskell), oriented by-> lecturer Andre Santos (PhD, 1995, University of Glasgow), who was-> responsible for figuring out a lot of FunGEn implementation details.-> -> I would like to thank also the following people who contributed for the development of FunGEn:-> -> - Sven Panne-> - Jay Cox-> - Geber Ramalho-> - Carlos Andre Pessoa-> - Charles Madeira-> - Monique Monteiro-> - The people at the Haskell mailing lists-> -> FunGEn can be distributed freely, in the hope that it will be useful, but-> WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY-> or FITNESS FOR A PARTICULAR PURPOSE. I would thank you if you cite my name-> and this site if you are going to use FunGEn for other things besides home-> programming.+**<s>Current</s> 2002 Status**++Some feedback indicated that the first version of FunGEn was not as "functional" as it was desired: some game issues were still being dealt through an imperative fashion. This way, the authors of this project decided to change the game engine philosophy: programmers should describe a game as a set of "specifications" rather than defining its behavior imperatively. One plausible alternative for accomplishing this task is porting the Clean Game Library (CGL) to Haskell, adding some FunGEn specific features. Hence, this is the actual status of the FunGEn project: it is being rebuilt in order to provide game programming mechanisms following the CGL concepts. This really demands some time, but the authors expect a new version to be released soon.++... PLEASE NOTE: this is the very first version of FunGEn, and it was released just to get some feedback from game programmers. You are strongly invited to <A HREF="mailto:awbf@cin.ufpe.br">tell</A> your game programming experiences with FunGEn, helping us to release a definitive, stable version). Ok, after this disclaimer, please fell yourself free to take a quick tour in the site; it contains a lot of useful information for those who are really interested in trying a new game programming experience. Nice coding...++**Todo**++Here you have a list of some upcoming FunGEn features, and some other+desired features (but with no implementation prevision yet).++- Support map scrolling (coming soon);+- Support mouse input management (coming soon);+- Make a polygon map definition avaiable (coming soon);+- Make sound avaible to non-Win32 platforms;+- Create, if possible, some operators to avoid the excessive (x <- ...) syntax;+- Support auto-animated objects;+- Create a GLUT independent font support (or perhaps extend it);+- Improve the installation process;+- Upgrade FunGEn to be both a 2D (bidimensional) and 2D 1/2 (bi and a half dimensional) engine;+- Create a map editor/generator (possibly in other language, or using the brand new Haskell GUI...);+- Take courage to start thinking about the 3D world...++Would you like to suggest a feature? Feel free to do it. Would you like to+implement a feature? Please do it! Keep in touch.++**Credits**++FunGEn was created by Andre Furtado, Computation Science graduation+student at the Informatics Center (CIn) of the Federal University of+Pernambuco (UFPE), as part of a Scientific Iniciation (PIBIC/CNPq)+research project (Creating a Game Platform Using Haskell), oriented by+lecturer Andre Santos (PhD, 1995, University of Glasgow), who was+responsible for figuring out a lot of FunGEn implementation details.++I would like to thank also the following people who contributed for the development of FunGEn:++- Sven Panne+- Jay Cox+- Geber Ramalho+- Carlos Andre Pessoa+- Charles Madeira+- Monique Monteiro+- The people at the Haskell mailing lists++FunGEn can be distributed freely, in the hope that it will be useful, but+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY+or FITNESS FOR A PARTICULAR PURPOSE. I would thank you if you cite my name+and this site if you are going to use FunGEn for other things besides home+programming.
+ TUTORIAL.md view
@@ -0,0 +1,407 @@+# FunGEn pong tutorial++This is a markdown reconstruction of the +[original HTML tutorial](https://htmlpreview.github.io/?https://github.com/haskell-game/fungen/blob/master/site/example.html).++Here we'll develop, step by step, a small version of a very famous game+called Pong. The player will use the left and right arrow keys to move a+bar horizontally, and he will need to hit a ball that moves around the+screen. When the ball is hit, a sound is played and 10 points is added to+the score, which will be displayed in the bottom left corner of the+screen. All of this is done using less than 80 lines of code, showing how+powerful is the combination of Haskell and game engines. (The following+code is explained in almost all details, but since you read it once,+you'll get very familiar with FunGEn. Nevertheless, it is possible to+understand everything reading only the blue shaded code. It is very+intuitive.)++Ok, here we go. The first thing to do is to declare the main module and to+import module FunGEn:++    module Main where++    import FunGEn++Now it is necessary to define an algebraic data type for the game special+attributes (in this case, the score, which is represented by an Int type):++    data GameAttribute = Score Int++Before we start to implement the main function, let's define two type+synonyms that will help our code to be more legible. The first refers to+the objects of the game. In FunGEn, every object has the type `GameObject s`,+where s is the type of the object special attribute (usually an+algebraic data type such as `GameAttribute`. Since we won't need any+special attribute for objects, let's leave the type s blank, or (). This+lead us to the following code:++    type PongObject = GameObject ()++The second type synonym refers to the type of the actions of our game. In+FunGEn, a game has the type `IOGame t s u v a`, where++- t is the type of the game special attributes;+- s is the type of the object special attributes;+- u is the type of the game levels;+- v is the type of the map tile special attribute, in case we use a `Tile Map` as the background of our game;+- a is the type returned by each action of the game (such as the Int for "IO Int")++The name IOGame was chosen to remind that each action deals with a Game,+but an IO operation can also be performed between game actions (such as+the reading of a file or printing something in the prompt).++Let's think about each one of these types. The type t was already defined+before: it is the algebraic data GameAttribute, which contains information+about the score. We can leave s as () because, as mentioned before, we+won't use special attributes for objects. Our game will not have different+levels, so we can also set u as (). Finally, because we won't use a Tile+Map as our game background, we can also set v as (). So the action of our+game (call it PongAction) will be a type synonym just like that++    type PongAction a = IOGame GameAttribute () () () a++Notice that the a type is not pre-defined. The same way that a function+returns an IO Int, IO Char, etc., our game functions will be able to+return an PongAction Int, PongAction Char and so on. In other words, the a+type refers to the value produced by each of our game actions. (If you are+used to Monads, you may have already noticed that IOGame is a state+tranformer monad: it transforms the state Game producing a value of type+a.++Ok, that was the most boring part. Now we can start to do some nice+coding. Let's define our Main function. In FunGEn, you must think of it as+having the following structure:++    main :: IO ()+    main = do+            <some local definitions>+            <a call to funInit using the definitions above>++The first local definition, generally, tells how our game window will look+like. We need to specify its initial position (x,y) in pixels, its+(width,height) in pixels and its title. The following code says that our+window will have its initial position at the top left corner of the screen+(0,0), its (width,height) will be (250,250) and its title will be "A brief+example!":++    main :: IO ()+    main = do+            let winConfig = ((0,0),(250,250),"A brief example!")++Now let's use other local definition to specify the kind of map+(background) we are going to use. Let's use a Texture Map. This kind of+map fills the whole window with copies of a specified texture pattern,+loaded from a bitmap (.bmp) file. We need then to tell from which bmp file+are we going to use the texture, the horizontal and vertical size of the+texture and, finally, the size of the map. Suppose the file tex.bmp+contains the following texture and that we'll use it in our map:++<img src="site/tex.bmp" />++Suppose also that each copy of this texture will have a size of 50, both+horizontally and vertically. This means that its width will occupy (50/250+= 20%) of the window, and the same to its height. If the value 250 were+used instead of 50, there would be only one copy of the texture, filling+the whole window, because it would occupy (250/250 = 100%) of the window+both horizontally and vertically.++So that's the trick to find how big will be the texture in a texture map:+its width will occupy (100\*TexWidth/WindowWidth)% of the screen width,+and its height will ocuppy (100\*TexHeight/WindowHeight)% of the window+height. In our case (50 for both width and heigth of the texture), the+window will have exactly 25 copies of the texture, since we have 50x50+sized squares filling a 250x250 sized window.++Finally, we define the size of the map, which generally is the same of the+window (in our game, (250,250)). All of this blah-blah-blah to code is+(remember we are still "inside" the let):++	    bmpList = [("tex.bmp",Nothing)]+	    gameMap = textureMap 0 50 50 250.0 250.0++Please notice that we used a local definition called bmpList to specify a+list of all bitmap filepaths we'll use in our game (in this case, only+"tex.bmp"). The Nothing value indicates that we don't want any invisible+colors when drawing the bitmap. It is very common, however, to use the+magenta color as an invisible color. If we used it, all magenta pixels of+our bitmap would not be drawn. We'll talk more about colors later.++The position of the "tex.bmp" filepath in the list (the first position is+0) is the first parameter of function textureMap. Yes, it is a function,+not a type constructor. FunGEn will use this function to do some internal+computations, an then produce the appropriate constructor for the+map. Keep in mind the signature of textureMap:++    textureMap <index of texture> <relative width of texture> <relative heigth of texture>+               <map width in pixels> <map height in pixels>++Now let's define our game objects. A game object is an important entity of+the game. In our game, we can think of the ball and the bar as the game+objects.++In FunGEn, objects must be grouped. For example, in the pac-man game, one+group of objects would be composed by the ghosts, another group by the+pac-man food and, finally, a last group containing only the pac-man+itself. As you may have noticed, groups of objects contains similar or+closely related objects. In Pong, the ball and the bar have no close+relation, so we'll have two distinct groups: one containing the bar and+the other, the ball. To create a group, we need to call function+objectGroup, which receives the name of the group and a list of object as+parameters. Into code:++            bar    = objectGroup "barGroup"  [createBar]+            ball   = objectGroup "ballGroup" [createBall]++Notice that createBar and createBall are functions that will create the+bar and the ball respectively, and we'll need to define them later.++The next local definition initializes the game special attribute (in our+game, the score). So let's define the inial score as 0, using:++            initScore = Score 0++Now we'll write the last local definition of our game. This one will be+responsible for identifying which keyboard keys will have a meaning in our+game. We do this defining a list of triples, where each triple contains a+key, an event related to that key and an action (a function to be+performed when the event happens). There are three possible events related+to a keyboard key:++1. It was pressed;+2. It was released;+3. It is being pressed.++Events 1 and 3 are different. Event 1 happens once when the key is+pressed, and won't happen again until the key is released and then pressed+again. Event 3, othwewise, happens a lot of times between the press and+the release of a key. In FunGEn, each of these events are implemented as a+type constructor, as respectively shown below:++1. Pressed+2. Released+3. StillDown++In our game, what we'll need is the following:++- While the left arrow key is being pressed (StillDown), call a function+  that moves the bar to left (say moveBarToLeft)+- While the right arrow key is being pressed (StillDown), call a function+  that moves the bar to right (say moveBarToRight).++Nothing would be more intuitive:++            input = [(KeySpecial KeyRight, StillDown, moveBarToRight),+                     (KeySpecial KeyLeft,  StillDown, moveBarToLeft)]++Now we have a lot of local definitions, each one doing a different thing:++- Configuration of the game window (winConfig);+- Definition of a list containing all the filepaths of the bitmaps used in the game (bmpList);+- Definition of the game map (gameMap);+- Definition of the object groups of the game (bar and ball);+- Initialization of the game special attribute, or score (initScore);+- Specification of the keys that will be interpreted in our game and their respective event actions (input).++What should we do with them? Call function funInit! Here you have it (the 3 other parameters will be explained below): ++        funInit winConfig gameMap [bar,ball] () initScore input gameCycle (Timer 40) bmpList++Notice that:++- The object groups must be passed as a list ([bar,ball]);+- The fourth parameters refers to the initial game level. Since in this case there are no levels, it is left as ();+- The seventh parameter is a function that will be executed at each game cycle. It is the heart of the game and the place where we'll check if the bar hits the ball, for example. We called this function gameCycle, and it must be implemented by us (as shown later);+- The eighth parameter tells the game execution rate, in miliseconds. In this case, at each 40 miliseconds we'll have an execution (or cycle) of the game. During the game cycle, the map and objects are drawn, the objects are moved (according to their position and speed, as will be shown later), and our function gameCycle is called. Besides (Timer x), another possible value for this parameter is Idle: the game execution will happen as faster as possible, according to the CPU availability.++Important (1): Using Idle can make your game to run at different rates in different computers.++Important (2): Using a very slow value for x in (Timer x) can cause an unexpected behavior in the game execution rate, since there may be not enough time between the game cycles for the CPU to process data.++Now we are done with Main, and the only thing left to do is to implement+some functions called in it. The first two of them is the creation of the+ball and the bar.++The function createBall must return a game object. We have already defined+the type of this object in the beginning of our code: PongObject. So this+will be the type of createBall. Now how do we really create and object in+FunGEn? A very simple and useful structure for doing this is the following+one:++    createObject :: <ObjectType>+    createObject = let objPic = <define the picture of the object>+                   in <call object function using objPic and other desired params>++The picture refers to how an object will be drawn (or rendered). In FunGEn, the picture of an object can be:++- A texture;+- A basic primitive (polygon or circle)++Both the bar and the ball will have basic primitives for their+pictures. Let's consider the ball a green circle, with a radius of 3.0+screen units, i.e., its width and heigth will occupy (100*3/250)% of the+screen's width and height respectively. Let's also define its initial+position in the screen as (125,125) screen units, and its speed as (-5,5)+screen units per cycle. The (0,0) position is always located in the bottom+left corner, so the ball will start the game in the middle of the screen,+moving to the top left corner (FunGEn is responsible for moving all game+objects, according to their speed and position). After the first game+cycle, the new ball position will be (120,130), for example.++All of this object creation is done by the following code: ++    createBall :: PongObject+    createBall = let ballPic = Basic (Circle 3.0 0.0 1.0 0.0 Filled)+                 in object "ball" ballPic False (125,125) (-5,5) ()++Let's take a look at the above code. The first value after the constructor+Circle defines the radius of the ball. The next three numbers refers to+the color of the ball, in the RGB (Red Green Blue) format. Each of the+three colors can vary from 0.0 to 1.0, so (1.0 1.0 1.0) represents white+(the total presence of all colors) while (0.0 0.0 0.0) represents black+(the total absence of all three colors). Hence, the value (0.0 1.0 0.0)+says that our ball is green. Finally, the value Filled says that the+interior os the ball is also green. If we used Unfilled instead, only the+border of the ball (its most outside circumference) would be drawn.++The function object is then called to effectively create the object. Its+parameters are the name of the object, its picture, its sleeping status,+its initial position, its inicial speed and its special attribute (which,+as we defined before, does not make any sense in our game). If the+sleeping status of the ball were True, FunGEn would ignore it when moving,+drawing and checking collisions between it and other objects. One can+always change the attributes of and object by get and set functions, as it+will be shown later.++Important: FunGEn automatically creates the size attribute for an object.++The creation of the bar is very similar. Suppose we'd like an unfilled+white rectangle to represent the picture of the bar, and that its initial+position is (125,30) screen units and its initial speed is null (0,0). The+code would be:++    createBar :: PongObject+    createBar = let barBound = [(-25,-6),(25,-6),(25,6),(-25,6)]+                barPic = Basic (Polyg barBound 1.0 1.0 1.0 Unfilled)+                in object "bar" barPic False (125,30) (0,0) ()++The only new thing in the above code is the local definition barBound,+which is a list of points related to the centre of the bar. They must be+listed in counter-clockwise order. They're also expressed in screen units.++Now let's define the function moveBarToRight, which will be called when+the player holds the right keyboard key. Basically, what we'll need to do+is to find (retrieve) the bar object from the game and increment its+position (let's increment it by 5 units). The following code does this+task:++    moveBarToRight :: PongAction ()+    moveBarToRight = do+        obj <- findObject "bar" "barGroup"+        (pX,pY) <- getObjectPosition obj+        (sX,_)  <- getObjectSize obj+        if (pX + (sX/2) + 5 <= 250)+                then (setObjectPosition ((pX + 5),pY) obj)+                else (setObjectPosition ((250 - (sX/2)),pY) obj)++The functions findObject, getObjectPosition, getObjectSize,+setObjectPosition and setObjectPosition are all FunGEn+functionalities. The first of them find an object, given its name and the+name of its group. Notice that there is a check to avoid the bar from+moving beyond the right window boundary. This check needs to know the+position and size of the ball, which are retrieved via getObjectPosition+and getObjectSize respectively. After the check is done, the new position+of the bar is set properly, via setObjectPosition, which receives the new+position (x,y) and the object to be modified as parameters. Notice that+the type of this function (and of all functions that changes the state of+our game) is PongAction (), as defined before.++Important: You'll may have problems if you create two objects with the+same name in the same group, or two groups with the same name. But no+problems will occur if two objects, from two different groups, have the+same name.++To the moveBarToLeft function, the routine is almost the same. The+difference is that now we need to avoid the bar from moving to beyond the+left window boundary. The final check is then modified; the code for this+function is:++    moveBarToLeft :: PongAction ()+    moveBarToLeft = do+        obj <- findObject "bar" "barGroup"+        (pX,pY) <- getObjectPosition obj+        (sX,_)  <- getObjectSize obj+        if (pX - (sX/2) - 5 >= 0)+                then (setObjectPosition ((pX - 5),pY) obj)+                else (setObjectPosition (sX/2,pY) obj)++Don't give up now, we're almost done. The last thing remaining to do is the+implementation of the gameCycle function. It will be executed every cycle,+so it is the right place to describe the game behavior. Its first task is+to retrieve the score (the game special attribute) and print it on the+screen. Hence, the beginning of this fuction would be:++    gameCycle :: PongAction ()+    gameCycle = do+        (Score n) <- getGameAttribute+        printOnScreen (show n) BitmapTimesRoman24 (0,0) 1.0 1.0 1.0++Both getGameAttribute and printOnScreen functions are FunGEn pre-defined+functions. The first retrieves the game special attribute, and the later+prints a string in the screen, given the string, the font type, the+position of the bottom left corner of the text and the color in RBG.++Now let's find the ball object and check if it have collided with any+border of the map. If the collision was against the left or right border,+the horizontal speed must be changed. Case the top border was hit, the+vertical position must be the one changed. If, otherwise, the collision+was against the bottom border, the player loses the game and we finish it+(calling funExit) This is done by the following piece of code (remember+we're still inside gameCycle:++        ball <- findObject "ball" "ballGroup"+        col1 <- objectLeftMapCollision ball+        col2 <- objectRightMapCollision ball+        when (col1 || col2) (reverseXSpeed ball)+        col3 <- objectTopMapCollision ball+        when col3 (reverseYSpeed ball)+        col4 <- objectBottomMapCollision ball+        when col4 (funExit)++It is worth observing that the exiting function funExit, the collision+detection functions and the speed reversing functions are also provided by+FunGEn, and that a when corresponds to an if without an else (for monadic+actions, such as IO or IOGame).++Now we check if the bar have hit the ball. We first find the bar object,+call the objectsCollision function and do the check. If it happened, we+call FunGEn sound function to play a sound, then reverse the speed of the+ball and, finally, add 10 points to the score. Into code:++        bar <- findObject "bar" "barGroup"+        col5 <- objectsCollision ball bar+        when col5+                (do sound "hit.wav" [Async]+                    reverseYSpeed ball+                    setGameAttribute (Score (n + 10)))++Notice that the sound function receives the sound filepath and a list of+configurations. Here, Async says that the game execution must not wait for+the sound to be completely executed to continue; the sound plays and the+game execution continues at the same time.++Ok, now we ARE DONE. When executed, our game would look like:++<img src="site/brief.gif" />++Please notice that, in our example, when the ball hits one of the sides of+the bar, its horizontal speed won't change, so it will "enter" the+bar. Try it! Can you change the above code to avoid this...?++One last thing: would you like to check the frame per seconds rate of the+game? Try adding the following code line to the end of gameCycle and see+what happens:++        showFPS BitmapTimesRoman24 (30,0) 1.0 0.0 0.0++And that's it. We created a game with many different functionalities and a+nice performance in less than 80 lines of code. Hope you enjoyed!
+ site/brief.gif view

binary file changed (absent → 22978 bytes)

+ site/example.html view
@@ -0,0 +1,499 @@+<HTML>+<HEAD>+<TITLE>FunGEn 0.1 - A Brief Example</TITLE>+<BODY BGCOLOR=#000000 LINK=lightblue ALINK=yellow VLINK=lightblue>+<FONT FACE=arial SIZE=3 COLOR=white>++<DIV ALIGN=center>+<FONT SIZE=+4 COLOR=blue><B>A Brief Example</B></FONT>+<HR>++<P><I><B>Please note: this tutorial corresponds to FunGEn 0.1 windows version and is (slightly) out of date. To view or run the latest example code, see the <A HREF="..">main page</A>.</B></I>++</DIV>+<P>Here we'll develop, step by step, a small version of a very famous game called <I>Pong</I>. The player will use the left and right arrow keys to move a bar horizontally, and he will need to hit a ball that moves around the screen. When the ball is hit, a sound is played and 10 points is added to the score, which will be displayed in the bottom left corner of the screen. All of this is done using <B>less than 80 lines of code</B>, showing how powerful is the combination of Haskell and game engines. (The following code is explained in <B>almost all</B> details, but since you read it once, you'll get very familiar with FunGEn. Nevertheless, it is possible to understand everything reading only the blue shaded code. It is very intuitive.)++<P>Ok, here we go. The first thing to do is to declare the main module and to import module FunGEn:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>+<PRE>+module Main where++import Graphics.UI.Fungen++</TD>+</PRE>+</TR>+</TABLE>++<P>Now it is necessary to define an algebraic data type for the game special attributes (in this case, the score, which is represented by an <CODE>Int</CODE> type):++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>+<PRE>+data GameAttribute = Score Int++</TD>+</PRE>+</TR>+</TABLE>++<P>Before we start to implement the main function, let's define two type synonyms that will help our code to be more legible. The first refers to the objects of the game. In FunGEn, every object has the type <CODE>GameObject s</CODE>, where <CODE>s</CODE> is the type of the object special attribute (usually an algebraic data type such as <CODE>GameAttribute</CODE>. Since we won't need any special attribute for objects, let's leave the type <CODE>s</CODE> blank, or <CODE>()</CODE>. This lead us to the following code:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>+<PRE>+type PongObject = GameObject ()++</TD>+</PRE>+</TR>+</TABLE>++<P>The second type synonym refers to the type of the <I>actions</I> of our game. In FunGEn, a game has the type <CODE>IOGame t s u v a</CODE>, where+<UL>+	<LI><CODE>t</CODE> is the type of the <FONT COLOR=yellow>game special attributes</FONT>;+	<LI><CODE>s</CODE> is the type of the <FONT COLOR=yellow>object special attributes</FONT>;+	<LI><CODE>u</CODE> is the type of the <FONT COLOR=yellow>game levels</FONT>;+	<LI><CODE>v</CODE> is the type of the <FONT COLOR=yellow>map tile special attribute</FONT>, in case we use a <B>Tile Map</B> as the background of our game;+	<LI><CODE>a</CODE> is the <FONT COLOR=yellow>type returned</FONT> by each action of the game (such as the <CODE>Int</CODE> for <CODE>"IO Int"</CODE>)++</UL>++<P>The name <CODE>IOGame</CODE> was chosen to remind that each action deals with a <CODE>Game</CODE>, but an <CODE>IO</CODE> operation can also be performed between game actions (such as the reading of a file or printing something in the prompt).++<P>Let's think about each one of these types. The type <CODE>t</CODE> was already defined before: it is the algebraic data <CODE>GameAttribute</CODE>, which contains information about the score. We can leave <CODE>s</CODE> as <CODE>()</CODE> because, as mentioned before, we won't use special attributes for objects. Our game will not have different levels, so we can also set <CODE>u</CODE> as <CODE>()</CODE>. Finally, because we won't use a Tile Map as our game background, we can also set <CODE>v</CODE> as <CODE>()</CODE>. So the action of our game (call it <CODE>PongAction</CODE>) will be a type synonym just like that+	+<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>+<PRE>+type PongAction a = IOGame GameAttribute () () () a++</TD>+</PRE>+</TR>+</TABLE>++<P>Notice that the <CODE>a</CODE> type is not pre-defined. The same way that a function returns an <CODE>IO Int</CODE>, <CODE>IO Char</CODE>, etc., our game functions will be able to return an <CODE>PongAction Int</CODE>, <CODE>PongAction Char</CODE> and so on. In other words, the <CODE>a</CODE> type refers to the value produced by each of our game actions. (If you are used to <CODE>Monads</CODE>, you may have already noticed that <CODE>IOGame</CODE> is a state tranformer monad: it transforms the state <CODE>Game</CODE> producing a value of type <CODE>a</CODE>.++<P>Ok, that was the most boring part. Now we can start to do some nice coding. Let's define our <CODE>Main</CODE> function. In FunGEn, you must think of it as having the following structure:++<BR><BR>+<TABLE BGCOLOR = #FFBBBB WIDTH = 100%>+<TR>+<TD>++<PRE>+main :: IO ()+main = do+	&#60;some local definitions&#62;+	&#60;a call to <B>funInit</B> using the definitions above&#62;++</TD>+</PRE>+</TR>+</TABLE>++<P>The first local definition, generally, tells how our game window will look like. We need to specify its initial position (x,y) in pixels, its (width,height) in pixels and its title. The following code says that our window will have its initial position at the top left corner of the screen (0,0), its (width,height) will be (250,250) and its title will be <CODE>"A brief example!"</CODE>:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+main :: IO ()+main = do+        let winConfig = ((0,0),(250,250),"A brief example!")++</TD>+</PRE>+</TR>+</TABLE>++<P>Now let's use other local definition to specify the kind of map (background) we are going to use. Let's use a <B>Texture Map</B>. This kind of map fills the whole window with copies of a specified texture pattern, loaded from a bitmap (.bmp) file. We need then to tell from which bmp file are we going to use the texture, the horizontal and vertical size of the texture and, finally, the size of the map. Suppose the file tex.bmp contains the following texture and that we'll use it in our map:<BR>++<BR><CENTER><IMG SRC="tex.bmp"><BR></CENTER>++<P>Suppose also that each copy of this texture will have a size of 50, both horizontally and vertically. This means that its width will occupy <NOBR><FONT COLOR=red>(50/250 = 20%)</FONT></NOBR> of the window, and the same to its height. If the value 250 were used instead of 50, there would be only one copy of the texture, filling the whole window, because it would occupy <NOBR><FONT COLOR=red>(250/250 = 100%)</FONT></NOBR> of the window both horizontally and vertically.++<P>So that's the trick to find how big will be the texture in a texture map: its width will occupy <NOBR><FONT COLOR=red>(100*TexWidth/WindowWidth)%</FONT></NOBR> of the screen width, and its height will ocuppy <NOBR><FONT COLOR=red>(100*TexHeight/WindowHeight)%</FONT></NOBR> of the window height. In our case (50 for both width and heigth of the texture), the window will have exactly 25 copies of the texture, since we have 50x50 sized squares filling a 250x250 sized window.++<P>Finally, we define the size of the map, which generally is the same of the window (in our game, (250,250)). All of this blah-blah-blah to code is (remember we are still "inside" the <CODE>let</CODE>):+        +<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+	    bmpList = [("tex.bmp",Nothing)]+	    gameMap = textureMap 0 50 50 250.0 250.0++</TD>+</PRE>+</TR>+</TABLE>++<P>Please notice that we used a local definition called <CODE>bmpList</CODE> to specify a list of all bitmap filepaths we'll use in our game (in this case, only "tex.bmp"). The <CODE>Nothing</CODE> value indicates that we don't want any <I>invisible colors</I> when drawing the bitmap. It is very common, however, to use the <FONT COLOR=magenta>magenta</FONT> color as an invisible color. If we used it, all magenta pixels of our bitmap would not be drawn. We'll talk more about colors later.++<P>The position of the "tex.bmp" filepath in the list (the first position is 0) is the first parameter of function <CODE>textureMap</CODE>. Yes, it is a function, not a type constructor. FunGEn will use this function to do some internal computations, an then produce the appropriate constructor for the map. Keep in mind the signature of <CODE>textureMap</CODE>:++<BR><BR>+<TABLE BGCOLOR = #FFBBBB WIDTH = 100%>+<TR>+<TD>++<PRE>+textureMap &#60;index of texture&#62; &#60;relative width of texture&#62; &#60;relative heigth of texture&#62;+	   &#60;map width in pixels&#62; &#60;map height in pixels&#62;++</TD>+</PRE>+</TR>+</TABLE>++<P>Now let's define our game objects. A game object is an important entity of the game. In our game, we can think of the ball and the bar as the game objects.++<P>In FunGEn, objects must be grouped. For example, in the <B>pac-man</B> game, one group of objects would be composed by the ghosts, another group by the pac-man food and, finally, a last group containing only the pac-man itself. As you may have noticed, groups of objects contains similar or closely related objects. In <I>Pong</I>, the ball and the bar have no close relation, so we'll have two distinct groups: one containing the bar and the other, the ball. To create a group, we need to call function <CODE>objectGroup</CODE>, which receives the name of the group and a list of object as parameters. Into code:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+            bar    = objectGroup "barGroup"  [createBar]+            ball   = objectGroup "ballGroup" [createBall]++</TD>+</PRE>+</TR>+</TABLE>++<P>Notice that <CODE>createBar</CODE> and <CODE>createBall</CODE> are functions that will create the bar and the ball respectively, and we'll need to define them later.++<P>The next local definition initializes the game special attribute (in our game, the score). So let's define the inial score as 0, using:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+            initScore = Score 0++</TD>+</PRE>+</TR>+</TABLE>++<P>Now we'll write the last local definition of our game. This one will be responsible for identifying which keyboard keys will have a meaning in our game. We do this defining a list of triples, where each triple contains a key, an event related to that key and an action (a function to be performed when the event happens). There are three possible events related to a keyboard key:++<OL>+	<LI>It was pressed;+	<LI>It was released;+	<LI>It is being pressed.+</OL>++<P>Events 1 and 3 are different. Event 1 happens once when the key is pressed, and won't happen again until the key is released and then pressed again. Event 3, othwewise, happens a lot of times between the press and the release of a key. In FunGEn, each of these events are implemented as a type constructor, as respectively shown below:++<OL>+	<LI><CODE>Pressed</CODE>+	<LI><CODE>Released</CODE>+	<LI><CODE>StillDown</CODE>+</OL>++<P>In our game, what we'll need is the following:++<UL>+	<LI>While the left arrow key is being pressed (<CODE>StillDown</CODE>), call a function that moves the bar to left (say <CODE>moveBarToLeft</CODE>);+	<LI>While the right arrow key is being pressed (<CODE>StillDown</CODE>), call a function that moves the bar to right (say <CODE>moveBarToRight</CODE>).+	+</UL>++<P>Nothing would be more intuitive:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+            input = [(KeySpecial KeyRight, StillDown, moveBarToRight),+                     (KeySpecial KeyLeft,  StillDown, moveBarToLeft)]++</TD>+</PRE>+</TR>+</TABLE>++<P>Now we have a lot of local definitions, each one doing a different thing:++<UL>+	<LI>Configuration of the game window (<CODE><FONT COLOR=yellow>winConfig</FONT></CODE>);+	<LI>Definition of a list containing all the filepaths of the bitmaps used in the game (<CODE><FONT COLOR=yellow>bmpList</FONT></CODE>);+	<LI>Definition of the game map (<CODE><FONT COLOR=yellow>gameMap</FONT></CODE>);+	<LI>Definition of the object groups of the game (<CODE><FONT COLOR=yellow>bar</FONT></CODE> and <FONT COLOR=yellow><CODE>ball</CODE></FONT>);+	<LI>Initialization of the game special attribute, or score (<CODE><FONT COLOR=yellow>initScore</FONT></CODE>);+	<LI>Specification of the keys that will be interpreted in our game and their respective event actions (<CODE><FONT COLOR=yellow>input</FONT></CODE>).++</UL>+	+<P>What should we do with them? Call function <CODE><B>funInit</B></CODE>! Here you have it (the 3 other parameters will be explained below):++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+        funInit winConfig gameMap [bar,ball] () initScore input gameCycle (Timer 40) bmpList++</TD>+</PRE>+</TR>+</TABLE>++<P>Notice that:++<UL>+	<LI>The object groups must be passed as a list (<CODE>[bar,ball]</CODE>);+	<LI>The fourth parameters refers to the initial game level. Since in this case there are no levels, it is left as <CODE>()</CODE>;+	<LI>The seventh parameter is a function that will be executed at each game cycle. It is the heart of the game and the place where we'll check if the bar hits 	 the ball, for example. We called this function <CODE>gameCycle</CODE>, and it must be implemented by us (as shown later);+	<LI>The eighth parameter tells the game execution rate, in miliseconds. In this case, at each 40 miliseconds we'll have an execution (or cycle) of the game. During the game cycle, the map and objects are drawn, the objects are moved (according to their position and speed, as will be shown later), and our function <CODE>gameCycle</CODE> is called. Besides (<CODE>Timer x</CODE>), another possible value for this parameter is <CODE>Idle</CODE>: the game execution will happen as faster as possible, according to the CPU availability.+</UL>++<P><FONT COLOR=#AAFFCC><B><I>Important (1):</I></B> Using <CODE>Idle</CODE> can make your game to run at different rates in different computers.<BR>+<B><I>Important (2):</I></B> Using a very slow value for <CODE>x</CODE> in <CODE>(Timer x)</CODE> can cause an unexpected behavior in the game execution rate, since there may be not enough time between the game cycles for the CPU to process data.</FONT>++<P>Now we are done with <CODE>Main</CODE>, and the only thing left to do is to implement some functions called in it. The first two of them is the creation of the ball and the bar.++<P>The function <CODE>createBall</CODE> must return a game object. We have already defined the type of this object in the beginning of our code: <CODE>PongObject</CODE>. So this will be the type of <CODE>createBall</CODE>. Now how do we really create an object in FunGEn? A very simple and useful structure for doing this is the following one:++<BR><BR>+<TABLE BGCOLOR = #FFBBBB WIDTH = 100%>+<TR>+<TD>++<PRE>+createObject :: &#60;ObjectType&#62;+createObject = let objPic = &#60;define the picture of the object&#62;+	       in &#60;call <B>object</B> function using <B>objPic</B> and other desired params&#62;++</TD>+</PRE>+</TR>+</TABLE>++<P>The <B>picture</B> refers to how an object will be drawn (or rendered). In FunGEn, the picture of an object can be:++<UL>+	<LI>A texture;+	<LI>A basic primitive (polygon or circle)+</UL>++<P>Both the bar and the ball will have basic primitives for their pictures. Let's consider the ball a green circle, with a radius of 3.0 screen units, i.e., its width and heigth will occupy <NOBR><FONT COLOR=red>(100*3/250)%</FONT></NOBR> of the screen's width and height respectively. Let's also define its initial position in the screen as (125,125) screen units, and its speed as (-5,5) screen units per cycle. The (0,0) position is always located in the bottom left corner, so the ball will start the game in the middle of the screen, moving to the top left corner (FunGEn is responsible for moving all game objects, according to their speed and position). After the first game cycle, the new ball position will be (120,130), for example.++<P>All of this object creation is done by the following code:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+createBall :: PongObject+createBall = let ballPic = Basic (Circle 3.0 0.0 1.0 0.0 Filled)+	     in object "ball" ballPic False (125,125) (-5,5) ()++</TD>+</PRE>+</TR>+</TABLE>++<P>Let's take a look at the above code. The first value after the constructor <CODE>Circle</CODE> defines the radius of the ball. The next three numbers refers to the color of the ball, in the RGB (<FONT COLOR=red>Red</FONT> <FONT COLOR=lightgreen>Green</FONT> <FONT COLOR=blue>Blue</FONT>) format. Each of the three colors can vary from 0.0 to 1.0, so <NOBR>(1.0 1.0 1.0)</NOBR> represents white (the total presence of all colors) while <NOBR>(0.0 0.0 0.0)</NOBR> represents black (the total absence of all three colors). Hence, the value <NOBR>(0.0 1.0 0.0)</NOBR> says that our ball is green. Finally, the value <CODE>Filled</CODE> says that the interior os the ball is also green. If we used <CODE>Unfilled</CODE> instead, only the border of the ball (its most outside circumference) would be drawn.++<P>The function <CODE>object</CODE> is then called to effectively create the object. Its parameters are the name of the object, its picture, its sleeping status, its initial position, its inicial speed and its special attribute (which, as we defined before, does not make any sense in our game). If the sleeping status of the ball were <CODE>True</CODE>, FunGEn would ignore it when moving, drawing and checking collisions between it and other objects. One can always change the attributes of and object by <CODE>get</CODE> and <CODE>set</CODE> functions, as it will be shown later.++<P><FONT COLOR=#AAFFCC><B><I>Important:</I></B> FunGEn automatically creates the <CODE>size</CODE> attribute for an object.</FONT>++<P>The creation of the bar is very similar. Suppose we'd like an unfilled white rectangle to represent the picture of the bar, and that its initial position is (125,30) screen units and its initial speed is null (0,0). The code would be:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+createBar :: PongObject+createBar = let barBound = [(-25,-6),(25,-6),(25,6),(-25,6)]+                barPic = Basic (Polyg barBound 1.0 1.0 1.0 Unfilled)+            in object "bar" barPic False (125,30) (0,0) ()++</TD>+</PRE>+</TR>+</TABLE>++<P>The only new thing in the above code is the local definition <CODE>barBound</CODE>, which is a list of points related to the centre of the bar. They must be listed in counter-clockwise order. They're also expressed in screen units.++<P>Now let's define the function <CODE>moveBarToRight</CODE>, which will be called when the player holds the right keyboard key. Basically, what we'll need to do is to find (retrieve) the bar object from the game and increment its position (let's increment it by 5 units). The following code does this task:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+moveBarToRight :: PongAction ()+moveBarToRight = do+        obj <- findObject "bar" "barGroup"+        (pX,pY) <- getObjectPosition obj+        (sX,_)  <- getObjectSize obj+        if (pX + (sX/2) + 5 <= 250)+        	then (setObjectPosition ((pX + 5),pY) obj)+        	else (setObjectPosition ((250 - (sX/2)),pY) obj)++</TD>+</PRE>+</TR>+</TABLE>++<P>The functions <CODE>findObject</CODE>, <CODE>getObjectPosition</CODE>, <CODE>getObjectSize</CODE>, <CODE>setObjectPosition</CODE> and <CODE>setObjectPosition</CODE> are all FunGEn functionalities. The first of them find an object, given its name and the name of its group. Notice that there is a check to avoid the bar from moving beyond the right window boundary. This check needs to know the position and size of the ball, which are retrieved via <CODE>getObjectPosition</CODE> and <CODE>getObjectSize</CODE> respectively. After the check is done, the new position of the bar is set properly, via <CODE>setObjectPosition</CODE>, which receives the new position (x,y) and the object to be modified as parameters. Notice that the type of this function (and of all functions that changes the state of our game) is <CODE>PongAction ()</CODE>, as defined before.++<P><FONT COLOR=#AAFFCC><B><I>Important:</I></B> You'll may have problems if you create two objects with the same name in the same group, or two groups with the same name. But no problems will occur if two objects, from two different groups, have the same name.</FONT>+++<P>To the <CODE>moveBarToLeft</CODE> function, the routine is almost the same. The difference is that now we need to avoid the bar from moving to beyond the left window boundary. The final check is then modified; the code for this function is:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+moveBarToLeft :: PongAction ()+moveBarToLeft = do+        obj <- findObject "bar" "barGroup"+        (pX,pY) <- getObjectPosition obj+        (sX,_)  <- getObjectSize obj+        if (pX - (sX/2) - 5 >= 0)+        	then (setObjectPosition ((pX - 5),pY) obj)+        	else (setObjectPosition (sX/2,pY) obj)++</TD>+</PRE>+</TR>+</TABLE>++<P>Dont'give up now, we're almost done. The last thing remaining to do is the implementation of the <CODE>gameCycle</CODE> function. It will be executed every cycle, so it is the right place to describe the game behavior. Its first task is to retrieve the score (the game special attribute) and print it on the screen. Hence, the beginning of this fuction would be:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+gameCycle :: PongAction ()+gameCycle = do+        (Score n) <- getGameAttribute+        printOnScreen (show n) BitmapTimesRoman24 (0,0) 1.0 1.0 1.0++</TD>+</PRE>+</TR>+</TABLE>++<P>Both <CODE>getGameAttribute</CODE> and <CODE>printOnScreen</CODE> functions are FunGEn pre-defined functions. The first retrieves the game special attribute, and the later prints a string in the screen, given the string, the font type, the position of the bottom left corner of the text and the color in RBG.++<P>Now let's find the ball object and check if it have collided with any border of the map. If the collision was against the left or right border, the horizontal speed must be changed. Case the top border was hit, the vertical position must be the one changed. If, otherwise, the collision was against the bottom border, the player loses the game and we finish it (calling <CODE>funExit</CODE>) This is done by the following piece of code (remember we're still inside <CODE>gameCycle</CODE>:+++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+        ball <- findObject "ball" "ballGroup"+        col1 <- objectLeftMapCollision ball+        col2 <- objectRightMapCollision ball+        when (col1 || col2) (reverseXSpeed ball)+        col3 <- objectTopMapCollision ball+        when col3 (reverseYSpeed ball)+        col4 <- objectBottomMapCollision ball+        when col4 (funExit)++</TD>+</PRE>+</TR>+</TABLE>++<P>It is worth observing that the exiting function <CODE>funExit</CODE>, the collision detection functions and the speed reversing functions are also provided by FunGEn, and that a <CODE>when</CODE> corresponds to an <CODE>if</CODE> without an <CODE>else</CODE> (for monadic actions, such as <CODE>IO</CODE> or <CODE>IOGame</CODE>).++<P>Now we check if the bar have hit the ball. We first find the bar object, call the <CODE>objectsCollision</CODE> function and do the check. If it happened, we call FunGEn <CODE>sound</CODE> function to play a sound, then reverse the speed of the ball and, finally, add 10 points to the score. Into code:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+        bar <- findObject "bar" "barGroup"+        col5 <- objectsCollision ball bar+        when col5+        	(do sound "hit.wav" [Async]+        	    reverseYSpeed ball+        	    setGameAttribute (Score (n + 10)))++</TD>+</PRE>+</TR>+</TABLE>++<P>Notice that the <CODE>sound</CODE> function receives the sound filepath and a list of configurations. Here, <CODE>Async</CODE> says that the game execution must not wait for the sound to be completely executed to continue; the sound plays and the game execution continues at the same time.++<P>Ok, now we <B>ARE DONE</B>. When executed, our game would look like:<BR>++<BR><CENTER><IMG SRC="brief.gif"><BR></CENTER>++<P>Please notice that, in our example, when the ball hits one of the sides of the bar, its horizontal speed won't change, so it will "enter" the bar. Try it! Can you change the above code to avoid this...?++<P>One last thing: would you like to check the frame per seconds rate of the game? Try adding the following code line to the end of <CODE>gameCycle</CODE> and see what happens:++<BR><BR>+<TABLE BGCOLOR = #BABAFF WIDTH = 100%>+<TR>+<TD>++<PRE>+        showFPS BitmapTimesRoman24 (30,0) 1.0 0.0 0.0++</TD>+</PRE>+</TR>+</TABLE>++<P>And that's it. We created a game with many different functionalities and a nice performance in less than 80 lines of code. Hope you enjoyed!++<BR><BR>+<DIV ALIGN=center>+<HR>+<FONT SIZE=2>+<A HREF=".."><B><I>FunGEn</I></B></A> - <A HREF="mailto:awbf@cin.ufpe.br">Andre W B Furtado</A><BR>+Last updated in 19/04/2002<BR>+Informatics Center (CIn) - UFPE<BR>+Recife - PE - Brazil<BR>+</DIV>+</BODY>+</HTML>
+ site/logo.gif view

binary file changed (absent → 18455 bytes)

+ site/pong.png view

binary file changed (absent → 139508 bytes)

+ site/tex.bmp view

binary file changed (absent → 3126 bytes)

+ site/worms.png view

binary file changed (absent → 215292 bytes)