Advertisement
Guest User

Untitled

a guest
Jul 20th, 2019
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.34 KB | None | 0 0
  1. //
  2. // WEREWOLFS VS VAMPIRES
  3. // by Roman Sharkov
  4. //
  5. package main
  6.  
  7. import (
  8. "errors"
  9. "fmt"
  10. "log"
  11. "math/rand"
  12. "time"
  13.  
  14. "github.com/Pallinder/go-randomdata"
  15. )
  16.  
  17. /******************************
  18. CONFIG
  19. ******************************/
  20.  
  21. var confNumWerewolfs = uint(7)
  22. var confNumVampires = uint(4)
  23.  
  24. var confWerewolfHealth = random(400, 750)
  25. var confWerewolfHitChanceMin = random(15, 20)
  26. var confWerewolfHitChanceMax = random(20, 30)
  27. var confWerewolfDodgeChanceMin = random(5, 10)
  28. var confWerewolfDodgeChanceMax = random(10, 20)
  29. var confWerewolfAttackStrenghtMin = random(10, 20)
  30. var confWerewolfAttackStrenghtMax = random(20, 60)
  31.  
  32. var confVampireHealth = random(800, 1200)
  33. var confVampireHitChanceMin = random(25, 35)
  34. var confVampireHitChanceMax = random(35, 40)
  35. var confVampireDodgeChanceMin = random(45, 50)
  36. var confVampireDodgeChanceMax = random(50, 60)
  37. var confVampireAttackStrenghtMin = random(5, 15)
  38. var confVampireAttackStrenghtMax = random(15, 30)
  39.  
  40. /******************************
  41. UTILS
  42. ******************************/
  43.  
  44. // FactionWerewolfs identifies the werewolf faction
  45. const FactionWerewolfs = "werewolfs"
  46.  
  47. // FactionVampires identifies the vampire faction
  48. const FactionVampires = "vampires"
  49.  
  50. func init() {
  51. rand.Seed(time.Now().Unix())
  52. }
  53.  
  54. func random(min, max float64) float64 {
  55. if min > max {
  56. panic(fmt.Errorf("min (%.2f) greater max (%.2f)", min, max))
  57. }
  58. return min + rand.Float64()*(max-min)
  59. }
  60.  
  61. func randomInt(min, max int) int {
  62. if min > max {
  63. panic(fmt.Errorf("min (%d) greater max (%d)", min, max))
  64. }
  65. n := max - min
  66. if n < 1 {
  67. return 0
  68. }
  69. return rand.Intn(n) + min
  70. }
  71.  
  72. // luck returns true if we had luck given the chance percentage
  73. func luck(chance float64) bool {
  74. if chance > 100 || chance < 0 {
  75. panic(fmt.Errorf("invalid chance value: %.2f", chance))
  76. }
  77. return random(0, 100) < chance
  78. }
  79.  
  80. // ErrMissed is an error that's returned by Attack when a figher misses
  81. var ErrMissed = errors.New("missed")
  82.  
  83. // ErrDodged is an error that's returned by TakeDamage when a figher dodges
  84. var ErrDodged = errors.New("dodged")
  85.  
  86. // FighterProperties represents common figher properties
  87. type FighterProperties struct {
  88. name string
  89. health float64
  90. maxHealth float64
  91. hitChanceMin float64
  92. hitChanceMax float64
  93. dodgeChanceMin float64
  94. dodgeChanceMax float64
  95. attackStrenghtMin float64
  96. attackStrenghtMax float64
  97. stats FighterStats
  98. }
  99.  
  100. // IsAlive implements the Fighter interface
  101. func (f *FighterProperties) IsAlive() bool {
  102. return f.health > 0
  103. }
  104.  
  105. // Name implements the Fighter interface
  106. func (f *FighterProperties) Name() string {
  107. return f.name
  108. }
  109.  
  110. // Stats implements the Fighter interface
  111. func (f *FighterProperties) Stats() FighterStats {
  112. return f.stats
  113. }
  114.  
  115. // Attack implements the Fighter interface
  116. // Tries to cause damage to the opponent.
  117. // Returns an error if either missed or opponent dodged
  118. func (f *FighterProperties) Attack(opponent Fighter) (
  119. damageCaused float64,
  120. killed bool,
  121. err error,
  122. ) {
  123. if !luck(random(f.hitChanceMin, f.hitChanceMax)) {
  124. f.stats.Misses++
  125. return 0, false, ErrMissed
  126. }
  127. damage := random(f.attackStrenghtMin, f.attackStrenghtMax)
  128. killed, err = opponent.TakeDamage(damage)
  129. if err != nil {
  130. f.stats.Misses++
  131. return 0, false, err
  132. }
  133.  
  134. f.stats.Hits++
  135. if killed {
  136. f.stats.Kills++
  137. }
  138. f.stats.DamageCaused += damage
  139.  
  140. return damage, killed, nil
  141. }
  142.  
  143. // TakeDamage implements the Fighter interface
  144. func (f *FighterProperties) TakeDamage(damage float64) (
  145. killed bool,
  146. err error,
  147. ) {
  148. if luck(random(f.dodgeChanceMin, f.dodgeChanceMax)) {
  149. f.stats.Dodges++
  150. return false, ErrDodged
  151. }
  152. f.health -= damage
  153. f.stats.DamageTaken += damage
  154. f.stats.HealthRemaining = f.health
  155. if f.health < 0 {
  156. f.stats.HealthRemaining = 0
  157. f.health = 0
  158. return true, nil
  159. }
  160. return false, nil
  161. }
  162.  
  163. // Props implements the Fighter interface
  164. func (f *FighterProperties) Props() FighterProperties {
  165. return *f
  166. }
  167.  
  168. // Fighter represents an abstract fighter
  169. type Fighter interface {
  170. Faction() string
  171. IsAlive() bool
  172. Attack(Fighter) (damageCaused float64, killed bool, err error)
  173. TakeDamage(damage float64) (killed bool, err error)
  174. Name() string
  175. Props() FighterProperties
  176. Stats() FighterStats
  177. }
  178.  
  179. /******************************
  180. WEREWOLF
  181. ******************************/
  182.  
  183. // Werewolf represents a fighting werewolf
  184. type Werewolf struct {
  185. *FighterProperties
  186. }
  187.  
  188. // NewWerewolf creates a new randomly parameterized werewolf
  189. func NewWerewolf() *Werewolf {
  190. health := confWerewolfHealth
  191. return &Werewolf{
  192. FighterProperties: &FighterProperties{
  193. name: randomdata.SillyName(),
  194. health: health,
  195. maxHealth: health,
  196. hitChanceMin: confWerewolfHitChanceMin,
  197. hitChanceMax: confWerewolfHitChanceMax,
  198. dodgeChanceMin: confWerewolfDodgeChanceMin,
  199. dodgeChanceMax: confWerewolfDodgeChanceMax,
  200. attackStrenghtMin: confWerewolfAttackStrenghtMin,
  201. attackStrenghtMax: confWerewolfAttackStrenghtMax,
  202. },
  203. }
  204. }
  205.  
  206. // Faction implements the Fighter interface
  207. func (w *Werewolf) Faction() string {
  208. return FactionWerewolfs
  209. }
  210.  
  211. /******************************
  212. VAMPIRE
  213. ******************************/
  214.  
  215. // Vampire represents a fighting vampire
  216. type Vampire struct {
  217. *FighterProperties
  218. gender int
  219. }
  220.  
  221. // NewVampire creates a new randomly parameterized vampire
  222. func NewVampire() *Vampire {
  223. gender := randomdata.RandomGender
  224. health := confVampireHealth
  225. return &Vampire{
  226. gender: gender,
  227. FighterProperties: &FighterProperties{
  228. // Because vampire names usually end with the noble "von"-form
  229. name: randomdata.FirstName(gender) +
  230. " von " +
  231. randomdata.SillyName(),
  232. health: health,
  233. maxHealth: health,
  234. hitChanceMin: confVampireHitChanceMin,
  235. hitChanceMax: confVampireHitChanceMax,
  236. dodgeChanceMin: confVampireDodgeChanceMin,
  237. dodgeChanceMax: confVampireDodgeChanceMax,
  238. attackStrenghtMin: confVampireAttackStrenghtMin,
  239. attackStrenghtMax: confVampireAttackStrenghtMax,
  240. },
  241. }
  242. }
  243.  
  244. // Faction implements the Fighter interface
  245. func (w *Vampire) Faction() string {
  246. return FactionVampires
  247. }
  248.  
  249. /******************************
  250. BATTLE
  251. ******************************/
  252.  
  253. // Battle represents a battle between werewolfs and vampires
  254. type Battle struct {
  255. initialWerewolfs []Fighter
  256. initialVampires []Fighter
  257. werewolfs []Fighter
  258. vampires []Fighter
  259. stats *BattleStats
  260. }
  261.  
  262. // NewBattle creates a new battle
  263. func NewBattle(numWerewolfs, numVampires uint) *Battle {
  264. b := &Battle{}
  265.  
  266. b.werewolfs = make([]Fighter, numWerewolfs)
  267. for i := range b.werewolfs {
  268. b.werewolfs[i] = NewWerewolf()
  269. }
  270.  
  271. b.vampires = make([]Fighter, numVampires)
  272. for i := range b.vampires {
  273. b.vampires[i] = NewVampire()
  274. }
  275.  
  276. b.initialWerewolfs = make([]Fighter, len(b.werewolfs))
  277. b.initialVampires = make([]Fighter, len(b.vampires))
  278. copy(b.initialWerewolfs, b.werewolfs)
  279. copy(b.initialVampires, b.vampires)
  280.  
  281. return b
  282. }
  283.  
  284. // Stats returns the statistics
  285. func (b *Battle) Stats() BattleStats {
  286. return b.stats.Clone()
  287. }
  288.  
  289. // Werewolfs returns the list of all werewolfs
  290. func (b *Battle) Werewolfs() []Fighter {
  291. return b.initialWerewolfs
  292. }
  293.  
  294. // Vampires returns the list of all vampires
  295. func (b *Battle) Vampires() []Fighter {
  296. return b.initialVampires
  297. }
  298.  
  299. func (b *Battle) pickRandomFighter(faction string) (Fighter, int) {
  300. switch faction {
  301. case FactionWerewolfs:
  302. if len(b.werewolfs) < 1 {
  303. // No more werewolfs left on the battlefield
  304. return nil, 0
  305. }
  306. idx := randomInt(0, len(b.werewolfs)-1)
  307. return b.werewolfs[idx], idx
  308. case FactionVampires:
  309. if len(b.vampires) < 1 {
  310. // No more vampires left on the battlefield
  311. return nil, 0
  312. }
  313. idx := randomInt(0, len(b.vampires)-1)
  314. return b.vampires[idx], idx
  315. default:
  316. panic(fmt.Errorf("invalid faction: %s", faction))
  317. }
  318. }
  319.  
  320. // determineWinner returns an empty string if none of the factions won yet
  321. func (b *Battle) determineWinner() string {
  322. if len(b.werewolfs) < 1 {
  323. // No more werewolfs left on the battlefield
  324. return FactionVampires
  325. } else if len(b.vampires) < 1 {
  326. // No more vampires left on the battlefield
  327. return FactionWerewolfs
  328. }
  329. return ""
  330. }
  331.  
  332. func (b *Battle) executeAttackWafe(attackerFaction string) {
  333. var attackerDesignation string
  334. var attackedFaction string
  335. var attackers []Fighter
  336. switch attackerFaction {
  337. case FactionWerewolfs:
  338. attackerDesignation = "werewolf"
  339. attackedFaction = FactionVampires
  340. attackers = b.werewolfs
  341. case FactionVampires:
  342. attackerDesignation = "vampire"
  343. attackedFaction = FactionWerewolfs
  344. attackers = b.vampires
  345. default:
  346. panic(fmt.Errorf("invalid faction: %s", attackerFaction))
  347. }
  348.  
  349. removeFighter := func(fromFaction string, id int) {
  350. switch fromFaction {
  351. case FactionWerewolfs:
  352. s := b.werewolfs
  353. s[len(s)-1], s[id] = s[id], s[len(s)-1]
  354. b.werewolfs = s[:len(s)-1]
  355. case FactionVampires:
  356. s := b.vampires
  357. s[len(s)-1], s[id] = s[id], s[len(s)-1]
  358. b.vampires = s[:len(s)-1]
  359. }
  360. }
  361.  
  362. for _, attacker := range attackers {
  363. opponent, opponentID := b.pickRandomFighter(attackedFaction)
  364. if opponent == nil {
  365. // No more opponent left
  366. return
  367. }
  368. damageTaken, killed, err := attacker.Attack(opponent)
  369. switch err {
  370. case ErrDodged:
  371. b.stats.pushLog(fmt.Sprintf(
  372. "%s dodged attack of %s %s",
  373. opponent.Name(),
  374. attackerDesignation,
  375. attacker.Name(),
  376. ))
  377. case ErrMissed:
  378. b.stats.pushLog(fmt.Sprintf(
  379. "%s %s missed %s",
  380. attackerDesignation,
  381. attacker.Name(),
  382. opponent.Name(),
  383. ))
  384. case nil:
  385. if killed {
  386. // Opponent killed
  387. b.stats.pushLog(fmt.Sprintf(
  388. "%s %s struck a fatal blow to %s!",
  389. attackerDesignation,
  390. attacker.Name(),
  391. opponent.Name(),
  392. ))
  393. // Remove opponent from the list of fighters
  394. removeFighter(attackedFaction, opponentID)
  395. } else {
  396. // Opponent still lives
  397. b.stats.pushLog(fmt.Sprintf(
  398. "Hit! %s %s caused %.2f damage to %s",
  399. attackerDesignation,
  400. attacker.Name(),
  401. damageTaken,
  402. opponent.Name(),
  403. ))
  404. }
  405. }
  406. }
  407. }
  408.  
  409. // Run starts the fight and returns the statistics
  410. func (b *Battle) Run() {
  411. b.stats = &BattleStats{
  412. log: make([]BattleLogEntry, 0),
  413. }
  414. nextAttacker := FactionWerewolfs
  415. for {
  416. winner := b.determineWinner()
  417. if winner != "" {
  418. b.stats.winnerFaction = winner
  419. return
  420. }
  421. switch nextAttacker {
  422. case FactionWerewolfs:
  423. b.executeAttackWafe(FactionWerewolfs)
  424. nextAttacker = FactionVampires
  425. case FactionVampires:
  426. b.executeAttackWafe(FactionVampires)
  427. nextAttacker = FactionWerewolfs
  428. }
  429. }
  430. }
  431.  
  432. // BattleLogEntry represents a battle log entry
  433. type BattleLogEntry struct {
  434. time time.Time
  435. message string
  436. }
  437.  
  438. // FighterStats represents the statistics of an individual fighter
  439. type FighterStats struct {
  440. Misses uint
  441. Hits uint
  442. DamageTaken float64
  443. DamageCaused float64
  444. HealthRemaining float64
  445. Kills uint
  446. Dodges uint
  447. }
  448.  
  449. // BattleStats represents the battle statistics
  450. type BattleStats struct {
  451. winnerFaction string
  452. log []BattleLogEntry
  453. }
  454.  
  455. // Clone returns an exact copy of the battle statistics
  456. func (bstat *BattleStats) Clone() BattleStats {
  457. log := make([]BattleLogEntry, len(bstat.log))
  458. copy(log, bstat.log)
  459. return BattleStats{
  460. winnerFaction: bstat.winnerFaction,
  461. log: log,
  462. }
  463. }
  464.  
  465. // pushLog pushes a new log entry into the battle statistics
  466. func (bstat *BattleStats) pushLog(message string) {
  467. if len(message) < 1 {
  468. panic(fmt.Errorf("missing battle log message"))
  469. }
  470. bstat.log = append(bstat.log, BattleLogEntry{
  471. time: time.Now(),
  472. message: message,
  473. })
  474. }
  475.  
  476. /******************************
  477. MAIN
  478. ******************************/
  479.  
  480. func main() {
  481. b := NewBattle(confNumWerewolfs, confNumVampires)
  482. log.Print("Battle begins...")
  483. b.Run()
  484.  
  485. // Print stats log
  486. stats := b.Stats()
  487. fmt.Println(" ")
  488. for i, entry := range stats.log {
  489. fmt.Printf("%d: %s\n", i+1, entry.message)
  490. }
  491. fmt.Println(" ")
  492. fmt.Println(stats.winnerFaction + " win!")
  493.  
  494. // Print fighter stats
  495. fighters := b.Werewolfs()
  496. fighters = append(fighters, b.Vampires()...)
  497.  
  498. fmt.Printf("\n Fighter Stats:\n\n")
  499. for _, fighter := range fighters {
  500. faction := fighter.Faction()
  501. props := fighter.Props()
  502. stats := fighter.Stats()
  503. fmt.Printf(" %s %s \n", faction, fighter.Name())
  504. fmt.Printf(
  505. " Health: %.2f/%.2f\n",
  506. stats.HealthRemaining,
  507. stats.HealthRemaining+props.maxHealth,
  508. )
  509. fmt.Printf(" Kills: %d\n", stats.Kills)
  510. fmt.Printf(" Hits: %d\n", stats.Hits)
  511. fmt.Printf(" Dodges: %d\n", stats.Dodges)
  512. fmt.Printf(" Misses: %d\n", stats.Misses)
  513. fmt.Printf(" Damage: %.2f\n", stats.DamageCaused)
  514. fmt.Printf("\n")
  515. }
  516. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement