Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // WEREWOLFS VS VAMPIRES
- // by Roman Sharkov
- //
- package main
- import (
- "errors"
- "fmt"
- "log"
- "math/rand"
- "time"
- "github.com/Pallinder/go-randomdata"
- )
- /******************************
- CONFIG
- ******************************/
- var confNumWerewolfs = uint(7)
- var confNumVampires = uint(4)
- var confWerewolfHealth = random(400, 750)
- var confWerewolfHitChanceMin = random(15, 20)
- var confWerewolfHitChanceMax = random(20, 30)
- var confWerewolfDodgeChanceMin = random(5, 10)
- var confWerewolfDodgeChanceMax = random(10, 20)
- var confWerewolfAttackStrenghtMin = random(10, 20)
- var confWerewolfAttackStrenghtMax = random(20, 60)
- var confVampireHealth = random(800, 1200)
- var confVampireHitChanceMin = random(25, 35)
- var confVampireHitChanceMax = random(35, 40)
- var confVampireDodgeChanceMin = random(45, 50)
- var confVampireDodgeChanceMax = random(50, 60)
- var confVampireAttackStrenghtMin = random(5, 15)
- var confVampireAttackStrenghtMax = random(15, 30)
- /******************************
- UTILS
- ******************************/
- // FactionWerewolfs identifies the werewolf faction
- const FactionWerewolfs = "werewolfs"
- // FactionVampires identifies the vampire faction
- const FactionVampires = "vampires"
- func init() {
- rand.Seed(time.Now().Unix())
- }
- func random(min, max float64) float64 {
- if min > max {
- panic(fmt.Errorf("min (%.2f) greater max (%.2f)", min, max))
- }
- return min + rand.Float64()*(max-min)
- }
- func randomInt(min, max int) int {
- if min > max {
- panic(fmt.Errorf("min (%d) greater max (%d)", min, max))
- }
- n := max - min
- if n < 1 {
- return 0
- }
- return rand.Intn(n) + min
- }
- // luck returns true if we had luck given the chance percentage
- func luck(chance float64) bool {
- if chance > 100 || chance < 0 {
- panic(fmt.Errorf("invalid chance value: %.2f", chance))
- }
- return random(0, 100) < chance
- }
- // ErrMissed is an error that's returned by Attack when a figher misses
- var ErrMissed = errors.New("missed")
- // ErrDodged is an error that's returned by TakeDamage when a figher dodges
- var ErrDodged = errors.New("dodged")
- // FighterProperties represents common figher properties
- type FighterProperties struct {
- name string
- health float64
- maxHealth float64
- hitChanceMin float64
- hitChanceMax float64
- dodgeChanceMin float64
- dodgeChanceMax float64
- attackStrenghtMin float64
- attackStrenghtMax float64
- stats FighterStats
- }
- // IsAlive implements the Fighter interface
- func (f *FighterProperties) IsAlive() bool {
- return f.health > 0
- }
- // Name implements the Fighter interface
- func (f *FighterProperties) Name() string {
- return f.name
- }
- // Stats implements the Fighter interface
- func (f *FighterProperties) Stats() FighterStats {
- return f.stats
- }
- // Attack implements the Fighter interface
- // Tries to cause damage to the opponent.
- // Returns an error if either missed or opponent dodged
- func (f *FighterProperties) Attack(opponent Fighter) (
- damageCaused float64,
- killed bool,
- err error,
- ) {
- if !luck(random(f.hitChanceMin, f.hitChanceMax)) {
- f.stats.Misses++
- return 0, false, ErrMissed
- }
- damage := random(f.attackStrenghtMin, f.attackStrenghtMax)
- killed, err = opponent.TakeDamage(damage)
- if err != nil {
- f.stats.Misses++
- return 0, false, err
- }
- f.stats.Hits++
- if killed {
- f.stats.Kills++
- }
- f.stats.DamageCaused += damage
- return damage, killed, nil
- }
- // TakeDamage implements the Fighter interface
- func (f *FighterProperties) TakeDamage(damage float64) (
- killed bool,
- err error,
- ) {
- if luck(random(f.dodgeChanceMin, f.dodgeChanceMax)) {
- f.stats.Dodges++
- return false, ErrDodged
- }
- f.health -= damage
- f.stats.DamageTaken += damage
- f.stats.HealthRemaining = f.health
- if f.health < 0 {
- f.stats.HealthRemaining = 0
- f.health = 0
- return true, nil
- }
- return false, nil
- }
- // Props implements the Fighter interface
- func (f *FighterProperties) Props() FighterProperties {
- return *f
- }
- // Fighter represents an abstract fighter
- type Fighter interface {
- Faction() string
- IsAlive() bool
- Attack(Fighter) (damageCaused float64, killed bool, err error)
- TakeDamage(damage float64) (killed bool, err error)
- Name() string
- Props() FighterProperties
- Stats() FighterStats
- }
- /******************************
- WEREWOLF
- ******************************/
- // Werewolf represents a fighting werewolf
- type Werewolf struct {
- *FighterProperties
- }
- // NewWerewolf creates a new randomly parameterized werewolf
- func NewWerewolf() *Werewolf {
- health := confWerewolfHealth
- return &Werewolf{
- FighterProperties: &FighterProperties{
- name: randomdata.SillyName(),
- health: health,
- maxHealth: health,
- hitChanceMin: confWerewolfHitChanceMin,
- hitChanceMax: confWerewolfHitChanceMax,
- dodgeChanceMin: confWerewolfDodgeChanceMin,
- dodgeChanceMax: confWerewolfDodgeChanceMax,
- attackStrenghtMin: confWerewolfAttackStrenghtMin,
- attackStrenghtMax: confWerewolfAttackStrenghtMax,
- },
- }
- }
- // Faction implements the Fighter interface
- func (w *Werewolf) Faction() string {
- return FactionWerewolfs
- }
- /******************************
- VAMPIRE
- ******************************/
- // Vampire represents a fighting vampire
- type Vampire struct {
- *FighterProperties
- gender int
- }
- // NewVampire creates a new randomly parameterized vampire
- func NewVampire() *Vampire {
- gender := randomdata.RandomGender
- health := confVampireHealth
- return &Vampire{
- gender: gender,
- FighterProperties: &FighterProperties{
- // Because vampire names usually end with the noble "von"-form
- name: randomdata.FirstName(gender) +
- " von " +
- randomdata.SillyName(),
- health: health,
- maxHealth: health,
- hitChanceMin: confVampireHitChanceMin,
- hitChanceMax: confVampireHitChanceMax,
- dodgeChanceMin: confVampireDodgeChanceMin,
- dodgeChanceMax: confVampireDodgeChanceMax,
- attackStrenghtMin: confVampireAttackStrenghtMin,
- attackStrenghtMax: confVampireAttackStrenghtMax,
- },
- }
- }
- // Faction implements the Fighter interface
- func (w *Vampire) Faction() string {
- return FactionVampires
- }
- /******************************
- BATTLE
- ******************************/
- // Battle represents a battle between werewolfs and vampires
- type Battle struct {
- initialWerewolfs []Fighter
- initialVampires []Fighter
- werewolfs []Fighter
- vampires []Fighter
- stats *BattleStats
- }
- // NewBattle creates a new battle
- func NewBattle(numWerewolfs, numVampires uint) *Battle {
- b := &Battle{}
- b.werewolfs = make([]Fighter, numWerewolfs)
- for i := range b.werewolfs {
- b.werewolfs[i] = NewWerewolf()
- }
- b.vampires = make([]Fighter, numVampires)
- for i := range b.vampires {
- b.vampires[i] = NewVampire()
- }
- b.initialWerewolfs = make([]Fighter, len(b.werewolfs))
- b.initialVampires = make([]Fighter, len(b.vampires))
- copy(b.initialWerewolfs, b.werewolfs)
- copy(b.initialVampires, b.vampires)
- return b
- }
- // Stats returns the statistics
- func (b *Battle) Stats() BattleStats {
- return b.stats.Clone()
- }
- // Werewolfs returns the list of all werewolfs
- func (b *Battle) Werewolfs() []Fighter {
- return b.initialWerewolfs
- }
- // Vampires returns the list of all vampires
- func (b *Battle) Vampires() []Fighter {
- return b.initialVampires
- }
- func (b *Battle) pickRandomFighter(faction string) (Fighter, int) {
- switch faction {
- case FactionWerewolfs:
- if len(b.werewolfs) < 1 {
- // No more werewolfs left on the battlefield
- return nil, 0
- }
- idx := randomInt(0, len(b.werewolfs)-1)
- return b.werewolfs[idx], idx
- case FactionVampires:
- if len(b.vampires) < 1 {
- // No more vampires left on the battlefield
- return nil, 0
- }
- idx := randomInt(0, len(b.vampires)-1)
- return b.vampires[idx], idx
- default:
- panic(fmt.Errorf("invalid faction: %s", faction))
- }
- }
- // determineWinner returns an empty string if none of the factions won yet
- func (b *Battle) determineWinner() string {
- if len(b.werewolfs) < 1 {
- // No more werewolfs left on the battlefield
- return FactionVampires
- } else if len(b.vampires) < 1 {
- // No more vampires left on the battlefield
- return FactionWerewolfs
- }
- return ""
- }
- func (b *Battle) executeAttackWafe(attackerFaction string) {
- var attackerDesignation string
- var attackedFaction string
- var attackers []Fighter
- switch attackerFaction {
- case FactionWerewolfs:
- attackerDesignation = "werewolf"
- attackedFaction = FactionVampires
- attackers = b.werewolfs
- case FactionVampires:
- attackerDesignation = "vampire"
- attackedFaction = FactionWerewolfs
- attackers = b.vampires
- default:
- panic(fmt.Errorf("invalid faction: %s", attackerFaction))
- }
- removeFighter := func(fromFaction string, id int) {
- switch fromFaction {
- case FactionWerewolfs:
- s := b.werewolfs
- s[len(s)-1], s[id] = s[id], s[len(s)-1]
- b.werewolfs = s[:len(s)-1]
- case FactionVampires:
- s := b.vampires
- s[len(s)-1], s[id] = s[id], s[len(s)-1]
- b.vampires = s[:len(s)-1]
- }
- }
- for _, attacker := range attackers {
- opponent, opponentID := b.pickRandomFighter(attackedFaction)
- if opponent == nil {
- // No more opponent left
- return
- }
- damageTaken, killed, err := attacker.Attack(opponent)
- switch err {
- case ErrDodged:
- b.stats.pushLog(fmt.Sprintf(
- "%s dodged attack of %s %s",
- opponent.Name(),
- attackerDesignation,
- attacker.Name(),
- ))
- case ErrMissed:
- b.stats.pushLog(fmt.Sprintf(
- "%s %s missed %s",
- attackerDesignation,
- attacker.Name(),
- opponent.Name(),
- ))
- case nil:
- if killed {
- // Opponent killed
- b.stats.pushLog(fmt.Sprintf(
- "%s %s struck a fatal blow to %s!",
- attackerDesignation,
- attacker.Name(),
- opponent.Name(),
- ))
- // Remove opponent from the list of fighters
- removeFighter(attackedFaction, opponentID)
- } else {
- // Opponent still lives
- b.stats.pushLog(fmt.Sprintf(
- "Hit! %s %s caused %.2f damage to %s",
- attackerDesignation,
- attacker.Name(),
- damageTaken,
- opponent.Name(),
- ))
- }
- }
- }
- }
- // Run starts the fight and returns the statistics
- func (b *Battle) Run() {
- b.stats = &BattleStats{
- log: make([]BattleLogEntry, 0),
- }
- nextAttacker := FactionWerewolfs
- for {
- winner := b.determineWinner()
- if winner != "" {
- b.stats.winnerFaction = winner
- return
- }
- switch nextAttacker {
- case FactionWerewolfs:
- b.executeAttackWafe(FactionWerewolfs)
- nextAttacker = FactionVampires
- case FactionVampires:
- b.executeAttackWafe(FactionVampires)
- nextAttacker = FactionWerewolfs
- }
- }
- }
- // BattleLogEntry represents a battle log entry
- type BattleLogEntry struct {
- time time.Time
- message string
- }
- // FighterStats represents the statistics of an individual fighter
- type FighterStats struct {
- Misses uint
- Hits uint
- DamageTaken float64
- DamageCaused float64
- HealthRemaining float64
- Kills uint
- Dodges uint
- }
- // BattleStats represents the battle statistics
- type BattleStats struct {
- winnerFaction string
- log []BattleLogEntry
- }
- // Clone returns an exact copy of the battle statistics
- func (bstat *BattleStats) Clone() BattleStats {
- log := make([]BattleLogEntry, len(bstat.log))
- copy(log, bstat.log)
- return BattleStats{
- winnerFaction: bstat.winnerFaction,
- log: log,
- }
- }
- // pushLog pushes a new log entry into the battle statistics
- func (bstat *BattleStats) pushLog(message string) {
- if len(message) < 1 {
- panic(fmt.Errorf("missing battle log message"))
- }
- bstat.log = append(bstat.log, BattleLogEntry{
- time: time.Now(),
- message: message,
- })
- }
- /******************************
- MAIN
- ******************************/
- func main() {
- b := NewBattle(confNumWerewolfs, confNumVampires)
- log.Print("Battle begins...")
- b.Run()
- // Print stats log
- stats := b.Stats()
- fmt.Println(" ")
- for i, entry := range stats.log {
- fmt.Printf("%d: %s\n", i+1, entry.message)
- }
- fmt.Println(" ")
- fmt.Println(stats.winnerFaction + " win!")
- // Print fighter stats
- fighters := b.Werewolfs()
- fighters = append(fighters, b.Vampires()...)
- fmt.Printf("\n Fighter Stats:\n\n")
- for _, fighter := range fighters {
- faction := fighter.Faction()
- props := fighter.Props()
- stats := fighter.Stats()
- fmt.Printf(" %s %s \n", faction, fighter.Name())
- fmt.Printf(
- " Health: %.2f/%.2f\n",
- stats.HealthRemaining,
- stats.HealthRemaining+props.maxHealth,
- )
- fmt.Printf(" Kills: %d\n", stats.Kills)
- fmt.Printf(" Hits: %d\n", stats.Hits)
- fmt.Printf(" Dodges: %d\n", stats.Dodges)
- fmt.Printf(" Misses: %d\n", stats.Misses)
- fmt.Printf(" Damage: %.2f\n", stats.DamageCaused)
- fmt.Printf("\n")
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement