Project1-Hog
实现一个掷骰子游戏
规则
两位玩家依次掷任意数量(不多于10个)骰子,点数之和先达到GOAL的胜利。
- Sow Sad: 若其中一个骰子点数为一,则该玩家此轮得分为1
- Boar Brawl:
玩家可以选择不掷骰子,得分为
max(1,3*abs(对手得分十位数-自己得分个位数)),位数不够补零。 - Sus Fuss: 若一局结束后,玩家点数数值有三或四个因数(包括1和点数本身),该玩家点数会变为比当前数值大的最近的一个质数
Phase 1: Rules of the Game
模拟游戏的进行
Problem 0
熟悉dice.py,了解骰子的生成
- make_fair_dice(
)用于生成每面概率相等的SIDE面骰子 - make_test_dice(
)用于测试循环投出一系列指定值
掷出一次骰子的方法:调用生成骰子赋值给的变量e.g. six_sided()
Problem 1
实现掷骰子函数,并实现规则Sow Sad
在定义函数时,若出现形参后已经赋值的情况,该值代表该函数不传入参数时形参默认值
如
def roll_dice(num_rolls, dice=six_sided),若不传入dice参数则默认为six_sided
roll_dice()函数:
参数:
- num_rolls指掷骰子次数
- dice指传入的骰子(默认值为六面骰子)
理解:
- 该函数返回的是num_roll次结果之和,若其中有1则返回1
- 若上次调用函数掷骰子次数少于掷骰子次数,则下次掷骰子时会接着上次开始而非从头开始
- 在循环中return语句会结束一个循环
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def roll_dice(num_rolls, dice=six_sided):
"""Simulate rolling the DICE exactly NUM_ROLLS > 0 times. Return the sum of
the outcomes unless any of the outcomes is 1. In that case, return 1.
num_rolls: The number of dice rolls that will be made.
dice: A function that simulates a single dice roll outcome. Defaults to the six sided dice.
"""
# These assert statements ensure that num_rolls is a positive integer.
assert type(num_rolls) == int, 'num_rolls must be an integer.'
assert num_rolls > 0, 'Must roll at least once.'
# BEGIN PROBLEM 1
total = 0
flag = 0
for i in range(num_rolls):
temp = dice()
if temp==1:
flag = 1
total += temp
if flag:
return 1
else:
return total
# END PROBLEM 1Problem 2
实现规则Boar Brawl
boar_brawl()函数:
参数:
- player_score:自己的分数
- opponent_score:对手的分数
理解:
- 获取自己分数的个位数与对手分数的十位数
- 相减并取绝对值后乘三
- 若小于1则输出1,否则输出计算结果
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def boar_brawl(player_score, opponent_score):
"""Return the points scored by rolling 0 dice according to Boar Brawl.
player_score: The total score of the current player.
opponent_score: The total score of the other player.
"""
# BEGIN PROBLEM 2
player_one = player_score%10
opponent_ten = opponent_score%100//10
result = 3*abs(opponent_ten-player_one)
if result>=1:
return result
else:
return 1
# END PROBLEM 2注意:传入的参数不一定是十位数,需要进行一些处理
Problem 3
实现函数,将前两个规则结合,输出正确结果
take_turn()函数:
参数:
- num_rolls:掷骰子次数
- player_score:自己的分数
- opponent_score:对手的分数
- dice:使用的骰子
理解:
- 若num_rolls>0,则按照正常规则进行,获得本轮掷骰点数之和
- 若num_rolls=0,即不掷骰子,等同于选择使用Boar Brawl规则,执行该函数
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def take_turn(num_rolls, player_score, opponent_score, dice=six_sided):
"""Return the points scored on a turn rolling NUM_ROLLS dice when the
player has PLAYER_SCORE points and the opponent has OPPONENT_SCORE points.
num_rolls: The number of dice rolls that will be made.
player_score: The total score of the current player.
opponent_score: The total score of the other player.
dice: A function that simulates a single dice roll outcome.
"""
# Leave these assert statements here; they help check for errors.
assert type(num_rolls) == int, 'num_rolls must be an integer.'
assert num_rolls >= 0, 'Cannot roll a negative number of dice in take_turn.'
assert num_rolls <= 10, 'Cannot roll more than 10 dice.'
# BEGIN PROBLEM 3
if num_rolls==0:
return boar_brawl(player_score,opponent_score)
else:
return roll_dice(num_rolls,dice)
# END PROBLEM 3Problem 4
实现Sus Fuss规则
num_factors()函数:
参数:
- n:要计算的数n
理解:
- 返回数n的因数个数
- 1和n本身也算进去
My code:
1
2
3
4
5
6
7
8
9
def num_factors(n):
"""Return the number of factors of N, including 1 and N itself."""
# BEGIN PROBLEM 4
total = 0
for i in range(1,n+1):
if n%i==0:
total+=1
return total
# END PROBLEM 4sus_points()函数:
参数:
- score:某玩家的分数
理解:
- 该函数用于更新玩家在Sus Fuss规则下新的分数
- 即用于判断符合条件下大于该分数数值的下一个质数
- 若该数值不符合条件,则返回本身
My code:
1
2
3
4
5
6
7
8
9
10
11
def sus_points(score):
"""Return the new score of a player taking into account the Sus Fuss rule."""
# BEGIN PROBLEM 4
if num_factors(score)==3 or num_factors(score)==4:
while True:
score+=1
if is_prime(score):
return score
else:
return score
# END PROBLEM 4sus_update()函数:
参数:
- num_rolls:掷骰子次数
- player_score:自己的分数
- opponent_score:对手的分数
- dice:使用的骰子
理解:
- 用于输出num_rolls次掷骰子后,考虑以上三种规则后的点数之和
- 现根据take_turn()函数求出满足前两个规则的本轮分数之和,并累加到当前分数上
- 再判断此时分数是否满足第三个规则,进行相应分数改动
My code:
1
2
3
4
5
6
7
8
def sus_update(num_rolls, player_score, opponent_score, dice=six_sided):
"""Return the total score of a player who starts their turn with
PLAYER_SCORE and then rolls NUM_ROLLS DICE, *including* Sus Fuss.
"""
# BEGIN PROBLEM 4
score = player_score + take_turn(num_rolls, player_score, opponent_score, dice)
return sus_points(score)
# END PROBLEM 4Problem 5
完整实现游戏模拟
play()函数:
参数:
- strategy0:player0使用的策略
- strategy1:player1使用的策略
- update:使用的更新函数(有无sus)
- score0:player0的起始分数
- score1:player1的起始分数
- dice:使用的骰子
- goal:实现游戏结束的数值
理解:
- strategy指的是玩家掷骰子数量
- strategy函数传入自己与对手的分数,根据两者分数得出下次掷骰子数量
- 使用传入的update函数来决定分数改变策略(是否采用sus fuss规则)
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def play(strategy0, strategy1, update,
score0=0, score1=0, dice=six_sided, goal=GOAL):
"""Simulate a game and return the final scores of both players, with
Player 0's score first and Player 1's score second.
E.g., play(always_roll_5, always_roll_5, sus_update) simulates a game in
which both players always choose to roll 5 dice on every turn and the Sus
Fuss rule is in effect.
A strategy function, such as always_roll_5, takes the current player's
score and their opponent's score and returns the number of dice the current
player chooses to roll.
An update function, such as sus_update or simple_update, takes the number
of dice to roll, the current player's score, the opponent's score, and the
dice function used to simulate rolling dice. It returns the updated score
of the current player after they take their turn.
strategy0: The strategy for player0.
strategy1: The strategy for player1.
update: The update function (used for both players).
score0: Starting score for Player 0
score1: Starting score for Player 1
dice: A function of zero arguments that simulates a dice roll.
goal: The game ends and someone wins when this score is reached.
"""
who = 0 # Who is about to take a turn, 0 (first) or 1 (second)
# BEGIN PROBLEM 5
while score0<goal and score1 < goal:
if who==0:
score0 = update(strategy0(score0, score1), score0, score1, dice)
else:
score1 = update(strategy1(score1, score0), score1, score0, dice)
who = 1 - who
# END PROBLEM 5
return score0, score1Phase 2: Strategies
这部分将会根据自己与对手的分数生成每轮玩家的掷骰数(0-10)
Problem 6
返回一个函数,获取自己与对手分数并输出指定骰子个数
理解:
- 返回的是函数,有两个参数:自己与对手分数
- 无论两者分数多少,返回的总是一开始传入的指定骰子个数
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def always_roll(n):
"""Return a player strategy that always rolls N dice.
A player strategy is a function that takes two total scores as arguments
(the current player's score, and the opponent's score), and returns a
number of dice that the current player will roll this turn.
>>> strategy = always_roll(3)
>>> strategy(0, 0)
3
>>> strategy(99, 99)
3
"""
assert n >= 0 and n <= 10
# BEGIN PROBLEM 6
return lambda x,y: n
# END PROBLEM 6Problem 7
判断每种分数组合是否都有一种对应的掷骰个数(分数组合指一种自己与对手的分数)
is_always_roll()函数:
参数:
- strategy:掷骰策略
- goal:玩家胜利要达到的目标分数
理解:
- 自己与对手的得分在胜利之前可能性均有100种(0-99),故可能性组合有10000种
- 该函数实现了判断每种可能性组合下返回的掷骰数是否相同
- goal不一定是100
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def is_always_roll(strategy, goal=GOAL):
"""Return whether STRATEGY always chooses the same number of dice to roll
given a game that goes to GOAL points.
>>> is_always_roll(always_roll_5)
True
>>> is_always_roll(always_roll(3))
True
>>> is_always_roll(catch_up)
False
"""
# BEGIN PROBLEM 7
num = strategy(0,0)
for i in range(0,goal):
for j in range(0,goal):
if strategy(i,j)!=num:
return False
return True
# END PROBLEM 7Problem 8
返回一个函数,用来调用n次掷骰函数,并返回这n次掷骰得到的值之和的平均值
语法特性:*args
*args参数允许函数接受任意数量的位置参数,以元组形式传入
args可以改为其他名称,*必须有
应用:
- 定义的函数接受不定数量位置参数时
- 编写高阶函数(higher-order function)时,传递参数给内部的定义的函数
make_averaged()函数
参数:
- original_function:调用的掷骰函数
- times_called:调用次数
理解:
- 函数需要做到执行n次掷骰函数,并将返回值累加,最后求这n次和的平均值
- 在内联函数中使用了*args来表示若干参数,与调入的掷骰函数参数数量一致
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def make_averaged(original_function, times_called=1000):
"""Return a function that returns the average value of ORIGINAL_FUNCTION
called TIMES_CALLED times.
To implement this function, you will have to use *args syntax.
>>> dice = make_test_dice(4, 2, 5, 1)
>>> averaged_dice = make_averaged(roll_dice, 40)
>>> averaged_dice(1, dice) # The avg of 10 4's, 10 2's, 10 5's, and 10 1's
3.0
"""
# BEGIN PROBLEM 8
def average_cal(*args):
suma = 0
for i in range(times_called):
suma+=original_function(*args)
return suma/times_called
return average_cal
# END PROBLEM 8Problem 9
实现函数,枚举掷骰次数(1-10),看哪次的分数平均值最大
max_scoring_num_rolls()函数
参数:
- dice:使用的骰子
- times_called:调用次数
理解
- 遍历掷骰次数,调用make_averaged函数计算平均值,取1-10掷骰次数中平均值最大值
- 当平均值相等时取更小的
My code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def max_scoring_num_rolls(dice=six_sided, times_called=1000):
"""Return the number of dice (1 to 10) that gives the maximum average score for a turn.
Assume that the dice always return positive outcomes.
>>> dice = make_test_dice(1, 6)
>>> max_scoring_num_rolls(dice)
1
"""
# BEGIN PROBLEM 9
maxi = 0
maxc = 0
for i in range(1,11):
temp = make_averaged(roll_dice,times_called)(i,dice)
if temp > maxc:
maxc = temp
maxi = i
return maxi
# END PROBLEM 9Problem 10
采用Boar Brawl规则,若roll出0时根据该规则得到的分数比threshold大,返回0,否则返回掷骰次数
boar_strategy()函数
参数:
- score:自己的分数
- opponent_score:对手的分数
- threshold:阈值分数,若采用boar brawl得到的分数大于它则返回掷骰0次
- num_rolls:掷骰次数
理解:
- boar brawl规则是在掷骰0次下的特殊规则
- 若采用该规则策略获得的分数要比正常掷骰获得的分数更高,就采用该策略(返回掷骰次数0次)
- 否则按正常掷骰次数掷骰
My code:
1
2
3
4
5
6
7
8
def boar_strategy(score, opponent_score, threshold=11, num_rolls=6):
"""This strategy returns 0 dice if Boar Brawl gives at least THRESHOLD
points, and returns NUM_ROLLS otherwise. Ignore score and Sus Fuss.
"""
# BEGIN PROBLEM 10
if boar_brawl(score,opponent_score)>=threshold: return 0
return num_rolls # Remove this line once implemented.
# END PROBLEM 10Problem 11
采用Sus Fuss规则,若roll出0时根据该规则与Boar Brawl规则得到的分数与起始分数差值比threshold大,返回0,否则返回掷骰次数
sus_strategy()函数
参数:
- score:自己的分数
- opponent_score:对手的分数
- threshold:阈值分数,若采用boar brawl得到的分数大于它则返回掷骰0次
- num_rolls:掷骰次数
理解:
- 即在boar brawl规则下采用sus fuss规则得到分数
- 若采用该规则策略获得的分数要比正常掷骰获得的分数更高,就采用该策略(返回掷骰次数0次)
- 否则按正常掷骰次数掷骰
My code:
1
2
3
4
5
6
def sus_strategy(score, opponent_score, threshold=11, num_rolls=6):
"""This strategy returns 0 dice when your score would increase by at least threshold."""
# BEGIN PROBLEM 11
if sus_update(0,score,opponent_score) - score >=threshold: return 0
return num_rolls # Remove this line once implemented.
# END PROBLEM 11Problem 12
结合上述所有策略与自己的策略,实现最终策略
My code:
1
2
3
4
5
6
7
8
9
10
def final_strategy(score, opponent_score):
"""Write a brief description of your final strategy.
*** YOUR DESCRIPTION HERE ***
"""
# BEGIN PROBLEM 12
threshold = GOAL-score
if score>80 and score-opponent_score>20: return 0
return 6 # Remove this line once implemented.
# END PROBLEM 12