Commentaar | Haskell
| Scala
|
Haskell:
- main (11.11) is het entrypoint bij compilatie
- In ghci start je mens tegen mens met run empty O,
mens tegen computer met play empty O
|
import Data.Char
import Data.List
import System.IO
size :: Int
size = 3
type Grid = [[Player]]
data Player = O | B | X
deriving (Eq, Ord, Show)
next :: Player -> Player
next O = X
next B = B
next X = O
|
import scala.util.{Random => Rnd }
sealed abstract class Player
case object O extends Player
case object B extends Player
case object X extends Player
TicTacToe.main(Array())
object TicTacToe{
def main(args: Array[String]): Unit = {
tictactoe()
}
val size: Int = 3
type Grid = List[List[Player]]
val next: Player => Player = {
case O => X
case B => B
case X => O
}
|
|
-- 11.3
empty :: Grid
empty = replicate size (replicate size B)
full :: Grid -> Bool
full = all (/= B) . concat
turn :: Grid -> Player
turn g = if os <= xs then O else X
where
os = length (filter (==O) ps)
xs = length (filter (==X) ps)
ps = concat g
wins :: Player -> Grid -> Bool
wins p g = any line (rows ++ cols ++ dias)
where
line = all (== p)
rows = g
cols = transpose g
dias = [diag g, diag (map reverse g)]
diag :: Grid -> [Player]
diag g = [ g !! n !! n | n <- [0 .. size-1]]
won :: Grid -> Bool
won g = wins O g || wins X g
|
val empty: Grid = List.fill(size,size)(B)
val full: Grid => Boolean = gr => gr.flatten.forall(_!=B)
val turn: Grid => Player = gr => {
val ps = gr.flatten
val os = ps.filter(_==O).length
val xs = ps.filter(_==X).length
if (os<=xs) O else X
}
@annotation.tailrec
def diag(arr: Grid, result: List[Player] = Nil): List[Player] = {
if (arr.isEmpty) result
else
diag(arr.tail.map(_.tail), result :+ arr.head.head)
}
val wins: (Player, Grid) => Boolean = (p, g) => {
val rows = g
val cols = g.transpose
val dias = List(diag(g), diag(g.map(_.reverse)))
val line: List[Player] => Boolean = _.forall(_==p)
(rows ++ cols ++ dias).exists(line)
}
val won: Grid => Boolean = g => if (wins(O, g) || wins(X, g)) true else false
|
|
-- 11.4
putGrid:: Grid -> IO ()
putGrid = putStrLn . unlines . concat . interleave bar . map showRow
where bar = [replicate ((size*4)-1) '-']
showRow :: [Player] -> [String]
showRow = beside . interleave bar . map showPlayer
where
beside = foldr1 (zipWith (++))
bar = replicate 3 "|"
showPlayer :: Player -> [String]
showPlayer O = [" ", " O ", " "]
showPlayer B = [" ", " ", " "]
showPlayer X = [" ", " X ", " "]
interleave :: a -> [a] -> [a]
interleave x [] = []
interleave x [y] = [y]
interleave x (y:ys) = y : x : interleave x ys
|
val unlines: List[String] => String = strs => strs.mkString("\n")
val concat: List[List[String]] => List[String] = _.flatten
val showPlayer: Player => List[String] = {
case O => List(" O ")
case B => List(" ")
case X => List(" X ")
}
val interleave: (String , List[String]) => List[String] = {
case (x, Nil) => Nil
case (x, y::Nil) => y::Nil
case (x, y::ys) => y :: x :: interleave(x, ys)
}
val showRow: List[Player] => String = plrs => {
val beside = plrs.foldRight( "")(_+_)
val st0: String = interleave("|",List.fill(3)(" ")).mkString
val st1: String = interleave("|",plrs.map(showPlayer(_).mkString(""))).mkString
List(st0,st1,st0).mkString("\n")
}
val putGrid: Grid => Unit = g => {
val v1 = g.map(showRow)
val bar = "-" * (size*4-1)
val v2 = v1.mkString("\n"+bar+"\n")
println(v2+"\n")
}
|
|
-- 11.5
valid :: Grid -> Int -> Bool
valid g i = 0 <= i && i < size^2 && concat g !! i == B
move :: Grid -> Int -> Player -> [Grid]
move g i p =
if valid g i then [chop size (xs ++ [p] ++ ys)] else []
where (xs, B:ys) = splitAt i (concat g)
chop :: Int -> [a] -> [[a]]
chop n [] = []
chop n xs = take n xs : chop n (drop n xs)
|
val valid: (Grid, Int) => Boolean = (g, i) => {
val cellen = g.flatten
0 <= i && i <= size*size && cellen(i) == B
}
val chop: (Int, List[Player]) => List[List[Player]] = {
case (n, Nil) => Nil
case (n, xs ) => xs.take(n) :: chop (n, (xs.drop(n)))
}
val move: (Grid, Int, Player) => List[Grid] = (g,i,p) => {
if (valid(g, i)) {
val gridParts: (List[Player],List[Player]) = (g.flatten).splitAt(i)
val (xs, B::ys) = gridParts
val hersteldGrid: List[Player] = xs ::: List(p) ::: ys
val listGrid: List[List[Player]] = chop(size, hersteldGrid)
List(listGrid)
}
else
Nil
}
|
Het boek gebruikt in getNat() "prompt" i.p.v. "str". Verwarrend omdat prompt() ook een functie is. Veranderd in de Scala versie.
|
--11.6
getNat :: String -> IO Int
getNat prompt = do putStr prompt
xs <- getLine
if xs /= [] && all isDigit xs then
return (read xs)
else
do putStrLn "ERROR: Ongeldig nummer"
getNat prompt
|
val prompt: Player => String = p => "Speler " + show(p) + ", doe een zet: (1..9)"
val getNat: (String) => Int = str => {
println(str)
val ch: Int = scala.io.StdIn.readChar.toInt - 48
if(ch >= 1 && ch <= 9)
ch-1
else {
println("ERROR: Ongeldig nummer")
getNat(str)
}
}
type Pos = (Int,Int)
val cls: () => Unit = () => print ("\u001b[2J")
val show: Any => String = _.toString
val goto: Pos => Unit = pos => {
val (x,y) = (pos._1, pos._2)
print ("\u001b[" + y + ";" + x + "H")
}
|
|
-- 11.7
tictactoe :: IO ()
tictactoe = run empty O
run :: Grid -> Player -> IO ()
run g p = do cls
goto (1,1)
putGrid g
run' g p
run' :: Grid -> Player -> IO ()
run' g p | wins O g = putStrLn "Speler O wint!\n"
| wins X g = putStrLn "Speler X wint!\n"
| full g = putStrLn "Gelijk spel!\n"
| otherwise =
do i <- getNat (prompt p)
case move g i p of
[] -> do putStrLn "ERROR: Ongeldige zet"
run' g p
[g'] -> run g' (next p)
prompt :: Player -> String
prompt p = "Speler " ++ show p ++ ", doe een zet: "
-- Van pagina 134
type Pos = (Int,Int)
cls :: IO ()
cls = putStr "\ESC[2J"
goto :: Pos -> IO ()
goto (x,y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")
|
val run: (Grid, Player) => Unit = (g, p) => {
cls ()
goto(1,1)
putGrid(g)
run1(g,p)
}
val tictactoe: () => Unit = () => run(empty, O)
val run1: (Grid, Player) => Unit = (g,p) => {
if (wins(O,g)) println ("Speler O heeft gewonnen!\n")
else if (wins(X,g)) println ("Speler X heeft gewonnen!\n")
else if (full(g)) println ("Gelijk spel!\n")
else {
val i = getNat(prompt(p))
move(g,i,p) match {
case Nil => println ("ERROR: Ongeldige zet")
run1(g,p)
case List(g1: Grid) => run(g1, next(p))
}
}
}
|
Tot hier werd er gespeeld door twee mensen. Nu gaat de computer tegenspel bieden en wordt er een gameTree opgebouwd met alle mogelijke aflopen.
(Bij Schaak of Go kan dat maar tot beperkte diepte.)
|
data Tree a = Node a [Tree a]
deriving Show
gameTree :: Grid -> Player -> Tree Grid
gameTree g p = Node g [gameTree g' (next p) | g' <- moves g p]
moves :: Grid -> Player -> [Grid]
moves g p
| won g = []
| full g = []
| otherwise = concat [move g i p | i <- [0..((size^2)-1)]]
|
sealed trait Tree[+A]
case class Node[A](value: A, subTrees: List[Tree[A]]) extends Tree[A]
val moves: (Grid, Player) => List[Grid] = (g,p) => {
if (won(g) || full(g)) Nil
else (for(i <- 0 to size*size-1) yield move(g,i,p)).toList.flatten
}
val gameTree: (Grid, Player) => Tree[Grid] = (g,p) => {
val choices = for(g1 <- moves(g,p)) yield gameTree(g1,next(p))
Node(g, choices)
}
|
|
-- 11.9
prune :: Int -> Tree a -> Tree a
prune 0 (Node x _ ) = Node x []
prune n (Node x ts) = Node x [prune (n-1) t | t <- ts]
depth :: Int
depth = 9
|
val prune: (Int, Tree[Grid]) => Tree[Grid] = {
case (0, Node(x,_ )) => Node(x,Nil)
case (n, Node(x,ts)) => Node(x, for (t<-ts) yield prune(n-1, t))
}
val depth: Int = 9
|
|
-- 11.10
minimax :: Tree Grid -> Tree (Grid, Player)
minimax (Node g [])
| wins O g = Node (g,O) []
| wins X g = Node (g,X) []
| otherwise = Node (g,B) []
minimax (Node g ts)
| turn g == O = Node (g, minimum ps) ts'
| turn g == X = Node (g, maximum ps) ts'
where
ts' = map minimax ts
ps = [p | Node (_,p) _ <- ts']
bestmove ::Grid -> Player -> Grid
bestmove g p = head [g' | Node(g',p') _ <- ts, p' == best]
where
tree = prune depth (gameTree g p )
Node (_,best) ts = minimax tree
|
val minimax: (Tree[Grid]) => Tree[(Grid, Player)] = {
case (Node(g,Nil))=> if (wins(O,g)) Node((g,O),Nil)
else if (wins(X,g)) Node((g,X),Nil)
else Node((g,B),Nil)
case (Node(g,ts )) => val ts1: List[Tree[(Grid,Player)]]=ts.map(minimax)
val ps: List[Player] = for (Node((_,p),_) <- ts1) yield p
if(turn(g)==O) Node((g,minimum(ps)),ts1)
else Node((g,maximum(ps)),ts1)
}
val playerRank: Player => Int = {
case O =>1
case B =>2
case X =>3
}
val playerName: Player => String = {
case O => "O"
case B => " "
case X => "X"
}
val minimum: List[Player] => Player = _.sortBy(playerRank).head
val maximum: List[Player] => Player = _.sortBy(playerRank).reverse.head
val bestMove: (Grid, Player) => Grid = (g,p) => {
val tree: Tree[Grid] = prune(depth, gameTree(g,p))
val Node((_,best),ts) = minimax(tree)
val result = for(Node((g1,p1),_)<-ts; if p1==best)yield g1
if (result != Nil) result.head else empty
}
|
|
main :: IO ()
main = do hSetBuffering stdout NoBuffering
play empty O
play :: Grid -> Player -> IO ()
play g p = do cls
goto (1,1)
putGrid g
play' g p
play' :: Grid -> Player -> IO ()
play' g p
| wins O g = putStrLn "Speler O heeft gewonnen!\n"
| wins X g = putStrLn "Speler X heeft gewonnen!\n"
| full g = putStrLn "Het is een gelijk spel!\n"
| p == O = do i <- getNat (prompt p)
case move g i p of
[] -> do putStrLn "ERROR: Ongeldige zet"
play' g p
[g'] -> play g' (next p)
| p ==X = do putStr "Speler X denkt ..."
(play $! (bestmove g p)) (next p)
|
val play: (Grid, Player) => Unit = (g, p) => {
cls ()
goto(1,1)
putGrid(g)
play1(g,p)
}
val play1: (Grid, Player) => Unit = (g,p) => {
if (wins(O,g)) println ("Speler O heeft gewonnen!\n")
else if (wins(X,g)) println ("Speler X heeft gewonnen!\n")
else if (full(g)) println ("Gelijk spel!\n")
else if(p==O){
val i = getNat(prompt(p))
move(g,i,p) match {
case Nil => println ("ERROR: Ongeldige zet")
play1(g,p)
case List(g1: Grid) => play(g1, next(p))
}
}
else if(p==X){
println("X denkt even na ...")
play(bestMove(g,p),next(p))
}
else println("Oeps")
}
}
|