Add more extensive examples
z0w0 opened this issue · 7 comments
There's currently only a flappy bird clone example. I'd like to see at least three more, such as:
- Shooter
- Platformer
- Physics playground
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
Though it doesn't strictly fit into any of the categories above, would there be interest in creating a sort of Minesweeper clone?
Yep. The categories were just examples, anything that's a game would be very welcome. Even if it's very simplistic
Okay, cool. I've started on the minesweeper example and it should be up and running soon.
@TheLostLambda hey, do you need any help with minesweeper? In terms os questions / answers etc.
Hmm, to be entirely honest, I recently started school up again so I haven't made too much progress on the minesweeper front. It looks like I got a decent ways with it. It currently allows you to reveal tiles and once revealed they will show as red if there is a mine there and white if there is not. Pressing the R key resets the game.
The bits that aren't finished are: placing flags, displaying the number of surrounding mines, auto-revealing fields, and actually being able to win or lose the game.
Now that you have brought it back to my attention, there is a decent chance that I will actually pick it up and finish it, but that isn't a guarantee — particularly with school filling my time. If I don't get around to finishing it soon, feel free to check out the code I have so far:
{-# LANGUAGE RecordWildCards #-}
import Data.Foldable (minimum)
import Data.List (nub)
import Data.Maybe (isJust, fromJust)
import Linear.V2 (V2 (V2))
import qualified System.Random as Rand
import Helm
import qualified Helm.Cmd as Cmd
import Helm.Color
import Helm.Engine.SDL (SDLEngine)
import qualified Helm.Engine.SDL as SDL
import Helm.Graphics2D
import qualified Helm.Keyboard as Key
import qualified Helm.Mouse as Mouse
import qualified Helm.Sub as Sub
import qualified Helm.Window as Win
screenSize :: V2 Int
screenSize = V2 600 600
padding :: Int
padding = 2
gameSize :: Int
gameSize = 15
mineCount :: Int
mineCount = 30
data Action = Idle | Init Rand.StdGen | Restart | Resize (V2 Int) | Click Mouse.MouseButton (V2 Int)
type Position = (Int, Int)
data Model = Model
{ revealed :: [Position]
, flags :: [Position]
, mines :: [Position]
, size :: V2 Int
, state :: Int
}
initial :: V2 Int -> (Model, Cmd SDLEngine Action)
initial s = ( Model
{ revealed = []
, flags = []
, mines = []
, size = s
, state = 0
}
, Cmd.execute Rand.newStdGen Init
)
update :: Model -> Action -> (Model, Cmd SDLEngine Action)
update model Idle = (model, Cmd.none)
update model@Model{..} (Click Mouse.LeftButton pos)
| isJust clickIndex = (model { revealed = nub $ fromJust clickIndex : revealed }, Cmd.none)
where clickIndex = decodeClick model pos
update model (Click _ _) = (model, Cmd.none)
update Model{..} Restart = initial size
update model (Resize s) = (model { size = s }, Cmd.none)
update model (Init rng) = (model { mines = take mineCount . nub $ zip (randomLst xGen) (randomLst yGen)}, Cmd.none)
where (xGen, yGen) = Rand.split rng
randomLst = Rand.randomRs (0, gameSize - 1)
decodeClick :: Model -> V2 Int -> Maybe (Int,Int)
decodeClick model (V2 mx my)
| null tile = Nothing
| otherwise = Just (head tile)
where positions = map (\(i,pos) -> (i, fmap round pos)) $ tilePositions model
tileRadius = round $ tileSize model / 2
ranges = map (\(i, V2 x y) -> (i, ((y + tileRadius, y - tileRadius)
,(x + tileRadius, x - tileRadius)))) positions
tile = map fst $ filter (\(_,((yu, yl), (xu, xl))) -> (my < yu) && (my > yl)
&& (mx < xu) && (mx > xl)) ranges
tileSize :: Model -> Double
tileSize Model{..} = fromIntegral (minimum size - padding * (gameSize + 1)) / fromIntegral gameSize
tilePositions :: Model -> [(Position, V2 Double)]
tilePositions model =
[ ((rn - 1, cn - 1), V2 (toPos rn) (toPos cn))| rn <- [1..gameSize], cn <- [1..gameSize]
, let toPos n = fromIntegral n * (tileSize model + fromIntegral padding) - tileSize model / 2]
subscriptions :: Sub SDLEngine Action
subscriptions = Sub.batch [ Win.resizes Resize
, Key.ups (\k -> case k of Key.RKey -> Restart
_ -> Idle)
, Mouse.ups Click
]
view :: Model -> Graphics SDLEngine
view model@Model{..} = Graphics2D . collage . map toTile $ tilePositions model
where toTile (index, pos) = move pos $ filled (tileColor model index) $ square (tileSize model)
tileColor :: Model -> Position -> Color
tileColor Model{..} pos
| pos `elem` revealed && pos `elem` mines = rgb 1 0 0
| pos `elem` revealed = rgb 1 1 1
| otherwise = rgb 0.5 0.5 0.5
main :: IO ()
main = do
engine <- SDL.startupWith $ SDL.defaultConfig
{ SDL.windowDimensions = screenSize }
run engine GameConfig
{ initialFn = initial screenSize
, updateFn = update
, subscriptionsFn = subscriptions
, viewFn = view
}
As for specific issues I've been having, there is nothing other than my lack of time that has set back the completion of minesweeper, but one little thing that has been bothering me is not being able to lock window resizing to a particular aspect ratio. I currently have support for nicely scaling the window's contents, but if it is wider than it is tall, for example, there is just a black empty space to the right of the actual minesweeper grid.
It would be nice if there was a way to allow resizing, but also force the width and height of the window to remain the same. Again, this is mearly a little annoyance that I had experienced, and isn't particularly hindering progress, but if you have a solution for the issue, that would be great!
Let me know if you have any questions and I will keep you all posted if I make any progress!
Brooks J Rady
@TheLostLambda interesting problem. Set is no exposed through Helm yet, so implementation is a little bit tacky and works directly with SDLEngine. Here is how I implemented aspect fit:
module Main where
import Helm
import Helm.Engine.SDL.Engine
import Helm.Graphics2D
import qualified Helm.Cmd as Cmd
import qualified Helm.Window as Window
import qualified Helm.Engine.SDL as SDL
import Data.StateVar (($=))
import Linear.V2 (V2(V2))
import qualified SDL.Video as Video
data Action = Idle | FitWindow Video.Window (V2 Int)
data Model = Model
initial :: (Model, Cmd SDLEngine Action)
initial = (Model, Cmd.none)
fit :: (V2 Int) -> (V2 Int)
fit (V2 width height) = V2 dimension dimension
where dimension = min width height
update :: Model -> Action -> (Model, Cmd SDLEngine Action)
update model Idle = (model, Cmd.none)
update model (FitWindow window size) = (model, resizeCommand)
where resizeCommand = Cmd.execute (Video.windowSize window $= fromIntegral <$> fit size) $ const Idle
subscriptions :: Video.Window -> Sub SDLEngine Action
subscriptions window = Window.resizes $ FitWindow window
view :: Model -> Graphics SDLEngine
view _ = Graphics2D $ collage []
main :: IO ()
main = do
engine@SDLEngine { window } <- SDL.startup
run engine GameConfig
{ initialFn = initial
, updateFn = update
, subscriptionsFn = subscriptions window
, viewFn = view
}
I added #121 to expose window size through Engine, although could be a smaller priority in compare with other tasks.
Does anyone wants to port Mario from Elm examples? We are kinda Elm inspired, it will makes sense to have game examples that they have :) http://debug.elm-lang.org/edit/Mario.elm Also it will be the first example with at least some sprites involved.