Python解决数字棒球游戏

数字棒球游戏是这样玩的:先由甲在心里想一个4位数,比如3092,不足4位的以0补足,比如12被认为是0012,这个数被称为目标。乙来猜这个数,方法是这样的,他先随机对甲说一个数,比如5382,甲告诉乙5382里有几个ball几个strike。所谓ball是指5382中有几个数出现在目标中,因为数字3和2都出现过,所以这里有ball=2。所谓strike是指在这2个ball中有几个位置是正确的。比如,上例中数字2的位置正确,所以strike=1。乙随后会进行分析,然后再猜一个数,甲再告诉乙ball和strike分别等于几。这个过程不断重复,直到乙猜出目标。注意,strike永远小于等于ball。以下是一个例子:

甲:我想好了一个四位数
乙:0115
甲:ball=1,strike=0
乙:5623
甲:ball=0,strike=0
乙:1798
甲:ball=1,strike=1
乙:8088
甲:ball=3,strike=1
乙:4088
甲:猜对了!

本节的目标是编写一个程序,人类出题,电脑来猜。大家看到这个问题很容易想到这样一个思路,当猜测5382得知ball=2,strikes=1时,意味着目标的形状像5*3*,5**3,*35*,*3*5,58**,5**8,......。把所有这些可能的模式记下来,然后在其中随机挑选一个,比如*35*,再随机拼出一个数据比如1359,让人类告知对比结果,再用结果更新上面的模式,......。这个过程比较复杂,事实上,我们还有一个简洁的思考方式:逆向思维+数据结构。

所谓逆向思维,就是说,不去试图建立目标的模式,而是反过来,排除不可能的模式。比如猜测5382得b2s1,那就把0~9999这一万个数中与5382对比不是b2s1的数排除。这样做的结果是确定的,不像正向思维得到的那么多的模式都是不确定的。

逆向思维的数据结构是一个含有0~9999这10000个数的列表candidates,每个数又用含有4个数字的列表表示。比如123就用[0, 1, 2, 3]表示。电脑首先从candidates中随机挑选一个数A猜测,人类告知ball和strike的值后,程序从candidates中删除一些数,这些数与A对比后的结果与上述ball及strike不等。然后电脑再从精简后的candidates里随机挑选一个数猜测,重复以上过程,直到乙猜出了这个数为止。

可见,只要建立了合适的数据结构——哪怕这个数据结构很简单——问题的困难也能迎刃而解。代码如下:

Python解决数字棒球游戏

# 文件: p04_03_ball_strike.py
import random

def get_candidates(num=4):  # 获取所有4位候选数
    return [to_list(i, num) for i in range(int(10**num))]

def to_list(n, num):  # 把数n转为num位数字列表
    result = []
    for _ in range(num):
        result.insert(0, n % 10)
        n //= 10
    return result

def get_balls_strikes(goal, guess): # 获取两个数对比后的结果
    balls = 0
    goal_copy = goal.copy()
    for digit in guess:
        if digit in goal_copy:
            balls += 1
            pos = goal_copy.index(digit)
            goal_copy[pos] = -1  # 每找到一个数就填上-1

    strikes = 0
    for d1, d2 in zip(guess, goal):  # 按位对比
        if d1 == d2:
            strikes += 1
    return balls, strikes

def guess(candidates):  # 从候选数中随机挑选一个数
    return candidates[random.randint(0, len(candidates)-1)]

def extract(candidates, guess, balls, strikes):
    # 删除不可能的候选数
    for i in range(len(candidates)-1, -1, -1):
        bs, ss = get_balls_strikes(candidates[i], guess)
        if ss != strikes or bs != balls:  # 如果对比结果不匹配
            del candidates[i]

def to_str(digit_list):
    return ''.join([chr(ord('0') + d) for d in digit_list])

if __name__ == '__main__':
    num = 4
    goal = input('请输入目标,一个最多%d位的正整数:' % num)
    goal = to_list(int(goal), num)
    candidates = get_candidates()
    guesses = 0
    while True:
        g = guess(candidates)
        guesses += 1
        print("我猜:", to_str(g))
        if g == goal:
            break
        balls, strikes = get_balls_strikes(goal, g)
        print("\tballs =", balls, "strikes =", strikes)
        extract(candidates, g, balls, strikes)
    print('猜对了,共猜了%d次' % guesses)

程序运行的结果如下:

请输入目标,一个最多4位的正整数:3450
我猜: 5848
	balls = 2 strikes = 0
我猜: 4553
	balls = 3 strikes = 1
我猜: 4365
	balls = 3 strikes = 0
我猜: 3574
	balls = 3 strikes = 1
我猜: 3450
猜对了,共猜了5次

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方林博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值