更仔细地查看了您的模型后,我可以确认我的初步印象:您使用的类型比您应该使用的类型多得多。其中许多类型应该是实例;在这种情况下,记录实例。对于何时应该使用类型或实例,这是一个很好的经验法则。如果这两个事物可以互换,那么它们应该是同一类型的两个实例。如果它们不可互换,那么(并且只有这样)它们应该是两种不同的类型。这是我的意思的一个例子。这是占据整个屏幕的代码部分:
type Weaponry =
| Dagger of Dagger
| Sword of Sword
| Axe of Axe
| Spear of Spear
| Staff of Staff
| LongBlade of Blade
| Spellbook of Spellbook
with
member x.Name =
match x with
| Dagger d -> d.ToString()
| Sword s -> s.ToString()
| Axe a -> a.ToString()
| Spear s -> s.ToString()
| Staff s -> s.ToString()
| LongBlade lb -> lb.ToString()
| Spellbook sb -> sb.ToString()
member x.Price =
match x with
| Dagger w -> w.Price
| Sword w -> w.Price
| Axe w -> w.Price
| Spear w -> w.Price
| Staff w -> w.Price
| LongBlade w -> w.Price
| Spellbook w -> w.Price
member x.Weight =
match x with
| Dagger w -> w.Weight
| Sword w -> w.Weight
| Axe w -> w.Weight
| Spear w -> w.Weight
| Staff w -> w.Weight
| LongBlade w -> w.Weight
| Spellbook w -> w.Weight
member x.Stats =
match x with
| Dagger w -> w.WeaponStats :> IStats
| Sword w -> w.WeaponStats :> IStats
| Axe w -> w.WeaponStats :> IStats
| Spear w -> w.WeaponStats :> IStats
| Staff w -> w.WeaponStats :> IStats
| LongBlade w -> w.WeaponStats :> IStats
| Spellbook w -> w.SpellStats :> IStats
所有这些项目之间有什么不同?这最后一行, where Spellbook
s have SpellbookStats
代替WeaponStats
。就是这样!至于你的其他武器类型——匕首、剑、斧头、矛等……它们的“形状”都是相同的。它们都有武器统计数据、价格、重量等。
这是整个武器模型的重新设计:
type ItemDetails = { Weight: float<kg>; Price: int<usd> }
type PhysicalWeaponType =
| Dagger
| Sword
| Axe
| Spear
| Staff
| LongBlade
type MagicalWeaponType =
| Spellbook
// Could later add wands, amulets, etc.
type WeaponDetails =
| PhysicalWeapon of PhysicalWeaponType * WeaponStat
| MagicalWeapon of MagicalWeaponType * SpellbookStats
type Weaponry =
{ Name: string
ItemDetails: ItemDetails
WeaponDetails: WeaponDetails }
with member x.Weight = x.ItemDetails.Weight
member x.Price = x.ItemDetails.Price
member x.Stats = match x.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats :> IStats
| MagicalWeapon (_, stats) -> stats :> IStats
// Now let's create some weapons. In the real game this would be read
// from a JSON file or something, so that the game is easily moddable
// by end users who want to add their own custom weapons.
let rustedDagger = {
Name = "Rusted dagger"
ItemDetails = { Weight = 2.10<kg>; Price = 80<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 5.60<dmg>; Defense = 1.20<def>; Intelligence = None; Speed = 1.00<spd>; Critical = 0.02<ctr>; HitLimit = 20<hl>; Rank = RankE })
}
let ironDagger = {
Name = "Iron dagger"
ItemDetails = { Weight = 2.80<kg>; Price = 200<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 9.80<dmg>; Defense = 2.30<def>; Intelligence = None; Speed = 1.10<spd>; Critical = 0.04<ctr>; HitLimit = 25<hl>; Rank = RankD })
}
let steelDagger = {
Name = "Steel dagger"
ItemDetails = { Weight = 4.25<kg>; Price = 350<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 13.10<dmg>; Defense = 3.00<def>; Intelligence = None; Speed = 1.15<spd>; Critical = 0.05<ctr>; HitLimit = 30<hl>; Rank = RankC })
}
let brokenSword = {
Name = "Broken sword"
ItemDetails = { Weight = 7.20<kg>; Price = 90<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 5.40<dmg>; Defense = 2.50<def>; Intelligence = None; Speed = 1.20<spd>; Critical = 0.01<ctr>; HitLimit = 10<hl>; Rank = RankE })
}
let rustedSword = {
Name = "Rusted sword"
ItemDetails = { Weight = 8.50<kg>; Price = 120<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 8.75<dmg>; Defense = 2.90<def>; Intelligence = None; Speed = 1.05<spd>; Critical = 0.03<ctr>; HitLimit = 20<hl>; Rank = RankD })
}
// And so on for iron and steel swords, plus all your axes, spears, staves and long blades.
// They should all be instances, not types. And spellbooks, too:
let rank1SpellbookDetails = { Weight = 0.05<kg>; Price = 150<usd> }
let rank2SpellbookDetails = { Weight = 0.05<kg>; Price = 350<usd> }
let bookOfFireball = {
Name = "Fireball"
ItemDetails = rank1SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 8.0<dmg>; AttackRange = 1; Rank = RankE; Uses = 30 ; ManaCost = 12.0<mp> })
}
// Same for Thunder and Frost
let bookOfHellfire = {
Name = "Hellfire"
ItemDetails = rank2SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 6.50<dmg>; AttackRange = 2; Rank = RankD; Uses = 25; ManaCost = 20.0<mp> })
}
// And so on for Black Fire and Storm of Blades
let computeCharacterOverallOffensive
// (rank: WeaponRank) // Don't need this parameter now
(weapon: Weaponry)
(cStats: CharacterStats) =
let weaponDamage =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Damage
| MagicalWeapon (_, stats) -> stats.Damage
let weaponRank =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Rank
| MagicalWeapon (_, stats) -> stats.Rank
// This should really be a method on the Rank type
let rankMultiplier =
match weaponRank with
| RankE -> 1.0100
| RankD -> 1.0375
| RankC -> 1.0925
| RankB -> 1.1250
| RankA -> 1.1785
| RankS -> 1.2105
cStats.Strength * rankMultiplier * weaponDamage
请注意武器类型的所有详细信息现在如何显示在一个屏幕上?而且重复的情况要少得多。我保留了不同类型的物理武器(匕首、剑等)之间的区别,因为你可能会拥有专门从事一种或两种类型的角色:剑专家不能使用斧头,或者他需要 50%使用斧头时的力量惩罚,等等。但我怀疑你是否会拥有一个只能使用铁匕首而不能使用钢匕首的角色。不同类型的匕首有完全可互换在这种游戏中——如果他们不这样做,玩家会感到非常惊讶。所以它们不应该是不同的类型。以及各种类型的物理武器almost可互换,因此它们的模型也应该尽可能相似。将统计数据放在不不同的部分,并保留类型 (Dagger
, Sword
, Axe
)作为物理武器之间的唯一区别。
这是一个非常长的答案,我仍然没有回答你关于镜头的实际问题!但由于我在查看代码时畏缩并想,“他为自己做了太多的工作”,所以我必须首先解决这部分问题。
我认为您可以将代码移交给https://codereview.stackexchange.com/ https://codereview.stackexchange.com/并要求那里的人看一下并提出加强模型的方法。一旦你的模型得到改进,我想你会发现镜头代码也更容易编写。正如我之前所说,不要尝试自己编写镜头代码!使用类似的库Aether https://xyncro.tech/aether or F#+ https://github.com/gmpl/FSharpPlus来帮你。站在你的立场上,我可能会选择 Aether,因为它比 F#+ 拥有更多的文档; F#+ 似乎(AFAICT)更针对那些已经使用过 Haskell 镜头并且不需要任何关于如何使用它们的提醒的人。
更新1:我建议你如何制作盔甲,还有另一个片段:
type CharacterProtectionStats = {
Defense : float<def>
Resistance : float<res>
Intelligence : float<intel> option
MagicResist : float<mgres>
Speed : float<spd>
EquipmentUsage : int<eu>
}
with
interface IStats with
member x.showStat() =
sprintf "Defense : %O - Resistance : %O - Magic resistance : %O - Speed : %O - Equipment usage : %O" x.Defense x.Resistance x.MagicResist x.Speed x.EquipmentUsage
type CharacterProtectionDetails = {
Name : string
// No Type field here, because that's staying in the DU
ItemDetails : ItemDetails
ArmorStats : CharacterProtectionStats
}
type Hat = Hat of CharacterProtectionDetails
type Armor = Armor of CharacterProtectionDetails
type Pants = Pants of CharacterProtectionDetails
// etc.
type CharacterProtection =
| Shield of Shield
// | Ring of Ring // REMOVED. Rings are different; see below.
| Gloves of Gauntlets
| Legs of Pants
| Armor of Armor
| Helmet of Hat
let sorcererHat = Hat {
Name = "Sorcerer Hat"
ItemDetails = { Weight = 1.0<kg>; Price = 120<usd> }
ArmorStats = { Defense = 1.20<def>; Resistance = 1.30<res>; Intelligence = Some 3.00<intel>; MagicResist = 1.80<mgres>; Speed = 1.00<spd>; EquipmentUsage = 100<eu> }
}
// Other hats...
let steelArmor = Armor.Armor {
Name = "Steel Armor"
ItemDetails = { Weight = 15.0<kg>; Price = 450<usd> }
ArmorStats = { Defense = 17.40<def>; Resistance = 6.10<res>; Intelligence = None; MagicResist = 2.30<mgres>; Speed = 0.945<spd>; EquipmentUsage = 100<eu> }
}
// "Armor.Armor" is kind of ugly, but otherwise it thinks "Armor" is
// CharacterProtection.Armor. If we renamed the CharacterProtection DU
// item to ChestProtection instead, that could help.
type AccessoryStats = {
ExtraStrength : float<str> option
ExtraDamage : float<dmg> option
ExtraHealth : float<hp> option
ExtraMana : float<mp> option
}
with
interface IStats with
member x.showStat() =
sprintf ""
static member Initial =
{ ExtraDamage = None; ExtraStrength = None; ExtraHealth = None; ExtraMana = None }
type Ring = {
Name : string
ItemDetails : ItemDetails
RingStats : AccessoryStats
}
type Amulet = {
Name : string
ItemDetails : ItemDetails
AmuletStats : AccessoryStats
}
type AccessoryItems =
| Ring of Ring
| Amulet of Amulet
// Could add other categories too
let standardRingDetails = { Weight = 0.75<kg>; Price = 275<usd> }
let strengthRing = {
Name = "Extra strength ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraStrength = Some 4.50<str> }
}
let damageRing = {
Name = "Extra damage ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraDamage = Some 5.00<dmg> }
}