freekick2-0.1.0: src/Main.hs
module Main() where
import System.IO.Error hiding (catch)
import Control.Monad
import System.Directory
import Data.Maybe
import Data.List
import System.IO (hPutStrLn, stderr)
import System.FilePath
import Control.Exception
import Prelude hiding (catch)
import Data.Ord
import Data.Function
import System.Random
import Control.Monad.State as State
import Control.Applicative
import Data.Binary
import qualified Data.ByteString.Char8 as Str
import Graphics.Rendering.OpenGL as OpenGL
import Graphics.UI.SDL as SDL hiding (SrcAlpha)
import Graphics.Rendering.FTGL as FTGL
import qualified Gen
import SDLUtils
import Tree
import Match.Match
import Drawing
import Listings
import Utils
import Paths_freekick2
data RenderContext = RenderContext {
renderfont :: Font
, smallerfont :: Font
, bgtexture :: TextureObject
}
data WorldContext = WorldContext {
rendercontext :: RenderContext
, worldteams :: TeamStructure
, hometeam :: Maybe (Gen.GenTeam, TeamOwner)
, awayteam :: Maybe (Gen.GenTeam, TeamOwner)
, tactics :: [((Int, Int, Int), Gen.GenFormation)]
}
type TeamStructure = Tree String (String, [Gen.GenTeam])
drawGenScene :: TextureObject -> [Button a] -> IO ()
drawGenScene tex btns = do
clear [ColorBuffer, DepthBuffer]
(w, h) <- getWindowSize
drawBox (Right tex) (color $ Color3 0.05 0.3 (0.1 :: GLfloat)) ((0, 0), (w, h)) (-1) Nothing
mapM_ drawButton btns
glSwapBuffers
type Material = Either SColor TextureObject
data Button a = Button { buttonMaterial :: Material
, buttonBox :: Camera
, buttonLabel :: String
, buttonFont :: Font
, buttonAction :: String -> a
}
drawButton :: Button a -> IO ()
drawButton b = drawBox (buttonMaterial b) (return ()) (buttonBox b) 0 (Just (buttonLabel b, buttonFont b))
modHometeam :: (Maybe (Gen.GenTeam, TeamOwner) -> Maybe (Gen.GenTeam, TeamOwner)) -> WorldContext -> WorldContext
modHometeam f c = c{hometeam = f (hometeam c)}
modAwayteam :: (Maybe (Gen.GenTeam, TeamOwner) -> Maybe (Gen.GenTeam, TeamOwner)) -> WorldContext -> WorldContext
modAwayteam f c = c{awayteam = f (awayteam c)}
type MenuBlock = StateT WorldContext IO
structureTeams :: [Gen.GenTeam] -> TeamStructure
structureTeams ts = f "World" ts (countryContinent . nationToString, continentToString) `g` (Gen.teamnation, nationToString) `g` (Gen.teamdivision, divisionToString)
where f :: (Ord a) => String -> [Gen.GenTeam] -> (Gen.GenTeam -> a, Gen.GenTeam -> String) -> TeamStructure
f n teams (func, nfunc) =
let ts' = splitBy func teams
in Node n (map (\tp -> Leaf (nfunc (head tp), tp)) ts')
g :: (Ord a) => TeamStructure -> (Gen.GenTeam -> a, Gen.GenTeam -> String) -> TeamStructure
g tr (func, nfunc) =
go tr
where go (Node i ts') = Node i (map go ts')
go (Leaf (i, ts')) = f i ts' (func, nfunc)
nationToString = showTeamNation . Gen.teamnation
divisionToString = showDivision . Gen.teamdivision
continentToString = show . countryContinent . nationToString
getFontAndTexture :: MenuBlock (Font, TextureObject)
getFontAndTexture = do
c <- rendercontext <$> State.get
return (renderfont c, bgtexture c)
getTwoFonts :: MenuBlock (Font, Font)
getTwoFonts = do
c <- rendercontext <$> State.get
return (renderfont c, smallerfont c)
getTSLabel :: TeamStructure -> String
getTSLabel (Node i _) = i
getTSLabel (Leaf (i, _)) = i
getTSChildrenTitles :: TeamStructure -> [String]
getTSChildrenTitles = (either (map getTSLabel) (map Gen.genteamname)) . getTSChildren
getTSTitles :: TeamStructure -> (String, [String])
getTSTitles t = (getTSLabel t, getTSChildrenTitles t)
getTSChildren :: TeamStructure -> Either [TeamStructure] [Gen.GenTeam]
getTSChildren (Node _ ts) = Left ts
getTSChildren (Leaf (_, ts)) = Right ts
getTSChildrenByTitle :: TeamStructure -> String -> Maybe (Either TeamStructure Gen.GenTeam)
getTSChildrenByTitle ts n =
case getTSChildren ts of
Left ts' -> liftM Left $ find (\t -> getTSLabel t == n) ts'
Right tms -> liftM Right $ find (\t -> Gen.genteamname t == n) tms
hasJust :: (Eq a) => a -> Maybe a -> Bool
hasJust _ Nothing = False
hasJust n (Just m) = n == m
rotateTeam :: Gen.GenTeam -> Maybe (Gen.GenTeam, TeamOwner) -> Maybe (Gen.GenTeam, TeamOwner)
rotateTeam t Nothing = Just (t, AIOwner)
rotateTeam _ (Just (t, AIOwner)) = Just (t, HumanOwner)
rotateTeam _ (Just (_, HumanOwner)) = Nothing
getOwner :: String -> WorldContext -> Maybe TeamOwner
getOwner t c =
let t1 = case hometeam c of
Nothing -> Nothing
Just (ht, ho) -> if Gen.genteamname ht == t then Just ho else Nothing
t2 = case awayteam c of
Nothing -> Nothing
Just (ht, ho) -> if Gen.genteamname ht == t then Just ho else Nothing
in t1 `mplus` t2
clickedOnTeam :: Gen.GenTeam -> MenuBlock ()
clickedOnTeam t = do
c <- State.get
if hasJust (Gen.genteamname t) (liftM (Gen.genteamname . fst) (hometeam c))
then modify $ modHometeam $ rotateTeam t
else if hasJust (Gen.genteamname t) (liftM (Gen.genteamname . fst) (awayteam c))
then modify $ modAwayteam $ rotateTeam t
else if isNothing (hometeam c)
then modify $ modHometeam $ rotateTeam t
else if isNothing (awayteam c)
then modify $ modAwayteam $ rotateTeam t
else return ()
browseTeams :: TeamStructure -> ButtonHandler
browseTeams toplevel _ = do
let (_, labels) = getTSTitles toplevel
if length labels == 1
then browserButtonHandler toplevel (head labels)
else do
mutLoop (browseTeams' toplevel)
return False
browserButtonHandler :: TeamStructure -> String -> MenuBlock Bool
browserButtonHandler toplevel lbl =
case getTSChildrenByTitle toplevel lbl of
Nothing -> return False
Just (Left t) -> browseTeams t (getTSLabel t)
Just (Right t) -> clickedOnTeam t >> return False
skinMagic, shirtMagic, shortsMagic, socksMagic, shoesMagic,
hair1Magic, hair2Magic, eyesMagic :: Gen.Color
skinMagic = (197, 169, 58)
shirtMagic = (255, 0, 0)
shortsMagic = (255, 240, 0)
socksMagic = (0, 0, 255)
shoesMagic = (3, 3, 3)
hair1Magic = (0, 0, 0)
hair2Magic = (16, 16, 16)
eyesMagic = (140, 85, 14)
colorKit :: Gen.Kit -> ChangeRGB
colorKit k c
| c == shirtMagic = Gen.kitfirstcolor k
| c == shortsMagic = Gen.kitshortcolor k
| c == socksMagic = Gen.kitsockscolor k
| otherwise = c
getRandomGen :: IO (Either Int StdGen)
getRandomGen = handle (\e -> hPutStrLn stderr ("Using random generator: " ++ show (e :: IOException)) >> newStdGen >>= return . Right) $ do
fp <- getAppUserDataDirectory appname
createDirectoryIfMissing True fp
ex <- doesFileExist (fp </> "seed")
if ex
then do
contents <- liftM Str.unpack $ Str.readFile (fp </> "seed")
case safeRead contents of
Nothing -> newStdGen >>= return . Right
Just i -> return (Left i)
else newStdGen >>= return . Right
startMatch
:: Font
-> Font
-> Gen.GenTeam
-> TeamOwner
-> Gen.GenTeam
-> TeamOwner
-> String
-> MenuBlock Bool
startMatch f1 f2 ht ho at ao _ = do
ptex <- liftIO $ loadDataTexture Nothing "share/grass1.png" Nothing Nothing
let hcf = colorKit (Gen.primarykit ht)
pltexhs <- liftIO $ loadDataTexture (Just hcf) "share/player1-s.png" (Just 0) (Just 32)
pltexhn <- liftIO $ loadDataTexture (Just hcf) "share/player1-n.png" (Just 0) (Just 32)
pltexhw <- liftIO $ loadDataTexture (Just hcf) "share/player1-w.png" (Just 0) (Just 32)
pltexhe <- liftIO $ loadDataTexture (Just hcf) "share/player1-e.png" (Just 0) (Just 32)
let acf = colorKit (Gen.primarykit at)
pltexas <- liftIO $ loadDataTexture (Just acf) "share/player1-s.png" (Just 0) (Just 32)
pltexan <- liftIO $ loadDataTexture (Just acf) "share/player1-n.png" (Just 0) (Just 32)
pltexaw <- liftIO $ loadDataTexture (Just acf) "share/player1-w.png" (Just 0) (Just 32)
pltexae <- liftIO $ loadDataTexture (Just acf) "share/player1-e.png" (Just 0) (Just 32)
balltex <- liftIO $ loadDataTexture Nothing "share/ball1.png" (Just 0) (Just 8)
playershadowtex <- liftIO $ loadDataTexture Nothing "share/player1shadow.png" (Just 0) (Just 32)
ballshadowtex <- liftIO $ loadDataTexture Nothing "share/ball1shadow.png" (Just 0) (Just 8)
gl1 <- liftIO $ loadDataTexture Nothing "share/goal1.png" Nothing Nothing
gl2 <- liftIO $ loadDataTexture Nothing "share/goal2.png" Nothing Nothing
gl1shadow <- liftIO $ loadDataTexture Nothing "share/goal1shadow.png" Nothing Nothing
gl2shadow <- liftIO $ loadDataTexture Nothing "share/goal2shadow.png" Nothing Nothing
allTactics <- tactics <$> State.get
let ballimg = ImageInfo balltex (0.4, 0.4)
let playershadowimg = ImageInfo playershadowtex (2, 2)
let ballshadowimg = ImageInfo ballshadowtex (0.4, 0.4)
htac = fromMaybe (snd $ head allTactics) $ lookup (Gen.teamtactics ht) allTactics
atac = fromMaybe (snd $ head allTactics) $ lookup (Gen.teamtactics at) allTactics
rgen <- liftIO $ getRandomGen
liftIO $ playMatch
rgen
(MatchTextureSet ptex
(PlayerTextureSet pltexhs pltexhn pltexhw pltexhe)
(PlayerTextureSet pltexas pltexan pltexaw pltexae)
playershadowimg
ballimg
ballshadowimg
(2, 2)
(ImageInfo gl1 (7.32, 2.44))
(ImageInfo gl2 (7.32, 3.44))
(ImageInfo gl1shadow (9.32, 4.44))
(ImageInfo gl2shadow (9.32, 4.44)))
f1 f2 (ht, htac, ho) (at, atac, ao)
return False
continueToMatch :: MenuBlock ()
continueToMatch = do
c <- State.get
(f1, f2) <- getTwoFonts
(w, h) <- liftIO $ getWindowSize
case hometeam c of
Nothing -> return ()
Just (ht, ho) -> do
case awayteam c of
Nothing -> return ()
Just (at, ao) -> do
let quitlabel = "Back"
title = "Match"
quitbutton = Button (Left SOrange) ((10, 10), (200, 30)) quitlabel f1 (\_ -> return True)
team1buttons = map
(\(n, t) ->
Button (Left SOrange)
((20, h - 100 - n * 25), (240, 20))
t f2 (\_ -> return False))
(zip [0..] t1labels)
t1labels = map Gen.plname (Gen.teamplayers ht)
team2buttons = map
(\(n, t) ->
Button (Left SOrange)
((520, h - 100 - n * 25), (240, 20))
t f2 (\_ -> return False))
(zip [0..] t2labels)
t2labels = map Gen.plname (Gen.teamplayers at)
titlebutton = Button (Left SOrange) ((w `div` 2 - 100, h - 50), (200, 30)) title f1 (\_ -> return False)
contlabel = "Play"
contbutton = Button (Left SOrange)
((w - 210, 10), (200, 30))
contlabel f1
(startMatch f1 f2 ht ho at ao)
allbuttons = contbutton : quitbutton : titlebutton : team1buttons ++ team2buttons
genLoop allbuttons
ownerToColor :: String -> WorldContext -> SColor
ownerToColor t c =
case getOwner t c of
Nothing -> SOrange
Just AIOwner -> SRed
Just HumanOwner -> SBlue
browseTeams' :: TeamStructure -> MenuBlock [Button (MenuBlock Bool)]
browseTeams' toplevel = do
let (title, labels) = getTSTitles toplevel
c <- State.get
(f1, f2) <- getTwoFonts
(w, h) <- liftIO $ getWindowSize
let quitlabel = "Back"
quitbutton = Button (Left SOrange) ((10, 10), (200, 30)) quitlabel f1 (\_ -> return True)
teambuttons = map
(\(n, t) ->
Button (Left (ownerToColor t c))
(if length labels > 10
then ((20 + 250 * (n `mod` 3), h - 100 - (n `div` 3) * 25), (240, 20))
else ((270, h - 150 - n * 35), (240, 30)))
t (if length labels > 10 then f2 else f1) (browserButtonHandler toplevel))
(zip [0..] labels)
titlebutton = Button (Left SOrange) ((w `div` 2 - 100, h - 50), (200, 30)) title f1 (\_ -> return False)
contlabel = "Play"
mcont = if isJust (hometeam c) && isJust (awayteam c)
then Just $ Button (Left SOrange) ((w - 210, 10), (200, 30)) contlabel f1 (\_ -> continueToMatch >> return False)
else Nothing
allbuttons = case mcont of
Nothing -> quitbutton : titlebutton : teambuttons
Just cn -> cn : quitbutton : titlebutton : teambuttons
return allbuttons
splitBy :: (Ord b) => (a -> b) -> [a] -> [[a]]
splitBy f = groupBy ((==) `on` f) . sortBy (comparing f)
checkGenButtonClicks :: (MonadIO m) => [Button (m Bool)] -> [SDL.Event] -> m Bool
checkGenButtonClicks btns evts = do
btnsclicked <- liftIO $ mouseClickInAnyM [ButtonLeft] (map buttonBox btns) evts
let mlbl = liftM buttonLabel $
btnsclicked >>= \b ->
find (\bt -> b == buttonBox bt) btns
case mlbl of
Nothing -> return False
Just lbl -> do
let mbt = find (\b -> buttonLabel b == lbl) btns
case mbt of
Nothing -> return False
Just bt -> (buttonAction bt) lbl
type ButtonHandler = String -> MenuBlock Bool
mutLoop :: MenuBlock [Button (MenuBlock Bool)] -> MenuBlock ()
mutLoop f = do
liftIO $ SDL.delay 40
btns <- f
(_, tex) <- getFontAndTexture
liftIO $ drawGenScene tex btns
evts <- liftIO $ pollAllSDLEvents
back <- checkGenButtonClicks btns evts
let escpressed = isJust $ specificKeyPressed [SDLK_ESCAPE] evts
if back || escpressed
then return ()
else mutLoop f
genLoop :: [Button (MenuBlock Bool)] -> MenuBlock ()
genLoop btns = mutLoop (return btns)
main :: IO ()
main = catch run (\e -> hPutStrLn stderr $ "Exception: " ++ show (e :: IOException))
loadDataFont :: Int -> Int -> FilePath -> IO Font
loadDataFont sz pt fp =
let act = (\fn -> createTextureFont fn >>= \f -> setFontFaceSize f sz pt >> return f)
in loadDataResource fp act
loadDataResource :: FilePath -> (FilePath -> IO a) -> IO a
loadDataResource fp act = do
fn <- getDataFileName fp
exists <- doesFileExist fn
when (not exists) $ do
throwIO $ mkIOError doesNotExistErrorType "loading data resource" Nothing (Just fn)
act fn
loadDataTexture :: Maybe ChangeRGB -> FilePath -> Maybe Int -> Maybe Int -> IO TextureObject
loadDataTexture cf fp mn mx = loadDataResource fp (loadTexture cf mn mx)
getUserDataTeams :: IO [Gen.GenTeam]
getUserDataTeams = handle (\e -> hPutStrLn stderr ("Exception when loading user data teams: " ++ show (e :: IOException)) >> return []) $ do
fp <- getAppUserDataDirectory appname
let dr = fp </> "teams"
createDirectoryIfMissing True dr
readDir dr
appname = "freekick2"
getUserDataTactics :: IO [Gen.SimpleFormation]
getUserDataTactics = handle (\e -> hPutStrLn stderr ("Exception when loading user data tactics: " ++ show (e :: IOException)) >> return []) $ do
fp <- getAppUserDataDirectory appname
let dr = fp </> "tactics"
createDirectoryIfMissing True dr
readDir dr
run :: IO ()
run = do
let width, height :: (Num a) => a
width = 800
height = 600
_ <- setVideoMode width height 0 [OpenGL]
depthFunc $= Nothing
blend $= Enabled
blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
clearColor $= Color4 0 0 0 1
viewport $= (Position 0 0, Size width height)
matrixMode $= Projection
loadIdentity
setCamera ((0, 0), (width, height))
matrixMode $= Modelview 0
texture Texture2D $= Enabled
tex <- loadDataTexture Nothing "share/bg.png" Nothing Nothing
f <- loadDataFont 24 48 "share/DejaVuSans.ttf"
f2 <- loadDataFont 16 48 "share/DejaVuSans.ttf"
teamdir <- getDataFileName "share/teams"
shareteamlist <- readDir teamdir
plteamlist <- getUserDataTeams
let allteams = structureTeams (shareteamlist ++ plteamlist)
tacticdir <- getDataFileName "share/tactics"
sharets <- readDir tacticdir
plts <- getUserDataTactics
let simplets = sharets ++ plts
let ts = zip (map Gen.simpleorder simplets) (map Gen.simpleFormationToGenFormation simplets)
let button1 = Button (Left SOrange) ((300, 200), (200, 30)) quitLabel f (\_ -> return True)
button2 = Button (Left SBlue) ((300, 400), (200, 30)) browseLabel f (browseTeams allteams)
browseLabel = "Friendly"
quitLabel = "Quit"
buttons = [button1, button2]
rc = RenderContext f f2 tex
evalStateT (genLoop buttons) (WorldContext rc allteams Nothing Nothing ts)
readDir :: (Binary a) => FilePath -> IO [a]
readDir fp = do
ex <- doesDirectoryExist fp
if not ex
then return []
else do
fs <- getDirectoryContents fp
tss <- forM fs $ \f -> do
isfile <- doesFileExist (fp </> f)
if isfile
then decodeFile (fp </> f)
else return []
return $ concat tss