-- Game.hs -- Tom Moertel -- CVS $Id: Game.hs,v 1.7 2002/09/09 20:53:40 thor Exp $ -- | The Game module provides types and functions for reading, -- representing, and analyzing games. module Game ( Game(..), GameTurn(..), TurnHistory , readProxyLog , mergeGames , gameHistory , gameTurnHistory , gameRobotNames ) where import Data.List (sort, union) import BasicTypes import Board import Commands -- | A 'Game' represents a complete game -- its entire history, as -- recorded from a player robot(s)'s point of view. Values of type -- 'Game' are represented in a way that conveniently allows different -- robots' views of the a game to be merged to allow for a more -- complete understanding of what happened during the game. data Game = Game { -- | Handshake the recording robot sent to -- the server to announce itself. If this -- is a merged 'Game', there wil be one -- handshake per robot that contributed -- a recording. gamePlayerHandshakes :: [( RobotID, String )] -- | Game board that the server sent , gameBoard :: Board -- | Robot configuration that server sent -- (one for each robot that contributed -- a recording) , gameRobotConfigs :: [( RobotID, RobotConfiguration )] -- | Initial reponse from server , gameInitialResponse :: [RobotCommandList] -- | All the turns of the game , gameTurns :: [GameTurn] -- | Any final information provided -- by the server after a robot -- died or the game ended (one entry -- per robot that contributed a -- recording). The 'RobotID' -- identifies the robot that -- received the epilogue -- messages. The @Int@ gives the -- turn on which the robot died or -- otherwise finished the game. The -- messages themselves are given in -- the @[String]@ field. , gameEpilogues :: [( RobotID , Int , [String] )] } deriving (Show, Read, Eq) -- | Creates a lookup list from RobotIDs to robot names by reading -- annotations that have been added to the gamePlayerHandshakes. An -- annotation takes the form of a slash followed by the robot's -- name. For example the robot configuration @(1, "Player")@ could -- be annotated as @(1, "Player\/My big bad bot")@ Note that there -- is no guarantee that a name annotation exists. Therefore callers -- should be prepared to empty names. gameRobotNames :: Game -> [(RobotID, String)] gameRobotNames g = map robotIDAndName (gamePlayerHandshakes g) where robotIDAndName (rid,annotated_hshake) = (rid, name) where name = case (dropWhile (/= '/') annotated_hshake) of ('/':annotation) -> annotation _ -> [] -- | Represents a turn of game play. data GameTurn = GameTurn { -- | Server's list of packages seen -- by robot player(s) this turn gtPackageInfo :: [( RobotID , [PackageInfo] )] -- | Commands sent by robot(s) to server , gtPlayerCommands :: [( RobotID, String )] -- | Response from server , gtServerResponse :: [ RobotCommandList ] } deriving (Show, Read, Eq, Ord) -- | This type represents everything that is independent of a robot's -- perspective that the server told us happened during a particular -- turn. It is essentially a 'GameTurn' without the commands that the -- robots requested of the server. type TurnHistory = ([(RobotID, [PackageInfo])], [RobotCommandList]) -- |Gets all the server information updates, which together represent a history -- of what happended during the game. gameTurnHistory :: Game -> [TurnHistory] gameTurnHistory g = map turnToTurnHistory (gameHistory g) where turnToTurnHistory t = (gtPackageInfo t, gtServerResponse t) -- |Gets all of the turns for the game. We create an artificial "Turn -- 0" based on the initial server response to make analysis easier. gameHistory :: Game -> [GameTurn] gameHistory g = GameTurn [] [] (gameInitialResponse g) : gameTurns g -- |Merges two 'Game' values into a composite 'Game'. Useful for -- combining 'Game' values that were recorded from the perspectives -- of competing robots into a single multi-perspective 'Game'. mergeGames :: Game -> Game -> Game mergeGames gA gB | differentBoards = error "Game boards are not the same." | incompatibleTurns = error "Game histories are not from the same game." | otherwise = mergedAB where differentBoards = (gameBoard gA) /= (gameBoard gB) incompatibleTurns = or (init (zipWith (/=) (turnHist gA) (turnHist gB))) turnHist = map snd . gameTurnHistory mergedAB = Game { gamePlayerHandshakes = handshakesAB , gameBoard = gameBoard gA , gameRobotConfigs = configsAB , gameInitialResponse = gameInitialResponse gA , gameTurns = turnsAB , gameEpilogues = epilogueAB } where handshakesAB = mergePart gamePlayerHandshakes configsAB = mergePart gameRobotConfigs epilogueAB = mergePart gameEpilogues mergePart part = sort $ (part gA) `union` (part gB) -- merging turns is a special case because the lists of turns -- from game A and game B may be of different lengths (some -- robots may have died earlier than others), so we merge into -- the longer of the two turnsAB = zipWith mergeTurn long short ++ drop (length short) long (gtA, gtB) = (gameTurns gA, gameTurns gB) (lenA, lenB) = (length gtA, length gtB) (long, short) = if lenA > lenB then (gtA,gtB) else (gtB,gtA) mergeTurn ta tb = ta { gtPackageInfo = mergeTPart gtPackageInfo , gtPlayerCommands = mergeTPart gtPlayerCommands } where mergeTPart part = sort $ (part ta) `union` (part tb) -- |Reads a game log recorded by RobotProxy and converts it into a -- 'Game'. readProxyLog :: String -> Game readProxyLog = readProxyLogLines . map (tail . snd . break (==' ')) -- strip labels . lines where readProxyLogLines (hdshake:board:config:initRCL:turnsAndEpilogue) = Game { gamePlayerHandshakes = addID $ read hdshake , gameBoard = read board , gameRobotConfigs = addID $ robotConfig , gameInitialResponse = read initRCL , gameTurns = turns , gameEpilogues = [( robotID , length turns , epilogue )] } where robotConfig = read config robotID = rcRbtID robotConfig addID x = [(robotID, x)] (turns, epilogue) = splitTurns turnsAndEpilogue splitTurns (pkgInfo:playerCmd:serverResponse:rest) = case serverResponse of '[':_ -> let (turns', epl) = splitTurns rest in ( GameTurn (addID (read pkgInfo)) (addID (read playerCmd)) (read serverResponse) : turns' , epl ) _ -> ( [ GameTurn (addID (read pkgInfo)) (addID (read playerCmd)) (read "[]") ] , map read (serverResponse:rest) ) splitTurns _ = ([],[]) readProxyLogLines _ = error "Bad proxy log format." -- ================================================================= -- -- Copyright (C) 2002 Thomas Moertel. -- -- This program is free software; you can redistribute it and/or -- modify it under the terms of the GNU General Public License -- as published by the Free Software Foundation; either version 2 -- of the License, or (at your option) any later version. -- -- The text of the GNU GPL may be found in the LICENSE file, -- included with this software, or online at the following URL: -- -- http://www.gnu.org/copyleft/gpl.html -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- Except as provided for under the terms of the GNU GPL, all rights -- are reserved worldwide. -- -- =================================================================