-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoplossingMiniMax.cs
220 lines (176 loc) · 7.07 KB
/
oplossingMiniMax.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
using System;
using System.Collections.Generic;
using System.Linq;
namespace minimax
{
abstract class Node {
public bool TurnPlayer1 { get; protected set; }
public abstract List<Node> PossibleMoves();
public float Utility() {
if (TurnPlayer1) {
return MaximinValue();
} else {
return MinimaxValue();
}
}
public float MaximinValue() {
if (TerminalTest()) {
// NOTE: Why return -1 when it is the AI's turn?
// If the turn is the AI's, it has not made its move yet. Yet it encounters a terminal state, which means the other player beat us and no decision can reverse this
return TurnPlayer1 ? -1 : +1;
} else if (GameFinishedWithoutWinner()) {
return 0;
} else {
return PossibleMoves().Select(possibleMove => possibleMove.MinimaxValue()).Max();
}
}
public float MinimaxValue() {
if (TerminalTest()) {
return TurnPlayer1 ? -1 : +1;
} else if (GameFinishedWithoutWinner()) {
return 0;
} else {
return PossibleMoves().Select(possibleMove => possibleMove.MaximinValue()).Min();
}
}
public abstract bool TerminalTest();
public abstract bool GameFinishedWithoutWinner();
public List<Edge> Actions { get; }
public Node() {
Actions = new List<Edge>();
}
public void AddAction(Edge action) {
Actions.Add(action);
}
public abstract void Log();
}
class Edge {
public Node FromNode { get; }
public Node ToNode { get; }
public Edge(Node from, Node to) {
FromNode = from;
ToNode = to;
}
}
class Problem {
public Node InitialState { get; set; }
}
class TicTacToeBoard : Node {
public const string EMPTY = " ";
public const string PLAYER1 = "X";
public const string PLAYER2 = "O";
public string[,] Board { get; }
public TicTacToeBoard(string[,] initialBoard, bool turnPlayer1) {
Board = initialBoard;
TurnPlayer1 = turnPlayer1;
}
public override List<Node> PossibleMoves() {
List<Node> possibleBoards = new List<Node>();
for (int row = 0; row < 3; row ++) {
for (int col = 0; col < 3; col ++) {
if (Board[row, col].Equals(EMPTY)) {
string[,] newBoard = Duplicate();
newBoard[row, col] = TurnPlayer1 ? PLAYER1 : PLAYER2;
possibleBoards.Add(new TicTacToeBoard(newBoard, !TurnPlayer1));
}
}
}
return possibleBoards;
}
public string[,] Duplicate() {
string[,] newGame = new string[3, 3];
for (int row = 0; row < 3; row ++) {
for (int col = 0; col < 3; col++) {
newGame[row, col] = Board[row,col];
}
}
return newGame;
}
public override bool TerminalTest() {
for (int row = 0; row < 3; row ++) {
if (Board[row, 0] == Board[row, 1] && Board[row, 1] == Board[row, 2] && Board[row, 0] != EMPTY) {
return true;
}
}
for (int col = 0; col < 3; col ++) {
if (Board[0, col] == Board[1, col] && Board[1, col] == Board[2, col] && Board[0, col] != EMPTY) {
return true;
}
}
if (Board[0, 0] == Board[1, 1] && Board[1, 1] == Board[2, 2] && Board[0, 0] != EMPTY) {
return true;
}
if (Board[0, 2] == Board[1, 1] && Board[1, 1] == Board[2, 0] && Board[0, 2] != EMPTY) {
return true;
}
return false;
}
public override bool GameFinishedWithoutWinner() {
if (TerminalTest()) {
return false; // There is winner
}
for (int row = 0; row < 3; row ++) {
for (int col = 0; col < 3; col ++) {
if (Board[row, col] == EMPTY) {
return false; // Game not finished
}
}
}
return true;
}
public override void Log() {
Console.WriteLine("---");
for (int row = 0; row < 3; row ++) {
for (int col = 0; col < 3; col++) {
Console.Write(Board[row,col] + ",");
}
Console.WriteLine();
}
Console.WriteLine("---");
}
}
class MinimaxSearch {
public Node MinimaxDecision(List<Node> possibleMoves) {
// Choose node with highest possible min value, because that choice will minimize chance of losing
return possibleMoves.OrderBy(possibleMove => possibleMove.Utility()).Last();
}
}
class Program
{
static void Main(string[] args)
{
MinimaxSearch algorithm = new MinimaxSearch();
/*
string[,] initialBoard = new string[,] {
{ TicTacToeBoard.PLAYER2, TicTacToeBoard.PLAYER1, TicTacToeBoard.PLAYER2 },
{ TicTacToeBoard.PLAYER1, TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY },
{ TicTacToeBoard.PLAYER1, TicTacToeBoard.PLAYER2, TicTacToeBoard.EMPTY }
};
*/
string[,] initialBoard = new string[,] {
{ TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY },
{ TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY },
{ TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY, TicTacToeBoard.EMPTY }
};
// We assume the AI starts first and tries to maximize its value. This is actually maximin instead of minimax, but the gist is the same
Node currState = new TicTacToeBoard(initialBoard, true);
while (!currState.TerminalTest() && !currState.GameFinishedWithoutWinner()) {
// Let AI make move
currState = algorithm.MinimaxDecision(currState.PossibleMoves());
// Wait for player input
currState.Log();
if (currState.TerminalTest() || currState.GameFinishedWithoutWinner()) {
break;
}
Console.WriteLine("Give row,column:");
string input = Console.ReadLine();
IEnumerable<int> rowCol = input.Split(",").Select(str => Convert.ToInt32(str));
string[,] currBoardConfig = ((TicTacToeBoard) currState).Duplicate();
currBoardConfig[rowCol.First(), rowCol.Last()] = TicTacToeBoard.PLAYER2;
currState = new TicTacToeBoard(currBoardConfig, true);
currState.Log();
}
Console.WriteLine("Game done");
}
}
}