packages feed

rl-satton-0.1.2.4: examples/Examples/TickTackToe.hs

{-|
 - This module relies on older version of library and thus couldn't be
 - compailed.
 -
 - FIXME: update the TickTackToe module
 --}
{-# LANGUAGE NondecreasingIndentation #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PartialTypeSignatures #-}
module TickTackToe where

import Imports
import qualified Data.List as List
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Prelude hiding(break)

import Util
import Monad
import Types
import MC.Types as MC
import MC.ES

data Cell = X | O | E
  deriving(Show,Read,Eq,Ord)

showCell X = "X"
showCell O = "O"
showCell E = " "

type Player = Cell

nextPlayer X = O
nextPlayer O = X
nextPlayer E = error "nextPlayer: result for E is undefined"

data Board = Board {
    bo_cells :: Map (Int,Int) Cell
  , bo_wins :: Player
  , bo_term :: Bool
} deriving(Show,Read,Ord,Eq)

type Action = (Int,Int)

board_ny = 3
board_nx = 3
(.+) (a,b) (c,d) = (a+c,b+d)
(.*) x (a,b) = (x*a,x*b)
board_inside (x,y) = x >=0 && x < board_nx && y >=0 && y < board_ny
board_winsize = 3
board_points = [(x,y) | x<-[0..board_nx-1], y<-[0..board_ny-1]]
board_dirs = [(0,1),(1,0),(1,1),(1,-1)]
board_rows = filter ( and . map board_inside ) $ [ map (\x -> p .+ (x .* d)) [0..board_winsize-1] | p <- board_points, d <- board_dirs]
board_wincheck = Map.fromList [ (p,filter (p`elem`) board_rows) | p <- board_points ]

at :: Board -> Action -> Cell
at Board{..} a = fromMaybe E (Map.lookup a bo_cells)

set a c Board{..} = Board (Map.insert a c bo_cells)

move :: Board -> Player -> Action -> Board
move b@Board{..} p a =
  case bo_term of
    False ->
      let
        bo_cells' = Map.insert a p bo_cells
        bo_last' = boardFree b == [a]
        bo_winner' = or $ flip map (board_wincheck ! a) $ \r {-row-} ->
                      all (==p) (flip map r $ (\ a -> fromMaybe E $ Map.lookup a bo_cells'))
      in
      Board bo_cells' (if bo_winner' then p else E) (bo_last' || bo_winner')
    True -> error $ "move called on terminal board"

emptyBoard :: Board
emptyBoard = Board Map.empty E False

boardFree :: Board -> [Action]
boardFree Board{..} = board_points \\ (Map.keys bo_cells)

-- FIXME: eliminate potantional forever loop
randomBoard :: (RandomGen g) => Player -> g -> (Board, g)
randomBoard p g =
  let
    check ((_,b,True),g') = (b,g')
    check ((_,_,False),g') =
      -- trace "randomBoard diverged" $
      randomBoard p g'
  in do
  check $
    flip runRnd g $ do
    flip execStateT (X, emptyBoard, True) $ do
    loop $ do
      nmoves <- (
        case p of
          X -> Monad.uniform $ filter even [0 .. board_nx*board_ny-1]
          O -> Monad.uniform $ filter odd [0 .. board_nx*board_ny-1])
      forM_ [1..nmoves] $ \m -> do
        player <- use _1
        board <- use _2
        let b's = filter (not . bo_term) $ map (move board player) (boardFree board)
        case null b's of
          True -> do
            _3 %= const False
            break ()
          False -> do
            b' <- Monad.uniform b's
            _1 %= nextPlayer
            _2 %= const b'
      break ()

showBoard :: (MonadIO m) => Board -> m ()
showBoard b =
  liftIO $ do
  putStrLn ".---."
  forM_ [0..board_ny-1] $ \y -> do
    putStr "|"
    forM_ [0..board_nx-1] $ \x -> do
      putStr $ showCell (b `at` (x,y))
    putStrLn "|"
  putStrLn ".---."

showRandomBoard :: _ -> IO ()
showRandomBoard seed = showBoard $ fst $ randomBoard X (pureMT seed)

showEpisode :: (Show num, MonadIO m, MC_Problem num T Board Action) => T num -> Episode Board Action -> m ()
showEpisode pr@T{..} e = do
  let es = episode_forward e
  showBoard $ view _1 $ head es
  forM_ es $ \(b,a,b') -> do
    liftIO $ putStrLn $ show a
    showBoard b'
  let (b,a,b') = head (episode_backward e)
  liftIO $ putStrLn $ "Player " ++ show t_player ++ " R " ++ show (mc_reward pr b a b')

-- | TickTackToe => T
data T num = T {
    t_vals :: Map Player (Q num Board Action)
  , t_player :: Player
} deriving(Show,Read)

bestAction :: (Fractional num, Ord num, RandomGen g)
  => T num -> Board -> Player -> g -> Maybe (Action, g)
bestAction T{..} b p g =
  let
    macts = Map.lookup b $ view q_map $ t_vals ! p
  in
  case macts of
    Just as -> Just (fst $ maximumBy (compare `on` (current . snd)) (Map.toList as), g)
    Nothing ->
      case boardFree b of
        [] -> Nothing
        x -> Just $ flip runRnd g $ Monad.uniform x


instance (Fractional num, Real num, Ord num) => MC_Problem num T Board Action where

  mc_state_nonterm T{..} = randomBoard t_player

  mc_actions T{..} = Set.fromList . boardFree

  mc_transition t@T{..} b a g =
    let
      {- Applying action -}
      b' = move b t_player a
    in
    case bo_term b' of
      True ->
        {- Win -}
        ((b', True), g)
      False ->
        let
          p' = nextPlayer t_player
        in
        case bestAction t b' p' g of
          Nothing ->
            {- Draw -}
            ((b', True), g)
          Just (a',g') ->
            {- Make opponent's move according to their current state-value -}
            let
              b'' = move b' p' a'
            in
            {- Next|Loose -}
            ((b'', bo_term b''), g')

  mc_reward T{..} b a b' =
    if bo_term b' then
      if | bo_wins b' == t_player -> 1
         | bo_wins b' == E -> 0
         | otherwise -> -1
    else
      0


instance (Fractional num, Real num, Ord num, Show num) => MC_Problem_Show num T Board Action

t0 = T {
    t_vals = Map.fromList [
        (X,emptyQ),
        (O,emptyQ)
      ],
    t_player = O
  }


t1 = T {
    t_vals = Map.fromList [
        (X,emptyQ),
        (O,emptyQ)
      ],
    t_player = X
  }


example :: (Show num, Fractional num, Ord num, Real num) => T num -> IO ()
example pr = do

  flip evalStateT ((0,0,0),pr) $ do

  loop $ do

    pr@T{..} <- use _2

    _1 %= const (0,0,0)

    let
      g = pureMT 33

      o = (MC.defaultOpts $ ES_Ext {
            eo_debug = \e@Episode{..} ES_State{..} -> do
              let winner = bo_wins (episodeFinal e)
              if | winner == t_player -> do
                  _1 . _1 %= (+1)
                 | winner == E -> do
                  _1 . _2 %= (+1)
                 | winner == (nextPlayer t_player) -> do
                  _1 . _3 %= (+1)
              when (0 == _ess_iter `mod` 100) $ do
                score <- use _1
                traceM (t_player, _ess_iter, score, sizeQ _ess_q)

              when (0 == _ess_iter `mod` 1000) $ do
                showEpisode pr e
          }) {
            o_max_iter = 10000
          }

      s = MC.ES.initialState (t_vals ! t_player) emptyGenericPolicy

    (s',g') <- MC.ES.policy_iteraton pr o s g

    let pr' = pr{
              t_vals = Map.insert t_player (_ess_q s') t_vals
            , t_player = nextPlayer t_player
            }

    _2 %= const pr'

    save "game" "data/TickTackToe" (show pr')


load :: forall num . (Show num, Read num, Fractional num, Ord num, Real num) => FilePath -> IO (T num)
load fp = do
  contents <- readFile fp
  return (read contents)

simulate :: (Show num, Fractional num, Ord num, Real num) => T num -> IO ()
simulate t = do
  flip evalStateT (X, emptyBoard) $ do
  loop $ do
    (player,board) <- get
    let (action, nmax) = maximumBy (compare `on` (current . snd)) $ Map.toList ((_q_map ((t_vals t) ! player)) ! board)
    let board' = move board player action
    liftIO $ do
      showBoard board
      putStrLn $ "Player is " ++ show player ++ "; Move is " ++ show action ++ " Learned " ++ show (avg_n nmax)
    when (bo_term board') $ do
      liftIO $ do
        showBoard board'
        putStrLn $ show (bo_wins board')
      break ()
    _1 %= nextPlayer
    _2 %= const board'