packages feed

intricacy-0.5.5: Metagame.hs

-- This file is part of Intricacy
-- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of version 3 of the GNU General Public License as
-- published by the Free Software Foundation, or any later version.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see http://www.gnu.org/licenses/.

module Metagame where

import Control.Applicative
import Data.Array
import Data.Binary
import Control.Monad
import Data.List (delete)
import Data.Char
import Data.Maybe

import GameStateTypes
import Lock

notesNeeded = 3
maxLocks = 3

type Codename = String

validCodeName name = length name == 3 && all validChar name
    where validChar c = isAscii c && isPrint c && not (isLower c) && c /= ' '

data UserInfo = UserInfo {codename::Codename, userLocks::Array LockIndex (Maybe LockInfo), notesRead::[NoteInfo]}
    deriving (Eq, Ord, Show, Read)

initUserInfo name = UserInfo name (array (0,maxLocks-1) $ zip [0..maxLocks-1] (repeat Nothing)) []

data LockInfo = LockInfo {lockSpec::LockSpec, public::Bool, notesSecured::[NoteInfo], lockSolutions::[NoteInfo], accessedBy::[Codename]}
    deriving (Eq, Ord, Show, Read)

initLockInfo ls = LockInfo ls False [] [] []

data NoteInfo = NoteInfo {noteAuthor::Codename, noteBehind::Maybe ActiveLock, noteOn::ActiveLock}
    deriving (Eq, Ord, Show, Read)

data ActiveLock = ActiveLock {lockOwner::Codename, lockIndex :: LockIndex}
    deriving (Eq, Ord, Show, Read)

data AccessedReason = AccessedPrivyRead | AccessedPrivySolved {noteReadByOwner::Bool} | AccessedEmpty | AccessedPub
    deriving (Eq, Ord, Show, Read)
winsPoint :: AccessedReason -> Bool
winsPoint (AccessedPrivySolved True) = False
winsPoint _ = True

getAccessInfo :: UserInfo -> UserInfo -> [Maybe AccessedReason]
getAccessInfo accessedUInfo accessorUInfo =
    let accessor = codename accessorUInfo
	mlinfos = elems $ userLocks accessedUInfo
	accessedSlot = maybe accessedAllExisting accessedLock
	accessedAllExisting = all (maybe True accessedLock) mlinfos
	accessedLock linfo = public linfo || accessor `elem` accessedBy linfo
    in map (maybe
	    (if accessedAllExisting then Just AccessedEmpty else Nothing)
	    (\linfo -> if public linfo then Just AccessedPub else
		if not $ accessedLock linfo then Nothing else Just $
		    if countRead accessorUInfo linfo >= notesNeeded then AccessedPrivyRead
			else AccessedPrivySolved $ not.null $
			    [ n
			    | n <- lockSolutions linfo 
			    , noteAuthor n == accessor
			    , noteBehind n == Nothing || n `elem` notesRead accessedUInfo ]))
	mlinfos

countRead :: UserInfo -> LockInfo -> Int
countRead reader tlock = fromIntegral $ length $
	filter (\n -> isNothing (noteBehind n) || n `elem` notesRead reader) $ lockSolutions tlock

data UserInfoDelta
    = AddRead NoteInfo
    | DelRead NoteInfo
    | PutLock LockSpec LockIndex
    | LockDelta LockIndex LockDelta
    deriving (Eq, Ord, Show, Read)
data LockDelta
    = SetPubNote NoteInfo
    | AddSecured NoteInfo
    | DelSecured NoteInfo
    | AddSolution NoteInfo
    | AddAccessed Codename
    | SetPublic
    deriving (Eq, Ord, Show, Read)

data Undeclared = Undeclared Solution LockSpec ActiveLock
    deriving (Eq, Ord, Show, Read)

-- | permanent serial number of a lock
type LockSpec = Int

-- | which of a user's three locks (0,1, or 2)
type LockIndex = Int

-- | solved state
type Hint = GameState

lockIndexChar :: LockIndex -> Char
lockIndexChar i = toEnum $ i + fromEnum 'A'

charLockIndex c = fromEnum (toUpper c) - fromEnum 'A'

alockStr :: ActiveLock -> String
alockStr (ActiveLock name idx) = name ++ [':',lockIndexChar idx]

applyDeltas :: UserInfo -> [UserInfoDelta] -> UserInfo
applyDeltas = foldr applyDelta

applyDelta :: UserInfoDelta -> UserInfo -> UserInfo
applyDelta (AddRead n) info = info { notesRead = n:(notesRead info) }
applyDelta (DelRead n) info = info { notesRead = delete n (notesRead info) }
applyDelta (PutLock ls li) info = info { userLocks = userLocks info // [(li, Just $ initLockInfo ls)] }
applyDelta (LockDelta li ld) info =
    info { userLocks = userLocks info // [(li, liftM (applyLockDelta ld) (userLocks info ! li))] }
applyLockDelta (SetPubNote n) lockinfo = lockinfo { lockSolutions = map
    (\n' -> if n' == n then n {noteBehind=Nothing} else n') (lockSolutions lockinfo) }
applyLockDelta (AddSecured n) lockinfo = lockinfo { notesSecured = n:(notesSecured lockinfo) }
applyLockDelta (DelSecured n) lockinfo = lockinfo { notesSecured = delete n $ notesSecured lockinfo }
applyLockDelta (AddSolution n) lockinfo = lockinfo { lockSolutions = n:(lockSolutions lockinfo) }
applyLockDelta (AddAccessed name) lockinfo = lockinfo { accessedBy = name:(delete name $ accessedBy lockinfo) }
applyLockDelta SetPublic lockinfo = lockinfo { public = True, lockSolutions = [], accessedBy = []}

instance Binary UserInfo where
    put (UserInfo name locks notes) = put name >> put locks >> put notes
    get = liftM3 UserInfo get get get

instance Binary UserInfoDelta where
    put (AddRead note) = put (0::Word8) >> put note
    put (DelRead note) = put (1::Word8) >> put note
    put (PutLock ls li) = put (2::Word8) >> put ls >> put li
    put (LockDelta li ld) = put (3::Word8) >> put li >> put ld
    get = do
	tag <- get :: Get Word8
	case tag of
	    0 -> AddRead <$> get
	    1 -> DelRead <$> get
	    2 -> PutLock <$> get <*> get
	    3 -> LockDelta <$> get <*> get

instance Binary LockDelta where
    put (SetPubNote note) = put (0::Word8) >> put note
    put (AddSecured note) = put (1::Word8) >> put note
    put (DelSecured note) = put (2::Word8) >> put note
    put (AddSolution note) = put (3::Word8) >> put note
    put (AddAccessed name) = put (4::Word8) >> put name
    put SetPublic = put (5::Word8)
    get = do
	tag <- get :: Get Word8
	case tag of
	    0 -> SetPubNote <$> get
	    1 -> AddSecured <$> get
	    2 -> DelSecured <$> get
	    3 -> AddSolution <$> get
	    4 -> AddAccessed <$> get
	    5 -> return SetPublic

instance Binary LockInfo where
    put (LockInfo spec pk notes solved accessed) = put spec >> put pk >> put notes >> put solved >> put accessed
    get = liftM5 LockInfo get get get get get

instance Binary NoteInfo where
    put (NoteInfo author behind on) = put author >> put behind >> put on
    get = liftM3 NoteInfo get get get

instance Binary ActiveLock where
    put (ActiveLock owner idx) = put owner >> put idx
    get = liftM2 ActiveLock get get