The String Combat Challenge in Java


The challenge

After a long conflict, the rulers of Nek and Glo have decided that a final duel should decide the fate of their countries. Each ruler will select their best knights and send them into battle, one after another. The last standing team will win the crown.

But the knights don’t want to die for nothing, so they ask you, the wise technomagican, to derive which team will win. Can you help them?

Task

You’re given two strings, s1 and s2. Both represent a team consisting of the characters a..z (repsenting life points 1..26) and A..Z (representing life points 27..52). In each round, the first two participants of both teams (aka their first characters) will duel.

The character with fewer life points will die and get removed, whereas the life points of the survivor get reduced by 2/3 (it has only 1/3 of its original value rounded to the closest integer). The winner will still participate in the duels. If both combatants have the same life points, they get both removed.

The duels stop whenever one of both strings is empty or null value. Unless both are empty or null value, you have to return the winning string and its remaining content, e.g. "Winner: s1(abc)". If both are empty string or null value, return "Draw".

Some easy example:
  combat("a","c")      == "Winner: s2(a)"    combat("a","a")     == "Draw"
  combat("abc","ab")   == "Winner: s1(c)"    combat("ab","ab")   == "Draw"
  combat("boy","girl") == "Winner: s2(fl)"   combat("dog","cat") == "Draw"

The solution in Java code

Option 1:

import java.util.*;
import java.util.stream.*;

public class StringCombat { 
  
    public static String combat(String s1, String s2) {
        List<Integer> list1 = s1 != null ? toRanks(s1) : Collections.emptyList();
        List<Integer> list2 = s2 != null ? toRanks(s2) : Collections.emptyList();

        while (!list1.isEmpty() && !list2.isEmpty()) {
            int i1 = list1.get(0);
            int i2 = list2.get(0);

            if (i1 > i2) {
                list2.remove(0);
                list1.set(0, Math.round(i1 * (1 / 3.0f)));
            } else if (i2 > i1) {
                list1.remove(0);
                list2.set(0, Math.round(i2 * (1 / 3.0f)));
            } else {
                list1.remove(0);
                list2.remove(0);
            }
        }

        if (list1.isEmpty() && list2.isEmpty()) {
            return "Draw";
        } else {
            String index = list2.isEmpty() ? "1" : "2";
            String rest = toString(list2.isEmpty() ? list1 : list2);
            return "Winner: s" + index + "(" + rest + ")";
        }
    }

    private static List<Integer> toRanks(String s) {
        return s.chars()
                .mapToObj(c -> c >= 'a' && c <= 'z' ? c - 'a' + 1 : c - 'A' + 27)
                .collect(Collectors.toList());
    }

    private static String toString(List<Integer> ranks) {
        StringBuilder builder = new StringBuilder();
        ranks.forEach(i -> builder.append((char) (i >= 1 && i <= 26 ? i - 1 + 'a' : i - 27 + 'A')));

        return builder.toString();
    }
    
}

Option 2:

public class StringCombat { 
    
 public static String combat(String s1, String s2) {
            if(s1==null)
                s1="";
            if(s2==null)
                s2="";

            if(s1.equals(s2)) return "Draw";
            String ct = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            StringBuilder str1 = new StringBuilder(s1), str2 = new StringBuilder(s2);

            int i = 0, j = 0, idx;
            while (str1.length() != 0 && str2.length() != 0) {

                if (ct.indexOf(str1.charAt(i)) > ct.indexOf(str2.charAt(j))) {
                    idx = ct.indexOf(str1.charAt(i));
                    str1.setCharAt(i, ct.charAt(Math.round(idx / (float) 3)));
                    str2.deleteCharAt(j);
                } else if (ct.indexOf(str1.charAt(i)) < ct.indexOf(str2.charAt(j))) {
                    idx = ct.indexOf(str2.charAt(j));
                    str2.setCharAt(j, ct.charAt(Math.round(idx / (float) 3)));
                    str1.deleteCharAt(i);
                } else {
                    str1.deleteCharAt(i);
                    str2.deleteCharAt(j);
                }

            }

            return str1.length() > str2.length() ? "Winner: s1(" + str1 + ")" : str1.length() == str2.length() ? "Draw" : "Winner: s2(" + str2 + ")";

    }
  
}

Option 3:

import java.util.List;
import java.util.stream.Collectors;

public class StringCombat {

  public static String combat(String s1, String s2) {
    if (s1 == null && s2 == null)
      return "Draw";
    if (s1 == null)
      return (s2.isEmpty()) ? "Draw" : "Winner: s2(" + s2 + ")";
    if (s2 == null)
      return (s1.isEmpty()) ? "Draw" : "Winner: s1(" + s1 + ")";
    if (s1.equals(s2))
      return "Draw";

    String life = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    List<Character> s1c = s1.chars().mapToObj(c -> (char) c).collect(Collectors.toList());
    List<Character> s2c = s2.chars().mapToObj(c -> (char) c).collect(Collectors.toList());
    while (s1c.size() > 0 && s2c.size() > 0) {
      int life1 = life.indexOf(s1c.get(0)) + 1;
      int life2 = life.indexOf(s2c.get(0)) + 1;
      if (life1 == life2) {
        s1c.remove(0);
        s2c.remove(0);
      } else if (life1 > life2) {
        s2c.remove(0);
        s1c.set(0, life.charAt(Math.round(life1 / 3f) - 1));
      } else if (life2 > life1) {
        s1c.remove(0);
        s2c.set(0, life.charAt(Math.round(life2 / 3f) - 1));
      }
    }

    if (s1c.isEmpty() && s2c.isEmpty())
      return "Draw";
    if (s2c.isEmpty())
      return "Winner: s1(" + s1c.stream().map(Object::toString).collect(Collectors.joining()) + ")";
    if (s1c.isEmpty())
      return "Winner: s2(" + s2c.stream().map(Object::toString).collect(Collectors.joining()) + ")";
    return "ERROR";
  }

}

Test cases to validate our solution

import java.util.LinkedList;
import java.util.List;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class StringCombatTest2 {

	private static List<String[]> tests = new LinkedList<>();

	@Before
	public void setUp() {
		tests.clear();
	}

	@Test
	public void shouldPassBasicTests() {
		tests.add(new String[] { "a", "a", "Draw", "string equivalence signifies instant draw" });
		tests.add(new String[] { "a", "b", "Winner: s2(a)", "expected b to outwin a" });
		tests.add(new String[] { "q", "b", "Winner: s1(f)", "expected q to outwin b" });
		tests.add(new String[] { "dog", "cat", "Draw" });
    tests.add(new String[] { "boy", "girl", "Winner: s2(fl)" });
		tests.add(new String[] { "abcde", "fghij", "Winner: s2(cj)" });
		tests.add(new String[] { "abCde", "fghij", "Winner: s1(b)" });
		tests.add(new String[] { "iGnfQxJYy", "MlHGtsGF", "Winner: s2(k)" });
		tests.add(new String[] { "vyTcJSGRGZcTq", "GFyfgBkDxNvgf", "Winner: s1(f)" });
		tests.add(new String[] { "vyTcJSGRGZcTq", "GFyfgBkDxNvgeq", "Draw" });
		executeTestBundle("basic tests bundle");
	}

	/** Test execution helper */
	private void executeTestBundle(String bundleName) {
		System.out.println("------Executing " + bundleName + "------");
		tests.forEach(test -> {
			String s1 = test[0], s2 = test[1], expected = test[2], msg = test.length > 3 ? test[3] : "",
					actual = StringCombat.combat(s1, s2);
			Assert.assertEquals(msg, expected, actual);
			System.out.printf("`%s` duels against `%s` : %s%n", s1, s2, actual);
		});
		System.out.println("------   end of bundle   ------");
	}

}