Wednesday, July 17, 2013

The Polar Game in Haskell, Day 7: Towards a GUI, Continued

So, trying wxHaskell. First, I want to try removing everything that might be left over from yesterday's experiments:



Pauls-Mac-Pro:~ paul$ brew list


gettext libffi pkg-config xz

Pauls-Mac-Pro:~ paul$ brew uninstall gettext libffi pkg-config xz

Uninstalling /usr/local/Cellar/gettext/0.18.3...

Uninstalling /usr/local/Cellar/libffi/3.0.13...

Uninstalling /usr/local/Cellar/pkg-config/0.28...

Uninstalling /usr/local/Cellar/xz/5.0.5...

Pauls-Mac-Pro:~ paul$ port installed

The following ports are currently installed:

libiconv @1.140 (active)

pkgconfig @0.280 (active)

Pauls-Mac-Pro:~ paul$ sudo port uninstall pkgconfig libiconv

---> Deactivating pkgconfig @0.280

---> Cleaning pkgconfig

---> Uninstalling pkgconfig @0.280

---> Cleaning pkgconfig

---> Deactivating libiconv @1.140

---> Cleaning libiconv

---> Uninstalling libiconv @1.140

---> Cleaning libiconv



Then install wx: I'm attempting the directions . BREW INSTALL WXMAC --USE-LLVM --DEVEL



brew install wxmac --devel

Warning: It appears you have MacPorts or Fink installed.

Software installed with other package managers causes known problems

for Homebrew. If a formula fails to build, uninstall MacPorts/Fink

and try again.



(There shouldn't be any libraries or binaries in the various paths to interfere, so I'll ignore this). And it seemed to succeed. So, next step from the instructions above: CHECK YOUR PATH TO MAKE SURE YOU ARE USING YOUR WXWIDGETS AND NOT THE DEFAULT MAC ONE. THE COMMAND WHICH WX-CONFIG should not return the file path /usr/bin/wx-config (On my system it returns /USR/LOCAL/BIN/WX-CONFIG). Next, CABAL INSTALL WX CABAL-MACOSX. That chugs away for a while and I see an unnervingly large number of warnings, but it builds. And then, I saved as hello-ex.hs and GHC --MAKE HELLOWORLD.HS and MACOSX-APP HELLO-WX and ./HELLO-WX.APP/CONTENTS/MACOS/HELLO-WX and the result runs and I get a window, although it pops up off the bottom of my primary display, and the application's main menu does not seem to render its menu items quite right (they say "Hide H" and "Quit H" instead of the application name). But still -- promising!



So -- some code. To facilitate working with a GUI module in a separate .HS file I am now calling the core logic ARCTICSLIDECORE.HS. and that file begins with MODULE ARCTICSLIDECORE WHERE. I don't have very much working yet, but here's what is in my ARCTICSLIDEGUI.HS file so far. First I define my module and do my imports:



module Main where



import Graphics.UI.WX

import ArcticSlideCore



Then I define some bitmaps. For purposes of experimentation I made .PNG files out of the original Polar game's CICN resources. I want to redraw them -- first, to avoid blatant copyright infringement and second, to make them bigger. But temporarily:



bomb = bitmap "bomb.png"

heart = bitmap "heart.png"

house = bitmap "house.png"

ice = bitmap "iceblock.png"

tree = bitmap "tree.png"



While they are game tiles as such, there are icons for the penguin facing in the four cardinal directions and icons for a breaking ice block and exploding bomb that were used in original animations:



penguine = bitmap "penguineast.png"

penguins = bitmap "penguinsouth.png"

penguinw = bitmap "penguinwest.png"

penguinn = bitmap "penguinnorth.png"



icebreak = bitmap "iceblockbreaking.png"

bombexplode = bitmap "bombexploding.png"



I noticed that wxHaskell's POINT type operates in reverse. I'm accustomed to C arrays where the higher-rank indices come first (so y, x or row index, column index for tile positions), but points are backwards. My icons are 24x24 pixels, so I rearrange and scale them like so:



posToPoint :: Pos -> Point

posToPoint pos = ( Point ( posX pos * 24 ) ( posY pos * 24 ) )



Now, some convenience function for drawing bitmaps based on Tile type or based on a wxHaskell bitmap. These are two more cases where I was not sure of the type signature, so I wrote the functions without them:



drawBmp dc bmp pos = drawBitmap dc bmp point True []

where point = posToPoint pos



drawTile dc tile pos = drawBmp dc bmp pos

where bmp = case tile of Bomb -> bomb

Heart -> heart

House -> house

Ice -> ice

Tree -> tree



GHCI says:



Prelude Main> :t drawBmp

drawBmp

:: Graphics.UI.WXCore.WxcClassTypes.DC a

-> Graphics.UI.WXCore.WxcClassTypes.Bitmap ()

-> ArcticSlideCore.Pos

-> IO ()



That boils down to DRAWBMP :: DC A -> BITMAP () -> POS -> IO (), and the signature for DrawTile similarly boils down to DRAWTILE :: DC A -> TILE -> POS -> IO (). Thanks, GHCI!



Next, I need a view method. This is just a placeholder test to verify that I can draw all my icons in the bounds where I expect them:



draw dc view

= do

drawTile dc Bomb ( Pos 0 0 )

drawTile dc Heart ( Pos 0 1 )

drawTile dc House ( Pos 0 2 )

drawTile dc Ice ( Pos 0 3 )

drawTile dc Tree ( Pos 0 4 )

drawBmp dc penguine ( Pos 1 0 )

drawBmp dc penguins ( Pos 1 1 )

drawBmp dc penguinw ( Pos 1 2 )

drawBmp dc penguinn ( Pos 1 3 )

drawBmp dc icebreak ( Pos 0 23 )

drawBmp dc bombexplode ( Pos 3 23 )



Now, my GUY function is where things get interesting and wxHaskell shows off a little. I read this that talks about some of the layout options and other tricks of the wxHaskell implementation, and discovered that this maps really nicely to defining my window in terms of a grid of icons. SPACE 24 24 returns a layout item of the appropriate size, and GRID returns a layout item when given spacing values (I want the icons touching, so I use 0 0) and a list of lists for rows and columns. To generate the proper structure of 4 rows of 24 columns I just take what I need from infinite lists: TAKE 4 $ REPEAT $ TAKE 24 $ REPEAT $ SPACE 24 24 Oh, that's nifty!



gui :: IO ()

gui

= do f t ]

set f [ layout := grid 0 0 $ take 4 $ repeat $

take 24 $ repeat $ space 24 24

,bgcolor := white

,on paint := draw

return ()



And finally, main:



main :: IO ()

main

= start gui



To build this for use as a MacOS X GUI app I just do GHC --MAKE ./ARCTICSLIDEGUI.HS, and if it compiles properly then MACOSX-APP ARCTICSLIDEGUI; ./ARCTICSLIDEGUI.APP/CONTENTS/MACOS/ARCTICSLIDEGUI and I have a little GUI window:



Sweet! Now I've got some more thinking to do. There's some plumbing that needs to get hooked up between the core game logic and the GUI layer. The core game logic is mostly factored the way I want it to be -- it gets a world and a penguin move and returns an updated world -- but I need to do a little more than just map the tiles to a series of drawTile calls. I might want to support timed sequences of changes to the GUI representation of the board -- for example, smooth sliding of game pieces and smooth walking of the penguin. The DRAW method should draw the board pieces and the penguin all at once, with no redundancy if possible. Sound effects would be nice. Animation for crushing an ice block and blowing up a mountain would be nice. I've got some ideas along these lines based on event queues and a timer, and some other pieces of sample code I've been looking at.



Meanwhile, if any of you would like to take a crack at redrawing the graphics, please be my guest. It would be nice if the replacement icons would fit on an iPhone or iPod Touch. 48x84 is just a little bit too big -- 48 pixels by 24 icons is 1152 pixels, and the iPhone 4 and 5 screens are 640x960 and 640x1136. 40 pixels wide would fit perfectly on an iPhone 4. Note that the icons don't actually have to be square -- there is room to spare vertically. It might be nice, though, to leave room for a few extra rows, to support game boards that break out of the original 4-row height.
Full Post

No comments:

Post a Comment