patternMinor
Racetrack game with reading the track from a file
Viewed 0 times
readingthefiletrackwithracetrackgamefrom
Problem
For this community challenge I made the racetrack game. I'm reading the race data from a text file, which is available here. This is only playable by entering the velocity at the console.
I've got two modules. I'm open for any comments.
Racetrack.hs
RacetrackGameData.hs
```
module RacetrackGameData (serializeGame, Game(..), Point, Cell(..), Player(..), playerIsInValidState, prettyGameOutput) where
import Data.List
import Data.Maybe
import Data.Matrix
import Data.Serialize
import qualified Data.ByteString.Char8 as BStr
type Point = (Int, Int)
data Player = Player { position :: Point, velocity :: Point, playerId :: Int } deriving (Show)
data Cell = Empty | Wall deriving (Show,
I've got two modules. I'm open for any comments.
Racetrack.hs
import Data.Serialize
import qualified Data.ByteString.Char8 as BStr
import Text.Read(readMaybe)
import RacetrackGameData
zipT :: (a -> b -> c) -> (a, a) -> (b, b) -> (c, c)
zipT f (x,y) (x', y') = (f x x',f y y')
isCrossingTheLine :: (Point,Point)->Point->Point->Bool
isCrossingTheLine line origin target =
let
reduce = uncurry $ zipT (-)
(dx,dy) = reduce line
(dx',dy') = reduce (origin, target)
in 0 /= dx * dy' - dx' * dy
updatePlayer :: Player -> Point -> Player
updatePlayer player newVelocity = player { position = newPosition, velocity = newVelocity }
where newPosition = zipT (+) (position player) newVelocity
playerInput :: Player -> IO Player
playerInput player = do
velo' Game -> IO ()
playGame allPlayers game = play allPlayers
where
play [] = error "No players"
play players@(current:nexts) = do
print current
putStrLn $ prettyGameOutput players game
current' String -> IO a
safeGetLine errorMessage = do
pt return p
Nothing -> do
putStrLn errorMessage
safeGetLine errorMessage
main :: IO()
main = do
decodedGame putStrLn err
Right game -> playGame (map (\i -> Player (startPosition game) (0,0) i) [0..n]) gameRacetrackGameData.hs
```
module RacetrackGameData (serializeGame, Game(..), Point, Cell(..), Player(..), playerIsInValidState, prettyGameOutput) where
import Data.List
import Data.Maybe
import Data.Matrix
import Data.Serialize
import qualified Data.ByteString.Char8 as BStr
type Point = (Int, Int)
data Player = Player { position :: Point, velocity :: Point, playerId :: Int } deriving (Show)
data Cell = Empty | Wall deriving (Show,
Solution
--- playGame
Your
Note that the list concatenation
In practice this probably won't matter since the
Alternatively just use a
--- playerIsInValidState
You should have a bounds check here. If (x,y) is out of bounds then
--- general organization
Games typically have the following types and organization:
If a move can produce simple output, perhaps use this form for
Note that
Your game loop can now look like this:
where
Your
playGame function has this structure:playGame players@(current:rest) = do
...
playGame (rest ++ current')Note that the list concatenation
as ++ bs is O(n) where n is the size of as. That is, as basically gets copied, so this method of rotating a list is not efficient.In practice this probably won't matter since the
players list will be small, but it is something to keep in mind. A data structure which amortizes the copying might work better in this case is a difference list such as is implemented by the dlist package.Alternatively just use a
Data.Map where the key is a player number, and keep track of whose turn it is with an Int modulo the number of players - much like you would do in a conventional language.--- playerIsInValidState
You should have a bounds check here. If (x,y) is out of bounds then
getElem will throw an exception.--- general organization
Games typically have the following types and organization:
GameState - a data type recording the state of the game
Move - a type representing a possible move
step :: GameState -> Move -> GameState
-- apply the move to the game state returning the
-- new game stateIf a move can produce simple output, perhaps use this form for
step:step :: GameState -> Move -> (GameState, String)Note that
step is a pure function which makes it very easy to test. You can create unit tests for your code which may be run automatically by your build process. Pure functions are also a lot easier to test directly in a ghci session.Your game loop can now look like this:
gameLoop :: GameState -> IO ()
gameLoop state = do
move <- askPlayer state
let (state', output) = step state move
if (not $ null output)
then putStrLn output
else return ()
if gameOver state
then return ()
else gameLoop state'where
askPlayer and gameOver have the following signatures:askPlayer :: GameState -> IO Move
-- ask the current player for a move
gameOver :: GameState -> Bool
-- return True if the game is overCode Snippets
playGame players@(current:rest) = do
...
playGame (rest ++ current')GameState - a data type recording the state of the game
Move - a type representing a possible move
step :: GameState -> Move -> GameState
-- apply the move to the game state returning the
-- new game statestep :: GameState -> Move -> (GameState, String)gameLoop :: GameState -> IO ()
gameLoop state = do
move <- askPlayer state
let (state', output) = step state move
if (not $ null output)
then putStrLn output
else return ()
if gameOver state
then return ()
else gameLoop state'askPlayer :: GameState -> IO Move
-- ask the current player for a move
gameOver :: GameState -> Bool
-- return True if the game is overContext
StackExchange Code Review Q#101737, answer score: 4
Revisions (0)
No revisions yet.