我正在尝试使用 Haskell 创建一个纸剪刀石头游戏来练习我对它的理解。
不幸的是,下面的源代码给出了不需要的答案。
例如:
>play pStone pRandom 1
1 games were played. Player 1 won 1 and player 2 won 1, making the match a draw.
如果进行 1 场比赛,则应该只有 1 场或 0 场胜利。
>play pStone pCopy 100
100 games were played. Player 1 won 1 and player 2 won 1, making the match a draw.
如果进行了 100 场比赛,其中(在第一轮之后)双方都下相同的棋步,那么应该只有 1 或 0 胜。
>play pCopy pAntiCopy 100
100 games were played. Player 1 won 31 and player 2 won 37, making player 2 the overall winner.
根据 pCopy 和 pAntiCopy 的预期定义,pAntiCopy 应该赢得 99 或 100,而 pCopy 应该赢得 0 或 1。
我相信这种行为最可能的原因是随机数是在最后评估的,这意味着应该依赖于相同随机数的 2 个值而不是依赖于 2 个单独的随机数。我在上面是正确的吗?
如果我是正确的,请你告诉我我应该如何纠正这个问题?如果我不正确,请你告诉我问题是什么以及应该如何解决?
我已经阅读了一个单独问题的解决方案,它建议生成一个随机数列表,然后使用它们,将它们作为参数从主函数传递给相关函数。不过,我不相信这在这里能很好地工作,因为所需的随机数的数量可能是从 0 到 2*numRounds 的任何值,这取决于正在使用的计划(我打算在这工作时创建更高级的计划)和代码的可读性会进一步降低。
我是 Haskell 和函数式编程的新手,所以我对下面源代码的风格表示歉意。如果您对如何改进它有任何建议,他们也很受欢迎。
import System.Random
data Move= Paper|Scissors|Stone deriving Show
type Plan=([IO Move]->[IO Move]->IO Move)
play :: Plan -> Plan -> Integer -> IO ()
play plan1 plan2 numRounds=do p1intwins<-p1wins;p2intwins<-p2wins;putStr(show numRounds ++ " games were played. Player 1 won " ++ show p1intwins ++ " and player 2 won " ++ show p2intwins ++ ", making " ++ (if p1intwins > p2intwins then "player 1 the overall winner." else (if p1intwins < p2intwins then "player 2 the overall winner." else "the match a draw."))) where (_, _, _, _, _, _, p1wins, p2wins)=(playRound (plan1, plan2, numRounds,[],[], 0, return 0, return 0))
playRound :: (Plan, Plan, Integer, [IO Move], [IO Move], Integer, IO Integer, IO Integer) -> (Plan, Plan, Integer, [IO Move], [IO Move], Integer, IO Integer, IO Integer)
playRound (plan1, plan2, numRounds, p1moves, p2moves, elapsedRounds, p1wins, p2wins)=if elapsedRounds==numRounds then (plan1, plan2, numRounds, p1moves, p2moves, elapsedRounds, p1wins, p2wins) else (playRound (plan1, plan2, numRounds, p1moves++[p1move], p2moves++[p2move], elapsedRounds+1, do p1win<-beatsCaller p1move p2move;p1intwins<-p1wins;return (p1intwins+if p1win then 1 else 0), do p2win<-beatsCaller p2move p1move;p2intwins<-p2wins;return(p2intwins+(if p2win then 1 else 0)) )) where p1move=plan1 p1moves p2moves; p2move=plan2 p2moves p1moves
beatsCaller :: IO Move -> IO Move -> IO Bool
beatsCaller iom1 iom2=do m1<-iom1;m2<-iom2;return(beats m1 m2)
beats :: Move -> Move -> Bool
beats Scissors Paper=True
beats Stone Scissors=True
beats Paper Stone=True
beats _ _=False
-- ###############Plans###################
pStone :: Plan
pStone _ _ = return Stone
pScissors :: Plan
pScissors _ _ = return Scissors
pPaper :: Plan
pPaper _ _ = return Paper
pUScissors :: Plan
pUScissors [] _ = randomMove
pUScissors _ _ = return Scissors
pCopy :: Plan
pCopy _ []= randomMove
pCopy _ theirMoves= last theirMoves
pRandom :: Plan
pRandom _ _=randomMove
pAntiCopy :: Plan
pAntiCopy [] _ = randomMove
pAntiCopy ourMoves _ = do ourMove<-last ourMoves;return(beaterOf ourMove)
-- ##############Plan Logic###############
beaterOf :: Move -> Move
beaterOf Scissors = Stone
beaterOf Paper = Scissors
beaterOf Stone = Paper
randomMove :: IO Move
randomMove=do x<-genRandom;return (doRMove x)
doRMove:: Int -> Move
doRMove rand
|rand==1 =Paper
|rand==2 =Scissors
|rand==3 =Stone
genRandom :: IO Int
genRandom =getStdRandom (randomR (1,3))