forked from medovina/breakthrough
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgame.cs
More file actions
194 lines (161 loc) · 6.2 KB
/
game.cs
File metadata and controls
194 lines (161 loc) · 6.2 KB
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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using static System.Console;
using static System.Math;
class Pos {
public int x, y;
public Pos(int x, int y) { this.x = x; this.y = y; }
public override string ToString() => $"({x}, {y})";
}
class Move {
public Pos from, to;
public Move(Pos from, Pos to) { this.from = from; this.to = to; }
public override string ToString() => $"{from} -> {to}";
}
// A game of Breakthrough.
class Game {
public const int Size = 7;
// squares[x, y] holds the piece at square (x, y):
// 0 = empty, 1 = player 1 (white), 2 = player 2 (black)
public int[,] squares = new int[Size, Size];
// pieces[p] is the number of pieces currently owned by player p.
// (The dummy entry pieces[0] is unused.)
public int[] pieces = new int[3];
// The player whose turn it is to play.
public int turn = 1;
// The number of moves that have been made in the game so far.
public int moves = 0;
// The player who has won, or 0 if nobody has won yet.
public int winner = 0;
// The random seed used to start this game, or -1 if none.
public int seed;
public SRandom random;
public Game(int seed) {
this.seed = seed;
random = new SRandom(seed);
for (int x = 0 ; x < Size ; ++x) {
squares[x, 0] = squares[x, 1] = 2;
squares[x, Size - 2] = squares[x, Size - 1] = 1;
}
pieces[1] = pieces[2] = 2 * Size;
}
// Create an independent copy of the game state.
public Game clone() {
Game g = (Game) MemberwiseClone();
g.squares = (int[,]) squares.Clone();
g.pieces = (int[]) pieces.Clone();
return g;
}
public int dir() => turn == 1 ? -1 : 1;
bool valid(Pos pos) => pos.x >= 0 && pos.x < Game.Size &&
pos.y >= 0 && pos.y < Game.Size;
// Return true if the given move is valid.
public bool validMove(Move move) {
Pos from = move.from, to = move.to;
return valid(from) && valid(to) &&
squares[from.x, from.y] == turn &&
to.y - from.y == dir() &&
Abs(to.x - from.x) <= 1 &&
squares[to.x, to.y] != turn &&
!(from.x == to.x && squares[to.x, to.y] > 0);
}
// Return a list of all possible moves for the current player.
public List<Move> possibleMoves() {
var ret = new List<Move>();
for (int x = 0 ; x < Size ; ++x)
for (int y = 0 ; y < Size ; ++y)
if (squares[x, y] == turn) {
Pos from = new Pos(x, y);
for (int x1 = x - 1; x1 <= x + 1 ; ++x1) {
Move move = new Move(from, new Pos(x1, y + dir()));
if (validMove(move))
ret.Add(move);
}
}
return ret;
}
// Update the game by having the current player make the given move.
// Returns true if a capture was made.
public bool move(Move m) {
if (validMove(m)) {
Pos from = m.from, to = m.to;
bool capture = (squares[to.x, to.y] > 0);
squares[from.x, from.y] = 0;
squares[to.x, to.y] = turn;
if (capture)
pieces[3 - turn] -= 1;
if (turn == 1 && to.y == 0 || turn == 2 && to.y == Size - 1 ||
pieces[3 - turn] == 0)
winner = turn;
turn = 3 - turn;
moves += 1;
return capture;
} else throw new Exception("illegal move");
}
// Reverse a previous move that was made. When calling this method,
// wasCapture must be true if the move was a capture.
public void unmove(Move m, bool wasCapture) {
moves -= 1;
turn = 3 - turn;
winner = 0;
if (wasCapture)
pieces[3 - turn] += 1;
squares[m.to.x, m.to.y] = wasCapture ? 3 - turn : 0;
squares[m.from.x, m.from.y] = turn;
}
}
// A Player is a strategy for playing the game. Given any game state,
// the Player's chooseMove method decides what move to make.
interface Player {
Move chooseMove(Game game);
}
class Program {
static void simulate(Player[] players, int games) {
string name(int p) => players[p].GetType().Name;
WriteLine($"playing {games} games");
int[] moves = new int[3];
long[] elapsed = new long[3];
int[] wins = new int[3];
for (int x = 0 ; x < games ; ++x) {
Game game = new Game(x);
while (game.winner == 0) {
Stopwatch sw = Stopwatch.StartNew();
Move move = players[game.turn].chooseMove(game.clone());
sw.Stop();
moves[game.turn] += 1;
elapsed[game.turn] += sw.ElapsedMilliseconds;
game.move(move);
}
WriteLine($"game {x}: winner = {name(game.winner)}");
wins[game.winner] += 1;
}
WriteLine($"{name(1)} won {wins[1]}, {name(2)} won {wins[2]}");
double avg1 = 1.0 * elapsed[1] / moves[1],
avg2 = 1.0 * elapsed[2] / moves[2];
WriteLine($"{name(1)} used {avg1:f1} ms/move, {name(2)} used {avg2:f1} ms/move");
}
[STAThread]
static void Main(string[] args) {
Player[] players = { null, new MyAgent(), new Clever() };
int seed = -1;
for (int i = 0 ; i < args.Length ; ++i)
switch (args[i]) {
case "-seed":
seed = int.Parse(args[i + 1]);
i += 1;
break;
case "-sim":
simulate(players, int.Parse(args[i + 1]));
return;
case "-swap":
(players[1], players[2]) = (players[2], players[1]);
break;
default:
WriteLine("unknown option: " + args[i]);
WriteLine("usage: breakthrough [-seed <num>] [-swap] [-sim <num>]");
return;
}
View.run(new Game(seed), players);
}
}