I want to implement the following in most idiomatic way possible
A player is on a map.
If he is in the same position with an arrow, he gets 1 damage
If he is in the same position with a creature, he gets damage equal to hp of creature
If he is in the same position with a coin, he gets 1$
If he is in the same position with a medication, he heals by 1
Here is a stub written for interaction:
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) Are classes and inheritance meant to be used in F#? Or are they just for compatibility with .Net? Sould I use records instead, and, if yes, how?
2)Switching on types is considered a bad practice in C#. Is it the same for F#? If yes what should I write instead of otherActorsWithSamePosition
? Implementing otherXsWithSamePosition
for each class X
derived from actor doesn't look like a scalable solution
UPDATE:
I tried to implement it with a discriminated union, but didn't manage to compile:
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
The problems:
1)More important:
I don't manage to make a discriminated union of existing records
Actor =
| Medication {x:int;y:int;symbol:char}
raises error about deprecated construct
type Medication = {x:int;y:int;symbol:char}
Actor =
| Medication
considers Medication
and Actor.Medication
different types
I used a rather ugly construct of
type Medication = {x:int;y:int;symbol:char}
Actor =
| Medication of Medication
but it prevents me from matching on interfaces.
2)No implicit interface imlementations in F#. This cod already has a lot of boilerplate elements like 'member this.X=this.X'. With something more complex than 'IActor' it will create more and more problems.
Can you please provide an example of correct usage of named parameters of Discriminated Unions parameters in F#? Does it help in this case?