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
|
-- 11.2 Basic declarations
import Data.Char
import Data.List
import System.IO
size :: Int
size = 3 -- In deze versie aanpasbaar
type Grid = [[Player]]
data Player = O | B | X
deriving (Eq, Ord, Show)
next :: Player -> Player
next O = X
next B = B
next X = O
|
// 11.2
import scala.util.{Random => Rnd } //In deze versie nog niet nodig
sealed abstract class Player
case object O extends Player
case object B extends Player
case object X extends Player
//Opstartcommando voor Ammonite-scriptfile. Uitcommentariëren bij gebruik van Swing-Gui
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
|
//-- 11.3
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 //TODO: proberen dit met een val-functie te doen.
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
|
// 11.4
// Libraryfunctie van Haskell:
val unlines: List[String] => String = strs => strs.mkString("\n")
// Libraryfunctie van Haskell:
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] = {//goed, maar niet generic
case (x, Nil) => Nil /*List()*/
case (x, y::Nil) => y::Nil /*List(y)*/
case (x, y::ys) => y :: x :: interleave(x, ys)
}
val showRow: List[Player] => String = plrs => {//Signature aangepast
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)
|
// 11.5
val valid: (Grid, Int) => Boolean = (g, i) => {
val cellen = g.flatten //Vreemd, lukt alleen met tussenstap
0 <= i && i <= size*size && cellen(i) == B
}
val chop/*[A]*/: (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) //Verwarrend: lijst van 1 Grid, Nil is failure
}
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
|
//--11.6
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)
}
}
//-- Van pagina 134
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")
|
// 11.7
val run: (Grid, Player) => Unit = (g, p) => {
cls ()
goto(1,1)
putGrid(g)
run1(g,p)
}
val tictactoe: () => Unit = () => run(empty, O) //mens <==> mens
//val tictactoe: () => Unit = () => play(empty, O) //mens <==> computer
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.)
|
-- 11.8
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)]]
|
// 11.8
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
|
/* Nu mens tegen computer*/
// 11.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
|
// 11.10
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)
//(for(Node((g1,p1),_)<-ts; if p1==best)yield g1).head //Original
val result = for(Node((g1,p1),_)<-ts; if p1==best)yield g1
if (result != Nil) result.head else empty
}
|
|
-- 11.11
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)
|
// 11.11
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")
}
}
|