File size: 15,960 Bytes
c871a64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
import random


piece_score = {'K': 1000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'p': 100,'--':0,'-':0}
CHECKMATE = 100000
STALEMATE = 0
DEPTH = 3


# Pawn (p = 100)
pawn_table = [
    [  0,  0,  0,  0,  0,  0,  0,  0],
    [  5, 10, 10,-20,-20, 10, 10,  5],
    [  5, -5,-10,  0,  0,-10, -5,  5],
    [  0,  0,  0, 20, 20,  0,  0,  0],
    [  5,  5, 10, 25, 25, 10,  5,  5],
    [ 10, 10, 20, 30, 30, 20, 10, 10],
    [ 50, 50, 50, 50, 50, 50, 50, 50],
    [  0,  0,  0,  0,  0,  0,  0,  0]
]

# Knight (N = 320)
knight_table = [
    [-50,-40,-30,-30,-30,-30,-40,-50],
    [-40,-20,  0,  0,  0,  0,-20,-40],
    [-30,  0, 10, 15, 15, 10,  0,-30],
    [-30,  5, 15, 20, 20, 15,  5,-30],
    [-30,  0, 15, 20, 20, 15,  0,-30],
    [-30,  5, 10, 15, 15, 10,  5,-30],
    [-40,-20,  0,  5,  5,  0,-20,-40],
    [-50,-40,-30,-30,-30,-30,-40,-50]
]

# Bishop (B = 330)
bishop_table = [
    [-20,-10,-10,-10,-10,-10,-10,-20],
    [-10,  5,  0,  0,  0,  0,  5,-10],
    [-10, 10, 10, 10, 10, 10, 10,-10],
    [-10,  0, 10, 10, 10, 10,  0,-10],
    [-10,  5,  5, 10, 10,  5,  5,-10],
    [-10,  0,  5, 10, 10,  5,  0,-10],
    [-10,  0,  0,  0,  0,  0,  0,-10],
    [-20,-10,-10,-10,-10,-10,-10,-20]
]

# Rook (R = 500)
rook_table = [
    [  0,  0,  0,  0,  0,  0,  0,  0],
    [  5, 10, 10, 10, 10, 10, 10,  5],
    [ -5,  0,  0,  0,  0,  0,  0, -5],
    [ -5,  0,  0,  0,  0,  0,  0, -5],
    [ -5,  0,  0,  0,  0,  0,  0, -5],
    [ -5,  0,  0,  0,  0,  0,  0, -5],
    [ -5,  0,  0,  0,  0,  0,  0, -5],
    [  0,  0,  0,  5,  5,  0,  0,  0]
]

# Queen (Q = 900)
queen_table = [
    [-20,-10,-10, -5, -5,-10,-10,-20],
    [-10,  0,  0,  0,  0,  5,  0,-10],
    [-10,  0,  5,  5,  5,  5,  5,-10],
    [ -5,  0,  5,  5,  5,  5,  0, -5],
    [  0,  0,  5,  5,  5,  5,  0, -5],
    [-10,  5,  5,  5,  5,  5,  0,-10],
    [-10,  0,  5,  0,  0,  0,  0,-10],
    [-20,-10,-10, -5, -5,-10,-10,-20]
]

# King (K = 1000) – Middlegame
king_table_mid = [
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-20,-30,-30,-40,-40,-30,-30,-20],
    [-10,-20,-20,-20,-20,-20,-20,-10],
    [ 20, 20,  0,  0,  0,  0, 20, 20],
    [ 20, 30, 10,  0,  0, 10, 30, 20]
]

# King (K = 1000) – Endgame
king_table_end = [
    [-50,-40,-30,-20,-20,-30,-40,-50],
    [-30,-20,-10,  0,  0,-10,-20,-30],
    [-30,-10, 20, 30, 30, 20,-10,-30],
    [-30,-10, 30, 40, 40, 30,-10,-30],
    [-30,-10, 30, 40, 40, 30,-10,-30],
    [-30,-10, 20, 30, 30, 20,-10,-30],
    [-30,-30,  0,  0,  0,  0,-30,-30],
    [-50,-30,-30,-30,-30,-30,-30,-50]
]

king_scores = [[0]*8 for _ in range(8)]
for r in range(8):
    for c in range(8):
        king_scores[r][c]= king_table_end[r][c]+ king_table_end[r][c]
for r in range(4):
    for c in range(8):
        pawn_table[r][c],pawn_table[7-r][c]=pawn_table[r][c],pawn_table[7-r][c]
    



peice_position_scores = {'N':knight_table,'K':king_scores,'N':knight_table,'B':bishop_table,'Q':queen_table,'p':pawn_table,'R':rook_table}

'''

use openings 

use numpy and better board representation

better use p.q or something like that

transposition tables 

save the evaluation  zobra hash 

add which moves it is stoping 

add attacking and defensive 

we can teach end game theory 

if apeice is attacked try to move that first 

storing  the data of moves not to recalculate 

'''





def random_move(valid_moves):
    ind=random.randint(0,len(valid_moves)-1)
    return valid_moves[ind]


## checking for greedy 
# for all moves check where i can have more peices/value 
#but we also need to score_material the next move so that the best possible comes out 


#greedy algorithim and try to get better position 
#score material on board
#assume black playing ai and check mate is worst
# go for level 2 
# we want to minize the maximum of opponent score 


def find_best_move_non_recursion(gs,valid_moves):
    turn = 1 if gs.whiteToMove else -1
    opponent_min_max_score=  CHECKMATE  #smallest of their maximums
    best_player_move = None
    random.shuffle(valid_moves)
    for  player_move in valid_moves:
        gs.make_move(player_move)
        opponent_moves = gs.get_valid_moves()
        if gs.check_mate:
                    opponent_max_score = -CHECKMATE
        elif gs.steale_mate:
                    opponent_max_score=STALEMATE
        else:
            opponent_max_score = -CHECKMATE
            random.shuffle(opponent_moves)
            for opponent_move in opponent_moves:
                gs.make_move(opponent_move)
                gs.get_valid_move()
                if gs.check_mate:
                        score =  CHECKMATE
                elif gs.steale_mate:
                        score=STALEMATE
                else:
                        score = -turn * score_material(gs.board)
                    
                if (score>opponent_max_score):
                        opponent_max_score=score   # try to find best move for opponent
                        
                gs.undo_move()
        if opponent_min_max_score> opponent_max_score:
            opponent_min_max_score = opponent_max_score # try to find best move for u which is worst(best) move 
            best_move  = player_move  # my new best is least of  all opponent bests 
        
        gs.undo_move()
            
    return best_move
# solve this recursively
# prune the branches we do not need


'''

helper method for best method 

'''
def find_best_move(gs, valid_moves, return_queue):
    global count, best_moves
    count = 0
    score = find_move_nega_max_alpha_beta(
        gs, gs.get_valid_moves(), DEPTH, -2*CHECKMATE, 2*CHECKMATE, 1
    )

    print("Top moves:")
    for score, mv in best_moves:
        print(mv.get_chess_notation(), "score:", score)

    # pick a random move among top N
    chosen_move = random.choice(best_moves)[1]
    return_queue.put(chosen_move)
    


'''

find min max move  

'''
def find_move_min_max(gs,valid_moves,depth,whiteToMove):
    global next_move
    if depth == 0 :
        return score_material(gs)
    if whiteToMove:  #maximize score 
        max_score = - CHECKMATE
        for move in valid_moves:
            gs.make_move(move)
            next_moves = gs.get_valid_moves()
            score = find_move_min_max(gs,next_moves,depth-1,False)
            if score>max_score:
                max_score=score
                if depth == DEPTH :
                    next_move = move 
            gs.undo_move()
        return max_score
    else:
        min_score =  CHECKMATE
        for move in valid_moves:
            gs.make_move(move)
            next_moves = gs.get_valid_moves()
            score = find_move_min_max(gs,next_moves,depth-1,True)
            if score<min_score:
                min_score=score
                if depth == DEPTH :
                    next_move = move 
            gs.undo_move()
        return min_score
    

'''

combine if else to one

'''

def find_move_nega_max(gs,valid_moves,depth,turn):
    #always try to maximize but with multilier
    global next_move,count
    count +=1
    if depth == 0 :
        return turn *  score_material(gs)
    
    max_score = CHECKMATE
    for move in valid_moves:
            gs.make_move(move)
            next_moves = gs.get_valid_moves()
            score = -find_move_nega_max(gs,next_moves,depth-1,-1 * turn) #this is very important 
            if score>max_score:
                max_score=score
                if depth == DEPTH :
                    next_move = move 
            gs.undo_move()
    return max_score

'''

the alpha beta pruning 

remove branches that wont make any good 

also depends on scoring algorithim

also add positional scores 

need to control more squares and attack more squares 

alpha beta these are the maximum and minimum u can acheive  values overall



if max_score>alpha then max_score is alpha

if alpha>beta then prune that branch 

ugot best else where no need for it 

'''
# Killer moves: 2 per depth (ply)
killer_moves = {}

# History heuristic: success count
history_heuristic = {
    'w': [[0 for _ in range(8)] for _ in range(8)],
    'b': [[0 for _ in range(8)] for _ in range(8)]
}
def order_moves(gs, moves):
    scored_moves = []

    for move in moves:
        score=0
        score -= score_material(gs)

        # 1. Captures (MVV-LVA style)
        if move.is_capture:
            attacker = move.peice_moved[1]
            victim = move.peice_captured[1] if move.peice_captured[1]!='-' else '--'
            score += ( piece_score.get(victim, 0))*10 - piece_score.get(attacker, 0) 

        # 2. Checks (simulate move and test)
        gs.make_move(move)
        if gs.incheck:
            score += 80
        gs.undo_move()

        # 3. Promotions
        if move.is_pawn_promotion:
            score += 150 + piece_score['Q']*10

        # 4. Castling (good for king safety)
        if move.castle:
            score += 50

        # 5. 
        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,1),(-1,1),(1,-1)]:
            rr, cc = move.end_row + dr, move.end_col + dc
            if 0 <= rr < 8 and 0 <= cc < 8:
                piece = gs.board[rr][cc]
                if piece != "--" and piece[0] == move.peice_moved[0]:  # same color
                    score += 1 
        score += score_material(gs)
        scored_moves.append((score, move))

    # Sort by score descending
    scored_moves.sort(key=lambda x: x[0], reverse=True)
    return [m for _, m in scored_moves]

# def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
#     global count, next_move
#     count += 1  # counts all nodes visited

#     if depth == 0:
#         return turn * score_material(gs)

#     max_score = -CHECKMATE
#     valid_moves=order_moves(gs,valid_moves)
#     for move in valid_moves:
#         gs.make_move(move)
#         next_moves = gs.get_valid_moves()
#         score = -find_move_nega_max_alpha_beta(
#             gs, next_moves, depth - 1, -beta, -alpha, -turn
#         )
#         gs.undo_move()

#         if score > max_score:
#             max_score = score
#             if depth == DEPTH:
#                 next_move = move

#         alpha = max(alpha, max_score)
#         if alpha >= beta:
#             break

#     return max_score


TOP_N = 5  # number of best moves you want

def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn): 
    if depth == 0:
        return turn * score_material(gs)

    max_score = -CHECKMATE
    scored_moves = [] 

    # move ordering to improve pruning
    valid_moves = order_moves(gs, valid_moves)

    for move in valid_moves:
        gs.make_move(move)
        next_moves = gs.get_valid_moves()
        score = -find_move_nega_max_alpha_beta(
            gs, next_moves, depth - 1, -beta, -alpha, -turn
        )
        gs.undo_move()

        scored_moves.append((score, move))

        max_score = max(max_score, score)
        alpha = max(alpha, max_score)
        if alpha >= beta:
            break  # alpha-beta cutoff

    # Only save best moves at root depth
    if depth == DEPTH:
        scored_moves.sort(key=lambda x: x[0], reverse=True)
        best_moves = [(score, move) for score, move in scored_moves[:TOP_N]]

    return max_score


'''

score the board

positive score good for white 

a negative score good for black

increase the scoring function 

counting attacking and defending moves 

'''



def score_material(self):
    """Full evaluation of the board with material, positional, mobility, defense, etc."""

    if self.check_mate:
        if self.whiteToMove:
            return -CHECKMATE 
        else:
            return CHECKMATE   
    elif self.steale_mate:
        return STALEMATE

    board = self.board
    score = 0

    white_squares_controlled = set()
    black_squares_controlled = set()

    # Material, piece-square, and piece defense evaluation
    for r in range(8):
        for c in range(8):
            square = (r, c)
            piece_info = board[r][c]

            if piece_info == "--":
                continue

            color, piece = piece_info[0], piece_info[1]

            base_value = piece_score[piece]

            if color == 'w':
                # Material value
                score += base_value 

                
                score += peice_position_scores[piece][r][c]

                # 
                moves = self.move_functions[piece](r,c,[])
                for move in moves:
                    white_squares_controlled.add((move.end_row, move.end_col))

                    # Bonus for defending own piece
                    if board[move.end_row][move.end_col][0] == 'w':
                        defended_piece = board[move.end_row][move.end_col][1]
                        score += piece_score[defended_piece] 

                    # Bonus for killing enemy valuable piece
                    if board[move.end_row][move.end_col][0] == 'b':
                        victim = board[move.end_row][move.end_col][1]
                        score += piece_score[victim]  *10
            elif color == 'b':
                score -= base_value 
                score -= peice_position_scores[piece][7 - r][c]

                moves = self.move_functions[piece](r,c,[])
                for move in moves:
                    black_squares_controlled.add((move.end_row, move.end_col))

                    # Defense bonus
                    if board[move.end_row][move.end_col][0] == 'b':
                        defended_piece = board[move.end_row][move.end_col][1]
                        score -= piece_score[defended_piece] 

                    # Killing enemy valuable piece
                    if board[move.end_row][move.end_col][0] == 'w':
                        victim = board[move.end_row][move.end_col][1]
                        score -= piece_score[victim] *10


    # Bishop pair bonus
    white_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'wB')
    black_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'bB')
    if white_bishops >= 2:
        score += 50
    if black_bishops >= 2:
        score -= 50

    # King safety (penalize exposed kings)
    score += self.king_safety( "w") - self.king_safety("b")
    score += (len(white_squares_controlled) - len(black_squares_controlled))*5
   

    return score

def get_best_n_moves(gs, n=5):
    """

    Returns best n moves for both White and Black.

    """
    best_white, best_black = [], []

    # White to move
    if gs.whiteToMove:
        moves = gs.get_valid_moves()
        scored = []
        for move in moves:
            gs.make_move(move)
            score = -find_move_nega_max_alpha_beta(
                gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, -1
            )
            gs.undo_move()
            scored.append((score, str(move)))
        scored.sort(key=lambda x: x[0], reverse=True)
        best_white = scored[:n]

    # Black to move
    else:
        moves = gs.get_valid_moves()
        scored = []
        for move in moves:
            gs.make_move(move)
            score = -find_move_nega_max_alpha_beta(
                gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, 1
            )
            gs.undo_move()
            scored.append((score, str(move)))
        scored.sort(key=lambda x: x[0], reverse=True)
        best_black = scored[:n]
    return best_white if best_white else best_black