diff --git a/airesources/Haskell/.gitignore b/airesources/Haskell/.gitignore new file mode 100644 index 000000000..24047bee7 --- /dev/null +++ b/airesources/Haskell/.gitignore @@ -0,0 +1,3 @@ +.stack-work/ +halite.exe +halite diff --git a/airesources/Haskell/Setup.hs b/airesources/Haskell/Setup.hs new file mode 100644 index 000000000..9a994af67 --- /dev/null +++ b/airesources/Haskell/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/airesources/Haskell/halite-haskell.cabal b/airesources/Haskell/halite-haskell.cabal new file mode 100644 index 000000000..6bf201a6f --- /dev/null +++ b/airesources/Haskell/halite-haskell.cabal @@ -0,0 +1,32 @@ +name: halite-haskell +version: 0.1.0.0 +synopsis: Halite starter pack for Haskell +author: Jesse Paroz + , Josef Vonasek +maintainer: jesse.paroz@gmail.com + , j.f.vonasek@gmail.com +build-type: Simple +cabal-version: >=1.10 + +library + hs-source-dirs: lib + exposed-modules: Halite, Halite.Networking, Halite.Types, Halite.Classes, Halite.Logic + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , random + +executable MyBot + hs-source-dirs: src + main-is: MyBot.hs + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , halite-haskell + , random + +executable RandomBot + hs-source-dirs: src + main-is: RandomBot.hs + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , halite-haskell + , random diff --git a/airesources/Haskell/lib/Halite.hs b/airesources/Haskell/lib/Halite.hs new file mode 100644 index 000000000..8259c46e9 --- /dev/null +++ b/airesources/Haskell/lib/Halite.hs @@ -0,0 +1,8 @@ +module Halite + ( module M + ) where + +import Halite.Networking as M +import Halite.Classes as M +import Halite.Types as M +import Halite.Logic as M diff --git a/airesources/Haskell/lib/Halite/Classes.hs b/airesources/Haskell/lib/Halite/Classes.hs new file mode 100644 index 000000000..1ec68aebd --- /dev/null +++ b/airesources/Haskell/lib/Halite/Classes.hs @@ -0,0 +1,12 @@ +module Halite.Classes + ( RandomReader(..) + ) where + +-- use type classes to manage side effects for your algorithm function +-- if you want to add your custom side effect just create a new class +-- class Monad m => MyCustomSideEffect m where ... +-- and change type signature of your algorithm to +-- algorithm :: (MyCustomSideEffect m, RandomReader m) => .... -> m [Move] + +class Monad m => RandomReader m where + rand :: Int -> m Int diff --git a/airesources/Haskell/lib/Halite/Logic.hs b/airesources/Haskell/lib/Halite/Logic.hs new file mode 100644 index 000000000..9346df261 --- /dev/null +++ b/airesources/Haskell/lib/Halite/Logic.hs @@ -0,0 +1,47 @@ +module Halite.Logic + ( toLocation + , distance + , angle + , moveToLocation + , site + ) where + +import Halite.Types + +toLocation :: Map -> (Int, Int) -> Location +toLocation (Map width height _) (x, y) = + Location (mod x width) (mod y height) + +distance :: Map -> Location -> Location -> Int +distance (Map width height _) (Location x1 y1) (Location x2 y2) = + (x2 - x1) `mod` width + (y2 - y1) `mod` height + +angle :: Map -> Location -> Location -> Double +angle (Map width height _) (Location x1 y1) (Location x2 y2) = + atan2 (fromIntegral $ foo dx width) (fromIntegral $ foo dy height) + where + dx = x2 - x1 + dy = y2 - y1 + foo a b + | a > b - a = a - b + | -a > b + a = a + b + | otherwise = a + +moveToLocation :: Map -> Move -> Location +moveToLocation _ (Move location Still) = location +moveToLocation (Map width height _) (Move (Location x y) dir) = case dir of + North -> if y == 0 + then Location x (height -1) + else Location x (y +1) + South -> if y == height -1 + then Location x 0 + else Location x (y -1) + West -> if x == 0 + then Location (width -1) y + else Location (x +1) y + East -> if x == width -1 + then Location 0 y + else Location (x +1) y + +site :: Map -> Location -> Site +site (Map _ _ sites) (Location x y) = sites !! y !! x diff --git a/airesources/Haskell/lib/Halite/Networking.hs b/airesources/Haskell/lib/Halite/Networking.hs new file mode 100644 index 000000000..6e409896d --- /dev/null +++ b/airesources/Haskell/lib/Halite/Networking.hs @@ -0,0 +1,75 @@ +module Halite.Networking + ( communicate + ) where + +import Halite.Types +import Data.List (zipWith4) +import System.Random (getStdGen) +import System.IO (hFlush, stdout) + +communicate + :: Monad m + => String + -> (ID -> Map -> m [Move]) + -> (m [Move] -> a -> ([Move], a)) + -> a + -> IO () +communicate name algorithm runMonad input = do + (myID, gameMap) <- getInit + sendInit name + loop (algorithm myID) gameMap input runMonad + +loop algorithm gameMap input runMonad = do + gameMap' <- getFrame gameMap + let (moves, input') = runMonad (algorithm gameMap') input + sendFrame moves + loop algorithm gameMap' input' runMonad + +getInit :: IO (ID, Map) +getInit = do + userID <- read <$> getLine + [w, h] <- map read . words <$> getLine + prod <- map read . words <$> getLine + mapc <- parseMapContents (w, h) prod <$> getLine + return (userID, Map w h mapc) + +sendInit :: String -> IO () +sendInit s = putStrLn s >> hFlush stdout + +getFrame :: Map -> IO Map +getFrame (Map w h cont) = + Map w h . parseMapContents (w, h) (getProds cont) <$> getLine + +sendFrame' :: [Move] -> IO () +sendFrame' = putStrLn . unwords . map showMove + +sendFrame :: [Move] -> IO () +sendFrame s = sendFrame' s >> hFlush stdout + + +parseMapContents :: (Int, Int) -> [Int] -> String -> [[Site]] +parseMapContents (w, h) productions s = + splitEvery w $ zipWith4 Site owners strengths productions locations + where + (owners', strengths) = splitMapContents (read <$> words s) (w * h) + owners = stretch owners' + stretch (a:b:bs) = replicate a b ++ stretch bs + stretch [] = [] + xs = concat $ replicate h [0 .. w - 1] + ys = concatMap (replicate w) [0 .. h - 1] + locations = zipWith Location xs ys + +splitMapContents :: [Int] -> Int -> ([Int], [Int]) +splitMapContents xs area = splitAt (length xs - area) xs + +splitEvery :: Int -> [a] -> [[a]] +splitEvery _ [] = [] +splitEvery n xs = first : splitEvery n rest + where + (first, rest) = splitAt n xs + +getProds :: [[Site]] -> [Int] +getProds = map siteProduction . concat + +showMove :: Move -> String +showMove (Move (Location x y) d) = unwords $ map show [x, y, fromEnum d] diff --git a/airesources/Haskell/lib/Halite/Types.hs b/airesources/Haskell/lib/Halite/Types.hs new file mode 100644 index 000000000..b4cf10224 --- /dev/null +++ b/airesources/Haskell/lib/Halite/Types.hs @@ -0,0 +1,82 @@ +{-# LANGUAGE DeriveFunctor #-} + +module Halite.Types + ( Direction(..) + , Location(..) + , Map(..) + , Site(..) + , Move(..) + , ID + , Effects(runEffects) -- you don't want to export constructor + , Test(runTest) + ) where + + +import Control.Monad (ap) +import System.Random (StdGen, randomR) +import Halite.Classes + +data Direction + = Still + | North + | East + | South + | West + deriving (Show, Eq, Ord, Enum) + +data Location = Location + { posX :: Int + , posY :: Int + } deriving (Show, Eq) + +data Site = Site + { siteOwner :: Int + , siteStrength :: Int + , siteProduction :: Int + , siteLocation :: Location + } deriving (Show, Eq) + +data Move = Move + { movelocation :: Location + , moveDirection :: Direction + } deriving (Show, Eq) + +data Map = Map + { mapWidth :: Int + , mapHeight :: Int + , mapContents :: [[Site]] + } deriving (Show, Eq) + +type ID = Int + +-- these two monads allow you to use side effects in your algorithm + +data Effects a = Effects {runEffects :: StdGen -> (a, StdGen)} + deriving (Functor) + +instance Applicative Effects where + pure x = Effects (\y -> (x, y)) + (<*>) = ap + +instance Monad Effects where + m >>= k = Effects $ \s -> + let (a, s') = runEffects m s + in runEffects (k a) s' + +instance RandomReader Effects where + rand i = Effects $ randomR (0, i-1) + +data Test a = Test {runTest :: [Int] -> (a, [Int])} + deriving (Functor) + +instance Applicative Test where + pure x = Test (\y -> (x, y)) + (<*>) = ap + +instance Monad Test where + m >>= k = Test $ \s -> + let (a, s') = runTest m s + in runTest (k a) s' + +instance RandomReader Test where + rand i = Test $ \(x:xs) -> (mod x i, xs) diff --git a/airesources/Haskell/runGame.bat b/airesources/Haskell/runGame.bat new file mode 100644 index 000000000..d3df1b1ea --- /dev/null +++ b/airesources/Haskell/runGame.bat @@ -0,0 +1,3 @@ +cabal configure +cabal build +.\halite.exe -d "30 30" "dist/build/MyBot/MyBot" "dist/build/RandomBot/RandomBot" diff --git a/airesources/Haskell/runGame.sh b/airesources/Haskell/runGame.sh new file mode 100755 index 000000000..396e2aa8e --- /dev/null +++ b/airesources/Haskell/runGame.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +if hash stack 2>/dev/null; then + stack build + build="$(stack path --dist-dir)/build" +else + cabal configure + cabal build + build="./dist/build" +fi + +./halite -d "30 30" "$build/MyBot/MyBot" "$build/RandomBot/RandomBot" diff --git a/airesources/Haskell/src/MyBot.hs b/airesources/Haskell/src/MyBot.hs new file mode 100644 index 000000000..18819a812 --- /dev/null +++ b/airesources/Haskell/src/MyBot.hs @@ -0,0 +1,21 @@ +module Main where + +import System.Random (getStdGen) +import Halite + +main :: IO () +main = getStdGen >>= communicate name algorithm runEffects + +name = "Awesome Haskell Bot" + +algorithm :: RandomReader m => ID -> Map -> m [Move] +algorithm me g@(Map width height sites) = + randomMoves $ filter ((== me) . siteOwner) (concat sites) + +randomMoves :: RandomReader m => [Site] -> m [Move] +randomMoves = traverse $ randMove . siteLocation + where + randMove location = do + direction <- toEnum <$> rand 5 + return $ Move location direction + diff --git a/airesources/Haskell/src/RandomBot.hs b/airesources/Haskell/src/RandomBot.hs new file mode 100644 index 000000000..ddb2546ca --- /dev/null +++ b/airesources/Haskell/src/RandomBot.hs @@ -0,0 +1,20 @@ +module Main where + +import System.Random (getStdGen) +import Halite + +main :: IO () +main = getStdGen >>= communicate name algorithm runEffects + +name = "Random Haskell Bot" + +algorithm :: RandomReader m => ID -> Map -> m [Move] +algorithm me g@(Map width height sites) = + randomMoves $ filter ((== me) . siteOwner) (concat sites) + +randomMoves :: RandomReader m => [Site] -> m [Move] +randomMoves = traverse $ randMove . siteLocation + where + randMove location = do + direction <- toEnum <$> rand 5 + return $ Move location direction diff --git a/airesources/Haskell/stack.yaml b/airesources/Haskell/stack.yaml new file mode 100644 index 000000000..7a275c54c --- /dev/null +++ b/airesources/Haskell/stack.yaml @@ -0,0 +1,35 @@ +# This file was automatically generated by stack init +# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) +resolver: lts-7.8 + +# Local packages, usually specified by relative directory name +packages: +- '.' +# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) +extra-deps: [] + +# Override default flag values for local packages and extra-deps +flags: {} + +# Extra package databases containing global packages +extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true + +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: >= 1.0.0 + +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 + +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] + +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor diff --git a/website/advanced_game_server.php b/website/advanced_game_server.php index a2cf8341d..236445a2f 100644 --- a/website/advanced_game_server.php +++ b/website/advanced_game_server.php @@ -39,6 +39,8 @@
The following build automators are used: