@@ -78,7 +78,7 @@ pub struct BitBoard {
78
78
/// How do (i, j) coordinates map to bits in the board?
79
79
/// (i, j) is represented as the bit number (i * 7 + j), counted from
80
80
/// the least significant bit. So if you lay out a number like
81
- /// 0b0000000000000011111111111111111111111111111111111 in blocks of 7
81
+ /// 0b1100000000000011111111111111111111111111111111111 in blocks of 7
82
82
/// from least significant to most significant bit
83
83
/// (which is also what the Debug impl does) like so:
84
84
///
@@ -89,7 +89,7 @@ pub struct BitBoard {
89
89
/// 1 1 1 1 1 1 1
90
90
/// 1 1 1 1 1 1 1
91
91
/// 0 0 0 0 0 0 0
92
- /// 0 0 0 0 0 0 0
92
+ /// 0 0 0 0 0 1 1
93
93
/// ```
94
94
/// then this 2D array effectively has a coordinate system that has i going from the
95
95
/// top (0) to the bottom (6), and j going from the left (0) to the right (6).
@@ -176,111 +176,52 @@ impl BitBoard {
176
176
( self . bits & BOARD_MASK ) . count_ones ( )
177
177
}
178
178
179
- pub ( crate ) fn center ( self ) -> ( i8 , i8 ) {
180
- let ( offset_i, offset_j) = self . offset ( ) ;
181
- ( offset_i + 3 , offset_j + 3 )
182
- }
183
-
184
- pub ( crate ) fn recenter_to ( self , new_center : ( i8 , i8 ) ) -> BitBoard {
185
- let ( offset_i, offset_j) = self . offset ( ) ;
186
- let ( new_offset_i, new_offset_j) = ( new_center. 0 - 3 , new_center. 1 - 3 ) ;
187
- debug_assert ! ( ( offset_i - new_offset_i) . abs( ) < 4 ) ;
188
- debug_assert ! ( ( offset_j - new_offset_j) . abs( ) < 4 ) ;
189
-
190
- let board_bits = self . bits & BOARD_MASK ;
191
-
192
- // Imagine the 49 board bits like this (top left is lowest bit, bottom right highest, and
193
- // i selects the row, j the column):
194
- //
195
- // . . . . . . .
196
- // . . . . . . .
197
- // . . . . 1 . .
198
- // . . 1 x . 1 .
199
- // . . . . 1 n .
200
- // . . . . . . .
201
- // . . . . . . .
202
- //
203
- // The center is marked with x, cards are marked with 1 (since they are 1s in the bitset),
204
- // and the new center is marked with n. We need to shift n to land on x.
205
- // It's important to realize that there will never be any wraparound in the sense that a
206
- // 1 lands on the other side of the board, as long as the new center is a valid center for
207
- // a bitboard that contains the same cards.
208
-
209
- let diff = ( offset_i - new_offset_i) * 7 + ( offset_j - new_offset_j) ;
210
- let board_bits_shifted = if diff > 0 {
211
- // Since the new center has lower coordinates, the local coordinates will have
212
- // higher coordinates => shift left
213
- board_bits << diff
214
- } else {
215
- board_bits >> diff. abs ( )
216
- } ;
217
- // No ones should have gotten lost
218
- debug_assert_eq ! (
219
- board_bits. count_ones( ) ,
220
- ( board_bits_shifted & BOARD_MASK ) . count_ones( )
221
- ) ;
222
- let offset_bits = encode_offset ( new_offset_i, new_offset_j) ;
223
- Self {
224
- bits : offset_bits | board_bits_shifted,
225
- }
226
- }
227
-
228
- pub ( crate ) fn shift_lossy ( self , new_center : ( i8 , i8 ) ) -> BitBoard {
229
- assert ! ( new_center. 0 >= -52 ) ;
230
- assert ! ( new_center. 1 >= -52 ) ;
231
- assert ! ( new_center. 0 <= 52 ) ;
232
- assert ! ( new_center. 1 <= 52 ) ;
233
-
234
- let ( offset_i, offset_j) = self . offset ( ) ;
235
- let ( new_offset_i, new_offset_j) = ( new_center. 0 - 3 , new_center. 1 - 3 ) ;
236
- let ( diff_i, diff_j) = ( new_offset_i - offset_i, new_offset_j - offset_j) ;
237
- let mask_i = SHIFT_MASK_I [ ( diff_i + 7 ) . clamp ( 0 , 14 ) as usize ] ;
238
- let mask_j = SHIFT_MASK_J [ ( diff_j + 7 ) . clamp ( 0 , 14 ) as usize ] ;
239
- let valid_bits = self . bits & mask_i & mask_j;
240
- let shift_by = diff_i * 7 + diff_j;
241
- let bits_shifted = if shift_by > 0 {
242
- valid_bits >> shift_by
243
- } else {
244
- valid_bits << shift_by. abs ( )
245
- } ;
246
- let offset_bits = encode_offset ( new_offset_i, new_offset_j) ;
247
- Self {
248
- bits : offset_bits | bits_shifted,
249
- }
250
- }
251
-
252
- /// Compute the difference to another `BitBoard`.
253
- ///
254
- /// Both boards must be centered around the same point, otherwise this
255
- /// function will panic.
179
+ /// Computes the difference to another `BitBoard`.
256
180
#[ must_use]
257
181
pub fn difference ( self , other : BitBoard ) -> BitBoard {
258
- assert_eq ! ( self . bits & IJ_MASK , other. bits & IJ_MASK ) ;
182
+ // Recenter the other bitboard to our center, which may lose some coordinates.
183
+ // But that's fine - since these coordinates are not representable in our bitboard,
184
+ // they can't be contained in our bitboard anyway.
259
185
Self {
260
- bits : self . bits & !( other. bits & BOARD_MASK ) ,
186
+ bits : self . bits & !other. board_bits_shifted_to_offset_lossy ( self . offset ( ) ) ,
261
187
}
262
188
}
263
189
190
+ /// Checks whether there are any horizontal, vertical or diagonal lines of length 4
191
+ /// passing through the specified point (in a 7 x 7 area centered on the point).
192
+ ///
193
+ /// Any lines that are found are returned in a new `BitBoard`. The result is therefore
194
+ /// a subset of the input.
195
+ ///
196
+ /// Only valid for point coordinates in the range `[-52, 52]`.
264
197
#[ must_use]
265
- pub ( crate ) fn detect_central_lines ( self ) -> BitBoard {
198
+ pub fn lines_going_through_point ( self , point_i : i8 , point_j : i8 ) -> BitBoard {
199
+ debug_assert ! ( point_i >= -52 ) ;
200
+ debug_assert ! ( point_j >= -52 ) ;
201
+ debug_assert ! ( point_i <= 52 ) ;
202
+ debug_assert ! ( point_j <= 52 ) ;
203
+
204
+ let offset = ( point_i - 3 , point_j - 3 ) ;
205
+ // The bits we may lose by shifting to the new center are exactly those that are
206
+ // more than 3 fields away from the point. They don't count anyway.
207
+ let bits_centered = self . board_bits_shifted_to_offset_lossy ( offset) ;
208
+
266
209
let mut line_bits = 0 ;
267
210
// These patterns are lines on the 7x7 board - horizontal, vertical, and two diagonal.
268
- // They already are zero outside of BOARD_MASK, so self.bits & pattern is the same as
269
- // (self.bits & BOARD_MASK) & pattern.
270
211
for pattern in [
271
212
0xfe00000u64 ,
272
213
0x204081020408u64 ,
273
214
0x1010101010101u64 ,
274
215
0x41041041040u64 ,
275
216
] {
276
- let pattern_intersect = self . bits & pattern;
217
+ let pattern_intersect = bits_centered & pattern;
277
218
debug_assert ! ( pattern_intersect. count_ones( ) <= 4 ) ;
278
219
if pattern_intersect. count_ones ( ) == 4 {
279
220
line_bits |= pattern_intersect;
280
221
}
281
222
}
282
223
Self {
283
- bits : ( self . bits & IJ_MASK ) | line_bits,
224
+ bits : encode_offset ( offset . 0 , offset . 1 ) | line_bits,
284
225
}
285
226
}
286
227
@@ -303,6 +244,25 @@ impl BitBoard {
303
244
fn offset ( self ) -> ( i8 , i8 ) {
304
245
decode_offset ( self . bits )
305
246
}
247
+
248
+ fn board_bits_shifted_to_offset_lossy ( self , new_offset : ( i8 , i8 ) ) -> u64 {
249
+ debug_assert ! ( new_offset. 0 >= -55 ) ;
250
+ debug_assert ! ( new_offset. 1 >= -55 ) ;
251
+ debug_assert ! ( new_offset. 0 <= 49 ) ;
252
+ debug_assert ! ( new_offset. 1 <= 49 ) ;
253
+
254
+ let ( offset_i, offset_j) = self . offset ( ) ;
255
+ let ( diff_i, diff_j) = ( new_offset. 0 - offset_i, new_offset. 1 - offset_j) ;
256
+ let mask_i = SHIFT_MASK_I [ ( diff_i + 7 ) . clamp ( 0 , 14 ) as usize ] ;
257
+ let mask_j = SHIFT_MASK_J [ ( diff_j + 7 ) . clamp ( 0 , 14 ) as usize ] ;
258
+ let valid_bits = self . bits & mask_i & mask_j;
259
+ let shift_by = diff_i * 7 + diff_j;
260
+ if shift_by > 0 {
261
+ valid_bits >> shift_by. min ( 63 )
262
+ } else {
263
+ valid_bits << shift_by. abs ( ) . min ( 63 )
264
+ }
265
+ }
306
266
}
307
267
308
268
fn decode_offset ( bits : u64 ) -> ( i8 , i8 ) {
@@ -386,6 +346,24 @@ mod python {
386
346
fn py_is_empty ( & self ) -> bool {
387
347
self . is_empty ( )
388
348
}
349
+
350
+ #[ pyo3( name = "difference" ) ]
351
+ fn py_difference ( & self , other : BitBoard ) -> BitBoard {
352
+ self . difference ( other)
353
+ }
354
+
355
+ #[ pyo3( name = "lines_going_through_point" ) ]
356
+ fn py_lines_going_through_point ( & self , point_i : i8 , point_j : i8 ) -> BitBoard {
357
+ self . lines_going_through_point ( point_i, point_j)
358
+ }
359
+
360
+ fn __len__ ( & self ) -> usize {
361
+ self . num_entries ( ) as usize
362
+ }
363
+
364
+ fn __bool__ ( & self ) -> bool {
365
+ !self . is_empty ( )
366
+ }
389
367
}
390
368
}
391
369
@@ -405,20 +383,47 @@ mod tests {
405
383
}
406
384
407
385
#[ test]
408
- fn recenter ( ) {
386
+ fn shift_far ( ) {
409
387
let bb = BitBoard :: empty_board_centered_at ( 12 , 30 )
388
+ . insert ( 11 , 32 )
410
389
. insert ( 12 , 30 )
411
390
. insert ( 12 , 33 )
412
391
. insert ( 15 , 30 ) ;
413
- assert_eq ! ( bb. bits, bb. recenter_to( ( 15 , 33 ) ) . recenter_to( ( 12 , 30 ) ) . bits) ;
392
+ // None of the coordinates on the board are representable with that offset.
393
+ let bits_shifted = bb. board_bits_shifted_to_offset_lossy ( ( 0 , 0 ) ) ;
394
+ assert_eq ! ( bits_shifted, 0 ) ;
414
395
}
415
396
416
397
#[ test]
417
398
fn shift ( ) {
418
399
let bb = BitBoard :: empty_board_centered_at ( 12 , 30 )
419
- . insert ( 12 , 30 )
400
+ . insert ( 11 , 32 )
401
+ . insert ( 12 , 31 )
420
402
. insert ( 12 , 33 )
421
403
. insert ( 15 , 30 ) ;
422
- assert_eq ! ( bb. bits, bb. shift_lossy( ( 15 , 33 ) ) . shift_lossy( ( 12 , 30 ) ) . bits) ;
404
+ let ( offset_i, offset_j) = ( 11 , 31 ) ;
405
+ let bits_shifted = bb. board_bits_shifted_to_offset_lossy ( ( offset_i, offset_j) ) ;
406
+ let bb_shifted = BitBoard {
407
+ bits : bits_shifted | encode_offset ( offset_i, offset_j) ,
408
+ } ;
409
+ let coordinates_after_shift = Vec :: from_iter ( bb_shifted) ;
410
+ assert_eq ! ( coordinates_after_shift, vec![ ( 11 , 32 ) , ( 12 , 31 ) , ( 12 , 33 ) ] ) ;
411
+ }
412
+
413
+ #[ test]
414
+ fn detect_line ( ) {
415
+ let bb = BitBoard :: empty_board_centered_at ( 10 , 10 )
416
+ . insert ( 8 , 11 )
417
+ . insert ( 11 , 11 )
418
+ . insert ( 12 , 11 )
419
+ . insert ( 13 , 11 ) ;
420
+ assert_eq ! (
421
+ Vec :: from_iter( bb. lines_going_through_point( 11 , 11 ) ) ,
422
+ Vec :: from_iter( bb)
423
+ ) ;
424
+ assert_eq ! (
425
+ Vec :: from_iter( bb. lines_going_through_point( 9 , 9 ) ) ,
426
+ Vec :: new( )
427
+ ) ;
423
428
}
424
429
}
0 commit comments