loading...

Daily Challenge #43 - Boardgame Fight Resolver

thepracticaldev profile image dev.to staff ・1 min read

Today's challenge is a smaller part of a larger idea.

You are creating a board game, similar to a mix between Fire Emblem and Chess. The game features four unique pieces: Swordsman, Cavalry, Archer, and Pikeman. Each piece has its own advantages and weaknesses in combat against other pieces.

You must write a function fightResolve that takes the attacking and defending pieces as input parameters and returns the winning piece.

The outcome of the fight between two pieces depends on which piece attacks, the type of the attacking piece and the type of the defending piece.

Archers > Swordsmen > Pikemen > Cavalry > Archers

Archers always win against swordsmen, swordsmen always win against pikemen, pikemen always win against cavalry and cavalry always win against archers.

If a matchup occurs that was not previously mentioned (for example Archers vs Pikemen) the attacker will always win.


This challenge comes from user Brysen on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❀️

Discussion

markdown guide
 

CSS

Add the attributes data-piece1 and data-piece2 to any element, and it will announce the winner:

[data-piece1][data-piece2]::before {
  content: attr(data-piece1) ' vs ' attr(data-piece2) ': ';
}

/* By default, piece 1 will win */
[data-piece1][data-piece2]::after {
  content: attr(data-piece1) ' wins!';
}

/* Only in some particular cases, piece 2 will win */
[data-piece2="archers"i][data-piece1="swordsmen"i]::after,
[data-piece2="swordsmen"i][data-piece1="pikemen"i]::after,
[data-piece2="pikemen"i][data-piece1="cavalry"i]::after,
[data-piece2="cavalry"i][data-piece1="archers"i]::after { 
  content: attr(data-piece2) ' wins!'
}

In CSS Selectors Level 4, there is an option to make some selectors case-insensitive (adding an i at the end as displayed above). And it is fairly well supported (haven't tried on IE). So it doesn't matter if the user writes "archers" or "Archers" or "aRcHeRs", all of them will be matched.

 

This is by far the most clever CSS I have ever seen. I applaud you.

 
 

Okay close it all, we have a winner!

 

Look ma, pattern matching!

fightResolve :: String -> String -> String
fightResolve "Swordsman" "Archer"  = "Archer"
fightResolve "Pikeman" "Swordsman" = "Swordsman"
fightResolve "Cavalry" "Pikeman"   = "Pikeman"
fightResolve "Archer" "Cavalry"    = "Cavalry"
fightResolve attacker _            = attacker
 

Ahh, pattern matches. So elegance, much match ( *-*)

 

F#:

type Piece = Archer | Swordsman | Pikeman | Cavalry

let fightResolve attacker defender =
  match attacker, defender with
  | Swordsman, Archer 
  | Pikeman, Swordsman 
  | Cavalry, Pikeman 
  | Archer, Cavalry -> defender
  | _ -> attacker
 

One could also turn the fight itself into a type:

type Piece = Archer | Swordsman | Pikeman | Cavalry

type Fight =
  { Attacker : Piece
    Defender : Piece }

let fightResolve fight =
  let { Attacker = attacker; Defender = defender } = fight
  match attacker, defender with
  | Swordsman, Archer 
  | Pikeman, Swordsman 
  | Cavalry, Pikeman 
  | Archer, Cavalry -> defender
  | _ -> attacker
 


#!/bin/bash

# ( attack , defend )
# archer: 1; swordsmen: 2; pikemen: 3; cavalry: 4;
function fightResolve {
    echo "$1$2" | grep -qEe '(21|32|43|14)' - &&
        return $2
    return $1
}
 

I'm surprised no-one has used the age old xor-the-second-bit-of-the-first-characters-and-xor-the-fifth-bit-of-first-and-second-characters trick πŸ˜‰

In C:

const char *defenderWins(const char *attacker, const char *defender) {
    return (((attacker[0] ^ defender[0]) & 2) | ((attacker[1] ^ defender[0]) & 0x10)) == 0x12;
}

Clearly, this doesn't really need any testing, but here's an exhaustive dump, err, full set of trials, just to be pedantic

#include <stdio.h>

const char *winner(const char *attacker, const char *defender) {
    return (((attacker[0] ^ defender[0]) & 2) | ((attacker[1] ^ defender[0]) & 0x10)) == 0x12 ? defender : attacker;
}

int main()
{
    const char *teams[] = { "archers","swordsmen", "pikemen", "cavalry" };
    const size_t teamCount=sizeof(teams)/sizeof(*teams);

    for (int attackeri = 0; attackeri<teamCount; ++attackeri) {
        for (int defenderi = 0; defenderi<teamCount; ++defenderi) {
            const char *attacker = teams[attackeri];
            const char *defender = teams[defenderi];
            printf("%s -> %s : %s wins\n", attacker, defender, winner(attacker,defender));
        }
    }
    return 0;
}

prints:

archers -> archers : archers wins
archers -> swordsmen : archers wins
archers -> pikemen : archers wins
archers -> cavalry : cavalry wins
swordsmen -> archers : archers wins
swordsmen -> swordsmen : swordsmen wins
swordsmen -> pikemen : swordsmen wins
swordsmen -> cavalry : swordsmen wins
pikemen -> archers : pikemen wins
pikemen -> swordsmen : swordsmen wins
pikemen -> pikemen : pikemen wins
pikemen -> cavalry : pikemen wins
cavalry -> archers : cavalry wins
cavalry -> swordsmen : cavalry wins
cavalry -> pikemen : pikemen wins
cavalry -> cavalry : cavalry wins
 

Care to explain how it works? I don't get it πŸ˜…

 

Sure thing. It's a "don't look behind the curtain" kind of thing πŸ§™β€β™‚οΈ
here's the rest of the code...

const a = [
  'archers',
  'swordsmen', 'pikemen', 'cavalry'
]
let i1 = 2,
  i2 = 1,
  i3 = 3,
  i4 = 3,
  i5 = 2,
  i6 = 2,
  i7 = 2,
  i8 = 2

winner_mank = (s1, s2) => ((s1.charCodeAt(i1) >> i3) ^ ((s2.charCodeAt(i2)) >> i4)) & ((s1.charCodeAt(i5) >> i7) ^ ((s2.charCodeAt(i6)) >> i8)) & 1 ? s2 : s1

winner_good = (s1, s2) => a.indexOf(s1) == (a.indexOf(s2) + 1) % 4 ? s2 : s1

verify = () => {
  for (const s1 of a) {
    for (const s2 of a) {
      if (winner_good(s1, s2) != winner_mank(s1, s2)) return
    }
  }
  console.log({
    i1,
    i2,
    i3,
    i4,
    i5,
    i6,
    i7,
    i8
  })
  debugger
  return true
}

for (i1 = 0; i1 < 7; i1++) {
  for (i2 = 0; i2 < 7; i2++) {
    for (i3 = 0; i3 < 8; i3++) {
      for (i4 = 0; i4 < 8; i4++) {
        for (i5 = 0; i5 < 7; i5++) {
          for (i6 = 0; i6 < 7; i6++) {
            for (i7 = 0; i7 < 8; i7++) {
              for (i8 = 0; i8 < 8; i8++) {
                verify()
              }
            }
          }
        }
      }
    }
  }
}

winner_mank is reducible to the one posted when i1=i3=0, i2=i4=1, i5=1,i7=0, i6=i8=4. And I felt it was may as well be in C.
I was lying about the age old trick thing. Upside is it's really really quick, downsides are all the other things πŸ˜‚

 

Perl solution, using a hash of known fight results.

#!/usr/bin/perl
use warnings;
use strict;

{   my %stronger = (
        archers   => 'swordsmen',
        swordsmen => 'pikemen',
        pikemen   => 'cavalry',
        cavalry   => 'archers');
    sub fight_resolve {
        my ($attacker, $defender) = @_;
        ($stronger{$defender} // "") eq $attacker ? $defender : $attacker
    }
}

use Test::More tests => 16;

is fight_resolve('archers', 'archers'    ), 'archers';
is fight_resolve('archers', 'swordsmen'  ), 'archers';
is fight_resolve('archers', 'pikemen'    ), 'archers';
is fight_resolve('archers', 'cavalry'    ), 'cavalry';
is fight_resolve('swordsmen', 'archers'  ), 'archers';
is fight_resolve('swordsmen', 'swordsmen'), 'swordsmen';
is fight_resolve('swordsmen', 'pikemen'  ), 'swordsmen';
is fight_resolve('swordsmen', 'cavalry'  ), 'swordsmen';
is fight_resolve('pikemen', 'archers'    ), 'pikemen';
is fight_resolve('pikemen', 'swordsmen'  ), 'swordsmen';
is fight_resolve('pikemen', 'pikemen'    ), 'pikemen';
is fight_resolve('pikemen', 'cavalry'    ), 'pikemen';
is fight_resolve('cavalry', 'archers'    ), 'cavalry';
is fight_resolve('cavalry', 'swordsmen'  ), 'cavalry';
is fight_resolve('cavalry', 'pikemen'    ), 'pikemen';
is fight_resolve('cavalry', 'cavalry'    ), 'cavalry';
 

Rust Solution: Playground

fn fight_resolver(defender: Class, attacker: Class) -> Class {
    match (defender, attacker) {
        (Class::Swordsmen, Class::Archer)
        | (Class::Pikemen, Class::Swordsmen)
        | (Class::Cavalry, Class::Pikemen)
        | (Class::Archer, Class::Cavalry) => defender,
        (_, att) => att,
    }
}
 

Rust:

enum Piece {
    Archer,
    Swordsman,
    Pikeman,
    Cavalry,
}

fn fight_resolve<'a>(attacker: &'a Piece, defender: &'a Piece) -> &'a Piece {
    use Piece::*;

    match (attacker, defender) {
        (Swordsman, Archer) | (Pikeman, Swordsman) | (Cavalry, Pikeman) | (Archer, Cavalry) => {
            defender
        }
        (attacker, _) => attacker,
    }
}
 

How bout some G O L F

f=(a,d)=>(t='ASPC',(t.indexOf(d[0])-t.indexOf(a[0])+1)%4?a:d)

If I understood the problem correctly, the defender only wins if it is the "previous" element on the list. Hence, I just store a string with the initials in the correct order, check the initial index. If the defender is just before the attacker, it wins.

r=['Archers', 'Swordsmen', 'Pikemen', 'Cavalry'];
s='',r.forEach(e=>r.forEach(k=>s+=`${e} VS ${k}: ${f(e,k)}\n`)),s 


"Archers VS Archers: Archers
Archers VS Swordsmen: Archers
Archers VS Pikemen: Archers
Archers VS Cavalry: Cavalry
Swordsmen VS Archers: Archers
Swordsmen VS Swordsmen: Swordsmen
Swordsmen VS Pikemen: Swordsmen
Swordsmen VS Cavalry: Swordsmen
Pikemen VS Archers: Pikemen
Pikemen VS Swordsmen: Swordsmen
Pikemen VS Pikemen: Pikemen
Pikemen VS Cavalry: Pikemen
Cavalry VS Archers: Cavalry
Cavalry VS Swordsmen: Cavalry
Cavalry VS Pikemen: Pikemen
Cavalry VS Cavalry: Cavalry
"
 

I put a front end on it with React:

I kept the actual method super simple though with a bunch of if statements! Though there are a whole bunch of more clever ways to do it :)

 

ruby <3

Swordsman, Cavalry, Archer, Pikeman = :swardsman, :cavalry, :archer, :pikeman

def fightResolve(attacking_piece, defending_piece)
  case [attacking_piece, defending_piece]
  when [Swordsman, Archer],
       [Pikeman, Swordsman],
       [Cavalry, Pikeman],
       [Archer, Cavalry]
    defending_piece
  else
    attacking_piece
  end
end
 

This looks quite similar to my F# solution, I like it. :-)

 

private static Map pair = new HashMap<>();

void challange_43()
{
    //Archers > Swordsmen > Pikemen > Cavalry > Archers
    pair.put("Archers","Swordsmen");
    pair.put("Swordsmen","Pikemen");
    pair.put("Pikemen","Cavalry");
    pair.put("Cavalry","Archers");
}

public static void main(String[] args) {

    Challange_43 object = new Challange_43();
    System.out.println("Winner between Pikemen, Cavalry is "+object.getWinner("Pikemen","Cavalry"));
    System.out.println("Winner between Archers Swordsmen is "+object.getWinner("Archers","Swordsmen"));
    System.out.println("Winner between Archers, Cavalry is "+object.getWinner("Archers","Cavalry"));


}

public String getWinner(String attacker, String defender)
{

    if(pair.containsKey(defender)&& pair.get(defender).equals(attacker))
        return defender;    
    else
        return attacker;    
}
 

Hmm...

There's never any reason to have certain pieces attack certain other pieces, then. What's the catch? Is it like Stratego where it's unclear what kind of piece you're attacking until after you make the attempt? Is there possibly a hit-point calculation, like in Civilization? Or do certain conditions take effect after certain outcomes, e.g., if you sacrifice a piece to a situation where it will lose, the other piece must advance to the free square as your opponent's next move?

just curious... I mean, there's gotta be some reason for the attacker to either be unable to anticipate that the defender will win, or some reason that attacking certain pieces in an otherwise known-losing situation would wind up not being a total bum deal for the player initiating the move πŸ˜…

 

private static Map pair = new HashMap<>();

void challange_43()
{
    //Archers > Swordsmen > Pikemen > Cavalry > Archers
    pair.put("Archers","Swordsmen");
    pair.put("Swordsmen","Pikemen");
    pair.put("Pikemen","Cavalry");
    pair.put("Cavalry","Archers");
}

public static void main(String[] args) {

    Challange_43 object = new Challange_43();
    System.out.println("Winner between Pikemen, Cavalry is "+object.getWinner("Pikemen","Cavalry"));
    System.out.println("Winner between Archers Swordsmen is "+object.getWinner("Archers","Swordsmen"));
    System.out.println("Winner between Archers, Cavalry is "+object.getWinner("Archers","Cavalry"));


}

public String getWinner(String attacker, String defender)
{

    if(pair.containsKey(defender)&& pair.get(defender).equals(attacker))
        return defender;    
    else
        return attacker;    
}