16
\$\begingroup\$

Related

Goal:

Given two creatures with optional combat abilities, return unique but consistent values that represent which creatures died, if any.

Input:

#Longest form:
[[P,T, "<abilities>"], [P,T, "<abilities>"]]
#Shortest form:
[[P,T], [P,T]]

Each creature will be given in the form of [P,T,"<abilities>"]. It will be in the form [P,T], [P,T,""], or [P,T,0] if it has no abilities, your choice on form. P is an integer >=0, T is an integer >=1. <abilities> is a subset of "DFI", or can be represented via a single number/bitstring if you wish. Order of the flags is also up to you.

Combat Mechanics:

Each creature has two stats, Power and Toughness in that order, and optional abilities. A creature's power is >=0. A creature's Toughness is >=1.

Each creature will simultaneously do damage equal to its power to the opposing creature (unless one has first-strike). If the value is greater than or equal to the opponent's toughness, it will die (unless it is indestructible).

Example: Alice is a 2/2, Bob is a 3/4, both with no abilities. Alice will do 2 damage to Bob and take 3 damage in return. Alice's toughness is 2 so it will die, Bob's toughness is 4 so it will live.

There are only 3 optional abilities we will consider for this (although there are more in the game). These will be one character flags:

  • [D]eathtouch: Any amount of damage (X>0) is considered lethal.
  • [F]irst Strike: Will deal its damage first, able to kill the other creature before it can attack back. If both creatures have First Strike, Resolve combat as normal.
  • [I]ndestructible: No amount of damage is considered lethal, including Deathtouch.

Output:

Any consistent value for each of the following four cases. State the four values in your answer, please. Example return value in parens:

  • Neither creature died (0)
  • 1st creature died (1)
  • 2nd creature died (2)
  • Both creatures died (3)

Rules:

  • Input is guaranteed to have two correctly formatted creatures.
  • If you are using characters for abilities, you can assume they're ordered how you want but post the order used if relevant.
  • If you are using a number/bitstring for abilities, post what encoding you're using. e.g.: 111 is D/F/I, 7 is D/F/I, etc.
  • If a creature has no abilities, It can also be taken as [P,T, ""] or number equivalent
  • Standard Loopholes Forbidden
  • This is so shortest code wins.

Examples:

Input: [[2,2], [1,1]]
Output: 2nd Dies
    
Input: [[0,2], [0,1]] #0/2 vs 0/1
Output: Neither Die
    
Input: [[2,1], [2,1]] #2/1 vs 2/1
Output: Both Die

Input: [[1,1, "D"], [2,2]] #1/1 Deathtoucher vs 2/2 
Output: Both Die

Input: [[2,2], [0,1, "D"]] #2/2 vs 0/1 Deathtoucher
Output: 2nd Dies

Input: [[2,2], [1,1, "DF"]] #2/2 vs 1/1 Deathtouch First-striker 
Output: 1st Dies
    
Input: [[0,2, "D"], [0,1, "DF"]] #0/2 Deathtoucher vs 0/1 Deathtouch First-striker
Output: Neither Die

Input: [[2,2], [2,2, "F"]] #2/2 vs 2/2 First-striker
Output: 1st Dies

Input: [[2,2, "I"], [1,1, "DF"]] #2/2 Indestructible vs 1/1 Deathtouch First-striker
Output: 2nd Dies

Input: [[9999,9999], [1,1, "I"]] #9999/9999 vs 1/1 Indestructible
Output: Neither Die

Input: [[2,2, "F"], [1,1, "F"]] #2/2 First-Striker vs 1/1 First-Striker
Output: 2nd Dies

#9/9 Deathtouch, Indestructible First-Striker vs 9/9 Deathtouch, Indestructible First-Striker
Input: [[9,9, "DFI"], [9,9, "DFI"]] 
Output: Neither Die
\$\endgroup\$
12
  • 1
    \$\begingroup\$ @user71546 Yeah. There's a bit more rules involved but in MtG, "Can'ts" trump "Cans." So functionally, Indestructible ignores Deathstrike. Edited that in to be more explicit \$\endgroup\$
    – Veskah
    Aug 25, 2018 at 0:29
  • 1
    \$\begingroup\$ @fəˈnɛtɪk, it still takes damage, it just doesn't die from it. Mind you, the question misstates the rule too. It should be "[Indestructible] permanents aren’t destroyed by lethal damage, and they ignore the state-based action that checks for lethal damage". \$\endgroup\$ Aug 25, 2018 at 9:15
  • 4
    \$\begingroup\$ "If a creature has no abilities, It must be parsed as [P,T]. [P,T,""] is not valid" is a bad rule. It discriminates against languages with strong typing for no benefit. \$\endgroup\$ Aug 25, 2018 at 9:16
  • 2
    \$\begingroup\$ @PeterTaylor I want to keep jagged arrays but you're right that it doesn't make it better. Thus the rule has been removed \$\endgroup\$
    – Veskah
    Aug 25, 2018 at 9:59
  • 1
    \$\begingroup\$ @Veskah Can I take "D", "F", "I" as numbers? D => 0, F => 1, I => 2 \$\endgroup\$ Aug 25, 2018 at 12:44

7 Answers 7

6
\$\begingroup\$

Perl 5, 248 bytes

...without spaces and newlines:

sub c{eval'
(P,T,A,p,t,a)=@_;
     A=~/F/&&a!~/F/&&a!~/I/ ? c( P,2e9,A=~s/F//r,p,t, a         )
    :a=~/F/&&A!~/F/&&A!~/I/ ? c( P,T, A,        p,2e9,a=~s/F//r )
    : do{
        P=1e9 ifA=~/D/&&P>0;
        p=1e9 ifa=~/D/&&p>0;
        T=3e9 ifA=~/I/;
        t=3e9 ifa=~/I/;
        T-=p;
        t-=P;
        T>0&&t>0  ? 0
            : T>0 ? 2
            : t>0 ? 1
            :       3
}'=~s,[pta],\$$&,gri }

Try it online!

My ungolfed version with the ten tests from @Veskah (OP), tests passes:

sub co { #combat
    my($p1,$t1,$a1, $p2,$t2,$a2)=@_; #p=power, t=toughness, a=abilities
    $a1=~s/F// and $a2=~s/F// if "$a1$a2"=~/F.*F/; #both F, no F
    return co($p1,2e9,$a1=~s/F//r, $p2,$t2,$a2        ) if $a1=~/F/ && $a2!~/I/;
    return co($p1,$t1,$a1,         $p2,2e9,$a2=~s/F//r) if $a2=~/F/ && $a1!~/I/;
    $p1=1e9 if $a1=~/D/ and $p1>0;
    $p2=1e9 if $a2=~/D/ and $p2>0;
    $t1=3e9 if $a1=~/I/;
    $t2=3e9 if $a2=~/I/;
    $t1-=$p2;
    $t2-=$p1;
    $t1<=0 && $t2<=0 ? "Both Die"
   :$t1<=0           ? "1st Dies"
   :$t2<=0           ? "2nd Dies"
                     : "Neither Die"
}

my @test=map{[/Input: .*? (\d+),(\d+)(?:,\s*"([FDI]+)")?
                      .*? (\d+),(\d+)(?:,\s*"([FDI]+)")?
           .*? Output: \s* (1st.Dies|2nd.Dies|Both.Die|Neither.Die)? /xsi]}
         split/\n\n/,join"",<DATA>;
my $t=0;
for(@test){ $t++;
  my $r=co(@$_);#result
  $r=~s,0,Neither Die,; $r=~s,3,Both Die,;
  print $$_[-1]=~/^$r/
    ? "Ok $t\n"
    : "Not ok, combat $t --> $r, wrong! (".join(",",@$_).")\n"
}
__DATA__
Input: [[2,2], [1,1]]
Output: 2nd Dies

Input: [[0,2], [0,1]] #0/2 vs 0/1
Output: Neither Die

Input: [[2,1], [2,1]] #2/1 vs 2/1
Output: Both Die

Input: [[1,1, "D"], [2,2]] #1/1 Deathtoucher vs 2/2
Output: Both Die

Input: [[2,2], [0,1, "D"]] #2/2 vs 0/1 Deathtoucher
Output: 2nd Dies

Input: [[2,2], [1,1, "DF"]] #2/2 vs 1/1 First-strike, Deathtoucher
Output: 1st Dies

Input: [[2,2], [2,2, "F"]] #2/2 vs 2/2 First-striker
Output: 1st Dies

Input: [[2,2, "I"], [1,1, "DF"]] #2/2 Indestructible vs 1/1 First-strike, Deatht.
Output: 2nd Dies

Input: [[99999,99999], [1,1, "I"]] #99999/99999 vs 1/1 Indestructible
Output: Neither Die

Input: [[2,2, "F"], [1,1, "F"]] #2/2 First-Striker vs 1/1 First-Striker
Output: 2nd Dies
\$\endgroup\$
4
\$\begingroup\$

JavaScript, 137 125 120 111 bytes

i=>(k=(a,b)=>!(b[2]%2)&&a[0]/(a[2]<=3)>=b[1],[c,d]=i,g=c[2]&2,h=k(c,d),j=k(d,c),d[2]&2-g&&(g?h&&2:j&&1)||j+2*h)

I am using bitmap numbers for abilities D=4 F=2 I=1 do "DFI" would be 7. My output is Neither Died 0, 1st Died 1, 2nd Died 2, Both died 3.

Tests with:

f([[2, 2, 0], [1,1, 0]]); // 2
f([[0, 2, 0], [0,1, 0]]); // 0
f([[2, 1, 0], [2,1, 0]]); // 3
f([[1, 1, 4], [2,2, 0]]); // 3
f([[2, 2, 0], [0,1, 4]]); // 2
f([[2, 2, 0], [1,1, 6]]); // 1
f([[2, 2, 0], [2,2, 2]]); // 1
f([[2, 2, 1], [1,1, 6]]); // 2
f([[99999, 99999, 0], [1,1, 1]]); // 0
f([[2, 2, 2], [1,1, 2]]); // 2)

This was my my first working code

const kills = (c1, c2) => { // Return true if c1 kills c2
    if (c2[2] % 2) {
        console.log("Indestructible");
        return false;
    }
    const c1p = c1[0] / (c1[2] <= 3); // Infinity if Deathtoucher && P > 0
    const c2t = c2[1];
    return c1p >= c2t;
}
const f = (input) => {
    console.log("Match:", input);
    const [c1, c2] = input;
    const f1 = (c1[2] & 2);
    const f2 = (c2[2] & 2);
    if (f2 !== f1) {
        if (f1) {
            if (kills(c1, c2)) {
                console.log("c1 killed c2 in first round");
                return 2;
            }
        } else {
            if (kills(c2, c1)) {
                console.log("c2 killed c1 in first round");
                return 1;
            }
        }
    }
    return kills(c2, c1) + 2 * kills(c1, c2);
};

Which I reduced to this intermediate:

const f = i => {
    const k = (a, b) => !(b[2] % 2) && a[0] / (a[2] <= 3) >= b[1];
    const [c, d] = i;
    const g = c[2] & 2;
    const h = k(c, d);
    const j = k(d, c);
    return d[2] & 2 - g &&
        (g  ? h && 2
            : j && 1
        ) || j + 2 * h
}
\$\endgroup\$
4
  • \$\begingroup\$ Welcome to PPCG! And very nice first solution :) I can see some potential for further golfing but I'm on my phone, after a few beers so can't test properly. \$\endgroup\$
    – Shaggy
    Aug 25, 2018 at 20:54
  • \$\begingroup\$ Here's a quick 7 byte saving, though: tio.run/##bc/RbsIgFAbg@z0FuxgBd7RwNEu2SPcgjERKtak1ZVHjle/… \$\endgroup\$
    – Shaggy
    Aug 25, 2018 at 21:00
  • \$\begingroup\$ @Shaggy. Nice one! Of course the comma operator - what a noob I am. \$\endgroup\$
    – James
    Aug 25, 2018 at 22:04
  • 1
    \$\begingroup\$ We were all new once :) \$\endgroup\$
    – Shaggy
    Aug 25, 2018 at 22:06
3
\$\begingroup\$

JavaScript (ES6), 83 76 bytes

Takes input as 6 distinct arguments: 2 x (Power, Toughness, Abilities). Abilities are expected as bitmasks with:

  • \$1\$ = Death Touch
  • \$2\$ = First Strike
  • \$4\$ = Indestructible

Returns \$0\$ for Neither Die, \$1\$ for 1st Dies, \$2\$ for 2nd Dies or \$3\$ for Both Die.

(p,t,a,P,T,A)=>(x=A<4&&p>=T|a&!!p)&(y=a<4&&P>=t|A&!!P)&&(a^A)&2?a+2>>1:x*2+y

Try it online!

Commented

(p, t, a, P, T, A) => // (p, t, a) = arguments for the first player (P1)
                      // (P, T, A) = arguments for the second player (P2)
  ( x =               // x is a flag which means 'P1 can kill P2',
                      // regardless of the 'First Strike' abilities
    A < 4 &&          // it is set to 1 if P2 is not Indestructible and:
    p >= T |          //   the power of P1 is greater than or equal to the toughness of P2
    a & !!p           //   or the power of P1 is not zero and P1 has the Death Touch
  ) &                 //
  ( y = a < 4 &&      // y is the counterpart of x and is computed the same way
    P >= t |          //
    A & !!P           //
  ) &&                // if both x and y are set
  (a ^ A) & 2 ?       // and exactly one player has the First Strike:
    a + 2 >> 1        //   return 2 if P1 has the First Strike, or 1 otherwise
  :                   // else:
    x * 2 + y         //   return the default outcome: x * 2 + y
\$\endgroup\$
3
\$\begingroup\$

C (gcc), 114 113 95 bytes

A lot of golfing thanks to ceilingcat and Logern.

g(Z{return F&1|F&4&&!(f&4||P<t)||!(f&2)&T>p;}
f(Z{return g(Z+2*g(p,t,f,P,T,F);}

Compile with -DZ=P,T,F,p,t,f).

Try it online!

We check (independently, because of the symmetry of combat mechanics) whether each of creatures survives the combat, which happens if either is true:

  • the creature is indestructible;
  • the creature has first strike AND the other one does not AND its power is greater or equal to other's toughnes (therefore we may disregard other's death touch);
  • other creature does not have death touch AND its power is less than our toughness.

(The former conditions are more important).

The inputs are power and toughness as integers, and abilities as a bitfield (1 = Indestructible, 2 = Death touch, 4 = First strike), the output is also a bitfield (1 = First creature survives, 2 = Second creature survives).

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Using a macro -DZ=P,T,F,p,t,f) 96 bytes-Try it online! \$\endgroup\$
    – user36046
    Sep 26, 2018 at 13:35
  • \$\begingroup\$ Using P=… instead of return … and removing the newline takes you to 85 bytes. \$\endgroup\$
    – user77406
    Oct 19, 2018 at 10:08
  • \$\begingroup\$ Also, -3 bytes by replacing logical operators &&, || with bitwise &, | \$\endgroup\$
    – user77406
    Oct 19, 2018 at 10:22
2
\$\begingroup\$

Retina 0.8.2, 123 bytes

\d+
$*
(.*1)(.*;)(.*1)
$3$2$1
F(.*)F
$1
1+D
1
1*(,1+)I
$1
(1+)(F?;1*,)(1+)
$3$2$1
(1*)1*,\1(1+)?
$#2
0(F)?;0(F)?
$#1;$#2
F

Try it online! Link includes test cases, although I've substituted 9 for 99999 for speed. Input uses the letters DFI although D must precede I. Output is in the format 1 for survives and 0 for dies. Explanation:

\d+
$*

Convert the stats to unary.

(.*1)(.*;)(.*1)
$3$2$1

Exchange the stats temporarily.

F(.*)F
$1

Two Fs cancel out.

1+D
1

Death Touch lowers the opponent's Toughness to 1.

1*(,1+)I
$1

Indestructable lowers the opponent's Power to 0.

(1+)(;1*,)(1+)
$3$2$1

Switch the Toughness back, so now you have P2,T1,F1;P1,T2,F2

(1*)1*,\1(1+)?
$#2

If the Toughness is higher than the opponent's Power then it survives.

0(F)?;0(F)?
$#1;$#2

If both would die, the one with First Strike survives.

F

Otherwise First Strike makes no difference.

\$\endgroup\$
1
\$\begingroup\$

C++, 177 131 127 121 bytes

Here's my not so short solution in C++. Abilities are 3 bits for each creature:

  1. D = 0x1 (0001)
  2. F = 0x2 (0010)
  3. I = 0x4 (0100)

And it simply returns 0: if nobody dies, 1: if the first creatures dies, 2: if the second creature dies and 3: if both creatures die.

[](int p,int t,int a,int r,int k,int b){return(a&2&&b^4)^(b&2&&a^4)?1+(a&2):((t<r||b&1&&r)&&a^4)+((k<p||a&1&&p)&&b^4)*2;}

Try it Online!

C++, 85 81 bytes (Alternative)

By slightly cheating and capture the variables in lambda and not pass them as arguments it is possible to get down to 81 bytes. I don't know if that's an acceptable solution so I post it as an alternative.

[&]{s=(a&2&&b^4)^(b&2&&a^4)?1+(a&2):((t<r||b&1&&r)&&a^4)+((k<p||a&1&&p)&&b^4)*2;}

Try it Online!

\$\endgroup\$
1
  • \$\begingroup\$ This is code-golf, such hacks are expected, if not required, in order to compete ... unless you're using purpose-built code-golfing languages, which changes the game a bit. \$\endgroup\$
    – 3D1T0R
    Sep 1, 2018 at 0:37
1
\$\begingroup\$

Perl 5, 245 bytes

$F[0]*=$F[4]if$F[2]=~/D/;$F[3]*=$F[1]if$F[5]=~/D/;$F[3]=0 if$F[2]=~/I/;$F[0]=0 if$F[5]=~/I/;$F[4]-=$F[0]if$F[2]=~/F/;$F[1]-=$F[3]if$F[5]=~/F/;if($F[1]>0&&$F[4]>0){$F[4]-=$F[0]if$F[2]!~/F/;$F[1]-=$F[3]if$F[5]!~/F/}$_=(0+($F[1]<=0)).(0+($F[4]<=0))

Run with -lapE

Ungolfed:

# Takes input in one lines, of the form:
# PPP TTT "<abilities>" PPP TTT "<abilities>"

$F[0] *= $F[4] if $F[2] =~ /D/;
$F[3] *= $F[1] if $F[5] =~ /D/;

$F[3] = 0 if $F[2] =~ /I/;
$F[0] = 0 if $F[5] =~ /I/;

$F[4] -= $F[0] if $F[2] =~ /F/;
$F[1] -= $F[3] if $F[5] =~ /F/;

if ($F[1] > 0 && $F[4] > 0) {
    $F[4] -= $F[0] if $F[2] !~ /F/;
    $F[1] -= $F[3] if $F[5] !~ /F/;
}

$_ = (0+ ($F[1] <= 0)) . (0+ ($F[4] <= 0));

"Deathtouch" translates to "your power is now multiplied by your enemy's toughness", and "indestructible" translates to "your enemy's power is now zero", with the latter taking precedent. The code runs two rounds, one where only the first-strikers get to attack, and the other where only non-first-strikers can attack. If the first round results in a death, the second round doesn't happen. Since we already dealt with the deathtouch and indestructible at the beginning, "death" is as simple as checking whether toughness is greater than zero or not.

\$\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.