branch: elpa/aidermacs commit 846f283f83999556a7eee59dcc71736db605d3fb Author: Kang Tu <tni...@gmail.com> Commit: GitHub <nore...@github.com>
doc: Add hard mode for battleship game using o3-mini high model (#70) * feat: Add aider-implement-todo function to implement TODOs in current context * feat: Add `aider-implement-todo` function description to README * docs(readme): update aider command description * feat: Add HardComputerPlayer class with probability density strategy feat: Implement hard mode for computer player using probability density strategy feat: Add computer_vs_hard method and corresponding unit test feat: Enhance probability grid with ship size weighting and adjacent bonuses feat: Add normal_first option to computer_vs_hard in battleship game feat: enhance HardComputerPlayer AI with hunt-target modes and probability optimization test: Add simulation to compare normal vs hard AI win rates feat: enhance HardComputerPlayer AI with adaptive learning and improved targeting feat: add quantum-inspired algorithms and advanced tactics to HardComputerPlayer refactor(game): simplify hard computer player with probability density strategy * chore: Translate Chinese comments to English in battleship examples chore: Replace Chinese comments with English in ComputerPlayer class * refactor: move decodeLoc method from HardComputerPlayer to HumanPlayer * feat(keybindings): add implement requirement menu option * docs(readme): clarify aider-implement-todo behavior and scope * docs(readme): update installation dependency formatting * docs: clarify package-vc-install as emacs built-in * docs: add emphasis to `aider-implement-todo` in README --------- Co-authored-by: Kang Tu <kang...@apple.com> --- README.org | 10 ++-- aider-doom.el | 1 + aider.el | 2 +- examples/battleship/fleet.py | 2 +- examples/battleship/game.py | 114 ++++++++++++++++++++++++++++++++++----- examples/battleship/test_game.py | 29 +++++++++- 6 files changed, 138 insertions(+), 20 deletions(-) diff --git a/README.org b/README.org index 9544b60b1a..80b1c1d885 100644 --- a/README.org +++ b/README.org @@ -43,8 +43,8 @@ When being called with the universal argument (`C-u`), a prompt will offer the u *** Write code: - (`aider-function-or-region-refactor`): If a region is selected, ask Aider to refactor the selected region. Otherwise, ask Aider to change / refactor the function under the cursor. - - (`aider-implement-todo`): Implement TODO comments in current context. - - If cursor is on a comment line, implement that specific comment. + - *(`aider-implement-todo`): Implement requirement in comments in-place, in current context.* + - If cursor is on a comment line, implement that specific comment in-place. - If cursor is inside a function, implement TODOs for that function. - Otherwise implement TODOs for the entire current file. @@ -66,8 +66,7 @@ You can add your own Elisp functions to support your specific use cases. Feel fr ** Vanilla Emacs Installation - [[https://aider.chat/docs/install.html][Install aider]] -- Install the dependency [[https://github.com/magit/transient][Transient]] using your package manager. -- Install the dependency [[https://github.com/magit/magit][Magit]] using your package manager. +- Install the emacs dependency library [[https://github.com/magit/transient][Transient]], and [[https://github.com/magit/magit][Magit]] using your package manager. - Install aider.el with the following code: *** With [[https://github.com/radian-software/straight.el?tab=readme-ov-file][Straight]] @@ -91,7 +90,8 @@ If you have Straight installed ;; Optional: Set a key binding for the transient menu (global-set-key (kbd "C-c a") 'aider-transient-menu)) #+END_SRC -*** With [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Fetching-Package-Sources.html#:~:text=One%20way%20to%20do%20this,just%20like%20any%20other%20package.][package-vc-install]] + +*** With [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Fetching-Package-Sources.html#:~:text=One%20way%20to%20do%20this,just%20like%20any%20other%20package.][package-vc-install]] (emacs built-in) Install Aider.el by running the following code within Emacs #+BEGIN_SRC emacs-lisp (package-vc-install '(aider :url "https://github.com/tninja/aider.el")) diff --git a/aider-doom.el b/aider-doom.el index 35a1a0c205..be8db18fc3 100644 --- a/aider-doom.el +++ b/aider-doom.el @@ -37,6 +37,7 @@ :desc "Architecture" "d" #'aider-architect-discussion :desc "Change" "c" #'aider-code-change :desc "Refactor Function or Region" "r" #'aider-function-or-region-refactor + :desc "Implement Requirement in-place" "i" #'aider-implement-todo :desc "Undo change" "u" #'aider-undo-last-change :desc "Show last commit" "g" #'aider-magit-show-last-commit ) diff --git a/aider.el b/aider.el index 066e84ab75..0654ed9e50 100644 --- a/aider.el +++ b/aider.el @@ -140,7 +140,7 @@ Affects the system message too.") ("r" "Refactor Function or Region" aider-function-or-region-refactor) ("U" "Write Unit Test" aider-write-unit-test) ("T" "Fix Failing Test Under Cursor" aider-fix-failing-test-under-cursor) - ("i" "Implement TODOs" aider-implement-todo) + ("i" "Implement Requirement in-place" aider-implement-todo) ("m" "Show Last Commit with Magit" aider-magit-show-last-commit) ("u" "Undo Last Change" aider-undo-last-change) ] diff --git a/examples/battleship/fleet.py b/examples/battleship/fleet.py index eb5d4f1a03..ff1c044c04 100644 --- a/examples/battleship/fleet.py +++ b/examples/battleship/fleet.py @@ -57,5 +57,5 @@ class FleetGroup: def show(self): for fleet in self.fleets: fleet.show() - print() # 添加一个空行以分隔不同的舰队 + print() # add an empty line to separate fleets diff --git a/examples/battleship/game.py b/examples/battleship/game.py index f8d0151f8d..d6e3164498 100644 --- a/examples/battleship/game.py +++ b/examples/battleship/game.py @@ -170,19 +170,19 @@ class ComputerPlayer(Player): board_size = self.board_size hit_history = self.hit_history - # 获取未击沉的船只大小列表 + # get sizes of unsunk ships remaining_sizes = [] for fleet in player.fleet_group.fleets: if not fleet.is_sunk(): remaining_sizes.append(fleet.size) - # 找出所有连续命中但未击沉的点 + # get all consecutive hit points that are not sunk hits_in_progress = [] hits_only = [(x, y) for x, y, hit in hit_history if hit] - # 分析连续命中点的方向 + # Analyze the orientation of consecutive hit points for x, y in hits_only: - # 检查是否已经是已击沉的船只的一部分 + # check if this hit is part of a sunk ship is_sunk = False for fleet in player.fleet_group.fleets: if fleet.is_sunk() and (x, y) in fleet.coordinates: @@ -192,38 +192,38 @@ class ComputerPlayer(Player): hits_in_progress.append((x, y)) if hits_in_progress: - # 如果有连续命中点,分析可能的方向 + # If there are multiple consecutive hit points, analyze possible orientation if len(hits_in_progress) > 1: - # 确定方向 + # Determine the orientation points = sorted(hits_in_progress) - if points[0][0] == points[1][0]: # 水平方向 + if points[0][0] == points[1][0]: # horizontal direction direction = 'horizontal' x = points[0][0] possible_y = [points[0][1]-1, points[-1][1]+1] candidates = [(x, y) for y in possible_y if 0 <= y < board_size[1]] - else: # 垂直方向 + else: # vertical direction direction = 'vertical' y = points[0][1] possible_x = [points[0][0]-1, points[-1][0]+1] candidates = [(x, y) for x in possible_x if 0 <= x < board_size[0]] else: - # 单个命中点,尝试四个方向 + # For a single hit point, try all four directions x, y = hits_in_progress[0] candidates = [(x-1,y), (x+1,y), (x,y-1), (x,y+1)] candidates = [(x,y) for x,y in candidates if 0 <= x < board_size[0] and 0 <= y < board_size[1]] - # 过滤掉已经打击过的位置 + # Filter out positions that have already been targeted candidates = [(x,y) for x,y in candidates if (x,y,True) not in hit_history and (x,y,False) not in hit_history] if candidates: x, y = random.choice(candidates) else: - # 如果没有合适的相邻点,随机选择 + # If no valid adjacent cell is available, choose randomly x, y = self._random_shot(board_size, hit_history) else: - # 没有进行中的命中,使用概率分布选择 + # If there are no ongoing hit sequences, use a random shot x, y = self._random_shot(board_size, hit_history) hit_result = self.hit(player, x, y) @@ -248,6 +248,82 @@ class ComputerPlayer(Player): return random.choice(possible_hits) +class HardComputerPlayer(ComputerPlayer): + """ + Hard mode: use probability density strategy for selecting the best move + """ + + def input_and_hit(self, player): + # Get board size and hit history + board_size = self.board_size + hit_history = self.hit_history + + # get lengths of all opponent's unsunk ships + remaining_ships = [fleet.size for fleet in player.fleet_group.fleets if not fleet.is_sunk()] + + # initialize probability grid with zeros + prob_grid = [[0] * board_size[1] for _ in range(board_size[0])] + + # record shot coordinates; elements in hit_history are tuples (x, y, bool) + shot_cells = {(x, y): True for x, y, _ in hit_history} + + # 对于每个剩余舰船的长度,尝试水平和垂直的放置,并累加概率分值 + for size in remaining_ships: + for x in range(board_size[0]): + for y in range(board_size[1]): + # horizontal placement: check if cells from (x, y) to (x, y+size-1) are within boundaries and not shot + if y + size <= board_size[1]: + if all((x, y + offset) not in shot_cells for offset in range(size)): + for offset in range(size): + prob_grid[x][y + offset] += size # Increase weight based on ship size + + # vertical placement: check if cells from (x, y) to (x+size-1, y) are within boundaries and not shot + if x + size <= board_size[0]: + if all((x + offset, y) not in shot_cells for offset in range(size)): + for offset in range(size): + prob_grid[x + offset][y] += size # Increase weight based on ship size + + # Add bonus for cells adjacent to unsunk hit cells + bonus = 3 # Bonus weight for adjacent cell + for x, y, hit in hit_history: + if hit: # cell was a hit + # Check if this hit cell belongs to an unsunk fleet + unsunk = False + for fleet in player.fleet_group.fleets: + if (x, y) in fleet.coordinates and not fleet.is_sunk(): + unsunk = True + break + if unsunk: + for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + nx, ny = x + dx, y + dy + if 0 <= nx < board_size[0] and 0 <= ny < board_size[1] and (nx, ny) not in shot_cells: + prob_grid[nx][ny] += bonus # Boost adjacent cells near successful hit + max_prob = -1 + candidates = [] + for x in range(board_size[0]): + for y in range(board_size[1]): + if (x, y) not in shot_cells: + cell_prob = prob_grid[x][y] + if cell_prob > max_prob: + max_prob = cell_prob + candidates = [(x, y)] + elif cell_prob == max_prob: + candidates.append((x, y)) + + import random + if candidates: + x, y = random.choice(candidates) + else: + # 如果没有候选,退回到父类的随机策略 + x, y = self._random_shot(board_size, hit_history) + + hit_result = self.hit(player, x, y) + self.log_hit(x, y, hit_result) + print(f"{player.name}'s board: ") + player.show_myself() + print("=====================================") + return hit_result + class Game: def __init__(self, fleet_names, board_size): @@ -283,6 +359,20 @@ class Game: self.add_players(player1, player2) self.run() + def computer_vs_hard(self, normal_first=True): + """ + # Start a game with ComputerPlayer vs HardComputerPlayer + """ + player1 = ComputerPlayer("Normal CPU", self.board_size) + player2 = HardComputerPlayer("Hard CPU", self.board_size) + player1.random_init(self.fleet_names) + player2.random_init(self.fleet_names) + if normal_first: + self.add_players(player1, player2) + else: + self.add_players(player2, player1) + self.run() + if __name__ == '__main__': game = Game(['carrier', 'battleship', 'cruiser', 'submarine', 'destroyer' diff --git a/examples/battleship/test_game.py b/examples/battleship/test_game.py index 6e46f9ec2b..bca9e1ae87 100644 --- a/examples/battleship/test_game.py +++ b/examples/battleship/test_game.py @@ -1,7 +1,7 @@ import unittest -from game import Player +from game import Player, Game, ComputerPlayer, HardComputerPlayer class TestPlayer(unittest.TestCase): @@ -13,3 +13,30 @@ class TestPlayer(unittest.TestCase): print() player.show_enemy() # player.fleet_group.show() + + def test_computer_vs_hard(self): + """ + # Unit test for battle between ComputerPlayer and HardComputerPlayer + """ + fleet_names = ['carrier', 'battleship', 'cruiser', 'submarine', 'destroyer'] + board_size = (10, 10) + normal_wins = hard_wins = 0 + for i in range(100): + game = Game(fleet_names, board_size) + if i % 2 == 0: + game.computer_vs_hard(normal_first=True) + if game.players[0].all_sunk(): + normal_wins += 1 + if game.players[1].all_sunk(): + hard_wins += 1 + else: + game.computer_vs_hard(normal_first=False) + if game.players[0].all_sunk(): + hard_wins += 1 + if game.players[1].all_sunk(): + normal_wins += 1 + print(f"Normal wins: {normal_wins}") + print(f"Hard wins: {hard_wins}") + +# Normal wins: 31 +# Hard wins: 69