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?
Is pattern matching on derived types idiomatic for F#?
I would say no because you have a basic fallacy that code with types in an object hierarchy is idiomatic F#.
I don't consider an object hierarchy of types to be idiomatic F# or functional. So the question is invalid. I take a historical perspective with F# coming from ML and OCaml as opposed to coming from an OO side. As I always suggest when doing functional code, forget what you know about OO as it will only lead you down the path of confusion. If you have to interface with OO then you will have to bite the bullet but leave OO out when possible.
Are classes and inheritance meant to be used in F#?
Or are they just for compatibility with .Net?
If you take a look at the MSDN article on F# classes under the section
When to Use Classes, Unions, Records, and Structures
you will see
Given the variety of types to choose from, you need to have a good understanding of what each type is designed for to select the appropriate type for a particular situation. Classes are designed for use in object-oriented programming contexts. Object-oriented programming is the dominant paradigm used in applications that are written for the .NET Framework. If your F# code has to work closely with the .NET Framework or another object-oriented library, and especially if you have to extend from an object-oriented type system such as a UI library, classes are probably appropriate.
If you are not interoperating closely with object-oriented code, or if you are writing code that is self-contained and therefore protected from frequent interaction with object-oriented code, you should consider using records and discriminated unions. A single, well thought–out discriminated union, together with appropriate pattern matching code, can often be used as a simpler alternative to an object hierarchy. For more information about discriminated unions, see Discriminated Unions (F#).
.
Should I use records instead, and, if yes, how?
No use a discriminated union at first and then if the data gets more complicated take a look at records. In certain situations I use records a lot but most of the time do not. This is a it depends
question.
Records have the advantage of being simpler than classes, but records are not appropriate when the demands of a type exceed what can be accomplished with their simplicity. Records are basically simple aggregates of values, without separate constructors that can perform custom actions, without hidden fields, and without inheritance or interface implementations. Although members such as properties and methods can be added to records to make their behavior more complex, the fields stored in a record are still a simple aggregate of values. For more information about records, see Records (F#).
Structures are also useful for small aggregates of data, but they differ from classes and records in that they are .NET value types. Classes and records are .NET reference types. The semantics of value types and reference types are different in that value types are passed by value. This means that they are copied bit for bit when they are passed as a parameter or returned from a function. They are also stored on the stack or, if they are used as a field, embedded inside the parent object instead of stored in their own separate location on the heap. Therefore, structures are appropriate for frequently accessed data when the overhead of accessing the heap is a problem. For more information about structures, see Structures (F#).
.
Switching on types is considered a bad practice in C#. Is it the same for F#?
See the answer to Is pattern matching on derived types idiomatic for F#?
How?
namespace Game
type Location = int * int
type TargetLocation = Location
type CurrentLocation = Location
type Symbol = char
type ActorType = CurrentLocation * Symbol
type HitPoints = int
type Health = int
type Money = int
type Creature = ActorType * HitPoints
// Player = Creature * Health * Money
// = (ActorType * HitPoints) * Health * Money
// = ((CurrentLocation * Symbol) * HitPoints) * Health * Money
// = ((Location * Symbol) * HitPoints) * Health * Money
// = (((int * int) * char) * int) * int * int
type Player = Creature * Health * Money
type Actor =
| Medication of ActorType
| Coin of ActorType
| Arrow of Creature * TargetLocation // Had to give arrow hit point damage
| Zombie of Creature * TargetLocation
module main =
[<EntryPoint>]
let main argv =
let player = ((((0,0),'p'),15),0,0)
let actors : Actor List =
[
Medication((0,0),'♥');
Zombie((((3,2),'Z'),3),(0,0));
Zombie((((5,1),'Z'),3),(0,0));
Arrow((((4,3),'/'),3),(2,1));
Coin((4,2),'$');
]
let updatePlayer player (actors : Actor list) : Player =
let interact (((((x,y),symbol),hitPoints),health,money) : Player) otherActor =
match (x,y),otherActor with
| (playerX,playerY),Zombie((((opponentX,opponentY),symbol),zombieHitPoints),targetLocation) when playerX = opponentX && playerY = opponentY ->
printfn "Player is hit by creature for %i hit points." zombieHitPoints
((((x,y),symbol),hitPoints - zombieHitPoints),health,money)
| (playerX,playerY),Arrow((((opponentX,opponentY),symbol),arrowHitPoints),targetLocation) when playerX = opponentX && playerY = opponentY ->
printfn "Player is hit by arrow for %i hit points." arrowHitPoints
((((x,y),symbol),hitPoints - arrowHitPoints),health,money)
| (playerX,playerY),Coin((opponentX,opponentY),symbol) when playerX = opponentX && playerY = opponentY ->
printfn "Player got 1$."
((((x,y),symbol),hitPoints),health,money + 1)
| (playerX,playerY),Medication((opponentX,opponentY),symbol) when playerX = opponentX && playerY = opponentY ->
printfn "Player is healed by 1."
((((x,y),symbol),hitPoints),health+1,money)
| _ ->
// When we use guards in matching, i.e. when clause, F# requires a _ match
((((x,y),symbol),hitPoints),health,money)
let rec updatePlayerInner player actors =
match actors with
| actor::t ->
let player = interact player actor
updatePlayerInner player t
| [] -> player
updatePlayerInner player actors
let rec play player actors =
let player = updatePlayer player actors
play player actors
// Since this is example code the following line will cause a stack overflow.
// I put it in as an example function to demonstrate how the code can be used.
// play player actors
// Test
let testActors : Actor List =
[
Zombie((((0,0),'Z'),3),(0,0))
Arrow((((0,0),'/'),3),(2,1))
Coin((0,0),'$')
Medication((0,0),'♥')
]
let updatedPlayer = updatePlayer player testActors
printf "Press any key to exit: "
System.Console.ReadKey() |> ignore
printfn ""
0 // return an integer exit code
Since this is not a complete game I did a few test to show the basics of a player interacting with the other actors.
Player is hit by creature for 3 hit points.
Player is hit by arrow for 3 hit points.
Player got 1$.
Player is healed by 1.
If you have specific questions on how this works, please ask a new question and refer back to this question.
Hopefully people from OO will see why those of us who switched to functional programming enjoy it and why you should not have any OO thoughts in your mind when doing functional code from the ground up. Again if you are interacting with other OO code then having OO thoughts is fine.
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? Yes, when it makes sense to structure your program in an object-oriented way.
In other words, OO makes it easy to implement the same operation over many shapes of data, but hard to add new operations; FP makes it easy to implement many different operations on data, but hard to modify your data. The approaches are complementary. Pick the one that makes the most sense for your problem. The nice thing about F# is that it has great support for both; you'll actually want to default to FP in F#, as the syntax is lighter and more expressive.
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
Switching on types leads to brittle code in OO, and that doesn't change in F#. You'll still run into the same issues. Switching on data types in FP is idiomatic however. If instead of class hierarchies you use DUs, then you'll have no choice but to switch (pattern-match) on your data types. And that's fine, because the compiler is there to help you with that, unlike in OO.
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
First let's define our domain model. One aspect of your design I find problematic is that you store object positions in the actors and have no actual map object. I find it a good design principle to have an object store only its intrinsic properties, and move the extrinsic properties where it makes more sense, keeping the domain model as small as possible. An actor's location is not an intrinsic property.
So, using idiomatic F# types:
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
One information this leaves out is the symbol, but this is really only a concern for rendering and thus best moved aside in a helper function:
let symbol = function
| Arrow -> '/'
| Medication -> '♥'
| Creature c ->
match c with
| Zombie _ -> 'X'
| Coin -> '$'
| Player _ -> '@'
Now since from your description there can be multiple actors on a single tile, we'll represent our map as an Actor list [][]
, i.e. a 2D map of lists of actors.
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}]
Note that this not a very functional approach as we'll mutate the array rather than create new ones, but in game programming this is common, for obvious performance reasons.
And now your playerInteraction function looks like this (actually implementing the spec rather printing out strings):
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
A question this leaves out is: how do I get the player's position? You could cache it, or just compute it on the fly every time. If the map is small this is not slow. Here's an example function for that (not optimized, if you use 2D maps extensively you'll want to implement fast generic iterators that don't allocate):
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With