Skip to content

Commit e4e4b06

Browse files
a1shadowsBusyByte
andauthored
[WIP] Minimax search function (#71)
* Game trait created. * parameter error resolved * removed implicit parameter * added missing isTerminalState function * minimaxsearch added * 1st requested change case a :: Nil used instead to improve consistency with other cases. * 2nd, 3rd, 4th, 5th, 6th requested changes made, errors resolved Integrated all requested changes and remedied handful of syntactical and logical errors. * added test problem for minimax * test added test spec for minimax * reformatting * shanges changes * review changes * errors fixed, spec added * clean Co-authored-by: Shawn Garner <[email protected]>
1 parent b514781 commit e4e4b06

File tree

4 files changed

+184
-7
lines changed

4 files changed

+184
-7
lines changed
Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
package aima.core.search.adversarial
22

3-
final case class UtilityValue(value: Double) extends AnyVal
3+
final case class UtilityValue(value: Double) extends AnyVal {
4+
def <(that: UtilityValue) = if (this.value < that.value) true else false
5+
def >(that: UtilityValue) = if (this.value > that.value) true else false
6+
def ==(that: UtilityValue) = if (this.value == that.value) true else false
7+
8+
}
49

510
/**
611
* @author Aditya Lahiri
12+
* @author Shawn Garner
713
*/
8-
trait Game[Player, State, Action] {
9-
def initialState: State
10-
def getPlayer(state: State): Player
11-
def getActions(state: State): List[Action]
12-
def result(state: State, action: Action): State
13-
def getUtility(state: State, player: Player): UtilityValue
14+
trait Game[P <: Player, S <: State, A <: Action] {
15+
def initialState: S
16+
def getPlayer(state: S): P
17+
def getActions(state: S): List[A]
18+
def result(state: S, action: A): S
19+
def isTerminalState(state: S): Boolean
20+
def getUtility(state: S): UtilityValue
1421
}
22+
23+
trait Player {}
24+
25+
trait State {}
26+
27+
trait Action {}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package aima.core.search.adversarial
2+
3+
import scala.annotation.tailrec
4+
5+
/**
6+
* @author Aditya Lahiri
7+
* @author Shawn Garner
8+
*/
9+
object MinimaxDecision {
10+
def minMaxDecision[PLAYER <: Player, STATE <: State, ACTION <: Action](
11+
g: Game[PLAYER, STATE, ACTION],
12+
noAction: ACTION
13+
): (STATE) => ACTION = { (state: STATE) =>
14+
@tailrec def maxMinValue(Actions: List[ACTION]): ACTION = Actions match {
15+
case Nil => noAction
16+
case singularAction :: Nil => singularAction
17+
case action1 :: action2 :: rest =>
18+
maxMinValue(
19+
(if (minValue(g.result(state, action1)) > minValue(g.result(state, action2))) action1
20+
else action2) :: rest
21+
)
22+
}
23+
24+
@tailrec def minMaxValue(Actions: List[ACTION]): ACTION = Actions match {
25+
case Nil => noAction
26+
case singularAction :: Nil => singularAction
27+
case action1 :: action2 :: rest =>
28+
minMaxValue(
29+
(if (maxValue(g.result(state, action1)) < maxValue(g.result(state, action2))) action1
30+
else action2) :: rest
31+
)
32+
}
33+
34+
def maxValue(state: STATE): UtilityValue = {
35+
if (g.isTerminalState(state))
36+
g.getUtility(state)
37+
else
38+
minValue(g.result(state, maxMinValue(g.getActions(state))))
39+
}
40+
41+
def minValue(state: STATE): UtilityValue = {
42+
if (g.isTerminalState(state))
43+
g.getUtility(state)
44+
else
45+
maxValue(g.result(state, minMaxValue(g.getActions(state))))
46+
}
47+
48+
maxMinValue(g.getActions(g.initialState))
49+
}
50+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package aima.core.search.problems
2+
3+
import aima.core.search.adversarial._
4+
5+
/**
6+
* @author Aditya Lahiri
7+
*/
8+
case class TwoPlyPlayer(name: String) extends Player
9+
10+
case class TwoPlyState(name: String) extends State
11+
case class TwoPlyAction(name: String) extends Action
12+
13+
object TwoPlyGame extends Game[TwoPlyPlayer, TwoPlyState, TwoPlyAction] {
14+
val adjacencyMat = Map(
15+
"A" -> List(TwoPlyAction("B"), TwoPlyAction("C"), TwoPlyAction("D")),
16+
"B" -> List(TwoPlyAction("E"), TwoPlyAction("F"), TwoPlyAction("G")),
17+
"C" -> List(TwoPlyAction("H"), TwoPlyAction("I"), TwoPlyAction("J")),
18+
"D" -> List(TwoPlyAction("K"), TwoPlyAction("L"), TwoPlyAction("M"))
19+
)
20+
val TwoPlyStates = Map(
21+
"A" -> TwoPlyState("A"),
22+
"B" -> TwoPlyState("B"),
23+
"C" -> TwoPlyState("C"),
24+
"D" -> TwoPlyState("D"),
25+
"E" -> TwoPlyState("E"),
26+
"F" -> TwoPlyState("F"),
27+
"G" -> TwoPlyState("G"),
28+
"H" -> TwoPlyState("H"),
29+
"I" -> TwoPlyState("I"),
30+
"J" -> TwoPlyState("J"),
31+
"K" -> TwoPlyState("K"),
32+
"L" -> TwoPlyState("L"),
33+
"M" -> TwoPlyState("M")
34+
)
35+
36+
@Override
37+
def initialState: TwoPlyState = TwoPlyStates("A")
38+
39+
@Override
40+
def getPlayer(state: TwoPlyState): TwoPlyPlayer = state.name match {
41+
case "B" | "C" | "D" => TwoPlyPlayer("MIN")
42+
case _ => TwoPlyPlayer("MAX")
43+
}
44+
45+
@Override
46+
def getActions(state: TwoPlyState): List[TwoPlyAction] = adjacencyMat(state.name)
47+
48+
@Override
49+
def result(state: TwoPlyState, action: TwoPlyAction): TwoPlyState = TwoPlyStates(action.name)
50+
51+
@Override
52+
def isTerminalState(state: TwoPlyState): Boolean = state.name match {
53+
case "A" | "B" | "C" | "D" => false
54+
case _ => true
55+
56+
}
57+
58+
@Override
59+
def getUtility(state: TwoPlyState): UtilityValue = state.name match {
60+
case "E" => UtilityValue(3)
61+
case "F" => UtilityValue(12)
62+
case "G" => UtilityValue(8)
63+
case "H" => UtilityValue(2)
64+
case "I" => UtilityValue(4)
65+
case "J" => UtilityValue(6)
66+
case "K" => UtilityValue(14)
67+
case "L" => UtilityValue(5)
68+
case "M" => UtilityValue(2)
69+
}
70+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package aima.core.search.adversarial
2+
3+
import aima.core.search.problems.{TwoPlyPlayer, TwoPlyState, TwoPlyAction, TwoPlyGame}
4+
import org.specs2.mutable.Specification
5+
6+
class MinimaxSearchSpec extends Specification {
7+
"Utility value of E" should {
8+
"be 3" in {
9+
TwoPlyGame.getUtility(TwoPlyState("E")) must beEqualTo(UtilityValue(3))
10+
}
11+
}
12+
13+
"Utility value of I" should {
14+
"be 4" in {
15+
TwoPlyGame.getUtility(TwoPlyState("I")) must beEqualTo(UtilityValue(4))
16+
}
17+
}
18+
"Utility value of K" should {
19+
"be 14" in {
20+
TwoPlyGame.getUtility(TwoPlyState("K")) must beEqualTo(UtilityValue(14))
21+
}
22+
}
23+
24+
"Initial State" should {
25+
"be A" in {
26+
TwoPlyGame.initialState must beEqualTo(TwoPlyState("A"))
27+
}
28+
}
29+
30+
"First player in the game" should {
31+
"be MAX" in {
32+
TwoPlyGame.getPlayer(TwoPlyGame.initialState) must beEqualTo(TwoPlyPlayer("MAX"))
33+
}
34+
}
35+
36+
"State to move to from intial state" should {
37+
"be B" in {
38+
MinimaxDecision.minMaxDecision[TwoPlyPlayer, TwoPlyState, TwoPlyAction](
39+
TwoPlyGame,
40+
TwoPlyAction("noAction")
41+
)(TwoPlyGame.initialState) must beEqualTo(TwoPlyAction("B"))
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)