Python编写的数字拼图游戏(含爬山算法人机对战功能)

2017-01-09 应根球,董付国 Python小屋 Python小屋

数字拼图游戏与拼图游戏原理一致,把打乱了的数字或图片经移动,拼成给定的目标数字或图片,其中总有一个空的地方,让相邻(上下左右)的方块移动,直至达到目标。

游戏代码由浙江温州永嘉县教师发展中心应根球老师提供,我略做修改和优化。

代码有点长,用手机阅读可能不太方便,可以复制地址到电脑上用浏览器查看。


import random


#显示数字拼图

def disp(s, d):

    #s和d是两个数字字符串,把0换成空格,把数字摆放到指定位置

    s = ''.join(s).replace('0', ' ')

    d = ''.join(d).replace('0', ' ')

    print('''

+---+---+---+        +---+---+---+

| {0[0]} | {0[1]} | {0[2]} |        | {1[0]} | {1[1]} | {1[2]} |

|---+---+---|        |---+---+---|

| {0[3]} | {0[4]} | {0[5]} |   ==>  | {1[3]} | {1[4]} | {1[5]} |

|---+---+---|        |---+---+---|

| {0[6]} | {0[7]} | {0[8]} |        | {1[6]} | {1[7]} | {1[8]} |

|---+---+---|        |---+---+---|

'''.format(s, d))


#移动数字

def move(s, numstr):

    if (numstr not in "12345678") or (not numstr):

        return

    

    t1 = s.index('0')

    t2 = s.index(numstr)

    #字符在字符串中的位置除3的商对应游戏图中的行下标

    #除3的余数对应游戏图中的列下标

    t = zip(divmod(t1,3), divmod(t2,3))

    t = ''.join([str(abs(i-j)) for i,j in t])

    #如果输入的数字与空格相邻则移动

    if t in ('01','10'):

        s[t1], s[t2] = s[t2], s[t1]


#获取空格周边可移动数字

def getMoveable(s):

    #空格位置的行、列坐标

    p, q = divmod(s.index('0'), 3)

    #空格上下的位置坐标

    ls = [(p, i) for i in (q+1, q-1) if i in range(3)]

    #空给左右的位置坐标

    ls += [(i, q) for i in (p+1, p-1) if i in range(3)]

    return ls


#爬山算法状态计算函数,从当前状态s到终态d所需要的总步数

def getInstance(s,d):

    #依次计算s和d中相同数字的距离,并求所有距离之和

    sumi = 0

    for n in '12345678':

        t1 = divmod(s.index(n), 3)

        t2 = divmod(d.index(n), 3)

        sumi += abs(t1[0]-t2[0]) + abs(t1[1]-t2[1])

    return sumi

    

#让机器根据与目标各数字差距之和的策略选定一个移动数字

def choiceNum(s, moveable, d):

    tmp = [100,'0']

    for i in moveable:

        s1 = s[::]

        s1[s1.index('0')], s1[s1.index(i)] = i, '0'

        getI = getInstance(s1, d)

        if getI < tmp[0]:

            tmp = getI, i

        elif getI == tmp[0]:

            tmp = getI, random.choice([i, tmp[1]])

    return tmp[1]


#打乱数字拼图顺序,以得到初始状态

def shufflemove(d, times):

    s2 = d[::]

    mov = '0'

    for i in range(times):

        mov1 = mov

        mov = [s2[x[0]*3+x[1]] for x in getMoveable(s2)]

        if mov1 in mov:

            mov.remove(mov1)

        mov = random.choice(mov)

        move(s2, mov)

    return s2


#做题,当who为computer时为计算机解题

#当who为human,即非computer时,人工解题

def do(s, d, who):

    s1 = s[::]

    num = ''

    bushu = 0

    while True:

        if who != 'computer':

            disp(s1,d)

        #已成功、步数太大或人工放弃时返回

        if s1==d or bushu>=1000 or num=='0':

            return bushu

        

        #确定可输入的数字

        n_of_m = [s1[x[0]*3+x[1]] for x in getMoveable(s1)]        

        if who == 'computer':

            #机器答题不允许后退

            if num in n_of_m:

                n_of_m.remove(num)

            num = choiceNum(s, n_of_m, d)

        else:

            #人工答题允许后退

            prompt = '第{0}步{1}(输入0退出)=>'.format(bushu, n_of_m)

            num = input(prompt)[0]

            #输入0步数设为999,视为主动放弃并退出

            if num == '0':

                bushu = 999

                                    

        #移动数字

        move(s1,num)

        bushu += 1


#主程序开始

print('-'*34)

print('拼图游戏'.center(34,'*'))

print('''

**玩法:左边的图通过移动空格相邻的数字到

空格处,最终得到右边的图,游戏即完成。只

要输入空格相邻的数字,该数字即被移到空格

处。=>左边的数字为你已经移动的步数及你可

移动的数字。完成任务的步数越少,你的游戏

成绩越高。祝你幸运!

''')

print('-'*34)


#为简化,设定固定数字目标,其中0显示为空方块

d = list('123804765')


#为简化,让机器从目标开始随机逆移动,先只移动6步

#也可以设置难度,移动步数越多,恢复越难。

s = shufflemove(d,6)


#先让计算机用简易爬山算法去解题,由于爬山算法本身的原因,不一定能得到最优解

cpstep = do(s, d, 'computer')


#显示开始与结束状态及机器解题情况

disp(s, d)

print('这个题目机器用了{0}步!\n'.format(cpstep))

if cpstep > 1000:

    print("机器用了1000步还没解出,看你的了!\n")


#用于计人工移动的步数

bushu = 0

#人工解题开始

hmstep = do(s, d, 'human')


#显示游戏结果

if hmstep < cpstep:

    print("你胜利,真了不起!")

elif hmstep == cpstep:

    print("你与机器持平局,加油!")

else:

    print("哈,你输给了机器,这可是用爬山算法哦!")



某次玩游戏的过程如下