Visualize a Nim board like an expert

Python 2, 150 196 206 bytes

def f(p):
 c=48;s=[l*'.'for l in p];m=2**len(bin(l))
 while m:
  if sum(m*'.'in l for l in s)>1:
   for i,l in enumerate(s):s[i]=l.replace('.'*m,chr(c)*m,`s`.count(chr(c)*m)<2)
   c+=1
  else:m/=2
 return s

Try it online!


Ruby, 169 164 148 bytes

->a{s=eval a*?^
c=?@
m={}
a.map{|x|z=x-(x^s);[$><<?-*z,x-=z,s=0]if z>0
n=1
eval'x&1>0?[$><<(m[n]||c.next!)*n,m[n]=!m[n]&&c*1]:0;n*=2;x/=2;'*x
puts}}

Try it online!

First, we initialize

  • the nim-sum with s=eval a*?^ (which is shorter than a.reduce:^)
  • the variable c, which stores the first unused unique character
  • a map m that maps power-of-two lengths to characters used to represent them

Then, looping over each pile, we run the following:

z=x-(x^s);[$><<?-*z,x-=z,s=0]if z>0

Per Wikipedia's strategy, if nim-sum XOR pile is less than pile, we should remove stones from that pile such that its length becomes nim-sum XOR pile. By storing the difference in the variable z, we can test to see whether this difference is positive, and if so 1.) print that many dashes, 2.) subtract it from the pile, and 3.) set the nim-sum variable to zero to prevent further stone removal.

n=1
eval'[...];n*=2;x/=2;'*x

Now we "loop" over each bit and keep track of their values by repeatedly dividing x by 2 and multiplying the accumulator n by 2. The loop is actually a string evaluated x times, which is far greater than the log2(x) times it's necessary, but no harm is done (aside from inefficiency). For each bit, we run the following if the bit is 1 (x&1>0):

$><<(m[n]||c.next!)*n

Print a character n times. If we already printed an unpaired group of this many stones, use that character; otherwise, use the next unused character (advancing c in-place due to the !).

m[n]=!m[n]&&c*1

If m[n] existed (i.e. we just completed a pair), then m[n] is reset. Otherwise, we just started a new pair, so set m[n] to the character we used (*1 is a short way to make a copy of c).