HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Racetrack game with reading the track from a file

Submitted by: @import:stackexchange-codereview··
0
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

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]) game


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,

Solution

--- playGame

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 state


If 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 over

Code 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 state
step :: 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 over

Context

StackExchange Code Review Q#101737, answer score: 4

Revisions (0)

No revisions yet.