Scala, 764 bytes
object B{
def main(a: Array[String]):Unit={
val v=false
val (m,l,k,r,n)=(()=>print("\033[H\033[2J\n"),a(0)toInt,a(1)toInt,scala.util.Random,print _)
val e=Seq.fill(k, l)(v)
m()
(0 to (l*k)/2-(l*k+1)%2).foldLeft(e){(q,_)=>
val a=q.zipWithIndex.map(r => r._1.zipWithIndex.filter(c=>
if(((r._2 % 2) + c._2)%2==0)!c._1 else v)).zipWithIndex.filter(_._1.length > 0)
val f=r.nextInt(a.length)
val s=r.nextInt(a(f)._1.length)
val i=(a(f)._2,a(f)._1(s)._2)
Thread.sleep(1000)
m()
val b=q.updated(i._1, q(i._1).updated(i._2, !v))
b.zipWithIndex.map{r=>
r._1.zipWithIndex.map(c=>if(c._1)n("X")else if(((r._2 % 2)+c._2)%2==0)n("O")else n("_"))
n("\n")
}
b
}
}
}
How it works
The algorithm first fills a 2D Sequence with false values. It determines how many iterations (open boxes) exist based on the command line arguments put in. It creates a fold with this value as the upper bound. The integer value of the fold is only used implicitly as a way to count how many iterations the algorithm should run for. The filled sequence we created previously is the starting sequence for the fold. This is used in generating a new 2D sequence of false values with their cooresponding indecies.
For example,
[[false, true],
[true, false],
[true, true]]
Will be turned into
[[(false, 0)], [(false, 1)]]
Note that all lists that are completely true (have a length of 0) are omitted from the result list. The algorithm then takes this list and picks a random list in the outermost list. The random list is chosen to be the random row we pick. From that random row, we again find a random number, a column index. Once we find these two random indices, we sleep the thread we are on for 1000 miliseconds.
After we're done sleeping, we clear the screen and create a new board with a true
value updated in the random indices we have created.
To print this out properly, we use map
and zip it with the index of the map so we have that in our context. We use the truth value of the sequence as to whether we should print an X
or either an O
or _
. To chose the latter, we use the index value as our guide.
###Interesting things to note
To figure out if it should print an O
or an _
, the conditional ((r._2 % 2) + c._2) % 2 == 0
is used. r._2
refers to the current row index while c._2
refers to the current column. If one is on an odd row, r._2 % 2
will be 1, therefore offsetting c._2
by one in the conditional. This ensures that on odd rows, columns are moved over by 1 as intended.
Printing out the string "\033[H\033[2J\n"
, according to some Stackoverflow answer I read, clears the screen. It's writing bytes to the terminal and doing some funky stuff I don't really understand. But I've found it to be the easiest way to go about it. It doesn't work on Intellij IDEA's console emulator, though. You'll have to run it using a regular terminal.
Another equation one might find strange to see when first looking at this code is (l * k) / 2 - (l * k + 1) % 2
. First, let's demystify the variable names. l
refers to the first arguments passed into the program while k
refers to the second one. To translate it, (first * second) / 2 - (first * second + 1) % 2
. The goal of this equation is to come up with the exact amount of iterations needed to get a sequence of all X's. The first time I did this, I just did (first * second) / 2
as that made sense. For every n
elements in each sublist, there are n / 2
bubbles we can pop. However, this breaks when dealing with inputs such as (11 13)
. We need to compute the product of the two numbers, make it odd if it's even, and even if it's odd, and then take the mod of that by 2. This works because rows and columns that are odd are going to require one less iteration to get to the final result.
map
is used instead of a forEach
because it has less characters.
Things that can probably be improved
One thing that really bugs me about this solution is the frequent use of zipWithIndex
. It's taking up so many characters. I tried to make it so that I could define my own one character function that would just perform zipWithIndex
with the value passed in. But it turns out that Scala does not allow an anonymous function to have type parameters. There is probably another way to do what I'm doing without using zipWithIndex
but I haven't thought too much about a clever way to do it.
Currently, the code runs in two passes. The first generates a new board while the second pass prints it out. I think that if one were to combine these two passes into one pass, that would save a couple of bytes.
This is the first code golf I've done so I'm sure there is a lot of room for improvement. If you'd like to see the code before I optimized for bytes as much as possible, here it is.