-
Notifications
You must be signed in to change notification settings - Fork 0
/
rules.js
321 lines (280 loc) · 10.3 KB
/
rules.js
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
* The rules of checkers.
* Ted Benson <eob@csail.mit.edu>
*
* This class implements the rules of American checkers. Note that we
* use the variant that does not require to "eat" another checker if that
* is a possibility.
*
*/
var Rules = function(board) {
/***************************************************************
* "Public" functions (if such a thing could be enforced)
* Everything you should need as the user of Rules.
*
* - makeMove(checker, turnDirection, playerDirection, toRow, toCol)
* - makeRandomMove(checker, playerDirection)
*
**************************************************************/
/**
* Attempts to make a move. If the move is valid, this will mutate
* the board object and return a list of jumped pieces that were removed
* from the board as a result.
*
* Input
* - checker: the checker object you would like to move
* - turnDirection: which direction (+1, -1) represents the current turn
* - playerDirection: which direction (+1, -1) represents the color of `checker` (first argument)
* - toRow: which row would you like to move checker to
* - toCol: which column would you like to move checker to
*
* Note about directions:
* For rule checking, the Rules object represents both turns and players by directions on the
* board, either +1 or -1, not by piece coloring. If the turn is +1 and the checker moved represents
* player -1 for exampe, the Rules object will reject this move and return null. Don't worry about
* kings being bidirectional -- this object knows how to take this into account.
*
* Success Return:
* { 'from_col': X,
* 'to_col': XPrime,
* 'from_row': Y,
* 'to_row':Z,
* 'made_king':false,
* 'removed':[
* {'col':X, 'row':Y, 'color':Z, 'isKing':true},
* {'col':X, 'row':Y, 'color':Z, 'isKing':false}
* ]
* }
*
* Error Return: (when the move is invalid)
* null
*/
this.makeMove = function(checker, turnDirection, playerDirection, toRow, toCol) {
var ramifications = this.ramificationsOfMove(checker,
turnDirection,
playerDirection,
toRow,
toCol);
var wasKingBefore = checker.isKing;
// Invalid move?
if (ramifications == null) {
return null;
}
// If valid, remember where we started
ramifications['from_col'] = checker.col;
ramifications['from_row'] = checker.row;
// Then mutate the board
// 1. Move the piece
board.moveTo(checker, ramifications['to_row'], ramifications['to_col']);
ramifications['made_king'] = ((! wasKingBefore) && (checker.isKing));
//if (ramifications['made_king']) alert("made king: " + ramifications['made_king']);
// 2. Remove dead checkers
for (var i=0; i<ramifications['remove'].length; i++) {
//if (ramifications['remove'][i]['isKing']) alert("removed king");
board.remove(
board.getCheckerAt(
ramifications['remove'][i]['row'],
ramifications['remove'][i]['col']
)
);
}
return ramifications;
}
/**
* Makes a random move. If no random move can be found for the player,
* returns null. If a random move can be found, provides a result object
* in the shape of that from makeMove(..)
*/
this.makeRandomMove = function(playerColor, playerDirection) {
// Get all checkers of my color
var checkers = board.getAllCheckers();
var myCheckers = [];
for (var i=0; i<checkers.length; i++) {
if (checkers[i].color == playerColor) {
myCheckers.push(checkers[i]);
}
}
// now randomly sort
myCheckers = this.shuffle(myCheckers);
for (var i=0; i<myCheckers.length; i++) {
var validMoves = this.validMovesFor(myCheckers[i], playerDirection);
if (validMoves.length > 0) {
// Randomize the moves
validMoves = this.shuffle(validMoves);
// Make the move!
var move = validMoves[0];
return this.makeMove(
myCheckers[i],
playerDirection,
playerDirection,
move.to_row,
move.to_col
);
}
}
// If were still here, no move is possible
return null;
}
/***************************************************************
* "Private" functions (if such a thing could be enforced)
* You probably don't need to call these
**************************************************************/
// Returns null on an invalid move
this.ramificationsOfMove= function(checker, turnDirection, playerDirection, toRow, toCol) {
if (playerDirection != turnDirection) {
return null;
}
var validMoves = this.validMovesFor(checker, playerDirection);
for (var i = 0; i<validMoves.length; i++) {
if ((validMoves[i].to_col == toCol) && (validMoves[i].to_row == toRow)) {
return validMoves[i];
}
}
return null;
}
/**
* Returns a list of valid moves.
*/
this.validMovesFor = function(checker, playerDirection) {
assertTrue(((playerDirection== 1) || (playerDirection== -1)), "Direction must be 1 or -1");
var validMoves = [];
/** A checker can move forward if:
* 1. The space is valid
* 2. The space isn't occupied
*/
for (var i = -1; i < 2; i++) {
if (i != 0) {
if (board.isValidLocation(checker.row + playerDirection, checker.col + i) &&
board.isEmptyLocation(checker.row + playerDirection, checker.col + i)) {
validMoves.push({'to_col': checker.col + i,
'to_row': checker.row + playerDirection,
'remove': []});
}
}
}
/** A checker can move backward if:
* 1. The space is valid
* 2. The space isn't occupied
* 3. The checker is a king
*/
for (var i = -1; i < 2; i++) {
if (i != 0) {
if (board.isValidLocation(checker.row - playerDirection, checker.col + i) &&
board.isEmptyLocation(checker.row - playerDirection, checker.col + i) &&
(checker.isKing == true)) {
validMoves.push({'to_col': checker.col + i,
'to_row': checker.row - playerDirection,
'remove': []});
}
}
}
/** A checker can jump
*/
var jumps = this.validJumpsFor(checker, playerDirection, [], checker.row, checker.col);
for (var i=0; i<jumps.length; i++) {
validMoves.push(this.collapseJumpSequence(jumps[i]));
}
return validMoves;
}
this.collapseJumpSequence = function(jumpSequence) {
var move = {
'to_col':jumpSequence[jumpSequence.length - 1].col,
'to_row':jumpSequence[jumpSequence.length - 1].row,
'remove':[]
};
for (var j=0; j<jumpSequence.length; j++) {
move['remove'].push({
'row':jumpSequence[j].killedRow,
'col':jumpSequence[j].killedCol,
'color':board.getCheckerAt(jumpSequence[j].killedRow, jumpSequence[j].killedCol).color,
'isKing':board.getCheckerAt(jumpSequence[j].killedRow, jumpSequence[j].killedCol).isKing
});
}
return move;
}
this.alreadyJumpedPiece = function(jumpSeq, row, col) {
var alreadyJumped = false;
for (j=0; j<jumpSeq.length; j++) {
if ((col == jumpSeq[j].killedCol) && (row == jumpSeq[j].killedRow)) {
alreadyJumped = true;
}
}
return alreadyJumped;
}
this.copyJumpSequence = function(jumpSeq) {
var newJumpSeq = [];
for (var j=0; j<jumpSeq.length; j++) {
newJumpSeq.push(jumpSeq[j]);
}
return newJumpSeq;
}
this.validJumpsFor = function(checker, playerDirection, jumpSeq, curRow, curCol) {
var possibleDestinations = [
[curRow + 2, curCol + 2],
[curRow + 2, curCol - 2],
[curRow - 2, curCol + 2],
[curRow - 2, curCol - 2]
];
var retSeqs = [];
for (var i=0; i<possibleDestinations.length; i++) {
var toRow = possibleDestinations[i][0];
var toCol = possibleDestinations[i][1];
if (this.isValidJump(checker, curRow, curCol, toRow, toCol, playerDirection)) {
// Add this jump to the list of jumps to return
var jumped = {
'killedCol':(curCol + toCol)/2,
'killedRow':(curRow + toRow)/2,
'col':toCol,
'row':toRow}
if (! this.alreadyJumpedPiece(jumpSeq, jumped.killedRow, jumped.killedCol)) {
// Copy the jumpSeq array to pass in
var newJumpSeq = this.copyJumpSequence(jumpSeq);
newJumpSeq.push(jumped);
var futureJumps = this.validJumpsFor(checker, playerDirection, newJumpSeq, toRow, toCol);
for (j = 0; j<futureJumps.length; j++) {
retSeqs.push(futureJumps[j]);
}
// This is the terminal leaf
var lastCopy = this.copyJumpSequence(jumpSeq);
lastCopy.push(jumped);
retSeqs.push(lastCopy);
} // if wasn't jumping existing piece
} // if is valid jump
} // for each possibnle destination
return retSeqs; // No valid seqs.
}
this.shuffle = function(array) {
var tmp, current, top = array.length;
if(top) while(--top) {
current = Math.floor(Math.random() * (top + 1));
tmp = array[current];
array[current] = array[top];
array[top] = tmp;
}
return array;
}
this.isValidJump = function(checker, fromRow, fromCol, toRow, toCol, playerDirection) {
// Is the to-location a valid one?
if (! board.isValidLocation(toRow, toCol)) return false;
// Is the to-location occupied?
if (! board.isEmptyLocation(toRow, toCol)) return false;
// Normal players must not jump backward
if ((((toRow - fromRow) * playerDirection) < 0) && (! checker.isKing)) return false;
// Jump must be two spaces horizontally
if (Math.abs(toRow - fromRow) != 2) return false;
if (Math.abs(toCol - fromCol) != 2) return false;
// A piece must jump another
if (board.isEmptyLocation((toRow+fromRow)/2, (toCol+fromCol)/2)) return false;
// A piece must not jump its own kind
if (board.getCheckerAt((toRow+fromRow)/2, (toCol+fromCol)/2).color == checker.color) return false;
return true;
}
/**
* UTIL
*/
function assertTrue(f, s){
if (!f) {
alert(s);
}
}
}