我希望以最恰当的方式实现以下内容
玩家在地图上。
如果他带箭头的位置相同,则会受到1点伤害
如果他与一个生物处于相同的位置,他的伤害等于生物的hp
如果他和一枚硬币在同一个位置,他会得到1 $
如果他与药物处于同一位置,他就会治愈1
这是为交互编写的存根:
open System
[<AbstractClass>]
type ActorBase(x,y,symbol)=
member this.X:int=x
member this.Y:int=y
member this.Symbol:char=symbol
type Medication(x,y)=
inherit ActorBase(x,y,'♥')
type Coin(x,y)=
inherit ActorBase(x,y,'$')
type Arrow(x,y,symbol,targetX,targetY) =
inherit ActorBase(x,y,symbol)
member this.TargetX=targetX
member this.TargetY=targetY
[<AbstractClass>]
type CreatureBase(x,y,symbol,hp) =
inherit ActorBase(x,y,symbol)
member this.HP:int=hp
type Player(x,y,hp, score) =
inherit CreatureBase(x,y,'@',hp)
member this.Score = score
type Zombie(x,y,hp,targetX,targetY) =
inherit CreatureBase(x,y,'z',hp)
member this.TargetX=targetX
member this.TargetY=targetY
let playerInteraction (player:Player) (otherActor:#ActorBase):unit =
printfn "Interacting with %c" otherActor.Symbol
match (otherActor :> ActorBase) with
| :? CreatureBase as creature -> printfn "Player is hit by %d by creature %A" (creature.HP) creature
| :? Arrow -> printfn "Player is hit by 1 by arrow"
| :? Coin -> printfn "Player got 1$"
| :? Medication -> printfn "Player is healed by 1"
| _ -> printfn "Interaction is not recognized"
let otherActorsWithSamePosition (actor:#ActorBase) =
seq{
yield new Zombie(0,0,3,1,1) :> ActorBase
yield new Zombie(0,1,3,1,1) :> ActorBase
yield new Arrow(0,0,'/',1,1) :> ActorBase
yield new Coin(0,0) :> ActorBase
yield new Medication(0,0) :> ActorBase
}
|> Seq.where(fun a -> a.X=actor.X && a.Y=actor.Y)
[<EntryPoint>]
let main argv =
let player = new Player(0,0,15,0)
for actor in (otherActorsWithSamePosition player) do
playerInteraction player actor
Console.ReadLine() |> ignore
0
1)类和继承是否意味着在F#中使用?或者他们只是为了兼容.Net?我可以使用记录代替,如果是,那该怎么办?
2)在C#中,打开类型被认为是一种不好的做法。对F#来说是一样的吗?如果是,我应该写什么而不是otherActorsWithSamePosition
?为从actor派生的每个类otherXsWithSamePosition
实现X
看起来不像是一个可扩展的解决方案
更新:
我试图用一个有区别的联盟实现它,但没有设法编译:
type IActor =
abstract member X:int
abstract member Y:int
abstract member Symbol:char
type IDamagable =
abstract member Damaged:int->unit
type IDamaging =
abstract member Damage:int
type Player =
{
X:int
Y:int
HP:int
Score:int
}
interface IActor with
member this.X=this.X
member this.Y=this.Y
member this.Symbol='@'
interface IDamagable with
member this.Damaged damage = printfn "The player is damaged by %d" damage
interface IDamaging with
member this.Damage = this.HP
type Coin =
{
X:int
Y:int
}
interface IActor with
member this.X=this.X
member this.Y=this.Y
member this.Symbol='$'
type Medication =
{
X:int
Y:int
}
interface IActor with
member this.X=this.X
member this.Y=this.Y
member this.Symbol='♥'
type Arrow =
{
X:int
Y:int
DestinationX:int
DestinationY:int
Symbol:char
}
interface IActor with
member this.X=this.X
member this.Y=this.Y
member this.Symbol=this.Symbol
interface IDamaging with
member this.Damage = 1
type Zombie =
{
X:int
Y:int
DestinationX:int
DestinationY:int
HP:int
}
interface IActor with
member this.X=this.X
member this.Y=this.Y
member this.Symbol='z'
interface IDamaging with
member this.Damage = this.HP
type Actor =
|Player of Player
|Coin of Coin
|Zombie of Zombie
|Medication of Medication
|Arrow of Arrow
let otherActorsWithSamePosition (actor:Actor) =
seq{
yield Zombie {X=0;Y=0; HP=3;DestinationX=1;DestinationY=1}
yield Zombie {X=0;Y=1; HP=3;DestinationX=1;DestinationY=1}
yield Arrow {X=0;Y=0; Symbol='/';DestinationX=1;DestinationY=1}
yield Coin {X=0;Y=0}
yield Medication {X=0;Y=0}
}
//Cannot cast to interface
|> Seq.where(fun a -> (a:>IActor).X=actor.X && (a:>IActor).Y=actor.Y)
let playerInteraction player (otherActor:Actor) =
match otherActor with
| Coin coin -> printfn "Player got 1$"
| Medication medication -> printfn "Player is healed by 1"
//Cannot check this
| :?IDamaging as damaging -> (player:>IDamagable).Damaged(damaging.Damage)
[<EntryPoint>]
let main argv =
let player = Player {X=0;Y=0;HP=15;Score=0}
for actor in (otherActorsWithSamePosition player) do
playerInteraction player actor
Console.ReadLine() |> ignore
0
问题:
1)更重要的是:
我没有设法对现有记录进行区别联合
Actor =
| Medication {x:int;y:int;symbol:char}
引发了已弃用的构造
的错误type Medication = {x:int;y:int;symbol:char}
Actor =
| Medication
考虑Medication
和Actor.Medication
种不同类型
我使用了一个相当难看的构造
type Medication = {x:int;y:int;symbol:char}
Actor =
| Medication of Medication
但它阻止我在接口上匹配。
2)F#中没有隐式接口imlement。这个鳕鱼已经有很多样板元素,例如&#39;成员this.X = this.X&#39;。有一些比“IActor”更复杂的东西。它会产生越来越多的问题。
请您提供一个在F#中正确使用Discriminated Unions参数的命名参数的示例?这种情况有帮助吗?
答案 0 :(得分:3)
1)类和继承是否意味着在F#中使用?或者是他们 只是为了与.Net兼容?我应该使用记录,如果 是的,怎么样? 是的,当以面向对象的方式构建程序时是有意义的。
换句话说,OO可以很容易地在多种形状的数据上实现相同的操作,但很难添加新的操作; FP可以轻松地对数据执行许多不同的操作,但很难修改数据。这些方法是互补的。选择对您的问题最有意义的那个。 F#的好处在于它对两者都有很大的支持;你实际上想要在F#中默认为FP,因为语法更轻,更具表现力。
2)在C#中,打开类型被认为是一种不好的做法。是吗? F#也一样吗?如果是的话,我应该写什么而不是 otherActorsWithSamePosition?实现otherXsWithSamePosition 从演员派生的每个类X看起来都不像是一个可扩展的解决方案
接通类型会导致OO中的代码变弱,而且F#中的代码不会发生变化。你还会遇到同样的问题。 然而,在FP中切换数据类型是惯用的。如果您使用DUs而不是类层次结构,那么除了在数据类型上切换(模式匹配)之外别无选择。这很好,因为编译器可以帮助你,不像OO。
玩家在地图上。
- 如果他带箭头的位置相同,则会受到1点伤害
- 如果他与一个生物处于相同的位置,他的伤害等于生物的hp
- 如果他和一枚硬币在同一个位置,他就会获得1美元
- 如果他与药物处于同一位置,他会以1
治愈
首先让我们定义我们的域模型。您发现问题的一个方面是您将对象位置存储在actor中并且没有实际的地图对象。我发现让对象只存储其内在属性是一个很好的设计原则,并将外部属性移动到更有意义的地方,使域模型尽可能小。演员的位置不是内在属性。
所以,使用惯用的F#类型:
type Player =
{ Hp: int
Score: int }
type Zombie =
{ Hp: int
TargetLocation: (int*int) option }
type Creature =
| Zombie of Zombie
type Actor =
| Arrow
| Medication
| Creature of Creature
| Coin
| Player of Player
遗漏的一个信息是符号,但这实际上只是渲染的一个问题,因此最好在辅助函数中移开:
let symbol = function
| Arrow -> '/'
| Medication -> '♥'
| Creature c ->
match c with
| Zombie _ -> 'X'
| Coin -> '$'
| Player _ -> '@'
现在,从您的描述中,单个图块上可以有多个演员,我们将我们的地图表示为Actor list [][]
,即演员列表的2D地图。
let width, height = 10, 10
let map = Array.init height (fun y -> Array.init width (fun x -> List.empty<Actor>))
// Let's put some things in the world
map.[0].[1] <- [Arrow]
map.[2].[2] <- [Creature(Zombie { Hp = 10; TargetLocation = None })]
map.[0].[0] <- [Player { Hp = 20; Score = 0}]
请注意,这不是一个非常实用的方法,因为我们会改变数组而不是创建新数组,但在游戏编程中这很常见,这显然是出于性能原因。
现在你的playerInteraction函数看起来像这样(实际上是实现规范而不是打印字符串):
let applyEffects { Hp = hp; Score = score } actor =
let originalPlayer = { Hp = hp; Score = score }
match actor with
| Arrow -> { originalPlayer with Hp = hp - 1 }
| Coin -> { originalPlayer with Score = score + 1 }
| Medication -> { originalPlayer with Hp = hp + 1 }
| Creature(Zombie z) -> { originalPlayer with Hp = hp - z.Hp }
| _ -> originalPlayer
这个问题遗漏的问题是:如何获得玩家的位置?您可以缓存它,或者每次只是动态计算它。如果地图很小,这并不慢。这是一个示例函数(未优化,如果你广泛使用2D地图,你想要实现不能分配的快速通用迭代器):
let getPlayer: Player * (int * int) =
let mapIterator =
map
|> Seq.mapi(fun y row ->
row |> Seq.mapi(fun x actors -> actors, (x, y)))
|> Seq.collect id
mapIterator
|> Seq.pick(fun (actors, (x, y)) ->
actors |> Seq.tryPick(function
| Player p -> Some (p, (x, y))
| _ -> None))