Simulate a battle between two creatures

16

9

Welcome, brave code golfer! Today you will stand in the midst of a great battle between a goblin and an elf!

    goblin attacks elf!
    elf dodges!
    elf attacks goblin!
    elf hits goblin for 13 damage!
    goblin has 37 health left.
    goblin attacks elf!
    goblin hits elf for 1 damage!
    elf has 49 health left.
    elf attacks goblin!
    elf hits goblin for 19 damage!
    goblin has 18 health left.
    goblin attacks elf!
    goblin hits elf for 26 damage!
    elf has 23 health left.
    elf attacks goblin!
    elf hits goblin for 20 damage!
    goblin has been slain!

The Challenge

Your challenge is to simulate a battle, like the above one. You will recieve input in this form:

creatureName health strength defense accuracy agility

For example, the battle between the goblin and elf would be:

goblin 50 40 35 3 2 elf 50 35 30 4 5

The first and second creatures will alternate attacks.

  • Print 'creatureName attacks otherCreatureName!'
  • Check to see if the creature dodges. The creature will dodge an attack if (its agility times rand() divided by 2) is greater than (the attacker's accuracy times rand()).
    • If the creature dodges, print 'creatureName dodges!'
    • Otherwise, calculate the damage dealt by subtracting (the attacker's strength times rand()) and (the defender's defense times rand() divided by 2). Minimum damage is 1. Print 'creatureName hits otherCreatureName for (x) damage!' Then print 'creatureName has (x) health left.' unless the creature's health is 0 or less, in which case...
      • If the creature's health is 0 or less, print 'creatureName has been slain!' and end the program.

Rules

  • Shortest code wins.
  • Don't literally print 'creatureName,' but replace it with the creature's name. Don't print '(x) damage' or '(x) health;' replace them with the actual amount. (I have to specify this because some people are very creative with bending the rules. :P)

Doorknob

Posted 2013-08-05T13:10:59.577

Reputation: 68 138

2Why complicate the spec by halving the agility and defence? – Peter Taylor – 2013-08-05T13:21:06.760

@Peter Because otherwise the battles took too long. – Doorknob – 2013-08-05T13:22:32.673

2My point was: why not change the input e.g. to goblin 50 40 18 3 2 elf 50 35 15 4 5? – Peter Taylor – 2013-08-05T13:30:05.190

@Peter Meh, that just seemed too odd - why would the defense be so much lower? Anyway, it is a puzzle; no reason not to :P – Doorknob – 2013-08-05T13:32:17.793

I think it's not clear whether 'creatureName has (x) health left.' should be printed on the last round (when the creature is slain), and in case it should (i assumed it shouldn't) if (x) should be always 0 or it can be a negative number. – epidemian – 2013-08-06T05:57:02.890

@epid See sample output. – Doorknob – 2013-08-06T06:03:16.433

@Doorknob, yes; i was referring to the battle steps' description. But you're right, it's clear from the sample output. – epidemian – 2013-08-06T06:11:12.120

@epid Yes, the steps were unclear; I tried to clarify them a bit. – Doorknob – 2013-08-06T07:12:05.440

btw, nice challenge =) – C5H8NNaO4 – 2013-08-06T07:55:33.537

I squeezed 4 from my solution by switching to raw_input, but this means the input now needs to be quoted. Not sure if this means I need to add 2 to the score for the quotes or not. – None – 2013-08-07T00:45:20.623

@Lego Meh, some programs are run much more verbosely; I'd consider that as part of the execution. Don't add 2. – Doorknob – 2013-08-07T00:48:51.113

Answers

4

APL (249 244 242)

Procedural style this time, so (in Dyalog at least) you have to paste this into an editor window. I named it G (the first line is the name, I've included it because that's how it shows up in the editor window so it should probably be counted).

G
⎕ML←3
D A←{(⊂↑⍵),⍎¨1↓⍵}¨↓2 6⍴A⊂⍨' '≠A←⍞
→5
⎕←(↑D)'dodges!'
A D←D A
⎕←(↑A)'attacks','!',⍨↑D
→4/⍨>/?A[6],D[5]
⎕←A[1],'hits',D[1],'for','damage!',⍨D[2]-←1⌈-/?A[3],⌈D[4]÷2
→12/⍨D[2]≤0
⎕←D[1],'has',D[2],'health left!'
→5
⎕←D[1],'has been slain!'

(edit: used numeric GOTOs instead of line labels. Turns out that (in Dyalog at least) all defining a label X: does is set X to the line number, so might as well use the numbers directly.)

marinus

Posted 2013-08-05T13:10:59.577

Reputation: 30 224

6

Perl, 254 + 1

for((@a[0..5],@b)=split;$a[1]>0;@c=@a,@a=@b,@b=@c){say"$a[0] attacks $b[0]!\n",rand$b[5]/2<rand$a[4]?do{($==rand($a[2])-rand$b[3]/2)<1and$==1;"$a[0] hits $b[0] for $= damage!\n$b[0] has ",($b[1]-=$=)>0?"$b[1] health left.":"been slain!"}:"$b[0] dodges!"}

Run with perl -nM5.010 (or perl -nE '<code>'). Per meta, the -n switch counts as one extra character. No fancy golfing tricks in this code, except maybe the use of $= as the damage variable to save an int.

Edit: Hopefully, the damage calculation is correct now.

Ilmari Karonen

Posted 2013-08-05T13:10:59.577

Reputation: 19 513

6

Ruby, 292 264

v=$*
def p*a;puts a*' 'end
loop{a,b=v[0],v[6]
w=v.map &:to_i
p a,:attacks,b+?!
w[11]*rand/2>w[4]*rand ? (p b,:dodges!):(h=v[7]=w[7]-=d=[rand(w[2])-rand(w[9])/2,1].max
p a,:hits,b,:for,d,:damage!
p b,:has,h<1?"been slain!":"#{h} health left."
h<1&&exit)
v.rotate!6}

My first code golf entry; how that method definition parses is beyond me :)

Is it OK to read the input from command-line parameters (e.g. ruby battle.rb goblin 50 40 35 3 2 elf 50 35 30 4 5)?

Ideone run

epidemian

Posted 2013-08-05T13:10:59.577

Reputation: 531

Yes, that is okay. – Doorknob – 2013-08-06T05:36:11.613

6

CoffeeScript 454 432

Pass a string to b to get the results. I thought it would be more fun to have a graphic animated output, so I put a demo together. Just change the input box to change statistics and names. The pictures from from jpg.to which is the unofficial google-images API.

Super Action Demo

battle simulator in action

b=(s)->
 r=Math.random
 p=s.split ' '
 n=p.map Number
 l=''
 o=(x...)->l+=x.join(' ')+'!\n'
 while 1|i=!i
  c=(x)->n[i*6+x]*r()
  d=(x)->n[(i||6)+x]*r()
  t=->d(4)/2>c 5
  h=->Math.max c(2)-d(3)/2,1
  a=p[i*6]
  b=p[6+i*-6]
  o a,'attacks',b
  if c(5)/2>d 4
   e=Math.ceil h()
   q=n[(i||6)+1]-=e
   o a,'hits',b,'for',e,'damage'
   o b,'has',q,'health left'if q>0
  else
   o b,'dodges'
  if q<1
   o b,'has been slain'
   break
 l

Recommended Inputs (suggest your own):

charmander 50 40 25 3 2 bulbasaur 90 30 40 4 5
voldemort 9999 10 5 1 1 batman 20 50 10 1010 30

Brigand

Posted 2013-08-05T13:10:59.577

Reputation: 1 137

Wow, that's great! :D I would accept this answer if I could accept two, but I said in the rules that the shortest would be accepted, and I can only accept one answer :( So +1. And voldemort 9999 haha :D – Doorknob – 2013-08-11T03:01:25.443

@Doorknob, as an extra bonus, you can give it numbers like 10e42, but your browser may freeze... – Brigand – 2013-08-11T03:24:00.890

5

JavaScript; 347 341 333

As I always do, I shall start off with my own solution:

for(m=prompt().split(' '),r=Math.random,a=console.log,c=0,d=6;;){if(a(m[c]+' attacks '+m[d]+'!'),r()*m[c+4]>r()*m[d+5]/2){if(a(m[c]+' hits '+m[d]+' for '+(h=Math.max(~~(r()*m[c+2]-r()*m[d+3]/2),1))+' damage!'),(m[d+1]-=h)<1){a(m[d]+' has been slain!');break}a(m[d]+' has '+m[d+1]+' health left.')}else a(m[d]+' dodges!');t=c;c=d;d=t}

EDIT: apparently assigning console.log to a variable breaks on some browsers, so here's the same code with a function declaration instead:

for(m=prompt().split(' '),r=Math.random,a=function(x){console.log(x)},c=0,d=6;;){if(a(m[c]+' attacks '+m[d]+'!'),r()*m[c+4]>r()*m[d+5]/2){if(a(m[c]+' hits '+m[d]+' for '+(h=Math.max(~~(r()*m[c+2]-r()*m[d+3]/2),1))+' damage!'),(m[d+1]-=h)<1){a(m[d]+' has been slain!');break}a(m[d]+' has '+m[d+1]+' health left.')}else a(m[d]+' dodges!');t=c;c=d;d=t}

I was thinking of changing it to alert, but that would just be evil :P

Doorknob

Posted 2013-08-05T13:10:59.577

Reputation: 68 138

i get a Uncaught TypeError: Illegal invocation because of a=console.log. would'nt you have to bind the console object as context, e.g a=console.log.bind(console)? – C5H8NNaO4 – 2013-08-06T06:17:27.787

@C5H Hmm, that's odd. It works for me. – Doorknob – 2013-08-06T06:22:13.213

Just checked it in Firefox,Safari and Chrome, in Firefox it works =) Chrome gives me above, and Safari a TypeError – C5H8NNaO4 – 2013-08-06T06:42:21.383

@C5H :( Maybe I could replace it with alert, but that would be very annoying :P – Doorknob – 2013-08-06T07:09:59.260

:P Yes that would be indeed annoying, but at least it would save "6" characters and avoids the TypeError =) But i guess its Ok, at least it runs on Firefox =) – C5H8NNaO4 – 2013-08-06T07:38:47.167

4

JavaScript: 340 306

339:

for(m=Math.random,a=prompt().split(" "),c=[a,a.splice(6)],e;0<+c[0][1]&&0<+c[1][1];)c.reverse(),console.log(c[0][0]+" attacks "+c[1][0]+"!\n"+(c[0][4]*m()<c[1][5]*m()/2?c[1][0]+" dodges!":c[0][0]+" hits "+c[1][0]+" for "+(e=1+(c[0][2]*m()-c[1][3]*m()/2|0))+" damage!\n"+c[1][0]+" has "+(0>(c[1][1]-=e)?"been slain!":c[1][1]+" health left.")))

306:

for(var m=Math.random,a=prompt().split(" "),b=a.splice(6),d,e;0<+a[1]&&0<+b[1];d=a,a=b,b=d)console.log(a[0]+" attacks "+b[0]+"!\n"+(a[4]*m()<b[5]*m()/2?b[0]+" dodges!":a[0]+" hits "+b[0]+" for "+(e=a[2]*m()-b[3]*m()/2|0,e<=0?e=1:e)+" damage!\n"+b[0]+" has "+(0>(b[1]-=e)?"been slain!":b[1]+" health left.")))

Sample output:

goblin attacks elf!
elf dodges!
elf attacks goblin!
elf hits goblin for 21 damage!
goblin has 29 health left.
goblin attacks elf!
elf dodges!
elf attacks goblin!
elf hits goblin for 15 damage!
goblin has 14 health left.
goblin attacks elf!
goblin hits elf for 1 damage!
elf has 49 health left.
elf attacks goblin!
elf hits goblin for 16 damage!
goblin has been slain!   

Edit notes: +1 character, i missed the "!" after "dodges"
Oh and i forgot to actually put an output after "Sample output"
Changed the header to JavaScript, as @tbodt suggests

C5H8NNaO4

Posted 2013-08-05T13:10:59.577

Reputation: 1 340

Why don't you call it JavaScript to avoid confusing people? – tbodt – 2013-08-06T05:45:31.903

@tbodt I didn't thought it would confuse people =) Changed it to Javascript instead – C5H8NNaO4 – 2013-08-06T07:32:24.403

4

Python: 393

I squeezed a little harder, if I had 3 I could probably do print = p to shorten it by a few more, but I don't think there is much left in this one.

393:

from random import randrange as r
x = input().split()
t=range
a,b=6,0
for i in t(1,6)+t(7,12):x[i]=int(x[i])
while x[b+1] > 0:
 a,b=b,a;print x[a]+" attacks "+x[b]
 if r(x[a+5]/2)>r(x[b+5]):print x[a]+' dodges!';continue
 d=max(r(x[a+2])-r(x[b+3]/2),1);print x[a]," hits ",x[b]," for ",d," damage!";x[b+1]-=d
 if x[b+1]>0:print x[b]," has ",x[b+1]," health left."
print x[b]," has been slain"

399:

from random import randrange as r
x = raw_input().split()
t=range
a,b=6,0
for i in t(1,6)+t(7,12):x[i]=int(x[i])
while x[b+1] > 0:
 a,b=b,a;print x[a]+" attacks "+x[b]
 if r(x[a+5]/2) > r(x[b+5]):print x[a]+' dodges!';continue
 d=max(r(x[a+2])-r(x[b+3]/2),1);print x[a]," hits ",x[b]," for ",d," damage!";x[b+1]-=d                        if x[b+1]>0:print x[b]," has ",x[b+1]," health left."
print x[b]," has been slain"

I think people are skipping that if you have a good condition in the loop, you don't need to check if the target is below health.

user8777

Posted 2013-08-05T13:10:59.577

Reputation:

I check the health twice, in the loop header to end the battle, and in the loop to print either "has been slayed" or "hits ...", because the double health check is shorter than an extra console.log – C5H8NNaO4 – 2013-08-06T12:55:51.963

3

R: 387 characters

a=scan(,"");i=as.integer;r=runif;C=cat;X=data.frame(i(a[2:6]),i(a[8:12]));z=1;Y=c(a[1],a[7]);repeat{p=1+z%%2;n=X[,p];m=X[,-p];N=Y[p];M=Y[-p];C(N,"attacks",M,"\n");if(r(1)*n[5]<r(1)*m[5]/2){C(M,"dodges!\n")}else{C(N,"hits",M,"for",d<-max(round(n[2]*r(1)-m[3]*r(1)/2),1),"damages!\n");h=max(m[1]-d,0);if(h){C(M,"has",X[1,-p]<-h,"health left\n")}else{C(M,"has been slained!");break}};z=z+1}

Or fully-developed, with indentations and comments, to make things clearer:

a=scan(,"")                        # Read stdin as character vector
i=as.integer
r=runif
C=cat
X=data.frame(i(a[2:6]),i(a[8:12])) # Data frame with opponents stats
z=1                                # Turn counter 
Y=c(a[1],a[7])                     # Vector of opponents name
repeat{                            # shorter than while(T)
    p=1+z%%2                       # R indexing starts with 1 not 0
    n=X[,p]                        # Attacking opponent stats
    m=X[,-p]                       # Defending opponent stats
    N=Y[p]                         # Attacking opponent name
    M=Y[-p]                        # Defending opponent name
    C(N,"attacks",M,"\n")          # By default, cat separates elements with a space
    if(r(1)*n[4]<r(1)*m[5]/2){
        C(M,"dodges!\n")
    }else{
        C(N,"hits",M,"for",d<-max(round(n[2]*r(1)-m[3]*r(1)/2),1),"damages!\n")
        h=max(m[1]-d,0)            # Health after the strike
        if(h){                     # If health is not 0
            C(M,"has",X[1,-p]<-h,"health left\n")
        }else{                     # If it is
            C(M,"has been slained!")
            break
            }
    }
    z=z+1
}

plannapus

Posted 2013-08-05T13:10:59.577

Reputation: 8 610

2

C# - 464 453 chars

After a couple of nights on this I can't seem to get even close to the other entries (not that surprising for C#). Still room for improvement I suspect. No especially clever tricks.

using System;using System.Linq;class P{static void Main(string[]a){int D,h=1,z=6;
var n=a.Select(x=>int.TryParse(x,out D)?D:0).ToList();var r=new Random();Func<int>
R=()=>r.Next(101);for(;h>0;z=z>0?0:6){D=(n[8-z]*R()-n[3+z]*R()/2)/100;var d=n[5+z]
*R()/2>n[10-z]*R();h=d?h:n[1+z]-=D=D<1?1:D;Console.Write("{0} attacks {1}!\n"+(d?
"{1} dodges!\n":"{0} hits {1} for {2} damage!\n")+(d?"":"{1} has {3}\n"),a[6-z],a
[z],D,h>0?h+" health left.":"been slain!");}}}

Commented:

using System;
using System.Linq;
class P
{
    static void Main(string[] a)
    {
        int D, // string to int parsing and attack damage 
            h = 1, // health 
            z = 6; // index

        // painful requirement to convert the input strings to integers
        var n = a.Select(x => int.TryParse(x, out D) ? D : 0).ToList();

        // set up a function to return a new random number
        var r = new Random();
        Func<int> R = () => r.Next(101);

        // we'll exit the loop when the defender's health (h) is <= 0.
        // z is used as an index offset to get values out of the list, it flips between 0 an 6 each round
        for (; h > 0; z = z > 0 ? 0 : 6)
        {
            // calculate damage
            D = (n[8 - z] * R() - n[3 + z] * R() / 2) / 100;

            // see if defender dodges
            var d = n[5 + z] * R() / 2 > n[10 - z] * R();

            // subtract health from defender if necessary. store health of defender in h
            h = d ? h : n[1 + z] -= D = D < 1 ? 1 : D;

            // print the round
            Console.Write(
                "{0} attacks {1}!\n" + (d ? "{1} dodges!\n" : "{0} hits {1} for {2} damage!\n") + 
                (d ? "" : "{1} has {3}\n"), a[6 - z], a[z], D, h > 0 ? h + " health left." : "been slain!");
        }
    }
}

Igby Largeman

Posted 2013-08-05T13:10:59.577

Reputation: 353

1

Python 3, 314

from random import*
r=random
p=print
I=input().split()
H=1
while H>0:s,a,H,D,G=map(int,I[2:5:2]+I[7::2]);N=I[6];p(I[0],"attacks",N+"!");I=I[6:]+I[:6];X=max(int(s*r()-D*r()/2),1)*(G*r()/2<a*r());I[1]=H=H-X;X<1and p(N,"dodges!")or p(I[6],"hits",N,"for",X,"damage!\n"+N,"has",["been slain!","%s health left."%H][H>0])

Reinstate Monica

Posted 2013-08-05T13:10:59.577

Reputation: 929