import tkinter as tk

from tkinter import messagebox

class Gobang:

    def __init__(self):

        self.window = tk.Tk()

        self.window.title("五子棋(AI对战版)")

        

        # 初始化棋盘参数

        self.board_size = 15

        self.cell_size = 40

        self.board_margin = 80

        

        # 创建画布

        canvas_size = self.board_size * self.cell_size + self.board_margin + 30

        self.canvas = tk.Canvas(self.window, width=canvas_size, height=canvas_size)

        self.canvas.pack(padx=10, pady=10)

        

        try:

            # 加载背景图片

            self.bg_image = tk.PhotoImage(file="./background.png")

            # 显示背景图片

            self.canvas.create_image(canvas_size//2, canvas_size//2,

                                   image=self.bg_image)

        except Exception as e:

            print("背景图片加载失败:", e)

            # 如果加载图片失败,使用纯色背景

            self.canvas.create_rectangle(0, 0, canvas_size, canvas_size,

                                      fill='#CDBA96', outline='#CDBA96')

        

        # 添加悔棋次数限制

        self.undo_remaining = 10

        

        # 添加悔棋按钮

        button_x = self.board_margin + (self.board_size-1) * self.cell_size / 2

        button_y = self.board_margin - 40

        

        # 添加重新开始按钮

        self.restart_button = tk.Button(self.window, text="重新开始", command=self.reset_game,

                                      font=('Arial', 14, 'bold'),

                                      fg='black',

                                      bg='#CDBA96',

                                      activebackground='#BDB088',

                                      relief='flat')

        self.restart_button.place(x=button_x - 120, y=button_y - 10)

        

        # 绑定重新开始按钮的鼠标事件

        self.restart_button.bind('<Enter>', lambda e: e.widget.configure(bg='#BDB088'))

        self.restart_button.bind('<Leave>', lambda e: e.widget.configure(bg='#CDBA96'))

        

        # 悔棋按钮

        self.undo_button = tk.Button(self.window, text=f"悔棋(剩余{self.undo_remaining}次)", command=self.undo_move,

                                   font=('Arial', 14, 'bold'),

                                   fg='black',

                                   bg='#CDBA96',

                                   activebackground='#BDB088',

                                   relief='flat')

        self.undo_button.place(x=button_x - 20, y=button_y - 10)

        

        # 绑定鼠标事件来实现透明效果

        self.undo_button.bind('<Enter>', lambda e: e.widget.configure(bg='#BDB088'))

        self.undo_button.bind('<Leave>', lambda e: e.widget.configure(bg='#CDBA96'))

        

        # 添加历史记录

        self.move_history = []

        

        # 初始化棋盘数据

        self.board = [[0] * self.board_size for _ in range(self.board_size)]

        self.current_player = 1  # 1为玩家(黑子),2为电脑(白子)

        

        # 绘制棋盘

        self.draw_board()

        

        # 绑定鼠标点击事件

        self.canvas.bind('<Button-1>', self.handle_click)

        

    def draw_board(self):

        # 删除当前执子显示,直接开始绘制横线和坐标

        for i in range(self.board_size):

            y = self.board_margin + i * self.cell_size

            # 绘制横线

            self.canvas.create_line(self.board_margin, y,

                                  self.board_margin + (self.board_size-1) * self.cell_size, y,

                                  width=2.0, fill='#000000')  # 加粗线条

            # 添加纵坐标标记(A-O)

            self.canvas.create_text(self.board_margin - 20, y,

                                  text=chr(65 + i), font=('Arial', 11))

        

        # 绘制竖线和坐标

        for i in range(self.board_size):

            x = self.board_margin + i * self.cell_size

            # 绘制竖线

            self.canvas.create_line(x, self.board_margin,

                                  x, self.board_margin + (self.board_size-1) * self.cell_size,

                                  width=2.0, fill='#000000')  # 加粗线条

            # 添加横坐标标记(1-15)

            self.canvas.create_text(x, self.board_margin - 20,

                                  text=str(i + 1), font=('Arial', 11))

    def place_piece(self, x, y):

        # 记录这步棋

        self.move_history.append((x, y, self.current_player))

        

        # 在棋盘数据中记录

        self.board[y][x] = self.current_player

        

        piece_x = self.board_margin + x * self.cell_size

        piece_y = self.board_margin + y * self.cell_size

        

        # 绘制阴影

        shadow_offset = 2

        self.canvas.create_oval(piece_x-15+shadow_offset, piece_y-15+shadow_offset,

                              piece_x+15+shadow_offset, piece_y+15+shadow_offset,

                              fill='#666666', outline='#666666')

        

        # 设置棋子基本属性

        piece_color = 'black' if self.current_player == 1 else '#FFFFFF'

        gradient_color = '#444444' if self.current_player == 1 else '#e0e0e0'

        outline_color = 'black'

        

        # 主棋子

        self.canvas.create_oval(piece_x-15, piece_y-15,

                              piece_x+15, piece_y+15,

                              fill=piece_color, outline=outline_color)

        

        # 渐变效果

        self.canvas.create_oval(piece_x-12, piece_y-12,

                              piece_x+8, piece_y+8,

                              fill=gradient_color, stipple='gray50')

        

        # 高光效果

        self.canvas.create_oval(piece_x-10, piece_y-10,

                              piece_x-4, piece_y-4,

                              fill='white', stipple='gray75')

        

        # 添加渐变效果

        if self.current_player == 1:  # 黑棋

            gradient_color = '#444444'

            gradient_pos = 8  # 渐变位置

        else:  # 白棋

            gradient_color = '#e0e0e0'

            gradient_pos = 8  # 调整白棋渐变位置

            

        self.canvas.create_oval(piece_x-12, piece_y-12,

                              piece_x+gradient_pos, piece_y+gradient_pos,

                              fill=gradient_color, stipple='gray50')

        

        # 添加高光效果

        if self.current_player == 1:  # 黑棋

            shine_size = 6

            shine_pos = -10

        else:  # 白棋

            shine_size = 6

            shine_pos = -10

            

        self.canvas.create_oval(piece_x+shine_pos, piece_y+shine_pos,

                              piece_x+shine_pos+shine_size, piece_y+shine_pos+shine_size,

                              fill='white', stipple='gray75')

    

    def redraw_all_pieces(self):

        # 重新绘制所有已放置的棋子

        for y in range(self.board_size):

            for x in range(self.board_size):

                if self.board[y][x] != 0:

                    # 临时保存当前玩家

                    original_player = self.current_player

                    # 设置为对应的玩家

                    self.current_player = self.board[y][x]

                    # 绘制棋子

                    piece_x = self.board_margin + x * self.cell_size

                    piece_y = self.board_margin + y * self.cell_size

                    

                    # 绘制阴影

                    shadow_offset = 2

                    self.canvas.create_oval(piece_x-15+shadow_offset, piece_y-15+shadow_offset,

                                          piece_x+15+shadow_offset, piece_y+15+shadow_offset,

                                          fill='#666666', outline='#666666')

                    

                    # 设置棋子颜色

                    piece_color = 'black' if self.board[y][x] == 1 else '#FFFFFF'

                    gradient_color = '#444444' if self.board[y][x] == 1 else '#e0e0e0'

                    

                    # 主棋子

                    self.canvas.create_oval(piece_x-15, piece_y-15,

                                          piece_x+15, piece_y+15,

                                          fill=piece_color, outline='black')

                    

                    # 渐变效果

                    self.canvas.create_oval(piece_x-12, piece_y-12,

                                          piece_x+8, piece_y+8,

                                          fill=gradient_color, stipple='gray50')

                    

                    # 高光效果

                    self.canvas.create_oval(piece_x-10, piece_y-10,

                                          piece_x-4, piece_y-4,

                                          fill='white', stipple='gray75')

                    

                    # 恢复当前玩家

                    self.current_player = original_player

    def get_ai_move(self):

        # 简单AI:评估每个位置的分数,选择最优位置

        best_score = float('-inf')

        best_move = None

        

        for y in range(self.board_size):

            for x in range(self.board_size):

                if self.board[y][x] == 0:  # 空位置

                    score = self.evaluate_position(x, y)

                    if score > best_score:

                        best_score = score

                        best_move = (x, y)

        

        return best_move

    def evaluate_position(self, x, y):

        # 评估某个位置的分数

        score = 0

        directions = [(1,0), (0,1), (1,1), (1,-1)]

        

        for dx, dy in directions:

            # 计算AI落子后的连子数

            count_ai = 1

            # 正向检查

            temp_x, temp_y = x + dx, y + dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == 2:  # AI的子

                    count_ai += 1

                    temp_x += dx

                    temp_y += dy

                else:

                    break

                    

            # 反向检查

            temp_x, temp_y = x - dx, y - dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == 2:

                    count_ai += 1

                    temp_x -= dx

                    temp_y -= dy

                else:

                    break

            

            # 计算玩家在该位置的连子数(防守分数)

            count_player = 1

            # 正向检查

            temp_x, temp_y = x + dx, y + dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == 1:  # 玩家的子

                    count_player += 1

                    temp_x += dx

                    temp_y += dy

                else:

                    break

                    

            # 反向检查

            temp_x, temp_y = x - dx, y - dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == 1:

                    count_player += 1

                    temp_x -= dx

                    temp_y -= dy

                else:

                    break

            

            # 评分规则

            if count_ai >= 5:

                score += 100000  # 必胜

            elif count_ai == 4:

                score += 10000

            elif count_ai == 3:

                score += 1000

            elif count_ai == 2:

                score += 100

               

            if count_player >= 5:

                score += 50000  # 必须防守

            elif count_player == 4:

                score += 5000

            elif count_player == 3:

                score += 500

            elif count_player == 2:

                score += 50

        

        return score

    def handle_click(self, event):

        if self.current_player != 1:  # 如果不是玩家回合,直接返回

            return

            

        # 将点击位置转换为棋盘坐标

        x = round((event.x - self.board_margin) / self.cell_size)

        y = round((event.y - self.board_margin) / self.cell_size)

        

        # 检查是否在有效范围内

        if 0 <= x < self.board_size and 0 <= y < self.board_size:

            if self.board[y][x] == 0:  # 如果该位置没有棋子

                # 玩家落子

                self.place_piece(x, y)

                if self.check_win(x, y):

                    self.window.update()

                    messagebox.showinfo("游戏结束", "玩家获胜!")

                    self.reset_game()

                    return

               

                # 切换到AI回合

                self.current_player = 2

               

                # AI落子

                ai_x, ai_y = self.get_ai_move()

                if ai_x is not None and ai_y is not None:

                    self.place_piece(ai_x, ai_y)

                    if self.check_win(ai_x, ai_y):

                        self.window.update()

                        messagebox.showinfo("游戏结束", "电脑AI获胜!")

                        self.reset_game()

                        return

                    

                    # 切换回玩家回合

                    self.current_player = 1

                    self.redraw_board_and_pieces()

    def redraw_board_and_pieces(self):

        # 重绘棋盘和所有棋子

        self.canvas.delete("all")

        canvas_size = self.board_size * self.cell_size + self.board_margin + 30

        try:

            self.canvas.create_image(canvas_size//2, canvas_size//2,

                                   image=self.bg_image)

        except:

            self.canvas.create_rectangle(0, 0, canvas_size, canvas_size,

                                      fill='#CDBA96', outline='#CDBA96')

        

        # 更新悔棋按钮位置和文字

        button_x = self.board_margin + (self.board_size-1) * self.cell_size / 2

        button_y = self.board_margin - 40

        self.undo_button.place(x=button_x - 20, y=button_y - 10)

        self.undo_button.config(text=f"悔棋(剩余{self.undo_remaining}次)")

        

        self.draw_board()

        self.redraw_all_pieces()

    def check_win(self, x, y):

        directions = [(1,0), (0,1), (1,1), (1,-1)]  # 横、竖、右斜、左斜

        for dx, dy in directions:

            count = 1

            winning_pieces = [(x, y)]  # 记录获胜棋子的位置

            

            # 正向检查

            temp_x, temp_y = x + dx, y + dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == self.current_player:

                    count += 1

                    winning_pieces.append((temp_x, temp_y))

                    temp_x += dx

                    temp_y += dy

                else:

                    break

            

            # 反向检查

            temp_x, temp_y = x - dx, y - dy

            while 0 <= temp_x < self.board_size and 0 <= temp_y < self.board_size:

                if self.board[temp_y][temp_x] == self.current_player:

                    count += 1

                    winning_pieces.append((temp_x, temp_y))

                    temp_x -= dx

                    temp_y -= dy

                else:

                    break

            

            if count >= 5:

                self.highlight_winning_pieces(winning_pieces)

                return True

        return False

    def highlight_winning_pieces(self, positions):

        for x, y in positions:

            piece_x = self.board_margin + x * self.cell_size

            piece_y = self.board_margin + y * self.cell_size

            

            # 绘制高亮边框

            self.canvas.create_oval(piece_x-16, piece_y-16,

                                  piece_x+16, piece_y+16,

                                  outline='red',

                                  width=2)

    

    def undo_move(self):

        if self.undo_remaining <= 0:

            messagebox.showinfo("提示", "悔棋次数已用完!")

            return

            

        if len(self.move_history) >= 2:

            self.undo_remaining -= 1

            # 更新按钮文字

            self.undo_button.config(text=f"悔棋(剩余{self.undo_remaining}次)")

            

            # 移除最近的两步棋

            self.move_history.pop()  # 移除AI的落子

            self.move_history.pop()  # 移除玩家的落子

            

            # 重置棋盘

            self.board = [[0] * self.board_size for _ in range(self.board_size)]

            

            # 重新绘制棋盘

            self.redraw_board_and_pieces()

            

            # 重新执行所有历史移动

            current_player_backup = self.current_player

            for x, y, player in self.move_history:

                self.current_player = player

                self.board[y][x] = player

            self.current_player = current_player_backup

            

            # 重新绘制

            self.redraw_board_and_pieces()

    def reset_game(self):

        self.move_history = []  # 清空历史记录

        # 清空棋盘数据

        self.board = [[0] * self.board_size for _ in range(self.board_size)]

        # 清空画布

        self.canvas.delete("all")

        

        # 重新绘制背景

        canvas_size = self.board_size * self.cell_size + self.board_margin + 30

        try:

            # 重新显示背景图片

            self.canvas.create_image(canvas_size//2, canvas_size//2,

                                   image=self.bg_image)

        except:

            # 如果加载图片失败,使用纯色背景

            self.canvas.create_rectangle(0, 0, canvas_size, canvas_size,

                                      fill='#CDBA96', outline='#CDBA96')

        

        # 重新绘制棋盘

        self.draw_board()

        # 重置悔棋次数

        self.undo_remaining = 10

        self.undo_button.config(text=f"悔棋(剩余{self.undo_remaining}次)")

        

        # 重新绘制棋盘

        self.draw_board()

        # 重置当前玩家

        self.current_player = 1

    

    def run(self):

        self.window.mainloop()

if __name__ == "__main__":

    game = Gobang()

    game.run()

玩家为黑子,电脑AI为白子,支持玩家悔棋,现在可以做为单机娱乐的小游戏了玩了。