Issues (2)

src/ai.js (2 issues)

1
/**
2
 *  A module to calculate a move in Gomoku
3
 *
4
 *  Ripped and modified from Zebra_Gomoku by Stefan Gabos.
5
 *
6
 * Some day I have to write my own...
7
 *
8
 *  Visit {@link https://github.com/stefangabos/Zebra_Gomoku} for more information.
9
 */
10
"use strict";
11
12
function nextBestMove(board, boardSize) {
13
    var m,
14
        l,
15
        position,
16
        type,
17
        line,
18
        totalCells,
19
        consecutiveCells,
20
        emptySides,
21
        bestScore,
22
        cellScore,
23
        directionScore,
24
        score;
25
26
    // iterate through all the board's cells
27 16
    for (var i = boardSize * boardSize; i--; ) {
28
        // skip to next cell if this cell is owned by the computer
29
        // if (board[i] == 1) continue;
30
31
        // by default, the best move is the first free cell
32
        // (position, attack, defense)
33
34 1600
        if (bestScore === undefined) {
0 ignored issues
show
The variable bestScore seems to not be initialized for all possible execution paths.
Loading history...
35 16
            bestScore = [i, 0, 0];
36
        }
37
        // we will give a "score" to the position,
38
        // based on its surroundings horizontally, vertically and
39
        // on both diagonals; for example: for a row like 000XXPXX000 (where "0" means empty,
40
        // "X" represents the opponent's pieces and "P" is the position for which we are
41
        // determining the "score", we would check
42
        // 0|00XXP|XX000, 00|0XXPX|X000, 000|XXPXX|000, 000X|XPXX0|00, 000XX|PXX00|0,
43
        // and then we would do the same on the vertical, and on both diagonals)
44
45
        // cell's default score (attack and defense)
46 1600
        cellScore = [0, 0];
47
48
        // the 4 directions to check: vertical, horizontal, diagonal /, diagonal \ (in this order)
49 1600
        for (var j = 4; j--; ) {
50
            // the default score for the direction we're checking
51 6400
            directionScore = [0, 0];
52
53
            // check the 5 possible outcomes, as described above
54
            // (if we're checking whether the player won,
55
            // we'll do this iteration only once, checking for 5 in a row)
56 6400
            for (var k = !board[i] ? 5 : 1; k--; ) {
57
                // initialize the type of cells we're looking for,
58
                // and the array with the cells on the current direction
59 15680
                type = board[i] || undefined;
60 15680
                line = [];
61
62
                // check the 5 pieces for each possible outcome, plus the 2 sides
63 15680
                for (l = 7; l--; ) {
64
                    // used to compute position
65 73128
                    m = -5 + k + l;
66 73128
                    var n = i % boardSize;
67
68 73128
                    if (
69
                        // vertical
70
                        ((j === 0 &&
71
                            (position = i + boardSize * m) !== false &&
72
                            n == position % boardSize) ||
73
                            // horizontal
74
                            (j == 1 &&
75
                                (position = i + m) !== false &&
76
                                ~~(position / boardSize) == ~~(i / boardSize)) ||
77
                            // diagonal /
78
                            (j == 2 &&
79
                                (position = i - boardSize * m + m) !== false &&
80
                                ((position > i && position % boardSize < n) ||
81
                                    (position < i && position % boardSize > n) ||
82
                                    position == i)) ||
83
                            // diagonal \
84
                            ((j == 3 &&
85
                                (position = i + boardSize * m + m) !== false &&
86
                                ((position < i && position % boardSize < n) ||
87
                                    (position > i && position % boardSize > n))) ||
88
                                position == i)) &&
0 ignored issues
show
The variable position seems to not be initialized for all possible execution paths.
Loading history...
89
                        // the position is not off-board
90
                        position >= 0 &&
91
                        position < boardSize * boardSize &&
92
                        // the cell is of the same type as the ones we are looking for
93
                        // or, we are checking the score for an empty cell,
94
                        // the current position is empty,
95
                        // or is "undefined" (meaning we didn't yet find any non-empty cells)
96
                        (board[position] == type ||
97
                            (!board[i] && (!board[position] || undefined === type)) ||
98
                            // or we're just checking the sides
99
                            !l ||
100
                            l == 6)
101
                    ) {
102
                        // add position to the line
103 56572
                        line.push(position);
104
105
                        // if we're not just checking the sides,
106
                        // this is not an empty cell, and is of the same type as
107
                        // the ones we're looking for,
108
                        // update the type of cells we're looking for
109
                        // (we use ^ instead of !=)
110 56572
                        if (l && l ^ 6 && undefined === type && board[position]) {
111 1118
                            type = board[position];
112
                        }
113
114
                        // if the computed position is off-board,
115
                        // but this is a side - cell, save it as "undefined"
116 16556
                    } else if (!l || l == 6) {
117 6532
                        line.push(undefined);
118
                    } else {
119
                        // skip the rest of the test if we found this row to be "non-compliant"
120
                        // (a different type of cell than the ones we're looking for,
121
                        // one of the 5 cells is off - board)
122 10024
                        break;
123
                    }
124
                }
125
126
                // if we added exactly 7 position to our line, and
127
                // the line is not containing * only * empty cells
128 15680
                if (line.length == 7 && undefined !== type) {
129
                    // if we are checking whether the player won,
130
                    // set this flag so that later on we do not
131
                    // overwrite the value of the cell
132 1326
                    m = board[i] ? true : false;
133
134
                    // calculate the score when setting the current cell to
135
                    // the same type as the other ones we found
136 1326
                    board[i] = type;
137
138
                    // calculate the number of consecutive cells we get like this
139
                    // (we'll do this by looking in our "line" array)
140 1326
                    consecutiveCells = 0;
141 1326
                    totalCells = 0;
142 1326
                    emptySides = 0;
143
144
                    // the total number of cells of the same type
145 1326
                    for (l = 5; l--; ) {
146 6630
                        if (board[line[l + 1]] == type) {
147 4570
                            totalCells++;
148
                        }
149
                    }
150
151
                    // look to the left of the current cell
152 1326
                    for (
153
                        l = line.indexOf(i) - 1;
154
                        l >= 0;
155
                        // if the cell is of the same type,
156
                        //increment the number of consecutive cells
157
                        l--
158
                    ) {
159 1528
                        if (board[line[l]] == type) {
160 718
                            consecutiveCells++;
161
                        } else {
162
                            // otherwise
163
                            // if the adjacent cell is empty, increment the number of empty sides
164
                            // we have to use === 0 (instead of !) because it
165
                            // can also be "undefined"
166 810
                            if (board[line[l]] === 0) {
167 646
                                emptySides++;
168
                            }
169
170
                            // don't look further
171 810
                            break;
172
                        }
173
                    }
174
175
                    // look to the right of the current cell
176 1326
                    for (
177
                        l = line.indexOf(i);
178
                        l < line.length;
179
                        // if the cell is of the same type,
180
                        // increment the number of consecutive cells
181
                        l++
182
                    ) {
183 5194
                        if (board[line[l]] == type) {
184 4370
                            consecutiveCells++;
185
                        } else {
186
                            // otherwise
187
                            // if the adjacent cell is empty, increment the number of empty sides
188
                            // we have to use === 0 (instead of !)
189
                            // because it can also be "undefined"
190 824
                            if (board[line[l]] === 0) {
191 652
                                emptySides++;
192
                            }
193
194
                            // don't look further
195 824
                            break;
196
                        }
197
                    }
198
199
                    // give a score to the row based on the
200
                    // array below(number of cells / empty sides)
201 1326
                    score = [[0, 1], [2, 3], [4, 12], [10, 64], [256, 256]][
202
                        consecutiveCells >= totalCells
203
                            ? Math.min(consecutiveCells, 5) - 1
204
                            : totalCells - 1
205
                    ][consecutiveCells >= totalCells ? (emptySides ? emptySides - 1 : 0) : 0];
206
207
                    // reset the cell's value (unless we are looking to see if the player won)
208 1326
                    if (!m) {
209 734
                        board[i] = 0;
210
                    }
211
                    // } else if (score >= 256) {
212
                    //     // if the player won, update the score
213
                    //     score = 1024;
214
                    // }
215
216
                    // if, so far, this is the best
217
                    // attack / defense score(depending on the value of "type")
218
                    // for this direction, update it
219 1326
                    if (score > directionScore[type - 1]) {
220 1016
                        directionScore[type - 1] = score;
221
                    }
222
                }
223
            }
224
225
            // update the cell's attack and defense score
226
            // (we simply sum the best scores of all 4 directions)
227 6400
            for (k = 2; k--; ) {
228 12800
                cellScore[k] += directionScore[k];
229
            }
230
        }
231
232
        // used below
233 1600
        j = cellScore[0] + cellScore[1];
234 1600
        k = bestScore[1] + bestScore[2];
235
236
        // if cell's attack + defense score is better than
237
        // the current best attack and defense score
238
        // or, cell's score is equal to the best score,
239
        // but computer's move is better or equal to the player's,
240
        // and the current best move is not *exactly* the same
241 1600
        if (
242
            (j > k || (j == k && cellScore[0] >= bestScore[1])) &&
243
            // we're checking the score of an empty cell,
244
            // or we're checking to see if the player won and he won
245
            // (we don't update the score when checking if the
246
            // player won * unless * the player actually won)
247
            (!board[i] || cellScore[1] >= 1024)
248
249
            // update best score (position, attack, defense)
250
        ) {
251 234
            bestScore = [i, cellScore[0], cellScore[1]];
252
        }
253
    }
254
255
    // Calculation done return x, y
256
    var x, y;
257
258 16
    y = Math.floor(bestScore[0] / boardSize);
259 16
    x = bestScore[0] % boardSize;
260
261 16
    return { x: x, y: y };
262
}
263
264
function getRandomIntInclusive(min, max) {
265 4
    return Math.floor(Math.random() * (max - min + 1)) + min;
266
}
267
268
// randomMove :: ([Mark], Size) -> Position
269
//  Mark = 0 | 1 | 2
270
//  Size = Integer
271
//  Position = { x: Integer, y: Integer }
272
function randomMove(board, boardSize) {
273 6
    if (
274
        board.every(function(x) {
275 402
            return x !== 0;
276
        })
277
    ) {
278 4
        return null;
279
    }
280
    var x, y;
281
282 2
    do {
283 2
        x = getRandomIntInclusive(0, boardSize);
284 2
        y = getRandomIntInclusive(0, boardSize);
285
    } while (board[x + y * boardSize] !== 0);
286
287 2
    return { x: x, y: y };
288
}
289
290
// bestMove :: ([Mark], Size) -> Position
291
//  Mark = 0 | 1 | 2
292
//  Size = Integer
293
//  Position = { x: Integer, y: Integer }
294
function bestMove(board, boardSize) {
295 16
    var pos = nextBestMove(board, boardSize);
296
297
    // On occasion the algoritm calcs a position that is already taken
298
    // Catch that and randomize something.
299 16
    if (board[pos.x + pos.y * boardSize] !== 0) {
300 2
        pos = randomMove(board, boardSize);
301
    }
302
303 16
    return pos;
304
}
305
306
module.exports = { bestMove: bestMove, randomMove: randomMove };
307