-
Notifications
You must be signed in to change notification settings - Fork 0
/
MonteCarloEngine.cs
147 lines (132 loc) · 6.43 KB
/
MonteCarloEngine.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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarlosSeptica
{
public class MonteCarloEngine
{
// TODO FOR A GUY WITH TOO MUCH FREE TIME: REUSE THE TREE COMPUTED 'TILL NOW
// TODO FOR ANOTHER GUY WITH TOO MUCH FREE TIME: IF YOU HAVE ONLY ONE POSSIBLE MOVE
// THEN FUCKING DO IT AND STOP THINKING!
// ONE MORE SHIT: YOU DON'T KNOW WHAT CARDS YOUR OPPONENT HAS SO...
// SIMULATE WITH RANDOM CARDS FROM OPPONENT HAND AND DEALER SINCE YOU
// DON'T KNOW WHICH ONE IS WHERE. YOU KNOW JUST YOUR CARDS OR THE CARDS YOU'VE SEEN
/**
* Computes next movement to be done by Carlos A.I.
* <returns>0-3 for putting down a card in hand or -1 to skip round</returns>
*/
public static int GetAiNextMove(GameState currentGameState)
{
GameState clonedGameState = (GameState)currentGameState.Clone();
MonteCarloTree simulationTree = MonteCarloTree.Create(clonedGameState);
int simulations = Game.GetSimulationsNumber();
for(int simulation = 0; simulation < simulations; ++simulation)
{
// Select
TreeNode leaf = simulationTree.SelectUCB1();
// Expand
SepticaEngine expansionEngine = new SepticaEngine(leaf.State);
int[] futureMoves = expansionEngine.GetPossibleMoves(leaf.State.CurrentTurn);
foreach(int futureMove in futureMoves)
{
// Simulate
GameState futureState = (GameState)leaf.State.Clone();
TreeNode child = new TreeNode(futureState, futureMove);
// If the move we decide to do is to end round we also have to redistribute cards from the dealer
// WE LOST 2 FUCKING HOURS WITH THIS ERROR
Action onDistributeCards = () =>
{
// Look at PlayToEnd() for details
while (!futureState.PlayerAI.IsHandFull && !futureState.PlayerHuman.IsHandFull && !futureState.Dealer.IsEmpty())
{
futureState.PlayerAI.AddCardInHand(futureState.Dealer.GiveCard());
futureState.PlayerHuman.AddCardInHand(futureState.Dealer.GiveCard());
}
};
SepticaEngine playoutEngine = new SepticaEngine(futureState, () => { }, onDistributeCards, () => { });
playoutEngine.PutCardDown(futureState.CurrentTurn, futureMove);
if (playoutEngine.IsGameDone())
{
if(futureState.PlayerAI.Score > futureState.PlayerHuman.Score)
{
// Won
// Biggest possible value and lowest for parent
child.Victories = int.MaxValue;
// Parent should not be chosen again since I have a victory from it
leaf.Victories = int.MinValue;
// Backpropagate
leaf.AddChild(child);
child.Backpropagate(true);
// Also, if I have a victory I don't care what other expansions lead to
break;
}
else
{
// Lost
// Lowest possible value so it won't be chosen again
child.Victories = int.MinValue;
// Backpropagate
leaf.AddChild(child);
child.Backpropagate(false);
}
}
else
{
// If game is not done, then simulate
GameState stateToPlayout = (GameState)futureState.Clone();
bool won = PlayToEnd(stateToPlayout);
// Backpropagate
leaf.AddChild(child);
child.Backpropagate(won);
}
}
}
TreeNode bestBranch = simulationTree.RootNode.SelectMostVisitedChild();
return bestBranch.MoveDone;
}
/**
* Plays current game 'till end with random moves
* Performs a rollout simulation
* <returns>true if the game is won (tie is considered lost)</returns>
*/
public static bool PlayToEnd(GameState gameState)
{
// TODO: SHIT FUCK DICK REMOVE SEED
Random movinRandom = new Random(420);
bool gameFinished = false;
Action onFinishGame = () =>
{
gameFinished = true;
};
Action onDistributeCards = () =>
{
// The order doesn't mater since it's random
// In main game is made just4fun
// The important thing is to give them one to a player, one to the other player
// Because they should have equal amount of cards
// And the starting number of cards is even
while (!gameState.PlayerAI.IsHandFull && !gameState.PlayerHuman.IsHandFull && !gameState.Dealer.IsEmpty())
{
gameState.PlayerAI.AddCardInHand(gameState.Dealer.GiveCard());
gameState.PlayerHuman.AddCardInHand(gameState.Dealer.GiveCard());
}
};
Action onTurnChanged = () =>
{
// Well, I don't think I have to do somethin' here
};
SepticaEngine septicaEngine = new SepticaEngine(gameState, onFinishGame, onDistributeCards, onTurnChanged);
while (!gameFinished)
{
Player currentTurn = gameState.CurrentTurn;
int[] possibleMoves = septicaEngine.GetPossibleMoves(currentTurn);
int nextMove = possibleMoves[movinRandom.Next(possibleMoves.Length)];
septicaEngine.PutCardDown(currentTurn, nextMove);
}
return gameState.PlayerAI.Score > gameState.PlayerHuman.Score;
}
}
}