Implement the Zundoko Kiyoshi function

JavaScript (ES6), 72 bytes

f=x=>x%17^2?['doko ','zun '][j=Math.random()*2|0]+f(x<<7|j):'ki-yo-shi!'

Try it online!

How?

We keep track of the last 5 words in a 32-bit integer stored in \$x\$ (which is initially undefined). At each iteration, we left-shift it by 7 positions and set the LSB to \$0\$ for doko or \$1\$ for zun.

The sequence zun, zun, zun, zun, doko results in:

x = 10000001000000100000010000000

or \$270549120\$ in decimal, which is the only value for which we have \$x\equiv 2\pmod{17}\$, as shown in this table. This is our halt condition.


05AB1E, 54 50 44 43 42 39 37 bytes

[.•BΓßb¥•#TΩDˆè¯J30bÅ¿#}"ki-yo-shi!"»

Try it online!

I doubt string compression is going to help here. Prove me wrong. It did help. -5 thanks to @Kevin

Forgive me, Adnan, for writing such a long program.

Explained (old)

[1ÝΩ

First of all, we start an infinite loop, generate the range [0, 1] and pick a random object from that list.

D

We then duplicates that random number for later use.

.•BΓßb¥•#

Next, we push the compressed string "doko zun" and split it upon spaces.

sè,

Then, we swap that splitted string and the randomly generated number from earlier and index the string at that position. , prints the word to STDOUT.

ˆ¯5.£J

Here's where the fun begins. After the indexing and printing, we are left with the original random number on the stack. We append it to the global array and then push the global array to obtain the last 5 items from the list. This will be a string of 1s and 0s. The joining is simply to mould the list into a single string.

30b

We then convert the number 30 into its binary representation: 11110. This represents four zuns followed by a doko, as that's the order they appear in the compressed string from earlier.

Q#]

Then, we check to see if the last 5 items (which we joined into a string) is equal to the binary of 30. If it is, the infinite loop will stop, moving on to the next step. Otherwise, the above steps are repeated again.

"ki-yo-shi!",

At this stage, the loop has finished, meaning all that is left to do is print the required end string.


C#, 347 255 170 119 115 92 Bytes

for(int k=3;k!=1;Write(k%2<1?"zun ":"doko "))k=k<<7^new Random().Next(2);Write("ki-yo-shi!")

Explanation

// Only every 7th bit changes
// k is set to three so that the code won't terminate until
// at least 5 shifts have occured
// Terminate when k is one
// so that every 7th bit matches 00001
for(int k=3;k!=1;){
    k=k<<7;
    // Shift the bits in k
    k=k^new Random().Next(2);
    // Set the last bit to random value
    Write(k%2<1?"zun ":"doko ")
    // Output zun or doko based on last bit 
//zun = 0, doko = 1. kiyoshi = 00001
}
//we only get here if we have 00001
Write("ki-yo-shi!")

Try it online!

Added a TIO link and also saved almost 100 bytes :) (Not counting the class Program + static void Main() declarations and also implied using System).

My friends and I went back and forth with what I had originally and ended up with this. Basically cut the bytes in half. You can potentially run out of memory in the rare scenario that you never get a kiyoshi but whatever.

Thanks to Kevin and monicareinstate in the comments, this is now 119 bytes. Using the interactive compiler instead you can put functions outside of the main function and it implies using.

Last edit: we got this to 92 bytes! Can't even believe it given how verbose C# is.