鱼C论坛

 找回密码
 立即注册
查看: 2278|回复: 3

[技术交流] Python五子棋,第一版(弱智电脑)

[复制链接]
发表于 2021-2-16 21:45:30 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Python五子棋


version:1.0
  • 功能介绍:基本实现五子棋功能,判定棋型,给予得分,因为评分机制,导致防御还可以,进攻就和弱智差不多了
  • Model.py>>>主要实现的基本功能都在这里
  • checkerboard.py>>>和界面UI的交互,待下一版实现
  • cmd_board>>>测试用,cmd的打印界面
  • setting.py>>>全局变量设置的保存


version:1.1准备实现alpha_beta剪枝搜索,有大佬会的吗?

# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ version: ??
@ author: Alex
@ file: Model
@ datetime: 2021/02/09 - 14:08
@ explain:
"""
from setting import SIZE, ChessType, COMPUTER, PLAYER, VECTOR
from CoreCode.tools import LogHandler

from typing import List, Set, Tuple
from itertools import chain

# TODO =============== 数据类型声明 =============
# 坐标着点 x
X: int = int()
# 坐标着点 y
Y: int = int()
# 坐标
Point: tuple = (X, Y)


class GoBangBase:
    """棋盘数据和得分规则管理
        GoBangData实现
                对落子的存储,采用的hash字典存储 self[x,y] = Computer or Player
            ::data -> 五子棋的数据储存
            ::one_spot -> 待搜索的点
                所有已落子的八个方向,延伸5个点位。不包括已落子和超出边界的子

            def _result(self, p) -> List[str]:
                获取 p 点四个方向(xy轴及左右斜轴)的落子数据,拼接成字符串返回

            def get_xy_score(self, spot:List[int], flag: str) -> int:
                获取 spot 落点的具体评分

            def update_one(self, p: List[int]) -> Set[tuple]:
                获得p落点周围八个方位总计40个点,排除已落子和超出边界的点

            def evaluation(self) -> Tuple[int, Point]:
                对当前局面进行评价,获得敌我双方最大的收益点。

    """
    data = dict().fromkeys(range(SIZE * SIZE), "0")
    one_spot: set = set()
    vector = chain.from_iterable(list(chain.from_iterable(VECTOR)))

    @staticmethod
    def key(x: X, y: Y): return x * SIZE + y

    # TODO 筛选超出边界的点
    def f(self, p: Point):
        """
        对着点的筛选,A:没有超出边界. B:没有进行落子. C:不在待搜索的列表
        :param p:
        :return:
        """
        if -1 < p[0] < SIZE and -1 < p[1] < SIZE:
            if p in self and p not in self.one_spot:
                return True
        return False

    def __getitem__(self, key: Point) -> str:
        """
        :param key: List[int, int] -> [x, y]  落点的坐标
        :return: 落点的数据 '0'=空白 '1' or '2' 已经落子
        """
        if -1 < key[0] < SIZE and -1 < key[1] < SIZE:
            return self.data[self.key(*key)]
        return ""

    def __setitem__(self, key: Point, value: str):
        """
        :param key: Set(int, int) -> [x, y]  落点的坐标
        :param value: 落点的数据  1 or 2 玩家或者电脑AI
        :return: None
        """
        self.data[self.key(*key)] = value

    def __contains__(self, key):
        """
        :param key: List[int, int] -> [x, y]  落点的坐标
        :return: bool ->判断当前点是否可以落子
        """
        return self[key] == "0"

    def _result(self, p: Point) -> List[str]:
        """
        :param p: 五子棋落点, 拼接效果:x->x->p->x->x
        :return:四个方向的棋型, List[str]
        """
        def count(x, y): return [p[0] + x, p[1] + y]

        def func(iterator):
            return "".join([self[count(*e)] for e in iterator])
        return [func(v1)[::-1] + self[p] + func(v2) for v1, v2 in VECTOR]

    def get_xy_score(self, spot: Point, flag: str) -> int:
        """
        :param spot:
        :param flag:身份标识
        :return:四个方向的棋型的评分列表
        """
        self[spot] = flag
        chess_type = self._result(spot)
        self[spot] = "0"
        _flag = [COMPUTER, PLAYER][flag == COMPUTER]

        def get(chess):
            for score, rules in ChessType.ScoreRules.items():
                for rule in rules:
                    if rule.format(flag, _flag) in chess:
                        # TODO 玩家评分修正,玩家活三,单四
                        if flag == PLAYER:
                            if score == ChessType.LIVE_THREE:
                                score = ChessType.PLAYER_LIVE_THREE
                            if score == ChessType.RUSH_FOUR:
                                score = ChessType.PLAYER_RUSH_FOUR
                        return score
            return 0.5
        scores = sorted([get(chess) for chess in chess_type], reverse=True)
        return ChessType.score(scores)

    def update_one(self, p: Point) -> Set[Tuple]:
        """
        获取落点周围八个方位总计40个点,排除超出边界的点
        :param p: 落点点
        :return: set[tuple] -> ((x, y),(x1, y1)...)
        """
        self.one_spot.discard(p)
        # TODO 对p点计算位移点。
        def count(x, y): return p[0] + x, p[1] + y
        # TODO 对VECTOR展开,转化一维列表,通过count函数计算位移后的点
        vector = [count(*each) for each in chain.from_iterable(list(chain.from_iterable(VECTOR)))]
        r = set(filter(self.f, vector))
        return r

    def _evaluation(self, computer_scores, player_scores):
        """对相同收益进行比较
            例如对手出现活三,我们有两个点可以堵截,有一个点既能堵截,又能对我们形成收益。
        """
        score, move = player_scores[0]
        moves = []
        for sco, spot in player_scores[:8]:
            if sco == score:
                moves.append(spot)
        for sco, spot in computer_scores[:8]:
            if spot in moves:
                return sco, spot
        return False

    def evaluation(self) -> Tuple[int, tuple]:
        """
        对当前棋盘局面进行评估
        :return: Tuple[int, tuple] -> (score, (x, y))
        """
        # TODO 遍历待搜索的着点,对双方进行评分.返回双方评分收益大的一个点
        # TODO 遍历得道电脑和玩家落子最大收益
        computer_scores = [(self.get_xy_score(spot, COMPUTER), spot) for spot in self.one_spot]
        player_scores = [(-self.get_xy_score(spot, PLAYER), spot) for spot in self.one_spot]
        # TODO 排序获得最大收益点
        computer_scores = sorted(computer_scores, key=lambda p: p[0], reverse=True)
        player_scores = sorted(player_scores, key=lambda p: p[0])
        # TODO 如果电脑收益大于玩家,直接返回电脑得分点
        if computer_scores[0][0] > abs(player_scores[0][0]):
            return computer_scores[0]
        # TODO 如果电脑收益小于于玩家,进行收益进行比较
        res = self._evaluation(computer_scores, player_scores)
        if res:
            return res
        # TODO 返回玩家收益点
        return player_scores[0]
from CoreCode import GoBangBase
from setting import COMPUTER


class Board(GoBangBase):
    """
    棋盘管理:
        computer_order -->  电脑落子记录
        player_order   -->  电脑落子记录
    Method

    """
    computer_order: list = list()
    player_order: list = list()

    def modify(self, spot: list, identity: str) -> None:
        self[spot] = identity
        self.computer_order.append(spot) if identity == COMPUTER else self.player_order.append(spot)

    @staticmethod
    def game_over():
        return False

    def ai_move(self):
        try:
            result = self.evaluation()
        except IndexError:
            return 0, (8, 8)
        return result
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ version: ??
@ author: Alex
@ file: cmd_board
@ datetime: 2021/02/09 - 14:08
@ explain:
"""
from CoreCode import Board
from setting import SIZE, COMPUTER, PLAYER
from CoreCode.tools import LogHandler
log = LogHandler("borad", file=True)


class StrBoard(Board):

    yingshe = {
        "0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,
        "9":9,"a":10,"b":11,"c":12,"d":13
    }

    def print_board(self):
        str1 = "0123456789abcde"
        r = ''
        for x in range(SIZE):
            s = ""
            for y in range(SIZE):
                s += " " + self[(x, y)]
            r += str1[x]+"  " + s + "\n"
        str1 ="x/y " + " ".join(str1)
        print(r + str1)

    def main(self):
        next_move = COMPUTER
        while True:
            self.print_board()
            if next_move == PLAYER:
                r = input("请输入你要落子的位置(0-d):0d or 91>>>>")
                x,y = self.yingshe[r[0]], self.yingshe[r[1]]
                self[(x, y)] = PLAYER
                bb = self.update_one((x, y))
                self.one_spot |= bb
                next_move = COMPUTER
                self.player_order.append((x, y))
            else:
                r = self.ai_move()
                print(f"运行结果:{r}\n")
                self[r[1]] = COMPUTER
                bb = self.update_one(r[1])
                self.one_spot |= bb
                next_move = PLAYER
                self.computer_order.append(r[1])


if __name__ == '__main__':
    s = StrBoard()
    try:
        s.main()
    except KeyError:
        log.warning(f"\n{s.player_order}\n{s.computer_order}\n")
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ version: ??
@ author: Alex
@ file: setting
@ datetime: 2020/10/17 - 21:08
@ explain:
"""
# TODO 棋盘大小
SIZE = 15

# TODO VECTOR 向量,
f = lambda x, y: '[(0 {} v, 0 {} v) for v in range(1, 6)]'.format(x, y)
VECTOR = [
    # TODO X轴
    [eval(f("-", "*")), eval(f("+", "*"))],
    # TODO Y轴
    [eval(f("*", "+")), eval(f("*", "-"))],
    # TODO 左斜轴
    [eval(f("-", "+")), eval(f("+", "-"))],
    # TODO 右斜轴
    [eval(f("-", "-")), eval(f("+", "+"))],
]
# TODO 身份标识符,电脑落子代表1 玩家落子代表2
COMPUTER = "1"
PLAYER = "2"
# TODO 分支8,搜索深度 5,博弈树节点
BRANCH = 8
DEPTH = 3


class ChessType:
    """
    (1) 如果有一个方向已经成5连, 其分值记为5000;
    (2) 未出现 (1) , 有一个方向已经成活4, 其分值记为1000;
    (3) 未出现 (1) 、 (2) , 有两个方向出现单4, 其分值记为1000;
    (4) 未出现 (1) 、 (2) 、 (3) , 有两个方向出现单4和活3, 其分值记为998;
    (5) 未出现 (1) 、 (2) 、 (3) 、 (4) , 有两个方向成活3, 其分值记为954;
    (6) 未出现上述情况, 将4个方向中单子分值最大的2个值相加作为其分值。
    己方单4   73
    对方单4   84
    己方活3   67
    对方活3   79
    单3       16
    活2       13
    单2       7
    活1       3
    单1       1
    """
    ScoreRules = {
        # TODO 得分[int]:棋型List[str]
        #  如果有新的棋型,在此添加,范围仅限,单活1-4棋型
        #  TODO  眠一型
        1: ["0{0}{1}", "{1}{0}0"],
        #  TODO  活一型
        3: ["0{0}0"],
        #  TODO  眠二型
        7: ["0{0}{0}{1}", "0{0}0{0}{1}", "0{0}00{0}{1}", "{0}000{0}"],
        #  TODO  活二型
        13: ["0{0}{0}0", "0{0}0{0}0", "{0}00{0}", "0{0}{0}0"],
        #  TODO  眠三型
        16: [
            "0{0}{0}{0}{1}", "{1}{0}{0}{0}0", "0{0}0{0}{0}{1}",
            "{1}{0}0{0}{0}0", "0{0}{0}0{0}{1}", "{1}{0}{0}0{0}0",
            "{1}0{0}{0}{0}0{1}", "{0}00{0}{0}", "{0}0{0}0{0}"
        ],
        #  TODO  活三型
        67: ["0{0}{0}{0}0", "0{0}0{0}{0}0", "0{0}{0}0{0}0"],
        #  TODO  眠四型
        73: ["0{0}{0}{0}{0}{1}", "{0}0{0}{0}{0}", "{0}{0}0{0}{0}", "{0}{0}{0}0{0}", "{1}{0}{0}{0}{0}0"],
        #  TODO  活四型
        1000: ["0{0}{0}{0}{0}0"],
        #  TODO  连五型
        5000: ["{0}{0}{0}{0}{0}"]
    }
    LIVE_THREE: int = 67
    RUSH_FOUR: int = 73
    # TODO 对方活3及单四评分
    PLAYER_LIVE_THREE: int = 79
    PLAYER_RUSH_FOUR: int = 84
    # TODO 异常棋型得分,双活三,单四活三,双单四,
    DOUBLE_LIVE_THREE: int = 954
    RUSH_FOUR_LIVE_THREE: int = 998
    DOUBLE_RUSH_FOUR: int = 1000

    @staticmethod
    def score(scores) -> int:
        """返回最终评分
            如果有新的异常棋型得分,在此修改。
            scores->List[int]是一个落子的四个方向的棋型得分
        """
        value = scores[0] + scores[1]
        # TODO 连五棋型 5000分
        if value >= 5000:
            return 5000
        # TODO 双冲四棋型 1000分
        if value >= ChessType.RUSH_FOUR * 2:
            return ChessType.DOUBLE_RUSH_FOUR
        # TODO 活四+活三棋型 998分
        elif value >= ChessType.RUSH_FOUR + ChessType.LIVE_THREE:
            return ChessType.RUSH_FOUR_LIVE_THREE
        # TODO 双活三棋型 954分
        elif value >= ChessType.LIVE_THREE * 2:
            return ChessType.DOUBLE_LIVE_THREE
        # TODO 以上都不符
        return value

评分

参与人数 3荣誉 +13 鱼币 +8 贡献 +6 收起 理由
hrp + 3 + 3
ArouArou + 5 + 5 + 3 厉害了 老哥!
昨非 + 5 + 3 鱼C有你更精彩^_^

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-2-17 08:47:51 | 显示全部楼层
顶楼!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-2-17 09:23:05 From FishC Mobile | 显示全部楼层
学习
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-2-17 10:03:59 | 显示全部楼层
厉害
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-16 14:04

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表