16
\$\begingroup\$

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)
\$\endgroup\$
11
  • 2
    \$\begingroup\$ Why complicate the spec by halving the agility and defence? \$\endgroup\$ Aug 5, 2013 at 13:21
  • \$\begingroup\$ @Peter Because otherwise the battles took too long. \$\endgroup\$
    – Doorknob
    Aug 5, 2013 at 13:22
  • 2
    \$\begingroup\$ My point was: why not change the input e.g. to goblin 50 40 18 3 2 elf 50 35 15 4 5? \$\endgroup\$ Aug 5, 2013 at 13:30
  • \$\begingroup\$ @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 \$\endgroup\$
    – Doorknob
    Aug 5, 2013 at 13:32
  • \$\begingroup\$ 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. \$\endgroup\$
    – epidemian
    Aug 6, 2013 at 5:57

10 Answers 10

6
\$\begingroup\$

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.

\$\endgroup\$
6
\$\begingroup\$

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
\$\endgroup\$
2
  • \$\begingroup\$ 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 \$\endgroup\$
    – Doorknob
    Aug 11, 2013 at 3:01
  • \$\begingroup\$ @Doorknob, as an extra bonus, you can give it numbers like 10e42, but your browser may freeze... \$\endgroup\$
    – Brigand
    Aug 11, 2013 at 3:24
6
\$\begingroup\$

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

\$\endgroup\$
1
  • \$\begingroup\$ Yes, that is okay. \$\endgroup\$
    – Doorknob
    Aug 6, 2013 at 5:36
5
\$\begingroup\$

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

\$\endgroup\$
5
  • \$\begingroup\$ 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)? \$\endgroup\$
    – C5H8NNaO4
    Aug 6, 2013 at 6:17
  • \$\begingroup\$ @C5H Hmm, that's odd. It works for me. \$\endgroup\$
    – Doorknob
    Aug 6, 2013 at 6:22
  • \$\begingroup\$ Just checked it in Firefox,Safari and Chrome, in Firefox it works =) Chrome gives me above, and Safari a TypeError \$\endgroup\$
    – C5H8NNaO4
    Aug 6, 2013 at 6:42
  • \$\begingroup\$ @C5H :( Maybe I could replace it with alert, but that would be very annoying :P \$\endgroup\$
    – Doorknob
    Aug 6, 2013 at 7:09
  • \$\begingroup\$ :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 =) \$\endgroup\$
    – C5H8NNaO4
    Aug 6, 2013 at 7:38
4
\$\begingroup\$

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.)

\$\endgroup\$
4
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ 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 \$\endgroup\$
    – C5H8NNaO4
    Aug 6, 2013 at 12:55
4
\$\begingroup\$

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

\$\endgroup\$
2
  • \$\begingroup\$ Why don't you call it JavaScript to avoid confusing people? \$\endgroup\$
    – tbodt
    Aug 6, 2013 at 5:45
  • \$\begingroup\$ @tbodt I didn't thought it would confuse people =) Changed it to Javascript instead \$\endgroup\$
    – C5H8NNaO4
    Aug 6, 2013 at 7:32
3
\$\begingroup\$

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
}
\$\endgroup\$
2
\$\begingroup\$

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!");
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

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])
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.