Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/bench/tests/chess.lua
2721 views
1
2
local function prequire(name) local success, result = pcall(require, name); return success and result end
3
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
4
5
local RANKS = "12345678"
6
local FILES = "abcdefgh"
7
local PieceSymbols = "PpRrNnBbQqKk"
8
local UnicodePieces = {"♙", "♟", "♖", "♜", "♘", "♞", "♗", "♝", "♕", "♛", "♔", "♚"}
9
local StartingFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
10
11
--
12
-- Lua 5.2 Compat
13
--
14
15
if not table.create then
16
function table.create(n, v)
17
local result = {}
18
for i=1,n do result[i] = v end
19
return result
20
end
21
end
22
23
if not table.move then
24
function table.move(a, from, to, start, target)
25
local dx = start - from
26
for i=from,to do
27
target[i+dx] = a[i]
28
end
29
end
30
end
31
32
33
--
34
-- Utils
35
--
36
37
local function square(s)
38
return RANKS:find(s:sub(2,2)) * 8 + FILES:find(s:sub(1,1)) - 9
39
end
40
41
local function squareName(n)
42
local file = n % 8
43
local rank = (n-file)/8
44
return FILES:sub(file+1,file+1) .. RANKS:sub(rank+1,rank+1)
45
end
46
47
local function moveName(v )
48
local from = bit32.extract(v, 6, 6)
49
local to = bit32.extract(v, 0, 6)
50
local piece = bit32.extract(v, 20, 4)
51
local captured = bit32.extract(v, 25, 4)
52
53
local move = PieceSymbols:sub(piece,piece) .. ' ' .. squareName(from) .. (captured ~= 0 and 'x' or '-') .. squareName(to)
54
55
if bit32.extract(v,14) == 1 then
56
if to > from then
57
return "O-O"
58
else
59
return "O-O-O"
60
end
61
end
62
63
local promote = bit32.extract(v,15,4)
64
if promote ~= 0 then
65
move = move .. "=" .. PieceSymbols:sub(promote,promote)
66
end
67
return move
68
end
69
70
local function ucimove(m)
71
local mm = squareName(bit32.extract(m, 6, 6)) .. squareName(bit32.extract(m, 0, 6))
72
local promote = bit32.extract(m,15,4)
73
if promote > 0 then
74
mm = mm .. PieceSymbols:sub(promote,promote):lower()
75
end
76
return mm
77
end
78
79
local _utils = {squareName, moveName}
80
81
--
82
-- Bitboards
83
--
84
85
local Bitboard = {}
86
87
88
function Bitboard:toString()
89
local out = {}
90
91
local src = self.h
92
for x=7,0,-1 do
93
table.insert(out, RANKS:sub(x+1,x+1))
94
table.insert(out, " ")
95
local bit = bit32.lshift(1,(x%4) * 8)
96
for x=0,7 do
97
if bit32.band(src, bit) ~= 0 then
98
table.insert(out, "x ")
99
else
100
table.insert(out, "- ")
101
end
102
bit = bit32.lshift(bit, 1)
103
end
104
if x == 4 then
105
src = self.l
106
end
107
table.insert(out, "\n")
108
end
109
table.insert(out, ' ' .. FILES:gsub('.', '%1 ') .. '\n')
110
table.insert(out, '#: ' .. self:popcnt() .. "\tl:" .. self.l .. "\th:" .. self.h)
111
return table.concat(out)
112
end
113
114
115
function Bitboard.from(l ,h )
116
return setmetatable({l=l, h=h}, Bitboard)
117
end
118
119
Bitboard.zero = Bitboard.from(0,0)
120
Bitboard.full = Bitboard.from(0xFFFFFFFF, 0xFFFFFFFF)
121
122
local Rank1 = Bitboard.from(0x000000FF, 0)
123
local Rank3 = Bitboard.from(0x00FF0000, 0)
124
local Rank6 = Bitboard.from(0, 0x0000FF00)
125
local Rank8 = Bitboard.from(0, 0xFF000000)
126
local FileA = Bitboard.from(0x01010101, 0x01010101)
127
local FileB = Bitboard.from(0x02020202, 0x02020202)
128
local FileC = Bitboard.from(0x04040404, 0x04040404)
129
local FileD = Bitboard.from(0x08080808, 0x08080808)
130
local FileE = Bitboard.from(0x10101010, 0x10101010)
131
local FileF = Bitboard.from(0x20202020, 0x20202020)
132
local FileG = Bitboard.from(0x40404040, 0x40404040)
133
local FileH = Bitboard.from(0x80808080, 0x80808080)
134
135
local _Files = {FileA, FileB, FileC, FileD, FileE, FileF, FileG, FileH}
136
137
-- These masks are filled out below for all files
138
local RightMasks = {FileH}
139
local LeftMasks = {FileA}
140
141
142
143
local function popcnt32(i)
144
i = i - bit32.band(bit32.rshift(i,1), 0x55555555)
145
i = bit32.band(i, 0x33333333) + bit32.band(bit32.rshift(i,2), 0x33333333)
146
return bit32.rshift(bit32.band(i + bit32.rshift(i,4), 0x0F0F0F0F) * 0x01010101, 24)
147
end
148
149
function Bitboard:up()
150
return self:lshift(8)
151
end
152
153
function Bitboard:down()
154
return self:rshift(8)
155
end
156
157
function Bitboard:right()
158
return self:band(FileH:inverse()):lshift(1)
159
end
160
161
function Bitboard:left()
162
return self:band(FileA:inverse()):rshift(1)
163
end
164
165
function Bitboard:move(x,y)
166
local out = self
167
168
if x < 0 then out = out:bandnot(RightMasks[-x]):lshift(-x) end
169
if x > 0 then out = out:bandnot(LeftMasks[x]):rshift(x) end
170
171
if y < 0 then out = out:rshift(-8 * y) end
172
if y > 0 then out = out:lshift(8 * y) end
173
return out
174
end
175
176
177
function Bitboard:popcnt()
178
return popcnt32(self.l) + popcnt32(self.h)
179
end
180
181
function Bitboard:band(other )
182
return Bitboard.from(bit32.band(self.l,other.l), bit32.band(self.h, other.h))
183
end
184
185
function Bitboard:bandnot(other )
186
return Bitboard.from(bit32.band(self.l,bit32.bnot(other.l)), bit32.band(self.h, bit32.bnot(other.h)))
187
end
188
189
function Bitboard:bandempty(other )
190
return bit32.band(self.l,other.l) == 0 and bit32.band(self.h, other.h) == 0
191
end
192
193
function Bitboard:bor(other )
194
return Bitboard.from(bit32.bor(self.l,other.l), bit32.bor(self.h, other.h))
195
end
196
197
function Bitboard:bxor(other )
198
return Bitboard.from(bit32.bxor(self.l,other.l), bit32.bxor(self.h, other.h))
199
end
200
201
function Bitboard:inverse()
202
return Bitboard.from(bit32.bxor(self.l,0xFFFFFFFF), bit32.bxor(self.h, 0xFFFFFFFF))
203
end
204
205
function Bitboard:empty()
206
return self.h == 0 and self.l == 0
207
end
208
209
if not bit32.countrz then
210
local function ctz(v)
211
if v == 0 then return 32 end
212
local offset = 0
213
while bit32.extract(v, offset) == 0 do
214
offset = offset + 1
215
end
216
return offset
217
end
218
function Bitboard:ctz()
219
local result = ctz(self.l)
220
if result == 32 then
221
return ctz(self.h) + 32
222
else
223
return result
224
end
225
end
226
function Bitboard:ctzafter(start)
227
start = start + 1
228
if start < 32 then
229
for i=start,31 do
230
if bit32.extract(self.l, i) == 1 then return i end
231
end
232
end
233
for i=math.max(32,start),63 do
234
if bit32.extract(self.h, i-32) == 1 then return i end
235
end
236
return 64
237
end
238
else
239
function Bitboard:ctz()
240
local result = bit32.countrz(self.l)
241
if result == 32 then
242
return bit32.countrz(self.h) + 32
243
else
244
return result
245
end
246
end
247
function Bitboard:ctzafter(start)
248
local masked = self:band(Bitboard.full:lshift(start+1))
249
return masked:ctz()
250
end
251
end
252
253
254
function Bitboard:lshift(amt)
255
assert(amt >= 0)
256
if amt == 0 then return self end
257
258
if amt > 31 then
259
return Bitboard.from(0, bit32.lshift(self.l, amt-32))
260
end
261
262
local l = bit32.lshift(self.l, amt)
263
local h = bit32.bor(
264
bit32.lshift(self.h, amt),
265
bit32.extract(self.l, 32-amt, amt)
266
)
267
return Bitboard.from(l, h)
268
end
269
270
function Bitboard:rshift(amt)
271
assert(amt >= 0)
272
if amt == 0 then return self end
273
local h = bit32.rshift(self.h, amt)
274
local l = bit32.bor(
275
bit32.rshift(self.l, amt),
276
bit32.lshift(bit32.extract(self.h, 0, amt), 32-amt)
277
)
278
return Bitboard.from(l, h)
279
end
280
281
function Bitboard:index(i)
282
if i > 31 then
283
return bit32.extract(self.h, i - 32)
284
else
285
return bit32.extract(self.l, i)
286
end
287
end
288
289
function Bitboard:set(i , v)
290
if i > 31 then
291
return Bitboard.from(self.l, bit32.replace(self.h, v, i - 32))
292
else
293
return Bitboard.from(bit32.replace(self.l, v, i), self.h)
294
end
295
end
296
297
function Bitboard:isolate(i)
298
return self:band(Bitboard.some(i))
299
end
300
301
function Bitboard.some(idx )
302
return Bitboard.zero:set(idx, 1)
303
end
304
305
Bitboard.__index = Bitboard
306
Bitboard.__tostring = Bitboard.toString
307
308
for i=2,8 do
309
RightMasks[i] = RightMasks[i-1]:rshift(1):bor(FileH)
310
LeftMasks[i] = LeftMasks[i-1]:lshift(1):bor(FileA)
311
end
312
--
313
-- Board
314
--
315
316
local Board = {}
317
318
319
function Board.new()
320
local boards = table.create(12, Bitboard.zero)
321
boards.ocupied = Bitboard.zero
322
boards.white = Bitboard.zero
323
boards.black = Bitboard.zero
324
boards.unocupied = Bitboard.full
325
boards.ep = Bitboard.zero
326
boards.castle = Bitboard.zero
327
boards.toMove = 1
328
boards.hm = 0
329
boards.moves = 0
330
boards.material = 0
331
332
return setmetatable(boards, Board)
333
end
334
335
function Board.fromFen(fen )
336
local b = Board.new()
337
local i = 0
338
local rank = 7
339
local file = 0
340
341
while true do
342
i = i + 1
343
local p = fen:sub(i,i)
344
if p == '/' then
345
rank = rank - 1
346
file = 0
347
elseif tonumber(p) ~= nil then
348
file = file + tonumber(p)
349
else
350
local pidx = PieceSymbols:find(p)
351
if pidx == nil then break end
352
b[pidx] = b[pidx]:set(rank*8+file, 1)
353
file = file + 1
354
end
355
end
356
357
358
local move, castle, ep, hm, m = string.match(fen, "^ ([bw]) ([KQkq-]*) ([a-h-][0-9]?) (%d*) (%d*)", i)
359
if move == nil then print(fen:sub(i)) end
360
b.toMove = move == 'w' and 1 or 2
361
362
if ep ~= "-" then
363
b.ep = Bitboard.some(square(ep))
364
end
365
366
if castle ~= "-" then
367
local oo = Bitboard.zero
368
if castle:find("K") then
369
oo = oo:set(7, 1)
370
end
371
if castle:find("Q") then
372
oo = oo:set(0, 1)
373
end
374
if castle:find("k") then
375
oo = oo:set(63, 1)
376
end
377
if castle:find("q") then
378
oo = oo:set(56, 1)
379
end
380
381
b.castle = oo
382
end
383
384
b.hm = hm
385
b.moves = m
386
387
b:updateCache()
388
return b
389
390
end
391
392
function Board:index(idx )
393
if self.white:index(idx) == 1 then
394
for p=1,12,2 do
395
if self[p]:index(idx) == 1 then
396
return p
397
end
398
end
399
else
400
for p=2,12,2 do
401
if self[p]:index(idx) == 1 then
402
return p
403
end
404
end
405
end
406
407
return 0
408
end
409
410
function Board:updateCache()
411
for i=1,11,2 do
412
self.white = self.white:bor(self[i])
413
self.black = self.black:bor(self[i+1])
414
end
415
416
self.ocupied = self.black:bor(self.white)
417
self.unocupied = self.ocupied:inverse()
418
self.material =
419
100*self[1]:popcnt() - 100*self[2]:popcnt() +
420
500*self[3]:popcnt() - 500*self[4]:popcnt() +
421
300*self[5]:popcnt() - 300*self[6]:popcnt() +
422
300*self[7]:popcnt() - 300*self[8]:popcnt() +
423
900*self[9]:popcnt() - 900*self[10]:popcnt()
424
425
end
426
427
function Board:fen()
428
local out = {}
429
local s = 0
430
local idx = 56
431
for i=0,63 do
432
if i % 8 == 0 and i > 0 then
433
idx = idx - 16
434
if s > 0 then
435
table.insert(out, '' .. s)
436
s = 0
437
end
438
table.insert(out, '/')
439
end
440
local p = self:index(idx)
441
if p == 0 then
442
s = s + 1
443
else
444
if s > 0 then
445
table.insert(out, '' .. s)
446
s = 0
447
end
448
table.insert(out, PieceSymbols:sub(p,p))
449
end
450
451
idx = idx + 1
452
end
453
if s > 0 then
454
table.insert(out, '' .. s)
455
end
456
457
table.insert(out, self.toMove == 1 and ' w ' or ' b ')
458
if self.castle:empty() then
459
table.insert(out, '-')
460
else
461
if self.castle:index(7) == 1 then table.insert(out, 'K') end
462
if self.castle:index(0) == 1 then table.insert(out, 'Q') end
463
if self.castle:index(63) == 1 then table.insert(out, 'k') end
464
if self.castle:index(56) == 1 then table.insert(out, 'q') end
465
end
466
467
table.insert(out, ' ')
468
if self.ep:empty() then
469
table.insert(out, '-')
470
else
471
table.insert(out, squareName(self.ep:ctz()))
472
end
473
474
table.insert(out, ' ' .. self.hm)
475
table.insert(out, ' ' .. self.moves)
476
477
return table.concat(out)
478
end
479
480
function Board:pmoves(idx)
481
return self:generate(idx)
482
end
483
484
function Board:pcaptures(idx)
485
return self:generate(idx):band(self.ocupied)
486
end
487
488
local ROOK_SLIDES = {{1,0}, {-1,0}, {0,1}, {0,-1}}
489
local BISHOP_SLIDES = {{1,1}, {-1,1}, {1,-1}, {-1,-1}}
490
local QUEEN_SLIDES = {{1,0}, {-1,0}, {0,1}, {0,-1}, {1,1}, {-1,1}, {1,-1}, {-1,-1}}
491
local KNIGHT_MOVES = {{2,1}, {2,-1}, {-2,1}, {-2,-1}, {1,2}, {1,-2}, {-1,2}, {-1,-2}}
492
493
function Board:generate(idx)
494
local piece = self:index(idx)
495
local r = Bitboard.some(idx)
496
local out = Bitboard.zero
497
local type = bit32.rshift(piece - 1, 1)
498
local cancapture = piece % 2 == 1 and self.black or self.white
499
500
if piece == 0 then return Bitboard.zero end
501
502
if type == 0 then
503
-- Pawn
504
local d = -(piece*2 - 3)
505
local movetwo = piece == 1 and Rank3 or Rank6
506
507
out = out:bor(r:move(0,d):band(self.unocupied))
508
out = out:bor(out:band(movetwo):move(0,d):band(self.unocupied))
509
510
local captures = r:move(0,d)
511
captures = captures:right():bor(captures:left())
512
513
if not captures:bandempty(self.ep) then
514
out = out:bor(self.ep)
515
end
516
517
captures = captures:band(cancapture)
518
out = out:bor(captures)
519
520
return out
521
elseif type == 5 then
522
-- King
523
for x=-1,1,1 do
524
for y = -1,1,1 do
525
local w = r:move(x,y)
526
if self.ocupied:bandempty(w) then
527
out = out:bor(w)
528
else
529
if not cancapture:bandempty(w) then
530
out = out:bor(w)
531
end
532
end
533
end
534
end
535
elseif type == 2 then
536
-- Knight
537
for _,j in ipairs(KNIGHT_MOVES) do
538
local w = r:move(j[1],j[2])
539
540
if self.ocupied:bandempty(w) then
541
out = out:bor(w)
542
else
543
if not cancapture:bandempty(w) then
544
out = out:bor(w)
545
end
546
end
547
end
548
else
549
-- Sliders (Rook, Bishop, Queen)
550
local slides
551
if type == 1 then
552
slides = ROOK_SLIDES
553
elseif type == 3 then
554
slides = BISHOP_SLIDES
555
else
556
slides = QUEEN_SLIDES
557
end
558
559
for _, op in ipairs(slides) do
560
local w = r
561
for i=1,7 do
562
w = w:move(op[1], op[2])
563
if w:empty() then break end
564
565
if self.ocupied:bandempty(w) then
566
out = out:bor(w)
567
else
568
if not cancapture:bandempty(w) then
569
out = out:bor(w)
570
end
571
break
572
end
573
end
574
end
575
end
576
577
578
return out
579
end
580
581
-- 0-5 - From Square
582
-- 6-11 - To Square
583
-- 12 - is Check
584
-- 13 - Is EnPassent
585
-- 14 - Is Castle
586
-- 15-19 - Promotion Piece
587
-- 20-24 - Moved Pice
588
-- 25-29 - Captured Piece
589
590
591
function Board:toString(mark )
592
local out = {}
593
for x=8,1,-1 do
594
table.insert(out, RANKS:sub(x,x) .. " ")
595
596
for y=1,8 do
597
local n = 8*x+y-9
598
local i = self:index(n)
599
if i == 0 then
600
table.insert(out, '-')
601
else
602
-- out = out .. PieceSymbols:sub(i,i)
603
table.insert(out, UnicodePieces[i])
604
end
605
if mark ~= nil and mark:index(n) ~= 0 then
606
table.insert(out, ')')
607
elseif mark ~= nil and n < 63 and y < 8 and mark:index(n+1) ~= 0 then
608
table.insert(out, '(')
609
else
610
table.insert(out, ' ')
611
end
612
end
613
614
table.insert(out, "\n")
615
end
616
table.insert(out, ' ' .. FILES:gsub('.', '%1 ') .. '\n')
617
table.insert(out, (self.toMove == 1 and "White" or "Black") .. ' e:' .. (self.material/100) .. "\n")
618
return table.concat(out)
619
end
620
621
function Board:moveList()
622
local tm = self.toMove == 1 and self.white or self.black
623
local castle_rank = self.toMove == 1 and Rank1 or Rank8
624
local out = {}
625
local function emit(id)
626
if not self:applyMove(id):illegalyChecked() then
627
table.insert(out, id)
628
end
629
end
630
631
local cr = tm:band(self.castle):band(castle_rank)
632
if not cr:empty() then
633
local p = self.toMove == 1 and 11 or 12
634
local tcolor = self.toMove == 1 and self.black or self.white
635
local kidx = self[p]:ctz()
636
637
638
local castle = bit32.replace(0, p, 20, 4)
639
castle = bit32.replace(castle, kidx, 6, 6)
640
castle = bit32.replace(castle, 1, 14)
641
642
643
local mustbeemptyl = LeftMasks[4]:bxor(FileA):band(castle_rank)
644
local cantbethreatened = FileD:bor(FileC):band(castle_rank):bor(self[p])
645
if
646
not cr:bandempty(FileA) and
647
mustbeemptyl:bandempty(self.ocupied) and
648
not self:isSquareThreatened(cantbethreatened, tcolor)
649
then
650
emit(bit32.replace(castle, kidx - 2, 0, 6))
651
end
652
653
654
local mustbeemptyr = RightMasks[3]:bxor(FileH):band(castle_rank)
655
if
656
not cr:bandempty(FileH) and
657
mustbeemptyr:bandempty(self.ocupied) and
658
not self:isSquareThreatened(mustbeemptyr:bor(self[p]), tcolor)
659
then
660
emit(bit32.replace(castle, kidx + 2, 0, 6))
661
end
662
end
663
664
local sq = tm:ctz()
665
repeat
666
local p = self:index(sq)
667
local moves = self:pmoves(sq)
668
669
while not moves:empty() do
670
local m = moves:ctz()
671
moves = moves:set(m, 0)
672
local id = bit32.replace(m, sq, 6, 6)
673
id = bit32.replace(id, p, 20, 4)
674
local mbb = Bitboard.some(m)
675
if not self.ocupied:bandempty(mbb) then
676
id = bit32.replace(id, self:index(m), 25, 4)
677
end
678
679
-- Check if pawn needs to be promoted
680
if p == 1 and m >= 8*7 then
681
for i=3,9,2 do
682
emit(bit32.replace(id, i, 15, 4))
683
end
684
elseif p == 2 and m < 8 then
685
for i=4,10,2 do
686
emit(bit32.replace(id, i, 15, 4))
687
end
688
else
689
emit(id)
690
end
691
end
692
sq = tm:ctzafter(sq)
693
until sq == 64
694
return out
695
end
696
697
function Board:illegalyChecked()
698
local target = self.toMove == 1 and self[PieceSymbols:find("k")] or self[PieceSymbols:find("K")]
699
return self:isSquareThreatened(target, self.toMove == 1 and self.white or self.black)
700
end
701
702
function Board:isSquareThreatened(target , color )
703
local tm = color
704
local sq = tm:ctz()
705
repeat
706
local moves = self:pmoves(sq)
707
if not moves:bandempty(target) then
708
return true
709
end
710
sq = color:ctzafter(sq)
711
until sq == 64
712
return false
713
end
714
715
function Board:perft(depth )
716
if depth == 0 then return 1 end
717
if depth == 1 then
718
return #self:moveList()
719
end
720
local result = 0
721
for k,m in ipairs(self:moveList()) do
722
local c = self:applyMove(m):perft(depth - 1)
723
if c == 0 then
724
-- Perft only counts leaf nodes at target depth
725
-- result = result + 1
726
else
727
result = result + c
728
end
729
end
730
return result
731
end
732
733
734
function Board:applyMove(move )
735
local out = Board.new()
736
table.move(self, 1, 12, 1, out)
737
local from = bit32.extract(move, 6, 6)
738
local to = bit32.extract(move, 0, 6)
739
local promote = bit32.extract(move, 15, 4)
740
local piece = self:index(from)
741
local captured = self:index(to)
742
local tom = Bitboard.some(to)
743
local isCastle = bit32.extract(move, 14)
744
745
if piece % 2 == 0 then
746
out.moves = self.moves + 1
747
end
748
749
if captured == 1 or piece < 3 then
750
out.hm = 0
751
else
752
out.hm = self.hm + 1
753
end
754
out.castle = self.castle
755
out.toMove = self.toMove == 1 and 2 or 1
756
757
if isCastle == 1 then
758
local rank = piece == 11 and Rank1 or Rank8
759
local colorOffset = piece - 11
760
761
out[3 + colorOffset] = out[3 + colorOffset]:bandnot(from < to and FileH or FileA)
762
out[3 + colorOffset] = out[3 + colorOffset]:bor((from < to and FileF or FileD):band(rank))
763
764
out[piece] = (from < to and FileG or FileC):band(rank)
765
out.castle = out.castle:bandnot(rank)
766
out:updateCache()
767
return out
768
end
769
770
if piece < 3 then
771
local dist = math.abs(to - from)
772
-- Pawn moved two squares, set ep square
773
if dist == 16 then
774
out.ep = Bitboard.some((from + to) / 2)
775
end
776
777
-- Remove enpasent capture
778
if not tom:bandempty(self.ep) then
779
if piece == 1 then
780
out[2] = out[2]:bandnot(self.ep:down())
781
end
782
if piece == 2 then
783
out[1] = out[1]:bandnot(self.ep:up())
784
end
785
end
786
end
787
788
if piece == 3 or piece == 4 then
789
out.castle = out.castle:set(from, 0)
790
end
791
792
if piece > 10 then
793
local rank = piece == 11 and Rank1 or Rank8
794
out.castle = out.castle:bandnot(rank)
795
end
796
797
out[piece] = out[piece]:set(from, 0)
798
if promote == 0 then
799
out[piece] = out[piece]:set(to, 1)
800
else
801
out[promote] = out[promote]:set(to, 1)
802
end
803
if captured ~= 0 then
804
out[captured] = out[captured]:set(to, 0)
805
end
806
807
out:updateCache()
808
return out
809
end
810
811
Board.__index = Board
812
Board.__tostring = Board.toString
813
--
814
-- Main
815
--
816
817
local failures = 0
818
local function test(fen, ply, target)
819
local b = Board.fromFen(fen)
820
if b:fen() ~= fen then
821
print("FEN MISMATCH", fen, b:fen())
822
failures = failures + 1
823
return
824
end
825
826
local found = b:perft(ply)
827
if found ~= target then
828
print(fen, "Found", found, "target", target)
829
failures = failures + 1
830
for k,v in pairs(b:moveList()) do
831
print(ucimove(v) .. ': ' .. (ply > 1 and b:applyMove(v):perft(ply-1) or '1'))
832
end
833
--error("Test Failure")
834
else
835
print("OK", found, fen)
836
end
837
end
838
839
-- From https://www.chessprogramming.org/Perft_Results
840
-- If interpreter, computers, or algorithm gets too fast
841
-- feel free to go deeper
842
843
local testCases = {}
844
local function addTest(...) table.insert(testCases, {...}) end
845
846
addTest(StartingFen, 2, 400)
847
addTest("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 0", 1, 48)
848
addTest("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 0", 2, 191)
849
addTest("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", 2, 264)
850
addTest("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8", 1, 44)
851
addTest("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10", 1, 46)
852
853
854
local function chess()
855
for k,v in ipairs(testCases) do
856
test(v[1],v[2],v[3])
857
end
858
end
859
860
bench.runCode(chess, "chess")
861
862