module Game.VierGewinnt (
Spieler(..),
Spielstand,
Zug,
anfangundzuege,
berechneSpielstand,
brettVon,
grundstellung,
istMatt,
moeglicheZuege,
wertung,
) where
import qualified Data.List.Key as Key
import Data.Array (Array, Ix, array, range, inRange, listArray, (!), (//), )
import Data.Maybe (isNothing, catMaybes, )
type Brett = Array (Int,Int) (Maybe Spieler)
type Fuellstand = Array Int Int
type Wertung = Int
type Zug = Int
data Spieler =
Computer
| Mensch
deriving (Show,Read,Eq,Ord,Ix)
data Spielstand =
S {brettVon :: Brett,
fuellstandVon :: Fuellstand,
amZug :: Spieler,
wertung :: Wertung,
anfangundzuege :: (Spieler, [Zug])}
brettGroesse :: ((Int,Int),(Int,Int))
brettGroesse = ((1,1),(7,6))
posinfty, neginfty :: Wertung
posinfty = 10000
neginfty = -10000
gegner :: Spieler -> Spieler
gegner Mensch = Computer
gegner Computer = Mensch
moeglicheZuege :: Spielstand -> [Int]
moeglicheZuege spielstand =
let brett = brettVon spielstand
in filter (\i -> isNothing (brett!(i,6))) [1..7]
computerAmZug :: Spielstand -> Bool
computerAmZug spielstand =
amZug spielstand == Computer
istMatt, istPatt :: Spielstand -> Bool
istMatt spielstand = abs (wertung spielstand) >= 5000
istPatt spielstand = null (moeglicheZuege spielstand)
doTheMove :: Spielstand->Zug->Spielstand
doTheMove spielstand zug =
S neuesBrett neuerFuellstand derGegner neueWertung (anfang,zuege)
where
altesBrett,neuesBrett :: Brett
altesBrett = brettVon spielstand
alterFuellstand = fuellstandVon spielstand
drann = amZug spielstand
derGegner = gegner drann
alteWertung = wertung spielstand
neueWertung = updateWertung alteWertung altesBrett neuesBrett (zug,j)
neuesBrett=altesBrett // (((zug,j),Just drann):[])
neuerFuellstand=alterFuellstand // ((zug,j):[])
j=alterFuellstand!zug + 1
(anfang,altezuege)= anfangundzuege spielstand
zuege=altezuege++(zug:[])
_fmtWertung :: Wertung -> String
_fmtWertung w =
if -5000 < w && w < 5000
then show w
else " Matt in " ++ show (6000 - abs w)
updateWertung :: Int -> Brett-> Brett -> (Int,Int) -> Wertung
updateWertung alteWertung altesBrett neuesBrett (i,j)
= neueWertung
where
aktiv = aktiveVierer!(i,j)
alt = sum (map (werte . map (altesBrett!)) aktiv)
neu = sum (map (werte . map (neuesBrett!)) aktiv)
neueWertung :: Wertung
neueWertung =
if abs neu >= 5000
then maxmin neu
else maxmin (alteWertung + neu - alt)
maxmin x = max (min x 6000) (-6000)
w :: Int -> Int
w x = [0,1,5,50,posinfty] !! x
werte xs =
let xsm = catMaybes xs
computerSteine = length (filter (Computer ==) xsm)
menschSteine = length (filter (Mensch ==) xsm)
in if computerSteine>0 && menschSteine>0
then 0
else w computerSteine - w menschSteine
aktiveVierer :: Array (Int,Int) [[(Int,Int)]]
aktiveVierer = array brettGroesse
(map (\ix -> (ix, uncurry viererVon ix)) (range brettGroesse))
viererVon :: Int -> Int -> [[(Int, Int)]]
viererVon i j = filter (elem (i,j)) alleVierer
alleVierer :: [[(Int,Int)]]
alleVierer = [ [(i+l*ii,j+l*jj) | l<-[0..3] ]
| (i,j) <- range brettGroesse,
(ii,jj) <- inc i j ]
where
inc i j = filter (\(ii,jj) ->
inRange brettGroesse (i+3*ii,j+3*jj))
[(0,1),(1,-1),(1,0),(1,1)]
grundstellung :: Spieler -> Spielstand
grundstellung x = S leeresBrett leererFuellstand x 0 (x,[])
where
leeresBrett=listArray brettGroesse (repeat Nothing)
leererFuellstand=listArray (1,7) (repeat 0)
bewertungsKorrektur :: Wertung->Wertung
bewertungsKorrektur w
| w > 5000 = w-1
| w < -5000 = w+1
| otherwise = w
tiefe0 :: Int
tiefe0 = 0
search :: Int->Spielstand->(Wertung,[Zug])
search suchTiefe spielstand =
bewerteKnoten suchTiefe tiefe0 posinfty spielstand
bewerteKnoten :: Int->Int->Int->Spielstand->(Wertung,[Zug])
bewerteKnoten suchTiefe tiefe bound spielstand
| tiefe == suchTiefe = (wertung spielstand,[])
| istMatt spielstand = (wertung spielstand,[])
| istPatt spielstand = (0,[])
| tiefe < suchTiefe = bestesKind
| otherwise = error "bewerteKnoten: unmoeglicher Fall"
where
zuege1 = moeglicheZuege spielstand
kinder1= map (doTheMove spielstand) zuege1
(kinder,zuege) = unzip
((if computerAmZug spielstand
then reverse
else id)
(Key.sort (wertung . fst) (zip kinder1 zuege1)))
godown :: Int -> Spielstand -> (Wertung,[Zug])
godown=bewerteKnoten suchTiefe (tiefe+1)
godown1 (w,_) = godown w
wkind1=godown newbound (head kinder)
werteKinder = scanl (godown1) wkind1 (tail kinder)
paareWertZug = zip werteKinder zuege
bestesKind = verbinden (minmax' paareWertZug)
(minmax,newbound) =
if computerAmZug spielstand
then (maximum,neginfty)
else (minimum,posinfty)
worseThan bound0 =
if computerAmZug spielstand
then bound0 + 5
else bound0 - 5
rel :: Ord a => a->a->Bool
rel =
if computerAmZug spielstand
then (>)
else (<)
minmax' paareWertZug0 =
if any (\x->rel (fst (fst x)) (bewertungsKorrektur bound))
paareWertZug0
then ((worseThan (bewertungsKorrektur bound),[]),0)
else minmax paareWertZug0
verbinden ((wert,zuege0),zug)=(bewertungsKorrektur wert,zug:zuege0)
suchTiefeGesamt :: Int
suchTiefeGesamt = 6
berechneSpielstand :: (Spieler,[Zug]) -> Spielstand
berechneSpielstand (erster,zuege) =
let spielstand = foldl doTheMove (grundstellung erster) zuege
in if istMatt spielstand || not (computerAmZug spielstand)
then spielstand
else doTheMove spielstand
(head $ snd (search suchTiefeGesamt spielstand))