我正在尝试学习MVar,并且正在尝试制作以下文字游戏:
狩猎从100个生命点开始,然后是1级
1。一个功能会对生命值造成伤害(15,30) 2,另一个功能可以治愈生命点(5,10) 3.增加一级,因为玩家幸存 4.重复直到生命点达到0
生命点将是MVar整数,因此一个函数必须等待另一个函数完成更改其值(同步可变变量)
我要重复这2个功能,直到生命点达到0,然后显示玩家达到的级别
这是我到目前为止所得到的:
import Control.Monad
import Data.Char
import Control.Concurrent
import System.Random
lifePoints :: Integer
lifePoints = 100
damageCalculation :: StdGen -> MVar Integer -> IO Int
damageCalculation gen life = do
let (randDano, newGen) = randomR (15,30) gen :: (Int, StdGen)
a <- readMVar life
let newLife = life - randDano
a <- swapMVar newLife
return a
main :: IO ()
main = do
putStr "Welcome to Hunter of Monsters\n"
putStr "Whats your name? \n"
l <- getLine
putStr "Character\n"
putStrLn ("Name: " ++ l)
let life = lifePoints
putStrLn $ "Life Points: " ++ show life
putStr "The hunt begins\n"
a <- newEmptyMVar
forkIO $ do
putStr "Damage calculation\n"
lifeMVar <- newMVar life
gen <- getStdGen
let rest = damageCalculation gen lifeMVar
putStrLn $ " " ++ show rest
putMVar a ()
takeMVar a
putStrLn "Game over!"
在获取新的随机数时遇到麻烦,只需重复一个值即可。 损坏功能很简单,但是尝试更改生命值时出现错误。 另外,如何重复这些功能,直到寿命点达到0? 玩家级别也需要成为MVar吗?
答案 0 :(得分:2)
在编译之前,您的代码存在许多问题:
damageCalculation
尝试将life
用作MVar Integer
(即,包含整数的存储位置的“令牌”)和Integer
值本身 li>
swapMVar
提供了一个参数,它期望两个参数Int
和Integer
类型main
中,您已经将damageCalculation
视为纯函数而不是I / O操作修复所有这些错误将使程序可以进行类型检查(请参见下面的清单1)。
但是,仍然存在许多错误。 首先代码:
a <- readMVar life
let newLife = a - randDano
a <- swapMVar life newLife
如果多个线程试图更新损坏,则处于竞争状态。第一行读取MVar的值,但不执行任何形式的锁定或同步,然后第三行无条件地写入新生命(同时获取旧值的副本)。
例如,如果两个线程试图以以下交错方式分别扣除10点损坏,则会遇到问题:
Thread 1 Thread 2
-------- --------
a <- readMVar life -- fetch 100 life
a <- readMVar life -- fetch 100 life again
let newLife = ... -- deduct 10 to get 90
a <- swapMVar life newLife -- save 90 in the MVar
let newLife = ... -- deduct 10 to get 90
a <- swapMVar life newLife -- save 90 in the MVar
相反,您要使用一对takeMVar
和putMVar
这些函数,它们旨在提供自动同步:
Thread 1 Thread 2
-------- --------
a <- takeMVar life -- fetch 100 life
a <- takeMVar life -- nothing there, so block...
let newLife = ... -- BLOCKING -- deduct 10 to get 90
a <- putMVar life newLife -- BLOCKING -- save 90 in the MVar
-- WAKE UP -- fetch 90 from MVar
let newLife = ... -- deduct 10 to get 80
a <- putMVar life newLife -- save 80 in the MVar
在my answer to your other question中,我建议使用readMVar
和swapMVar
,但如果看一下该代码,您会发现情况截然不同-一个线程只需要读取MVar的当前值(例如,读取wHeld
以查看是否按下了“ W”),并且需要另一个线程来无条件地写入新值(例如,写入{{ 1}}以更新“ W”键的当前状态)。不需要锁定,因为您只有两个线程,一个线程始终在写,而一个线程总是在读取最新写的内容。
wHeld
的 second 问题是我认为您想返回最终寿命值,但是您返回的是damageCalculation
调用的结果减去损坏之前的 old 生命值。换句话说,您可能希望swapMVar
作为do-block的最后一行。
第三问题是您使用return newLife
的方式。当您写时:
StdGen
使用let (randDano, newGen) = randomR (15,30) gen :: (Int, StdGen)
的值创建一个随机的gen
值,然后返回生成器randDano
的更新值。如果您丢掉此newGen
并尝试再次使用newGen
,您将始终生成相同的gen
值。您可以使randDano
成为newGen
返回值的一部分,并将其用于下一次调用,也可以使生成器本身成为由damageCalculation
函数更新的MVar。由于您想练习使用MVar,因此后者似乎是必经之路。因此,您的damageCalculation
函数应如下所示:
damageCalculation
请注意damageCalculation :: MVar StdGen -> MVar Int -> IO Int
damageCalculation v_gen v_life = do
gen <- takeMVar v_gen
let (randDano, gen') = randomR (15,30) gen
putMVar v_gen gen'
life <- takeMVar v_life
let life' = life - randDano
putMVar v_life life'
return life'
/修改值/ takeMVar
模式的使用。
在putMVar
函数中,您可能希望在分叉之前先创建所有相关的MVar,例如:
main
对于损害计算,如果您要继续造成损害,直到寿命耗尽,请像这样定义一个循环函数,然后main :: IO ()
main = do
putStrLn "Welcome to Hunter of Monsters"
putStrLn "Whats your name?"
l <- getLine
putStrLn "Character"
putStrLn ("Name: " ++ l)
gen <- getStdGen
v_gen <- newMVar gen
putStrLn $ "Life Points: " ++ show lifePoints
v_life <- newMVar lifePoints
done <- newEmptyMVar
putStrLn "The hunt begins"
-- damage stuff here --
takeMVar done
putStrLn "Game over!"
对其进行循环
forkIO
完整的程序在下面的清单2中。
let doDamage = do
putStrLn "Damage calculation"
rest <- damageCalculation v_gen v_life
putStrLn $ " " ++ show rest
if rest > 0
then doDamage
else putMVar done ()
forkIO doDamage
import Control.Concurrent
import System.Random
lifePoints :: Int -- use Int throughout
lifePoints = 100
damageCalculation :: StdGen -> MVar Int -> IO Int -- use Int throughout
damageCalculation gen life = do
let (randDano, newGen) = randomR (15,30) gen :: (Int, StdGen)
a <- readMVar life
let newLife = a - randDano -- Use "a" (the value), not "life" (the MVar)
a <- swapMVar life newLife -- "swapMVar" needs an MVar ("life"), not just a value
return a
main :: IO ()
main = do
putStr "Welcome to Hunter of Monsters\n"
putStr "Whats your name? \n"
l <- getLine
putStr "Character\n"
putStrLn ("Name: " ++ l)
let life = lifePoints
putStrLn $ "Life Points: " ++ show life
putStr "The hunt begins\n"
a <- newEmptyMVar
forkIO $ do
putStr "Damage calculation\n"
lifeMVar <- newMVar life
gen <- getStdGen
rest <- damageCalculation gen lifeMVar -- IO action, so use '<-' not 'let'
putStrLn $ " " ++ show rest
putMVar a ()
takeMVar a
putStrLn "Game over!"