starter-snake-haskell (empty) → 1.0.0
raw patch · 17 files changed
+468/−0 lines, 17 filesdep +aesondep +basedep +containerssetup-changed
Dependencies added: aeson, base, containers, scotty, starter-snake-haskell, text
Files
- CHANGELOG.md +12/−0
- LICENSE +29/−0
- README.md +19/−0
- Setup.hs +2/−0
- app/Main.hs +45/−0
- src/Battlesnake/API/GameRequest.hs +23/−0
- src/Battlesnake/API/InfoResponse.hs +23/−0
- src/Battlesnake/API/MoveResponse.hs +17/−0
- src/Battlesnake/Core/Battlesnake.hs +30/−0
- src/Battlesnake/Core/Board.hs +25/−0
- src/Battlesnake/Core/Coordinate.hs +26/−0
- src/Battlesnake/Core/Direction.hs +13/−0
- src/Battlesnake/Core/Game.hs +21/−0
- src/Battlesnake/Core/Ruleset.hs +19/−0
- src/Battlesnake/Server.hs +72/−0
- starter-snake-haskell.cabal +89/−0
- test/Spec.hs +3/−0
+ CHANGELOG.md view
@@ -0,0 +1,12 @@+# Changelog++All notable changes to this project since `v1.0.0` will be documented in this+file.++This project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).++## 1.0.0 (2023-08-22)++### Added++- First package release
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2021, Alexander Pankoff+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,19 @@+# starter-snake-haskell - A Haskell Battlesnake starter++This repository contains a Haskell [Battlesnake](https://play.battlesnake.com) starter project.+It implements `v1` of the Battlesnake API and can be deployed to Heroku.++# Usage++This starter repo can be used in two different ways++1. Installed as a hackage library in your haskell project+1. As a fork that is subsequently modified to your needs++# Deployment++To deploy on Heroku you can use the provided `Procfile` when using the template+as a fork. Otherwise you will need to define your own `Procfile`.+Addtionally the+[heroku-buildpack-stack](https://github.com/mfine/heroku-buildpack-stack)+buildpack is required for the application to run on heroku
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,45 @@+module Main where++import Battlesnake.API.GameRequest+import qualified Battlesnake.API.InfoResponse as Info+import Battlesnake.API.MoveResponse+import Battlesnake.Core.Direction+import Battlesnake.Server++main :: IO ()+main =+ runBattlesnakeServer+ snakeInfo+ startHandler+ moveHandler+ endHandler++snakeInfo :: Info.InfoResponse+snakeInfo =+ Info.InfoResponse+ { Info.apiversion = "1",+ -- Optional Customization:+ Info.author = Nothing, -- TODO: Your Battlsnake username here+ Info.color = Nothing, -- TODO: pesonalize+ Info.head = Nothing, -- TODO: pesonalize+ Info.tail = Nothing, -- TODO: pesonalize+ Info.version = Nothing -- TODO: Version number or tag for your battlesnake+ }++-- TODO:+-- implement start handler if you need to allocate ressources or data at the+-- beginning of a game+startHandler :: GameRequest -> IO ()+startHandler = const $ return ()++-- TODO:+-- implement your game logic here.+moveHandler :: GameRequest -> IO MoveResponse+moveHandler g = do+ print g+ return $ MoveResponse UP (Just "always going up!")++-- TODO:+-- implement end handler if you want to clean up after a game has ended+endHandler :: GameRequest -> IO ()+endHandler = const $ return ()
+ src/Battlesnake/API/GameRequest.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.API.GameRequest where++import Battlesnake.Core.Battlesnake (Battlesnake)+import Battlesnake.Core.Board (Board)+import Battlesnake.Core.Game (Game)+import Data.Aeson (FromJSON)+import GHC.Generics++{-|+ The request sent to your battlesnake server on each turn.+ This contains all the information you need to determine your snakes next move.}+-}+data GameRequest = GameRequest+ { game :: Game, -- ^ Information about the current game. (See "Battlesnake.Core.Game")+ turn :: Integer, -- ^ The turn beeing played.+ board :: Board, -- ^ The game board. (See "Battlesnake.Core.Board")+ you :: Battlesnake -- ^ Your snake. (See "Battlesnake.Core.Battlesnake"")+ }+ deriving (Show, Generic)++instance FromJSON GameRequest
+ src/Battlesnake/API/InfoResponse.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.API.InfoResponse where++import Data.Aeson (ToJSON)+import Data.Text+import GHC.Generics++{-|+ The response to an info request.+ This can be used to modify the appearance of your snake on the game board.+-}+data InfoResponse = InfoResponse+ { apiversion :: Text, -- ^ The API version supported by the battlesnake server (currently 1).+ author :: Maybe Text, -- ^ The username of the author of the snake.+ color :: Maybe Text, -- ^ A color code used to set the color of the snake.+ head :: Maybe Text, -- ^ A custom head to use for the snake. (See https://play.battlesnake.com/customizations for options.)+ tail :: Maybe Text, -- ^ A custom tail to use for the snake. (See https://play.battlesnake.com/customizations for options.)+ version :: Maybe Text -- ^ The version of the snake.+ }+ deriving (Show, Generic)++instance ToJSON InfoResponse
+ src/Battlesnake/API/MoveResponse.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.API.MoveResponse where++import Battlesnake.Core.Direction+import Data.Aeson (ToJSON)+import Data.Text+import GHC.Generics++-- | The response to a move request.+data MoveResponse = MoveResponse+ { move :: Direction, -- ^ The direction for the next move.+ shout :: Maybe Text -- ^ An optional shout to be displayed with the move.+ }+ deriving (Show, Generic)++instance ToJSON MoveResponse
+ src/Battlesnake/Core/Battlesnake.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.Core.Battlesnake where++import Battlesnake.Core.Coordinate (Coordinate)+import Data.Aeson (FromJSON, ToJSON)+import Data.Text+import GHC.Generics+++{-|+ A snake in the game.+ See: https://docs.battlesnake.com/api/objects/battlesnake+-}+data Battlesnake = Battlesnake+ { id :: Text, -- ^ The ID of the snake.+ name :: Text, -- ^ The name of the snake.+ health :: Integer, -- ^ Current health of the snake (0-100).+ body :: [Coordinate], -- ^ A list of coordinates representing the snake's body.+ latency :: Text, -- ^ The snake's latency.+ head :: Coordinate, -- ^ The position of the snakes head+ length :: Integer, -- ^ The length of the snake.+ shout :: Text, -- ^ The snake's last moves shout.+ squad :: Maybe Text -- ^ The squad the snake is a part of (In squad mode).+ }+ deriving (Show, Generic)++instance ToJSON Battlesnake++instance FromJSON Battlesnake
+ src/Battlesnake/Core/Board.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.Core.Board where++import Battlesnake.Core.Battlesnake (Battlesnake)+import Battlesnake.Core.Coordinate (Coordinate)+import Data.Aeson (FromJSON, ToJSON)+import GHC.Generics++{-|+ The game board.+ See: https://docs.battlesnake.com/api/objects/board+-}+data Board = Board+ { height :: Integer, -- ^ The height of the board+ width :: Integer, -- ^ The width of the board+ food :: [Coordinate], -- ^ A list of coordinates representing food locations+ hazards :: [Coordinate], -- ^ A list of coordinates representing hazard locations+ snakes :: [Battlesnake] -- ^ A list of all snakes on the board+ }+ deriving (Show, Generic)++instance ToJSON Board++instance FromJSON Board
+ src/Battlesnake/Core/Coordinate.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.Core.Coordinate where++import Data.Aeson+ ( FromJSON (parseJSON),+ KeyValue ((.=)),+ ToJSON (toEncoding),+ pairs,+ withObject,+ (.:),+ )+import GHC.Generics++-- | Represents a coordinate on the game board.+data Coordinate = Coordinate+ { coordX :: Integer, -- ^ The X coordinate (0 indexed, from left to right)+ coordY :: Integer -- ^ The Y coordinate (0 indexed, from bottom to top)+ }+ deriving (Show, Eq, Generic)++instance ToJSON Coordinate where+ toEncoding (Coordinate x y) = pairs ("x" .= x <> "y" .= y)++instance FromJSON Coordinate where+ parseJSON = withObject "Coordinate" $ \v -> Coordinate <$> v .: "x" <*> v .: "y"
+ src/Battlesnake/Core/Direction.hs view
@@ -0,0 +1,13 @@+module Battlesnake.Core.Direction where++import Data.Aeson (ToJSON, Value (String), toJSON)+import Data.Text++{- |+ The possible directions your snake can move in.+ See: https://docs.battlesnake.com/api/requests/move+ -}+data Direction = UP | DOWN | LEFT | RIGHT deriving (Ord, Eq, Show)++instance ToJSON Direction where+ toJSON = String . toLower . pack . show
+ src/Battlesnake/Core/Game.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.Core.Game where++import Battlesnake.Core.Ruleset+import Data.Aeson (FromJSON)+import Data.Text+import GHC.Generics++{-|+ Information about the current game configuration.+ See: https://docs.battlesnake.com/api/objects/game+-}+data Game = Game+ { id :: Text, -- ^ The ID of the game.+ ruleset :: Ruleset, -- ^ The ruleset used in the game.+ timeout :: Integer -- ^ The maximum time in milliseconds the server will wait for a response from your server.+ }+ deriving (Show, Eq, Generic)++instance FromJSON Game
+ src/Battlesnake/Core/Ruleset.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE DeriveGeneric #-}++module Battlesnake.Core.Ruleset where++import Data.Aeson (FromJSON)+import Data.Text+import GHC.Generics++{-|+ Information about the ruleset used in the game.+ See: https://docs.battlesnake.com/api/objects/game+-}+data Ruleset = Ruleset+ { name :: Text, -- ^ The name of the ruleset.+ version :: Text -- ^ The version of the ruleset.+ }+ deriving (Show, Eq, Generic)++instance FromJSON Ruleset
+ src/Battlesnake/Server.hs view
@@ -0,0 +1,72 @@+module Battlesnake.Server (GameRequestHandler, runBattlesnakeServer) where++import Battlesnake.API.GameRequest+import qualified Battlesnake.API.InfoResponse as Info+import Battlesnake.API.MoveResponse+import Control.Monad.IO.Class (liftIO)+import Data.Functor ((<&>))+import Data.Maybe (fromMaybe)+import System.Environment (lookupEnv)+import System.IO (BufferMode(LineBuffering), hSetBuffering, stdout)+import Text.Read (readMaybe)+import Web.Scotty+++-- | A handler for a battlesnake server request.+type GameRequestHandler a = GameRequest -> IO a++{- |+ Run a battlesnake server. Runs the server on the port specified by the environment variable "PORT" or port 3000 if the variable is not set.+ * A "Battlesnake.API.InfoResponse" to be returned when the server receives an info request.+ * A 'GameRequestHandler' to be called when the server receives a start request.+ * A 'GameRequestHandler' to be called when the server receives a move request.+ * A 'GameRequestHandler' to be called when the server receives an end request.+-}+runBattlesnakeServer ::+ Info.InfoResponse ->+ GameRequestHandler () ->+ GameRequestHandler MoveResponse ->+ GameRequestHandler () ->+ IO ()+runBattlesnakeServer info startHandler moveHandler endHandler = do+ hSetBuffering stdout LineBuffering+ envPort <- lookupEnv "PORT" <&> (readMaybe =<<)+ scotty (fromMaybe 3000 envPort) $+ routes+ info+ startHandler+ moveHandler+ endHandler++routes ::+ Info.InfoResponse ->+ GameRequestHandler () ->+ GameRequestHandler MoveResponse ->+ GameRequestHandler () ->+ ScottyM ()+routes info startHandler moveHandler endHandler = do+ get "/" $ handleInfoRequest info+ post "/start" $ handleStartRequest startHandler+ post "/move" $ handleMoveRequest moveHandler+ post "/end" $ handleEndRequest endHandler++handleInfoRequest :: Info.InfoResponse -> ActionM ()+handleInfoRequest = json++handleStartRequest :: GameRequestHandler () -> ActionM ()+handleStartRequest handler = do+ gameRequest <- jsonData+ liftIO $ handler gameRequest+ return ()++handleMoveRequest :: GameRequestHandler MoveResponse -> ActionM ()+handleMoveRequest handler = do+ gameRequest <- jsonData+ nextMove <- liftIO $ handler gameRequest+ json nextMove++handleEndRequest :: GameRequestHandler () -> ActionM ()+handleEndRequest handler = do+ gameRequest <- jsonData+ liftIO $ handler gameRequest+ return ()
+ starter-snake-haskell.cabal view
@@ -0,0 +1,89 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.1.+--+-- see: https://github.com/sol/hpack++name: starter-snake-haskell+version: 1.0.0+synopsis: A Haskell Battlesnake starter+description: See README at <https://github.com/ccntrq/starter-snake-haskell>+category: Game, Web+homepage: https://github.com/ccntrq/starter-snake-haskell#readme+bug-reports: https://github.com/ccntrq/starter-snake-haskell/issues+author: Alexander Pankoff <ccntrq@screenri.de+maintainer: Alexander Pankoff <ccntrq@screenri.de>+copyright: 2021 Alexander Pankoff+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md+ LICENSE++source-repository head+ type: git+ location: https://github.com/ccntrq/starter-snake-haskell++library+ exposed-modules:+ Battlesnake.API.GameRequest+ Battlesnake.API.InfoResponse+ Battlesnake.API.MoveResponse+ Battlesnake.Core.Battlesnake+ Battlesnake.Core.Board+ Battlesnake.Core.Coordinate+ Battlesnake.Core.Direction+ Battlesnake.Core.Game+ Battlesnake.Core.Ruleset+ Battlesnake.Server+ other-modules:+ Paths_starter_snake_haskell+ hs-source-dirs:+ src+ default-extensions:+ OverloadedStrings+ ghc-options: -Wall+ build-depends:+ aeson >=1.4 && <3+ , base >=4.13 && <5+ , containers >=0.6 && <1+ , scotty >=0.11 && <1+ , text >=1.2 && <2+ default-language: Haskell2010++executable starter-snake-haskell-exe+ main-is: Main.hs+ other-modules:+ Paths_starter_snake_haskell+ hs-source-dirs:+ app+ default-extensions:+ OverloadedStrings+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends:+ aeson >=1.4 && <3+ , base >=4.13 && <5+ , containers >=0.6 && <1+ , scotty >=0.11 && <1+ , starter-snake-haskell+ , text >=1.2 && <2+ default-language: Haskell2010++test-suite starter-snake-haskell+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_starter_snake_haskell+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends:+ aeson >=1.4 && <3+ , base >=4.13 && <5+ , containers >=0.6 && <1+ , scotty >=0.11 && <1+ , starter-snake-haskell+ , text >=1.2 && <2+ default-language: Haskell2010
+ test/Spec.hs view
@@ -0,0 +1,3 @@+main :: IO ()+main = do+ putStrLn "Not implemented"