Yesterday I got my atomic chess bot to execute random legal moves. Today I made it choose moves with some intelligence. First I wrote an evaluation function that returns a number describing which player is favored in the position and by how much. My first version assigned very large numbers for king destruction or checkmate, or the material balance otherwise. Then I wrote a function tasked with selecting the best move in the current position. It does a breadth-first search on the tree of possible moves, bubbling the best variation up the tree as new nodes are evaluated.
That kind of worked in that it would blow up the opponent's king when possible, but it was extremely slow and vulnerable to common traps like 1. Nf3. Using a profiler, I found that a lot of time was spend just copying the board, so I switched to a list comprehension instead of deepcopy. That improved the nodes-per-time markedly, but the overall evaluations were still poor. The bug was in the bubbling up: once a child node's value was lowered (due to finding a better response), its parent's value was not. So the bot was effectively playing hope chess.
Fixing that made the evaluation numbers more reasonable, but the bot still fell for the opening traps because it couldn't see far enough ahead in time. Even with 20 seconds per move it only evaluated 3,000 positions. Looking at the profiler again, I saw that generating the list of legal moves is dramatically more expensive than evaluating a move. My search was deferring evaluation of newly discovered responses until it could enumerate their counter-responses, but that lets many possibilities go to waste. So I rearranged the logic so that responses are evaluated as soon as they're generated. That improved nodes per time by an order of magnitude!
But even so, obvious knight traps still tended to work on the bot, and it often made pointless moves (like moving an undeveloped rook back and forth). I made the evaluation function more sophisticated by slightly increasing the score of pawns as they moved toward promotion, which I hoped would also encourage the bot to push 1. ...f6 to stop the knight trap. Then, more interestingly, I increased the score of pieces that are attacking opposing pieces by a tenth of a pawn per attacked piece. That finally made the bot worry enough about the knight to dodge the trap. Around this point, it was able to defeat a couple humans! I further refined the evaluation function by counting king-explosion-threatening pieces as an extra third of a pawn, which encourages the bot to make threats.
Along the way, I added a couple features to the bot's FICS interface. It greets its opponent when the game starts, stating that it's an atomic game and explaining how to cancel the game if desired. (Other variant bots do this too.) At the end of the game, it congratulates its opponent or says "thanks for playing!" depending on the game's result. If the opponent doesn't make their first move within one minute, the bot cancels the game (to avoid accidental denial of service). Currently, just for testing, the bot states its depth, positions considered, and overall evaluation at each move.
Its main downfall now is time management. It's completely unaware of the clock, so it usually forfeits on time in short games. I'll work on that tomorrow.
No comments:
Post a Comment