-- ================================== -- Module name: FooMove -- Project: Foo -- Copyright (C) 2007 Bartosz Wójcik -- Created on: 01.10.2007 -- Last update: 07.04.2008 -- Version: % {- 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 3 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . -} -- ================================== module FooMove where import qualified Data.Map as Map import Data.List import FooField import Monad -- This module constructs space of possible moves for a given field, initial vertex and direction towards one of the goals. -- Provides bunch of funcions manipulating on moves. -- One of following has to be active: -- MoveSimple -- MoveImproved1 (expected acceleration x5 to x30) -- MergePlies -- MergePlies reduces numer of examined moves by recognizing duplications even if they are split into plies. -- Additionally it reduces moves' comparison overhead as it stops only when given number of plies is created. -- Comparing function gets at very beginnig final space of moves of given ply. -- MergePlies makes AlphaBeta useless, so it's cheaper to switch it off. -- Finally MergePlies hasn't resulted in playing machine acceleration and I dropped its further development. By now I'm not sure if this option compiles. -- #define MoveSimple #define MoveImproved1 -- #define MergePlies -- MoveImproved2 is optional for MoveImproved1 switched on. -- It switches on more efficient way of duplicated moves recognition. It assumes that duplicated move has to have a cycle -- and doesn't check moves that don't have cycles. #define MoveImproved2 -- Each move consists of passes. Pass is an existing edge between 2 vetices. Each pass can be either -- intermedium pass - such one that doesn't finish move; -- final pass - such one that finishes move. -- Vertex - vertex -- Graph - graph -- l - length of field -- w - width of field -- Bool - whether Vertex is active. Inactive vertices lead to LastPass. -- n - number of pass in the move -- i - number of depth. The real move is depth 0, move against is depth 1 etc. data Move = NextPass -- intermedate, recently constructed pass. Contains all information required to continue constructin space of passes. [Vertex] -- List of vertices constructing move until this point. Graph -- Graph after pass. Int Int -- Size of field. Lenght and width. Bool -- Active last vertex flag. Int -- Number of passes within the move Int -- Depth of the move. 0-inspected move; 1-ply + 1;... #ifdef MergePlies [Vertex] -- List of vertices constructing move until end of ply 0. #else [Move] -- List of moves that can start from this point under current circumstances #endif (Map.Map Int Bool) -- Set of edges of current move ordered by edge mark (Map.Map Vertex Int) -- Set of ordered vertices having number of occurences within the move. Used for cycle recognition. | Pass [Move] -- intermediate pass keeps just pointer to next passes | LastPass -- Last pass within the move. Doesn't finish the game. [Vertex] Graph Int Int Int Int [Move] -- See description of NextPass #ifdef MergePlies Vertex -- Last vertex after move of last examined ply. #endif | Goal [Vertex] -- Move finished in the opponent's goal. Keeps list of vertices constructing it. | LostGoal [Vertex] -- Move finished in the own goal. Keeps list of vertices constructing it. | LostHalfGoal [Vertex] Graph -- Move finished because of no passes available. Keeps list of vertices constructing it. | HalfGoal [Vertex] Graph -- The same but occured while examining possible opponent's moves. | UnfinishedMove [Vertex] Int -- Too many passes to examine this move further. instance Show (Move) where show (Pass _) = "Pass" #ifdef MergePlies show (LastPass v _ _ _ _ i _ _) = "LastPass " ++ concat (map show v) #else show (LastPass v _ _ _ _ i _) = "LastPass " ++ concat (map show v) #endif show (Goal v) = "Goal " ++ concat (map show v) show (LostGoal v) = "LostGoal " ++ concat (map show v) show (LostHalfGoal v _) = "LostHalfGoal " ++ concat (map show v) show (HalfGoal v _) = "HalfGoal " ++ concat (map show v) show (UnfinishedMove v _) = "UnfinishedMove " ++ concat (map show v) -- Cast move on vertices axe. vOfMove :: Move -> [Vertex] vOfMove (Pass _) = [] #ifdef MergePlies vOfMove (LastPass v _ _ _ _ _ _ _) = v #else vOfMove (LastPass v _ _ _ _ _ _) = v #endif vOfMove (Goal v) = v vOfMove (LostGoal v) = v vOfMove (LostHalfGoal v _) = v vOfMove (HalfGoal v _) = v vOfMove (UnfinishedMove v _) = v -- Returns field after move. graphAfterPass :: Graph -> Move -> Graph #ifdef MergePlies graphAfterPass g afterBestMove@(LastPass _ g' _ _ _ _ _ _) = g' #else graphAfterPass g afterBestMove@(LastPass _ g' _ _ _ _ _) = g' #endif graphAfterPass g afterBestMove@(LostHalfGoal _ g') = g' graphAfterPass g afterBestMove@(HalfGoal _ g') = g' graphAfterPass g _ = g -- ==================================== -- Function constructing space of moves -- ==================================== -- ========== -- MoveSimple -- ========== #ifdef MoveSimple nextPass :: Bool -> Move -> Move -- Below 3 lines have to be adjusted on experimental way for the processor power. nextPass d (NextPass vs g fL fW True 5 2 [] _ _) | vertices (head vs) g /= [] = UnfinishedMove vs 2 nextPass d (NextPass vs g fL fW True 8 1 [] _ _) | vertices (head vs) g /= [] = UnfinishedMove vs 1 nextPass d (NextPass vs g fL fW True 13 0 [] _ _) | vertices (head vs) g /= [] = UnfinishedMove vs 0 nextPass d (NextPass vs g fL fW True n i [] m mV) | even i && noPass = LostHalfGoal vs g | noPass = HalfGoal vs g | otherwise = Pass [nextPass d (NextPass (v':vs) (rmEdge v v' g) fL fW (active v' g) (n+1) i [] m mV) |v' <- vertices v g] where v = head vs noPass = vertices v g == [] nextPass d (NextPass vs g fL fW False n i [] _ _) | y == 0 && d || y == fL + 2 && not d = Goal vs | y == 0 && not d || y == fL + 2 && d = LostGoal vs | otherwise = LastPass vs g fL fW n i [nextMove d v g fL fW (i+1)] where y = snd v v = head vs -- ================================================================== -- The main function of this modul that returns full space of moves. -- ================================================================== -- Later I'll use lazyness to limit this space to size which I think will be practical. Full space of moves is by now too big for my hardware. -- d - if plays towards (_,0) goal -- v - initial vertex -- g - initial graph -- fL, fW - field size (length, width) -- i - depth number - first move has depth 0, answer depth 1, etc... nextMove :: Bool -> Vertex -> Graph -> Int -> Int -> Int -> Move nextMove d v g fL fW i = nextPass d (NextPass [v] g fL fW True 0 i [] Map.empty Map.empty) -- ================================================================== #endif -- Returns numer of possible moves for given tree of moves until given depth. sizeMove :: (Num b) => Int -> Move -> b sizeMove t (Pass m) = foldr ((+).sizeMove t) 0 m sizeMove t (LastPass _ _ _ _ _ i m) | i == t = 1 | otherwise = foldr ((+).sizeMove t) 0 m sizeMove _ _ = 1 -- Walks through tree of moves folding it using given function f. foldMove :: (b -> b -> b) -> b -> Move -> b foldMove f value (Pass m) = foldr (f . foldMove f value) value m foldMove _ value _ = value -- Maps given function on all leaves of tree of moves. mapMove :: (Move -> a) -> Move -> [a] mapMove f (Pass ms) = concat $ map (mapMove f) ms mapMove f m = [f m] -- =================================================== -- ============= -- MoveImproved1 -- ============= -- Processes current move one pass further. Move is processed so, that first all next passes for until now recognized moves are -- processed, then duplicated moves are removed and so on. Kind of "breadth-first search". #ifdef MoveImproved1 nextPass :: Bool -> Move -> [Move] nextPass d (NextPass vs g fL fW True n i [] m mV)| even i && noPass = [LostHalfGoal vs g] | noPass = [HalfGoal vs g] | otherwise = [NextPass (v':vs) (rmEdge v v' g) fL fW (active v' g) (n+1) i [] (Map.insert (markEdge fW v v') True m) (Map.insertWith (+) v' 1 mV) |v' <- vertices v g] where v = head vs noPass = vertices v g == [] nextPass d (NextPass vs g fL fW False n i [] _ _) | y == 0 && d || y == fL + 2 && not d = [Goal vs] | y == 0 && not d || y == fL + 2 && d = [LostGoal vs] | otherwise = [LastPass vs g fL fW n i [nextMove d v g fL fW (i+1)]] where y = snd v v = head vs -- ================================================================== -- The main function of this modul that returns full space of moves. -- ================================================================== -- Later I'll use lazyness to limit this space to size which I think will be practical. Full space of moves is by now too big for my hardware. -- d - if plays towards (_,0) goal (direction) -- v - initial vertex -- g - initial graph -- fL, fW - field size (length, width) -- i - depth number - first move has depth 0, answer depth 1, etc... nextMove :: Bool -> Vertex -> Graph -> Int -> Int -> Int -> Move nextMove d v g fL fW i = Pass (nNextMove d [NextPass [v] g fL fW True 1 i [] (Map.empty) (Map.insert v 1 Map.empty)]) -- ================================================================== -- Applies nextPass to all by now recognized moves. nextAllPasses :: Bool -> [Move] -> [Move] nextAllPasses d ls = (concat $ map (nextPass d) ls) -- Couple of functions that categorize moves. isNextPass :: Move -> Bool isNextPass (NextPass _ _ _ _ _ _ _ _ _ _) = True isNextPass _ = False isLastPass :: Move -> Bool isLastPass (LastPass _ _ _ _ _ _ _) = True isLastPass _ = False finishesMatch :: Move -> Bool finishesMatch (Goal _ ) = True finishesMatch (LostGoal _) = True finishesMatch (HalfGoal _ _) = True finishesMatch (LostHalfGoal _ _) = True finishesMatch _ = False -- Removes duplicated moves from the list using Map of edges, each move caries with it. -- Use it as follows: pruneMoves (Map.empty) ls [], where ls is list of moves to be pruned. pruneMoves :: Map.Map (Map.Map Int Bool) Bool -> [Move] -> [Move] pruneMoves _ [] = [] pruneMoves t (l@(NextPass vs g fL fW True n i ms m _):ls) | Map.member m t = pruneMoves t ls | otherwise = (l:(pruneMoves (Map.insert m True t) ls)) pruneMoves t (l:ls) = (l:(pruneMoves t ls)) -- For given list of moves it makes next step in BFS. -- This function already presorts moves in the following way. -- Moves that finish match are first. -- Moves that don't finish match are afterwards and they are presoted on their length - longer first, shorter afterwards. nNextMove :: Bool -> [Move] -> [Move] nNextMove _ [] = [] nNextMove d ls = newFinishingMatch ++ nNextMove d (((movesNotToPrune.newNotFinishedMoves) newMoves) ++ pruneMoves (Map.empty) ((movesPossiblyToPrune.newNotFinishedMoves) newMoves)) ++ newFinishedMoves where newMoves = nextAllPasses d ls newFinishingMatch = [m | m <- newMoves, finishesMatch m] newFinishedMoves = [m | m <- newMoves, isLastPass m] newNotFinishedMoves ls = [m | m <- ls, isNextPass m] #endif -- ============= -- MoveImproved2 -- ============= #ifndef MergePlies #ifdef MoveImproved2 movesPossiblyToPrune :: [Move] -> [Move] movesPossiblyToPrune ls = [m | m <- ls, cycleInLastPass m] movesNotToPrune :: [Move] -> [Move] movesNotToPrune ls = [m | m <- ls, not $ cycleInLastPass m] -- Function recognizes cycle after each BFS step. -- Cycle exists if the last visited vertex has been already visited within current move before. cycleInLastPass :: Move -> Bool cycleInLastPass (NextPass vs _ _ _ _ _ _ _ _ mV) = Map.findWithDefault 0 (head vs) mV > 1 -- Very rough and ... order of moves -- Argumets: Direction towards (_,0) goal and list of moves. lessThan :: Bool -> Move -> Move -> Bool lessThan _ (LostGoal _) _ = True lessThan _ (LostHalfGoal _ _) _ = True lessThan True (LastPass ((x,y):vs) _ _ _ _ _ _) (LastPass ((x',y'):vs') _ _ _ _ _ _) = y > y' lessThan False (LastPass ((x,y):vs) _ _ _ _ _ _) (LastPass ((x',y'):vs') _ _ _ _ _ _) = y < y' lessThan _ (LastPass _ _ _ _ _ _ _) _ = True lessThan _ (HalfGoal _ _) _ = True lessThan _ _ _ = False #else movesPossiblyToPrune :: [Move] -> [Move] movesPossiblyToPrune ls = ls movesNotToPrune :: [Move] -> [Move] movesNotToPrune ls = [] #endif #endif -- =================================================== -- ========== -- MergePlies -- ========== #ifdef MergePlies nextPass :: Bool -> Int -> Move -> [Move] nextPass d ply (NextPass vs g fL fW True n i [] m mV vs0)| even i && noPass = [LostHalfGoal vs g] | noPass = [HalfGoal vs g] | i > 0 = [NextPass (v':vs) (rmEdge v v' g) fL fW (active v' g) (n+1) i [] (Map.insert (markEdge fW v v') True m) (Map.insertWith (+) v' 1 mV) vs0 |v' <- vertices v g] | otherwise = [NextPass (v':vs) (rmEdge v v' g) fL fW (active v' g) (n+1) i [] (Map.insert (markEdge fW v v') True m) (Map.insertWith (+) v' 1 mV) (v':vs) |v' <- vertices v g] where v = head vs noPass = vertices v g == [] nextPass d ply (NextPass vs g fL fW False n i [] _ _ vs0) | y == 0 && d || y == fL + 2 && not d = [Goal vs] | y == 0 && not d || y == fL + 2 && d = [LostGoal vs] | ply > i = [NextPass (v':vs) (rmEdge v v' g) fL fW (active v' g) (n+1) (i+1) [] (Map.insert (markEdge fW v v') True m) (Map.insertWith (+) v' 1 mV) vs0 |v' <- vertices v g] | otherwise = [LastPass vs0 g fL fW n i [] (head vs) ] where y = snd v v = head vs -- ================================================================== -- The main function of this modul that returns full space of moves. -- ================================================================== -- d - if plays towards (_,0) goal -- v - initial vertex -- g - initial graph -- fL, fW - field size (length, width) -- i - depth number - first move has depth 0, answer depth 1, etc... -- ply - number of plies function has to explore. It finished when i > ply nextMove :: Bool -> Vertex -> Graph -> Int -> Int -> Int -> Int -> Move nextMove d v g fL fW i ply = Pass (nNextMove d ply [NextPass [v] g fL fW True 1 i [] (Map.empty) (Map.insert v 1 Map.empty) [v] ]) -- ================================================================== nextAllPasses :: Bool -> Int -> [Move] -> [Move] nextAllPasses d ply ls = (concat $ map (nextPass d ply) ls) isNextPass :: Move -> Bool isNextPass (NextPass _ _ _ _ _ _ _ _ _ _ _) = True isNextPass _ = False -- Removes duplicated moves from the list using Map of edges, each move caries with it. -- Use it as follows: pruneMoves (Map.empty) ls [], where ls is list of moves to be pruned. -- This function is not beautiful. It validates each move against Map of moves and updates this Map concurently. -- pruneMoves :: Map.Map (Map.Map Int Int) Bool -> [Move] -> [Move] -> [Move] -- pruneMoves _ [] os = os -- pruneMoves t (l@(NextPass vs g fL fW True n i ms m):ls) os | Map.member m t = pruneMoves t ls os -- | otherwise = pruneMoves (Map.insert m True t) ls (l:os) -- pruneMoves t (l:ls) os = pruneMoves t ls (l:os) pruneMoves :: Map.Map (Map.Map Int Bool) Bool -> [Move] -> [Move] pruneMoves _ [] = [] pruneMoves t (l@(NextPass vs g fL fW True n i ms m _ _):ls) | Map.member m t = pruneMoves t ls | otherwise = (l:(pruneMoves (Map.insert m True t) ls)) pruneMoves t (l:ls) = (l:(pruneMoves t ls)) nNextMove :: Bool -> Int -> [Move] -> [Move] nNextMove _ _ [] = [] nNextMove d ply ls = newFinishedMoves ++ nNextMove d ply (((movesNotToPrune.newNotFinishedMoves) newMoves) ++ pruneMoves (Map.empty) ((movesPossiblyToPrune.newNotFinishedMoves) newMoves)) where newMoves = nextAllPasses d ply ls newFinishedMoves = [m | m <- newMoves, not $ isNextPass m] newNotFinishedMoves ls = [m | m <- ls, isNextPass m] movesPossiblyToPrune :: [Move] -> [Move] movesPossiblyToPrune ls = [m | m <- ls, cycleInLastPass m] movesNotToPrune :: [Move] -> [Move] movesNotToPrune ls = [m | m <- ls, not $ cycleInLastPass m] cycleInLastPass :: Move -> Bool cycleInLastPass (NextPass vs _ _ _ _ _ _ _ _ mV _) = Map.findWithDefault 0 (head vs) mV > 1 #endif