# Implement the Zundoko Kiyoshi function

## Background

The Zundoko Kiyoshi function originates from this tweet by kumiromilk. Translated from Japanese, it reads roughly as follows:

The test for my Java lecture had a problem that said "Implement and describe your own function". I made it continuously output either "zun" or "doko" randomly; if the sequence "zun", "zun", "zun", "zun", "doko" appears, it outputs "ki-yo-shi!" and terminates. Then I got full marks and earned a unit.

This is in reference to Kiyoshi Hikawa's song Kiyoshi no Zundoko Bushi: when he sings the aforementioned line, the crowd cheers "ki-yo-shi!" in response.

Write a program or function that takes no input and replicates the behavior outlined in the tweet:

• Repeatedly output either zun or doko, choosing uniformly randomly each time.
• If the sequence ["zun", "zun", "zun", "zun", "doko"] appears in the output, output ki-yo-shi! and halt.

Example output:

doko
zun
zun
doko
doko
zun
zun
zun
zun
zun
doko
ki-yo-shi!


## Rules

• This is , so shortest answer in bytes wins.
• Your output can be a list of strings, or each string printed with some delimiter.
• Your output has to be non-deterministic.
• "your output has to be non-deterministic" - is it allowed to alternate between zun and doko forever and randomize the starting word? May 8 '20 at 3:02
• "your output has to be non-deterministic" - does employing pseudo-random numbers count as being non-deterministic?
– user92069
May 8 '20 at 3:06
• @mypronounismonicareinstate No. It has to be capable of eventually producing the sequence mentioned in the challenge, and endlessly alternating between the two strings doesn't allow for that. May 8 '20 at 3:27
• @Λ̸̸ Yes, unless you use the exact same seed for the PRNG every time. May 8 '20 at 3:41
• @xnor I think uniform randomness is what I meant when I wrote "random"; being capable of producing the ending sequence is by no means the only requirement. May 8 '20 at 6:15

# 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.

• f=x=>1-~x&31?['doko ','zun '][j=Math.random()*2|0]+f(x*2|j):'ki-yo-shi!' same 72B, no need =2mod17
– l4m2
May 13 '20 at 18:24

# 05AB1E, 545044434239 37 bytes

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


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.

• 39 bytes: -1 by changing 1ÝΩ to TΩ; -1 by removing both , and using a trailing » instead to join the stack; and -1 by changing the position of the TΩD so you won't need the s. May 8 '20 at 9:03
• @KevinCruijssen wow, I don't know how I missed that. Thanks! May 8 '20 at 9:08
• Oh, and another -2 by changing ¯5.£J30bQ to ¯J30bÅ¿. :) (PS: My name isn't Keven ;p) May 8 '20 at 9:09
• @KevinCruijssen aw h*ck. Sorry about that! May 8 '20 at 9:13
• Np, it isn't the first time my name is misspelled like that, haha ;) May 8 '20 at 9:34

# C#, 347255170119115 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.

• Hi, welcome to CGCC! Your program current continues looping even after printing "ki-yo-shi!". I think you forgot a break; in that if-block to terminate your infinite loop. :) As for some golfing tips: you are allowed to use (lambda) functions instead of full programs by default, which saves a lot of bytes in verbose languages like C#. Also, if you haven't seen it yet, tips for golfing in C# and tips for golfing in <all languages> might be interesting to read through. May 8 '20 at 9:40
• it would also be beneficial to add a TIO- link, so other can verify your program more easily (like almost all other answers have as well). :) Here is the one for your program - with the additional break;) I will first let you try to golf it a bit more yourself with the tips in my comment above, but if you want I could golf the code and explain what I've shortened. Enjoy your stay! :) May 8 '20 at 9:42
• Thanks, Kevin, for being so helpful! I'm admittedly not good with lambdas yet but learning new things everyday ;) May 8 '20 at 20:52
• Very nice golfs already, well done! You don't have to count the Action l= in your byte-count, although you do have to count the using System;. Also, if I read the challenge correctly, the newlines are mandatory, instead of those |. So your current answer would be this (coincidentally still 170 bytes). Also note how I've placed parts of the program in the header and footer on TIO, so it's easier to count the byte-count in the code-section (+ the 13 from the using System;). As for some things to golf: May 9 '20 at 11:08
• 121 bytes by switching to the interactive compiler which has implicit usings and can have top-level code outside functions and classes (I also moved the strings' declarations into the first "argument" of for, saving a semicolon): Try it online! May 9 '20 at 18:19

# Pyth, 444240 38 bytes

Wn30i>5Y2%2>"dzoukno"eaYO2;"ki-yo-shi!


Try it online!

Approach: Repeatedly generate a random number from the set {0, 1} using O2. Store that number in the array Y and use it to index into the string "dzoukno" (which is "zun" and "doko" interleaved). Once the last five elements of Y are [1,1,1,1,0], break the loop and print "ki-yo-shi!".

# K4, 564847 46 bytes

Solution:

{x,1?$zundoko}/[75-2/:<-5#;()],,"ki-yo-shi!"  Example: q)k){x,1?$zundoko}/[75-2/:<-5#;()],,"ki-yo-shi!"
"doko"
"zun"
"doko"
"doko"
"doko"
"doko"
"doko"
"doko"
"doko"
"zun"
"zun"
"zun"
"zun"
"zun"
"zun"
"doko"
"ki-yo-shi!"


Explanation:

Generate zun or doko and append to a list whilst the distinct set of the last 5 elements aren't zun zun zun zun doko, and add ki-yo-shi! to the end.

Indices to order zun zun zun zun doko ascending are 4 0 1 2 3. Converted from base-2 yields 75. Other combinations will not yield the same result.

{x,1?$zundoko}/[75-2/:<-5#;()],,"ki-yo-shi!" / solution ,"ki-yo-shi!" / 1-item list of "ki-yo-shi!" , / append to { }/[ ; ] / {function}/[while;starting value] () / empty list -5# / take last 5 elements < / indices to sort ascending 2/: / convert from base 2 75- / subtract from 75$zundoko                                / string ($) -> ("zun";"doko") 1? / choose (?) 1 item from domain ("zun";"doko") x, / append to input  Extra: • K (oK) TIO for 47 bytes as we need an extra set of brackets but can drop a colon... • You can get the best of both worlds (dropping a : but without adding ()) by using ngn/k Feb 12 at 17:48 # Python 2, 102 101 97 95 92 91 bytes -3 bytes thanks to @dingledooper ! Thanks @JhynjhiruuRekrap for reminding that Python source code can include unprintable characters! Thanks @xnor for saving 1 byte! import os s=1 while~s%32:s+=s-(os.urandom(1)>"");print"dzoukno"[~s%2::2] print"ki-yo-shi!"  Try it online! Stores the history as bits in the integer s, where 0,1 corresponds to "zun" and "doko". Each time, shift s by 1, then subtract the new bit. Stops when the lowest 5 bits of s are 11111, aka ~s%32 == 0. Generates a random 0 or 1 by generating a random byte, then checking if the byte is more than 127.  os.urandom(1)>"{unprintable DEL character}"  • It seems to me that you could simply replace the tilde with a 7F in a hex editor in your 92-byte solution and that would work. Python 2 still runs it just fine. May 8 '20 at 22:59 • Also, I don't really want to submit this because it's basically yours, but it can be ported to Python 3 in 97 bytes: import os s=0 p=print while s+2&31:i=os.urandom(1)[0]&1;p("dzoukno"[i::2]);s+=s+i p("ki-yo-shi!") Edit: ugh, newlines are a pain May 8 '20 at 23:52 • Nice solution! I think I shaved off a byte:TIO. I still wonder if there's bytes to save here. Too bad the "dzoukno"[~s%2::2] has the parity go the wrong way, and s%2*"zun"or"doko" doesn't save a byte because it wastes the space after print in Python 2. Of course the os.urandom part using parens is unfortunate too. – xnor May 9 '20 at 12:22 • @xnor Wow, the subtracting bit instead of adding is pretty cool. A lot of things are not ideal here unfortunately, but 1 byte is 1 byte :) May 9 '20 at 15:29 # APL (Dyalog Classic), 54 bytes ({⍵⌷'zun' 'doko'}¨⍪∘?∘2⍣{1 1 1 1 2≡¯5↑⍺}⍬)'ki-yo-shi!'  Try it online! ## Explanation ({⍵⌷'zun' 'doko'}¨⍪∘?∘2 {1 1 1 1 2≡¯5↑⍺} )'ki-yo-shi!' ⍬ ⍝ Start with an empty list ⍣ ⍝ Do this until the condition turns true: ⍪∘?∘2 ⍝ Append a random number in [1..2] {1 1 1 1 2≡¯5↑⍺} ⍝ Condition: The list ends in "1 1 1 1 2" ¨ ⍝ For every item in this list: {⍵⌷'zun' 'doko'} ⍝ 1-Index into the list ['zun','doko'] ( )'ki-yo-shi!' ⍝ After that: Append the string 'ki-yo-shi!' ⍝ To the end of the output list  • Nice job. You can wrap it in a niladic tradfn wrapper so that you can call it several times to demonstrate non-deterministic output. May 8 '20 at 5:47 • You can use Unicode and count 1 byte per character if you replace bytes with [bytes](https://github.com/abrudz/SBCS). Classic is just for legacy usage, and lacks a few features. May 8 '20 at 5:54 • @Bubbler I don't feel like changing it, because I'm in a hurry to answer other new questions. – user92069 May 8 '20 at 6:04 # Jelly, 36 bytes 2Xµị“zun“doko”Ṅȧ;ɼḄß“ki-yo-shi!”%?32  A full program which takes no input and prints as it runs. Try it online! ### How? 2Xµị“zun“doko”Ṅȧ;ɼḄß“ki-yo-shi!”%?32 - Main Link (no arguments) 2 - literal two X - random number in [1..x] -> z = 1 or 2 µ - start a new monadic chain, f(z) “zun“doko” - list of strings = ["zun", "doko"] ị - index into -> v = "zun" or "doko" Ṅ - print v and a newline and yield v ȧ - (v) logical AND (z) -> z ɼ - recall from the register (initially 0), apply - the following and store the result back - into the register: ; - concatenate Ḅ - convert from base-2 (e.g. [0,2,1,1,1,1,2] -> 96 - since 0×2⁶+2×2⁵+1×2⁴+1×2³+2×2²+1×2¹+2×2°=96) ? - if... % 32 - ...condition: modulo 32 (is non-zero) ß - ...then: call this Link again (Main Link) “ki-yo-shi!” - ...else: string "ki-yo-shi!" - implicit print  # PowerShell, 67 bytes do{random($a=,'zun'*4+'doko')-ov +o}until("$o"-match$a)'ki-yo-shi!'


Try it online!

Unrolled:

do{
$array=,'zun'*4+'doko' # 'zun','zun','zun','zun','doko' random$array -OutVariable +out # choose one element from the array randomly. 'doko' may appear in 20% of cases. it's still random.
# output a result to the output channel (console by default) AND to the variable $out. # '+out' means adding to$out instead of replacing of $out # see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters }until("$out" -match $array) # powershell casts the$array to a string implicitly
'ki-yo-shi!'


# Retina, 50 bytes

/(n¶.+){4}o$/^+?(\z zun¶ )\z doko¶ \z ki-yo-shi!  Try it online! Explanation: /(n¶.+){4}o$/^+


# C (gcc), 104 $$\\cdots\$$ 96 91 bytes

Saved 2 bytes thanks to Arnauld!!!

Saved a byte thanks to tsh!!!

Saved 5 bytes thanks to ceilingcat!!!

f(m){srand(time(0));for(m=3;m%32-1;m+=m+puts(rand()%2?"doko":"zun")-4);puts("ki-yo-shi!");}


Try it online!

How

$$\m\$$ stores a history of what's randomly been printed over time in its bits, the least-significant bit storing the most recent. A bit in $$\m\$$ at time $$\t\$$ is set to:

$$m_t = \left\{ \begin{array}{ll} 0 & \text{zun} \\ 1 & \text{doko} \end{array} \right.$$ When the binary pattern $$\00001_2\$$ occurs in the least-significant $$\5\$$ bits of $$\m\$$ the sequence $$\(\text{zun}, \text{zun}, \text{zun}, \text{zun}, \text{doko})\$$ has just appeared. $$\m\$$ is initialised to $$\11_2\$$ so, at the beginning, it appears as though $$\(\text{doko},\text{doko})\$$ has just occurred forcing at least $$\5\$$ spins before $$\00001_2\$$ can occur.

• (m&31) can be replaced with m%32 May 8 '20 at 8:37
• @Arnauld A simple but very interesting golf: $\mod{2^n}$ is the same as masking the $n$ least-significant bits - thanks! :-) May 8 '20 at 8:48
• What about m;r;f() -> f(m,r)?
– tsh
May 8 '20 at 10:27
• @tsh Nice one - thanks! :-) May 8 '20 at 11:29

# Python 2, 94 bytes

from random import*
s=""
while"zo">s[-21::19]:s+=choice(["zun,","doko,"])
print s+"ki-yo-shi!"


Try it online!

This is an improvement based on an older version of Surculose Sputum's answer.

The idea is to identify the ending sequence in the comma-separated string by checking that the 21st and 2nd characters from the end are z and o respectively.

...,zun,zun,zun,zun,doko,
^                  ^
z                  o


This is the only ending that satisfies this. To get a z in the 21st-to-last position, the last five words must be exactly four zun, and one doko,, since their lengths of 4 and 5 can add up to 21 only as 4*4+5. Then, the one doko, must be at the end to get o in the second-to-last position. Moreover, because zo is the largest string that could be made here, we can check inequality with < rather than !=, saving a byte.

It would be shorter to use urandom similarly to Surculose Sputum's new answer.

# Julia 1.0, 92 bytes

A simple recursive implementation. i keeps track of how often "zun" has occured.

f(i=0)=(r=rand(1:2);println([:zun,:doko][r]);r>1 ? i>3 ? print("ki-yo-shi!") : f() : f(i+1))


Try it online!

# Java 10, 148141137136125123112110 107 bytes

v->{var r="";for(int k=3;k!=1;r+=k%2<1?"zun/":"doko/")k=k<<7^(int)(Math.random()*2);return r+"ki-yo-shi!";}


Golfed version of the approach that @loohhoo used in her C# answer.
-4 bytes thanks to @ceilingcat.
An additional -11 bytes from @loohhoo's new approach (after I helped her golf it a bit), so make sure to upvote her if you haven't already!
-11 bytes by porting @Arnauld's JavaScript answer (thanks to @OlivierGrégoire)
And yet another -3 bytes thanks to @loohhoo's third new approach, haha ;D

Uses / instead of newlines as delimiter.

Try it online.

Explanation:

v->{                      // Method with empty unused parameter and String return-type
var r="";               //  Result-String, starting empty
k!=1;               //  Loop as long as k is not 1 yet:
;                   //    After every iteration:
r+=k%2<1?          //     If k is even:
"zun/"         //      Append "zun" and a "/" to the result
:               //     Else (k is odd):
"doko/")       //      Append "doko" and a "/" to the result
k=                    //    Change k to:
k<<7                //     First bit-wise left-shift it by 7
^(int)(Math.random()*2);
//     And then bitwise-XOR it with a random 0 or 1
return r                //  Return the result-String
+"ki-yo-shi!";} //  appended with "ki-yo-shi!"

• For some reason I used doku instead of doko in my answer... I'm arbitrarily blaming you.
– Neil
May 9 '20 at 19:51
• @Neil Lol, not sure how I misread that, but thanks for noticing I guess. ;) May 9 '20 at 20:48
• 112 bytes, by porting Arnauld's answer. May 11 '20 at 9:50
• @OlivierGrégoire Thanks. And another -2 by only doing j=2 once. :) May 11 '20 at 10:16
• Good catch! :-) May 11 '20 at 10:21

# Zsh, 83 bytes

>zun>doko
49744()<<<ki-yo-shi!>>*
*(){sum<<<$@||$@ ls|shuf -n1|tee -a *}
z* * *


Try it online!

Outputs to the files named zun and doko (both should be identical).

• >zun>doko - create the files zun and doko
• 49744() - define a function called 49744
• <<<ki-yo-shi! - output ki-yo-shi!
• >>* - append to all files
• *(){} - define a function with the names of all the files in the current directory (i.e. zun and doko)
• sum<<<$@ - take the checksum of the arguments •  - try to execute that as a command • the checksum of zun zun zun doko is 49744, which is defined as a command. All other combinations are not. • || - if that fails (i.e. the arguments are not zun zun zun doko): • ls - list all files in the current directory • shuf -n1 - pick one item at random • tee -a * - append to all the files in the current directory, as well as being captured by the  •  - then pass that as the last argument to: • $@ - execute the arguments: the first argument is the command to execute, the remaining ones are passed as arguments. Since the first argument is always either zun or doko, this actually recurses, passing all but the first argument, with the new random string appended.
• z* * * - initial recursive call - call the function with the name "all files that start with z" (i.e. zun),
• * * - with the arguments "all files" and "all files" (i.e. doko zun doko zun)
• -1: actually I sometimes get bad outputs such as "zun zun doko ki-yo-shi!" "zun zun zun doko ki-yo-shi!". You have to end with "zun zun zun zun doko ki-yo-shi!". Jun 13 at 9:51

# VBScript, 257 bytes

Randomize():z="zun,":d="doko,":Do:a=a&Array(z,d)(Round(Rnd())):Loop While UBound(Split(a,","))<5:Do:a=Split(a,","):a(0)="a":a=Replace(Join(a,",")&Array(z,d)(Round(Rnd())),"a,",e):MsgBox a:Loop While Not Cbool(Instr(a,"zun,zun,zun,doko,")):MsgBox"ki-yo-shi!"

• Hi, welcome to PPCG! Do you mind adding a header to that like the other answers? It goes like # Language, N bytes. Jun 19 at 15:55
• @EasyasPi fixed it! Sorry! Jun 19 at 17:01
• Does this actually output "zun" and "doko"? Jun 19 at 21:57
• @EasyasPi it doesn't, every x is either 0 ("zun") or 1 ("doko"), I'll fix that and repost with the actual number of bytes! Jun 19 at 22:48
• @EasyasPi Fixed the script, now it outputs 'zun' and 'doko'! :) Jun 19 at 23:10

# Raku, 61 bytes

(<doko zun>[{^2 .pick}...{:2[@_]%32==30}],'ki-yo-shi!')».say


Try it online!

# Charcoal, 38 bytes

Ｗ›⁴⌕✂υ±⁵Ｌυ¹doko⊞υ‽⪪dokozun⁴υki-yo-shi!


Try it online! Link is to verbose version of code. Explanation:

Ｗ›⁴⌕✂υ±⁵Ｌυ¹doko


Repeat until the string doko is the last of the last 5 strings in the initially empty list (thus the first four must all be zun)...

⊞υ‽⪪dokozun⁴


... split the string dokozun into substrings of length at most 4 and push one randomly to the list.

υki-yo-shi!


Output the list and ki-yo-shi!. (Charcoal automatically outputs each list element on its own line.)

# Wolfram Language, 109 bytes

Join[NestWhile[#~Join~{RandomChoice@{z,d}}&,{},#[[-5;;]]=!={z,z,z,z,d}&]/.z->"zun"/.d->"doko",{"ki-yo-shi!"}]


Try it online!

Pretty straightforward: randomly appends z or d to a list until the last five elements match {z,z,z,z,d}, converts each to the proper string, then appends "ki-yo-shi!".

# R161143 69 bytes

An initial & fairly clumsy attempt at a code golf challenge involving text!, and defining

b=c("doko","zun")

for(i in -3:which(apply(matrix((a<-sample(0:1,2e5,r=T))[outer(1:5,1:1e5,"+")],5)*2^(0:4),2,sum)==15)[1])cat(b[1+a[i+5]],"\n") cat("ki-yo-shi!")

Try it online!

Using the original Japanese characters with

b=c("ドコ","ズン")

and

cat("キヨシ!")

brings the score down to 137 bytes...

A better alternative to using cat( would be appreciated as the ,"\n" part wastes 5 bytes for a newline command.

A different approach reducing the count

e=0
while(e<1){x=rt(1,1)<0)
cat(b[1+x],"\n")
e=e*x-x+(!x&-3>e)}
cat(a)


a="ki-yo-shi!"


Try it online!

# ThumbGolf, 53 bytes

Machine code (little endian pairs):

2100 de78 0840 bf2c a008 a009 4149 de00
de3a f001 011f 291e d1f3 a001 de00 4770
696b 792d 2d6f 6873 2169 000a 757a 006e
6f64 6f6b 00


Commented assembly:

        // Include ThumbGolf wrapper macros
.include "thumbgolf.inc"
.globl main
.thumb_func
main:
// Set our counter bitmask to zero
movs    r1, #0
.Lloop:
// Store a 32-bit random integer into r0.
rand    r0
// Using lsrs, test the lowest bit.
lsrs    r0, r0, #1
// Set up an IT block, checking the carry flag set from lsrs
ite     cs
// If the lowest bit was 1, set r0 to "zun"
// Otherwise, set it to "doko"
// r1 = r1 * 2 + (rand() & 1)
// Print the string in r0
puts    r0
// Print a newline. We could technically put the newline in the strings
// themselves for a THEORETICALLY identical byte count, but the true
// reason for this (and the main reason I added this instruction) is to
// make it so the string literals are aligned, preventing wide ADR.
putspc  '\n'
// Mask the lowest 5 bits using wide AND
and     r1, r1, #0b11111
// 0b11110 means zun zun zun zun doko, loop while it is not that
cmp     r1, #0b11110
bne     .Lloop
.Lloop.end:
// Print "ki-yo-shi!"
puts    r0
// Return
bx      lr
// These string literals are very carefully placed so that they are
// all 4 byte aligned. That is the only reason for the trailing newline:
// it makes kiyoshi an even multiple of 4 bytes.
.Lkiyoshi:
.asciz "ki-yo-shi!\n"   // 12 bytes (aligned)
.Lzun:
.asciz "zun"            // 4 bytes (aligned)
.Ldoko:
.asciz "doko"           // 5 bytes (aligned)


Output is to stdout.

It wasn't easy figuring out how to align my string literals, but it was definitely worth it.

Note that I created ThumbGolf after this question was submitted.

# Pxem, 66 bytes (filename).

Unprintables are as a backslash followed by its code point in three-digit octal.

\001.t.z\002.r.wzun.p.m\001.+.t\001o.r.ao.zdok.p.m\004.xki-yo-shi!.p.d.a\001.tXX.a.a


XX.z
# heap: 1 as counter
.a\001.t.zXX.z
# choosing either zun or doko
.a\002.r.wXX.z
.azun.pXX.z
# increment counter
.a.m\001.+.tXX.z
# push o for next dot-z and get out
.a\001o.r.aXX.z
# if empty; then push o and get in
# else pop it and ignore next block
.ao.zXX.z
# reusing the o
# print kiyoshi and exit if counter is >4
.a.m\004.xki-yo-shi!.p.d.aXX.z
# else reset counter to 1
.a\001.tXX.aXX.z
# pop a garbage of an o if any
.a.a


Try it online!

# Ly, 96 bytes

"ki-oy-shi!">"okod">"nuz">666661[p09?5Gfps[p<0]p<0&:p[o](10)opl[p>0]p>l:rp&s&+lr2=fp*[<<<&o;]p1]


Try it online!

This code uses 4 stacks, one for each of the output lines, and one holding 0|1 indicators of the last 4 output choices (picked using random numbers). Then it writes the ki-oy-shi! line and exits if the sum of the choices (and last choice) show that the exit pattern was generated.

"ki-oy-shi!">"okod">"nuz">       # Push the strings onto separate stacks
66666                            # Initialize the stack to a "not done" state
1[p    ...code...    1]          # An infinite loop pattern
09?5Gfp                          # Pushes 0|1 on the stack randomly
s                         # Save the choice on the backup stack
[p<0]p<                  # Shift 1 or 2 stacks, depending on random choice
0&:p              # Duplicate the stack with a 0 sep, pop the top 0
[o](10)op     # Print one copy of the stack, plus LF, pop the 0
l[p>0]p>                         # Restore random choice, shift back 1 or 2 stacks
l:                       # Restore random choice, duplicate it on stack
rp                     # Reverse the stack, delete top (remove oldest choice)
&s&+l                # Save stack, sum the choices, restore stack
r2=fp           # Reverse stack to move sum on top, compare to 2
*          # Combine with latest choice, if 1 we are done
[<<<&o;]  # Time to go, switch stacks, print and exit
p # Delete the test results from the stack, then loop...


# Vyxalo, 36 bytes

{₀℅:£J:5NȯB30≠«ǎ⌈8τ«½¥i,|}ki-yo-∪Ǎ!


Try it Online!

-7 thanks to lyxal.

# APL (Dyalog Unicode), 51 bytes

(⌷∘'zun' 'doko'¨⍪∘?∘2⍣{(2,⍨4/1)≡¯5↑⍺}⍬)'ki-yo-shi!'


Try it online!

Slight improvement on the other solution, since the account is deleted.

# Knight, 54 bytes

;=aF;W-30;O I=x%R2"zun""doko"=a%+x*2a 32xO"ki-yo-shi!"


Try it online!

# Initialize with FALSE. We will coerce it to
# Number later, just get some form of 0.
`