-
Notifications
You must be signed in to change notification settings - Fork 0
/
SingleGameLogic.cs
160 lines (147 loc) · 5.39 KB
/
SingleGameLogic.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
using System.Collections.Generic;
using System;
using Android.App;
using Android.Content;
namespace Segmentus
{
class SingleGameLogic
{
const int StartGameDelay = 1300;
const int BotThinkingDuration = 1000;
const int BotMovingDuration = 1000;
public enum GameStatus { Empty, PlayersTurn, BotsTurn, Win, Lose };
const int MaxBotDepth = 4;
Dictionary<Bitmask128, float[]> statePredictions =
new Dictionary<Bitmask128, float[]>();
Bitmask128 curStateMask;
Bitmask128[] segmentProhibitMask;
static int botDepth;
public static int BotDepth
{
get { return botDepth; }
set
{
botDepth = value;
var prefs = Application.Context.GetSharedPreferences("AppPrefs",
FileCreationMode.Private);
var editor = prefs.Edit();
editor.PutInt("BotDepth", value);
editor.Commit();
}
}
static Random random = new Random();
public FieldData fieldData;
public event Action<GameStatus> StatusChanged;
public event Action<int> BotMoved;
float[] MakeStatePredictions(Bitmask128 mask)
{
float[] curPred = statePredictions[mask] = new float[MaxBotDepth + 1];
curPred[0] = 0.5f;
for (int i = 1; i <= MaxBotDepth; ++i)
curPred[i] = 0;
int c = 0;
for (int i = 0; i < fieldData.segmentsCnt; ++i)
{
if (!mask[i])
continue;
++c;
Bitmask128 nxt = mask & segmentProhibitMask[i];
float[] nxtPred;
if (!statePredictions.ContainsKey(nxt))
nxtPred = MakeStatePredictions(nxt);
else
nxtPred = statePredictions[nxt];
for (int j = 1; j <= MaxBotDepth; ++j)
curPred[j] += 1 - nxtPred[j - 1];
}
for (int i = 1; i <= MaxBotDepth; ++i)
curPred[i] /= c;
return curPred;
}
static SingleGameLogic()
{
var prefs = Application.Context.GetSharedPreferences("AppPrefs",
FileCreationMode.Private);
botDepth = prefs.GetInt("BotDepth", 2);
}
public SingleGameLogic()
{
fieldData = FieldDataGenerator.Generate();
curStateMask = new Bitmask128(fieldData.segmentsCnt);
segmentProhibitMask = new Bitmask128[fieldData.segmentsCnt];
for (int i = 0; i < fieldData.segmentsCnt; ++i)
{
segmentProhibitMask[i] = curStateMask;
foreach (int badSegID in fieldData.intersectedWith[i])
segmentProhibitMask[i][badSegID] = false;
}
statePredictions[Bitmask128.Zero] = new float[MaxBotDepth + 1];
statePredictions[Bitmask128.Zero][0] = 0.5f;
for (int i = 1; i <= MaxBotDepth; ++i)
statePredictions[Bitmask128.Zero][i] = 0;
MakeStatePredictions(curStateMask);
}
void DelayAction(Action action, int duration)
{
HandyAnimator delay = HandyAnimator.OfNothing(duration);
delay.After += action;
delay.core.Start();
}
public void OnPlayersMove(int segID)
{
curStateMask &= segmentProhibitMask[segID];
if (curStateMask.Equals(Bitmask128.Zero))
{
StatusChanged?.Invoke(GameStatus.Win);
return;
}
StatusChanged?.Invoke(GameStatus.BotsTurn);
DelayAction(MakeBotMove, BotThinkingDuration);
}
void MakeBotMove()
{
float minProb = 2;
List<int> segIDs = new List<int>();
for (int i = 0; i < fieldData.segmentsCnt; ++i)
{
if (!curStateMask[i])
continue;
Bitmask128 nxt = curStateMask & segmentProhibitMask[i];
float curProb = statePredictions[nxt][BotDepth];
if (curProb < minProb)
{
segIDs.Clear();
minProb = curProb;
}
if (Math.Abs(curProb - minProb) < float.Epsilon)
segIDs.Add(i);
}
int chosenSegID = segIDs[random.Next(segIDs.Count)];
BotMoved?.Invoke(chosenSegID);
curStateMask &= segmentProhibitMask[chosenSegID];
GameStatus nextStatus;
if (curStateMask.Equals(Bitmask128.Zero))
nextStatus = GameStatus.Lose;
else
nextStatus = GameStatus.PlayersTurn;
DelayAction(() => StatusChanged?.Invoke(nextStatus), BotMovingDuration);
}
public void StartGame()
{
GameStatus nextStatus;
if (random.Next(500) < 250)
nextStatus = GameStatus.PlayersTurn;
else
{
nextStatus = GameStatus.BotsTurn;
DelayAction(MakeBotMove, StartGameDelay + BotThinkingDuration);
}
DelayAction(() => StatusChanged?.Invoke(nextStatus), StartGameDelay);
}
public void ClearEvents()
{
BotMoved = null;
StatusChanged = null;
}
}
}