text:
Square snowflake
Produce this square snowflake.
XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
XX   X   X   X   X   X   X   X   X   X   X   X   X   X   X   XX
X XXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXX X
  XX       X X   X X       X X   X X       X X   X X       XX
X X XXX XXXX       XXXX XXXX       XXXX XXXX       XXXX XXX X X
XXX XX   X X X   X X X   X X X   X X X   X X X   X X X   XX XXX
X   X XXXX XXX   XXX XXXXX XXX   XXX XXXXX XXX   XXX XXXX X   X
      XX               X X   X   X   X X               XX
X   X X XXX XXX XXX XXXX               XXXX XXX XXX XXX X X   X
XXX XXX XX   X   X   X X X   X   X   X X X   X   X   XX XXX XXX
X X X   X XXXX   XXXXX XXX XXX   XXX XXX XXXXX   XXXX X   X X X
  XXXXX   XX       X X   X X X   X X X   X X       XX   XXXXX
X X   X X X XXX XXXX   XXXXX       XXXXX   XXXX XXX X X X   X X
XXXX XX XXX XX   X X X X   X X   X X   X X X X   XX XXX XX XXXX
X       X   X XXXX XXX XX XXXX   XXXX XX XXX XXXX X   X       X
              XX                               XX
X       X   X X XXX XXX XXX XXX XXX XXX XXX XXX X X   X       X
XXXX XX XXX XXX XX   X   X   X   X   X   X   XX XXX XXX XX XXXX
X X   X X X X   X XXXX   XXXXX   XXXXX   XXXX X   X X X X   X X
  XXXXX   XXXXX   XX       X X   X X       XX   XXXXX   XXXXX
X X X   X X   X X X XXX XXXX       XXXX XXX X X X   X X   X X X
XXX XXX XXXX XX XXX XX   X X X   X X X   XX XXX XX XXXX XXX XXX
X   X X X       X   X XXXX XXX   XXX XXXX X   X       X X X   X
      XXXXX XXX       XX               XX       XXX XXXXX
X   X X   X X X X   X X XXX XXX XXX XXX X X   X X X X   X X   X
XXX XXXX XXXX   XXX XXX XX   X   X   XX XXX XXX   XXXX XXXX XXX
X X X       X X X X X   X XXXX   XXXX X   X X X X X       X X X
  XXXXX   XXXXX   XXXXX   XX       XX   XXXXX   XXXXX   XXXXX
X X   X   X   X X X   X X X XXX XXX X X X   X X X   X   X   X X
XXXX XXX XXX XX XXXX XX XXX XX   XX XXX XX XXXX XX XXX XXX XXXX
X               X       X   X XXX X   X       X               X
                              XXX
X               X       X   X XXX X   X       X               X
XXXX XXX XXX XX XXXX XX XXX XX   XX XXX XX XXXX XX XXX XXX XXXX
X X   X   X   X X X   X X X XXX XXX X X X   X X X   X   X   X X
  XXXXX   XXXXX   XXXXX   XX       XX   XXXXX   XXXXX   XXXXX
X X X       X X X X X   X XXXX   XXXX X   X X X X X       X X X
XXX XXXX XXXX   XXX XXX XX   X   X   XX XXX XXX   XXXX XXXX XXX
X   X X   X X X X   X X XXX XXX XXX XXX X X   X X X X   X X   X
      XXXXX XXX       XX               XX       XXX XXXXX
X   X X X       X   X XXXX XXX   XXX XXXX X   X       X X X   X
XXX XXX XXXX XX XXX XX   X X X   X X X   XX XXX XX XXXX XXX XXX
X X X   X X   X X X XXX XXXX       XXXX XXX X X X   X X   X X X
  XXXXX   XXXXX   XX       X X   X X       XX   XXXXX   XXXXX
X X   X X X X   X XXXX   XXXXX   XXXXX   XXXX X   X X X X   X X
XXXX XX XXX XXX XX   X   X   X   X   X   X   XX XXX XXX XX XXXX
X       X   X X XXX XXX XXX XXX XXX XXX XXX XXX X X   X       X
              XX                               XX
X       X   X XXXX XXX XX XXXX   XXXX XX XXX XXXX X   X       X
XXXX XX XXX XX   X X X X   X X   X X   X X X X   XX XXX XX XXXX
X X   X X X XXX XXXX   XXXXX       XXXXX   XXXX XXX X X X   X X
  XXXXX   XX       X X   X X X   X X X   X X       XX   XXXXX
X X X   X XXXX   XXXXX XXX XXX   XXX XXX XXXXX   XXXX X   X X X
XXX XXX XX   X   X   X X X   X   X   X X X   X   X   XX XXX XXX
X   X X XXX XXX XXX XXXX               XXXX XXX XXX XXX X X   X
      XX               X X   X   X   X X               XX
X   X XXXX XXX   XXX XXXXX XXX   XXX XXXXX XXX   XXX XXXX X   X
XXX XX   X X X   X X X   X X X   X X X   X X X   X X X   XX XXX
X X XXX XXXX       XXXX XXXX       XXXX XXXX       XXXX XXX X X
  XX       X X   X X       X X   X X       X X   X X       XX
X XXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXXX   XXXX X
XX   X   X   X   X   X   X   X   X   X   X   X   X   X   X   XX
XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX

TIO link, and a more square spaced-out version.
How it's made
You start with a initial crystal (cell) at the center. Then, a new crystal is formed simultaneously at each empty space that touches exactly one existing crystal, looking at the 8 cells orthogonally or diagonally adjacent to it. Crystals remain indefinitely. Here is the snowflake after 3 steps, marking the crystals added at each step.
333 333
32   23
3 111 3
  101
3 111 3
32   23
333 333

We continue this for 31 steps, making a 63-by-63 snowflake with 1833 crystals.
This process is the Life-like (totalistic) cellular automaton B1/S012345678, also known as H-trees or Christmas Life.
Output
Output or print in any 2D array format with two distinct entries for crystal and empty space, or anything that displays as such. The grid must by exactly 63 by 63, that is having no wasted margin. It's fine to have trailing spaces for non-crystals or jagged arrays truncated to the last crystal in each row. A trailing newline is also OK.
Bit representations are not valid unless they display with two distinct symbols for the bits by default.

I also posted this challenge on Anarchy Golf. It requires a strict output format of the ASCII art above of X's and spaces.

Is this representation acceptable? Matrix with 0 and 1, displayed with spaces in betwee

@LuisMendo Yes, that's completely fine.

MATL, 21 19 bytes
l31:"TTYat3Y6Z+1=Y|

Try it online!
Explanation
l         % Push 1 (this is the initial 1×1 array)
31:"      % Do 31 times
  TTYa    %   Extend with a frame of zeros in 2D
  t       %   Duplicate
  3Y6     %   Push [1 1 1; 1 0 1; 1 1 1] (8-neighbourhood; predefined literal)
  Z+      %   2D convolution, maintaining size. This gives the number of active
          %   neighbours for each cell
  1=      %   Equal to 1? Element-wise
  Y|      %   Logical OR
          % End (implicit)
          % Display (implicit)

@Downvoter: any suggestion for improvement? What problem do you find with my answer?

Someone's just jealous ;)

APL (Dyalog Unicode), 31 29 27 bytes
(⊢∨1={≢⍸⍵}⌺3 3)⍣≡∘.×⍨32=⍳63

Try it online!
-4 bytes thanks to @ngn.
A full program that prints a boolean matrix.
How it works
(⊢∨1={≢⍸⍵}⌺3 3)⍣≡∘.×⍨32=⍳63
                     32=⍳63  ⍝ A 63-length vector whose center element is 1
                             ⍝ and the rest is 0
                 ∘.×⍨  ⍝ Outer product self by ×, giving initial state of CA
(             )⍣≡  ⍝ Run the cellular automaton until it stabilizes...
     {≢⍸⍵}⌺3 3     ⍝ Count ones in 3-by-3 subgrids
   1=              ⍝ Test if the 3×3 subgrid has exactly one alive cell
 ⊢∨                ⍝ or the cell is already alive

This is very nice.

¯63↑32↑1 -> 32=⍳63

{5⌷⍵∨1=+/⍵}∘,⌺3 3 -> (⊢∨1={+/,⍵}⌺3 3)

+/,⍵ (in mine) -> ≢⍸⍵

Python 2 with scipy, 105 bytes
from scipy.signal import*
A=[1],
K=1,1,1
exec"A=convolve2d(A,[K,(1,9,1),K]);A=(A>8)+(A==1)+0;"*31
print A

Try it online!
Use convolution to grow the array. The convolution kernel used is:
[[1, 1, 1]
 [1, 9, 1]
 [1, 1, 1]]

and the criteria for living cell is:
c >= 9 or c==1

aka when the cell is already living or when there is exactly 1 neighbor.
Note that NumPy by default only prints a summary of a large array, like so:
[[1 1 1 ... 1 1 1]
 [1 1 0 ... 0 1 1]
 [1 0 1 ... 1 0 1]
 ...
 [1 0 1 ... 1 0 1]
 [1 1 0 ... 0 1 1]
 [1 1 1 ... 1 1 1]]

Thus, I have to call numpy.set_printoptions to force Numpy to prints the entire array. This part is not included in the byte count, since I dont't think it's relevant to the actual problem.
This 114 bytes function returns the array instead of printing it out, thus circumventing the print option problem.

This truncated printing is fine, since it's the default numpy way to render arrays. I added "with scipy" to the header as per the convention that's it being installed is akin to a language version, as I expressed here.

The exec * 31 trick is amazing !

You might be interested in trying the anarchy version of this challenge that I posted

@xnor Thanks, I'll take a look. I've never done anarchy golf before, but it seems like the output is more restrictive. I might have to try a different approach.

J, 38 34 bytes
3 3(1 e.+/,4&{)@,;._3^:31*/~0=i:62

Try it online!
-3 bytes thanks to ngn
-1 byte thanks to Bubbler
Try it online!
Core ideas taken from Bubbler's elegant APL answer -- be sure to upvote him.
This was an experiment to see how close I could get using J, which lack's APL diamond operator and so is at a disadvantage.
Instead, J has SubArrays, which requires you to manually add the padding of zeros.  I opted here to add all the padding at once, up front: a 125 x 125 matix of zeros with a 1 in the center.
After each iteration, we'll lose 2 from our dimension, so after 31 iterations we'll be down to 63 x 63, which is what we need.

7812=i.,~125 -> */~0=i:62

Ofc!  Thanks @ngn.

You can remove the ] between ^:31 and */~.

Was just making that change as you commented :)

Just found a solution not using ;._3 with the same byte count. Btw, ;.3 / ;._3 is only called MaxCubes when it is called with one argument. With two args, it is Subarrays.

@Bubbler Thanks, edited. I like your solution, very clever way of doing the neighbor sums.

JavaScript (ES6),  149 138 136  135 bytes
Returns a binary matrix.
f=k=>k>31?m:f(-~k,m=(k?m:a=[...Array(63)]).map((r=a,y)=>r.map((v,x)=>k?v|!~(g=d=>d--&&g(d)-~~(m[y+~-(d/3)]||0)[x-1+d%3])(9):x*y==961)))

Try it online!
Commented
The same map() loops are used to initialize the 63x63 binary matrix m[] and to recursively update it. Doing a separate initialization of m[] before entering the recursion would cost more bytes.
f = k =>                         // f is a recursive function taking a counter k
  k > 31 ?                       // if k is greater than 31:
    m                            //   stop recursion and return m[]
  :                              // else:
    f(                           //   do a recursive call:
      -~k,                       //     increment k
      m = (                      //     update m[]
        k ? m                    //     if this is not the first iteration, use m[]
          : a = [...Array(63)]   //     otherwise, use a vector a[] of 63 entries
      ).map((r = a, y) =>        //     for each row r[] at position y, using a[]
                                 //     as a fallback for the first iteration:
        r.map((v, x) =>          //       for each value v at position x:
          k ?                    //         if this is not the first iteration:
            v | !~(              //           if v is already set, let it set
              g = d =>           //           or use the result of the recursive
                                 //           function g taking a direction d
              d-- &&             //             decrement d; stop if it's zero
              g(d) -             //             recursive call
              ~~(                //             subtract 1 if the cell at ...
                m[y + ~-(d / 3)] //               ... y + dy ...
                || 0             //
              )[x - 1 + d % 3]   //               ... and x + dx is set
            )(9)                 //           initial call to g with d = 9
          :                      //         else (first iteration):
            x * y == 961         //           set the cell iff x = y = 31 (because 31
                                 //           is prime, it's safe to test xy = 31²)
        )                        //       end of inner map()
      )                          //     end of outer map()
    )                            //   end of recursive call

T-SQL, 330 266 232 bytes
This takes a long time to execute(reason: must be something wrong with the question).
-22 Bytes thanks to @RossPresser
SELECT top 3969' 'z,IDENTITY(INT,0,1)i,63x
INTO t FROM sys.messages
WHILE @@ROWCOUNT>0UPDATE
t SET z=1WHERE(SELECT SUM(1*z)FROM t x
WHERE(t.i/x-i/x)/2=0and(t.i%x-i%x)/2=0)=1or i=1984and''=z
SELECT string_agg(z,'')FROM t GROUP BY i/x

This will execute in Microsoft SQL Server 2017 or higher. Try it online on dbfiddle.uk; link is set to use markdown for output so you can see the whole snowflake.
Before execute press ctrl-t for output to text instead of grid. This takes 60 seconds to execute on my computer.

Long time no "see". Thanks for the edit Ross.

I fully intend to try to golf this when I get time :)

@RossPresser I took another look and realized i could save some bytes. I would love if you found some additional golfing. Performance is better now

golfed out 22 bytes by using SELECT INTO instead of CREATE TABLE/INSERT. Might have killed the performance :)  Also fixed dbfiddle link to latest.

golfed out another 2 bytes by using 1 instead of 'x'

@t-clausen-dk this is my current golf

@RossPresser thanks for your golfing - I didn't know that identity method, that will probably also change some of  my earlier answers as well. I found 10 more bytes to golf. The script takes around 1 minute to execute, but this is not about performance

@t-clausen-dk Nice work! but the dbfiddle link still goes to an old version

Python 2, 125 bytes
s="%64c"%10
exec's*=63;s="".join(s[n][(s[n+3967:][:191]*3)[::64].strip()=="X":n!=2015]or"X"for n in range(4032));'*32
print s

Try it online!
This answer is by user "clock" based on hallvabo's solution, on the Anarchy Golf version of this challenge that I submitted. Note that output there is strict and is required to be exactly the picture of X's and spaces to STDOUT via a full program, with only an allowance for a trailing newline.
The most interesting part of this answer, in my opinion, is the concise construction (s[n+3967:][:191]*3)[::64] after s*=63 to get the nine neighbors of the cell counting itself in a flat newline-joined string representing the grid. To check if there's exactly one X among them, .strip() is called to get rid of whitespace on either side, and the result is checked to equal just "X".
It's interesting how simulating the steps on the string representation of the output directly (rather than an array of bits) not only saves on doing a conversion to characters later but also allows string-specific methods to be used in a golfy way.
Another neat trick, borrowed from hallvabo, is "%64c"%10 used to initialize s to 63 spaces followed by a newline for a line of the initial empty grid. Using the %c format, which converts an ASCII value to a character, is shorter than " "*63+"\n" or "%64s"%"\n".

Wolfram Language (Mathematica) + my LifeFind package, 47 bytes
<<Life`
Last@CA[{{1}},31,Rule->"B1/S01234568"]&

There isn't any living cell with 7 living neighbors in the first 31 steps, so I can golf the rule by 1 byte: B1/S012345678 -> B1/S01234568.

Charcoal, 38 37 bytes
Ｊ³¹¦³¹XＦ³²«ＵＭＫＡXＦ⁶³Ｆ⁶³«Ｊκλ›⁼№ＫＭX¹⁼ＫＫX

Try it online! Link is to verbose version of code. Explanation:
Ｊ³¹¦³¹X

Output the X in the centre of the square.
Ｆ³²«

Grow the snowflake 31 times. (The last loop is just used for its side-effect of replacing the -s with Xs.)
ＵＭＫＡX

Change all the -s to Xs.
Ｆ⁶³Ｆ⁶³«

Iterate over the square.
Ｊκλ

Jump to each position in the square.
›⁼№ＫＭX¹⁼ＫＫX

If the cell does not yet have an X but it has one neighbouring X then print a -.

05AB1E, 31 bytes
1¸¸31F0δ.ø¬0*¸.øD2Fε0.øü3O}ø}Θ~

05AB1E and matrices are not a good combination.. :/
Outputs as a 63x63 matrix of 0s and 1s for spaces and crystals respectively.
Try it online. (The footer is to pretty-print it, feel free to remove it to see the actual result.)
Explanation:
1¸¸            # Start with a matrix containing 1: [[1]]
 31F           # Loop 31 times:
    0δ.ø       #  Surround each row with leading and trailing 0
               #   i.e. [[1,1,1],[1,1,1],[1,1,1]] → [[0,1,1,1,0],[0,1,1,1,0],[0,1,1,1,0]]
    ¬          #  Take the first row (without popping the matrix)
               #   → [0,1,1,1,0]
     0*        #  Multiply each value by 0
               #   → [0,0,0,0,0]
       ¸       #  Wrap it into a list
               #   → [[0,0,0,0,0]]
        .ø     #  And surround the matrix with that row of 0s
               #   i.e. [[0,0,0,0,0],[0,1,1,1,0],[0,1,1,1,0],[0,1,1,1,0],[0,0,0,0,0]]
    D          #  Duplicate it
     2F        #  Loop 2 times:
       ε       #   Map each row to:
        0.ø    #    Surround the row with leading and trailing 0
               #     i.e. [0,1,1,1,0] → [0,0,1,1,1,0,0]
           ü3  #    Create overlapping triplets
               #     → [[0,0,1],[0,1,1],[1,1,1],[1,1,0],[1,0,0]]
             O #    Sum each triplet
               #     → [1,2,3,2,1]
       }ø      #   After the map: transpose/zip; swapping rows/columns
               #    i.e. [[0,0,0,0,0],[1,2,3,2,1],[1,2,3,2,1],[1,2,3,2,1],[0,0,0,0,0]]
               #     → [[0,1,1,1,0],[0,2,2,2,0],[0,3,3,3,0],[0,2,2,2,0],[0,1,1,1,0]]
      }Θ       #  After the inner loop: check for each whether it's 1 (1 if 1; 0 otherwise)
               #   i.e. [[1,2,3,2,1],[2,4,6,4,2],[3,6,9,6,3],[2,4,6,4,2],[1,2,3,2,1]]
               #    → [[1,0,0,0,1],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[1,0,0,0,1]]
        ~      #  Take the bitwise-OR of the values at the same positions in the matrices
               #   → [[1,0,0,0,1],[0,1,1,1,0],[0,1,1,1,0],[0,1,1,1,0],[1,0,0,0,1]]
               # (after the loop, the resulting matrix is output implicitly)

Jelly, 41 34 bytes
ı*Ɱ5I;$Ṗ+)FċⱮẹ1ịƲ;
32×1ıWÇṛ¡’ÆiḞŒṬ

Try it online!
-7 because to nobody's surprise there's two shorter ways to get \$32+32i\$ than “  ‘Æị, the symmetry of the neighborhood lets +Ɲ golf to I, we can trade two ¤s and a € for ) and $, ẹ exists, œ| is overkill, and selfing ċⱮ is outright superfluous
This is an absolute mess that could probably be outdone easily by someone with any clue as to how to mimic others' convolution-based approach. Outputs a two-dimensional list of ones and zeros, which are conveniently translated to the example's Xs and spaces by the testing footer provided.
ı*Ɱ5I;$Ṗ+)FċⱮẹ1ịƲ;    Monadic helper link: grow a list of complex numbers
         )            For each number:
ı*Ɱ5                  raise the imaginary unit 1j to each power from 1 to 5,
    I;$               prepend the differences between its adjacent elements,
       Ṗ              remove the extra 1j,
        +             and add each to the number.
          F           Flatten the resulting list of lists,
               ịƲ     take the elements at indices
             ẹ1       which are indices of 1 in
           ċⱮ         the list of how many times each element occurs,
                 ;    and append the original list.

32×1ıWÇṛ¡’ÆiḞŒṬ    Niladic main link: generate the snowflake
32                 Set the argument to 32,
  ×1ı              then multiply it by 1+1j
     W             and wrap it in a singleton list.
      Ç ¡          Grow that a number of times equal to
       ṛ ’         the argument minus 1.
          ÆiḞ      Convert the complex numbers to pairs of integers,
             ŒṬ    and interpret the pairs as coordinates of 1s in a 2D array.

PHP, 206 bytes
for($a[][]=1;++$n<32;){$b=$a;for($i=$n;$i>=-$n;$i--)for($j=$n;$j>=-$n;$j--){$a[$i][$j]=$a[$i][$j]&1;$c=0;for($k=2;--$k>-2;)for($l=2;--$l>-2;)if($k|$l)$c+=$b[$i+$k][$j+$l];1!=$c?:$a[$i][$j]=1;}}var_dump($a);

Try it online!
Well I'm getting used to horrible PHP answers :D don't blame the language, there are libs for matrixes.. displays an unordered array of 1 and 0, but with proper keys. change the first loop value (32) to generate any size snowflake..
24 bytes could have been saved by removing $a[$i][$j]=$a[$i][$j]&1; if we only needed the truthy values.

Python 2, 146 130 bytes
Prints a 2d-list of booleans.
R=range(63)
for i in R:m=[[i>0==(x*y-961)*0**m[y][x]*~-sum(sum(k[x+x%~x:x+2])for k in m[y+y%~y:y+2])for x in R]for y in R]
print m

Try it online!

Nice trick with the x%~x. How did you come up with that?

@xnor I didn't like the x+(x>0), so I tried to find something better. x-1+1%-~x would work, so I tried a few more arguments to %. If I hadn't arrived at x+x%~x by hand, I would have bruteforced it.

@xnor did a brute force search now, turns out -2%-~x and 0-2%~a both work at the same length.

You might be interested in trying the anarchy version of this challenge that I posted

R, 144 122 118 bytes
m=matrix(!-2112:2112,65)
for(k in 1:31){l=m;for(i in 67:4159)if(sum(l[i+-3:5%/%3+-1:1*65][-5])==1)m[i]=1}
m[2:64,2:64]

Try it online!
My first code golf attempt (except for secret ones that I never dared to post...), so nothing really clever going on, I'm afraid...
Edit 1: -4 bytes to remove useless variable definition
Edit 2: thanks to Giuseppe: -18 bytes!
Edit 3: thanks again to Giuseppe: -4 more bytes

Nice work! Some suggestions.

Thankyou!  I've edited-in your suggestions, with acknowledgement.  Very much appreciated!

118 bytes

Thankyou again Giuseppe.  Especially for (implicitly) pointing-out my beginner's errors (useless '>1'...)...  editing-in again now...

You've come a long way since this answer :-)

@Giuseppe - Thanks.  Not sure whether code-golfing has made my real-world skills any better, but it's fun...

Java (JDK), 243 bytes
v->{int N=63,K=N*N,g[][]=new int[N][N],w[]=new int[K],i=31,x,c,y;for(g[i][i]=1;i-->0;){for(x=K;x-->0;w[x]=c==1?x+1:0)for(c=0,y=K;y-->0;)if(g[y%N][y/N]>0&(Math.abs(x%N-y%N)|Math.abs(x/N-y/N))<2)c++;for(int z:w)if(z-->0)g[z%N][z/N]=1;}return g;}

Try it online!
Explanations
Basically, the algorithm is to fill two grids by consecutive merges to keep the data from being changed at runtime. For each square, we check if there are neighbours set (using a second full loop instead of a smaller 1-square distance to avoid byte-costly constraints). If there's exactly one neighbour, the square is added to the writing (to-be-merged) grid. Then when all the computation is done, the writing-grid is merged to the main grid. When all the 31 loops are done, the resulting grid is returned.
v->{
  int N=63,
      K=N*N,
      g[][]=new int[N][N],          // the grid and return-value, we read from it
      w[]=new int[K],               // the temp array to write in
      i=31,                         // the main iterator to expand the snowflakes
      x,c,y;                        // x the position being tested, c the count of neighbours, y the neighbour candidates
  for(    g[i][i]=1;                // Init the snowflake in its center
          i-->0;                    // Expand the snowflake 31 times
    ){
    for(    x=K;                     // init x to match all the positions
            x-->0;                   // For each position
            w[x]=c==1?x+1:0          // If the counter is exactly 1,
                                     // set x as a position that's not empty.
                                     // and store x+1 instead of 1 or x to ease the merge operation
                                     // and leave 0 as a default value
       )
      for(c=0,y=K;y-->0;)            // Reset the count and loop through all squares again
        if(  g[y%N][y/N]>0           // if y is set
            &( Math.abs(x%N-y%N)     // and if x and y are neighbours
              |Math.abs(x/N-y/N))<2
          )
          c++;                       // Then increase the neighbour-count.
    for(int z:w)                     // For each value to be merged
      if(z-->0)                      // which is +1'd and non default
        g[z%N][z/N]=1;               // Then place it in the grid.
  }
  return g;                          // Return the grid
}

c==1?x+1:0 and if(z-->0) can be c==1?x:-1 and if(z>=0) for -2.

Building on @KevinCruijssen 235 bytes

C (gcc), 207 206 192 bytes
-1 -14 bytes thanks to ceilingcat!

#define F(x)for(x=64;--x;
#define z M[r][c]
M[65][65]={[32][32]=32},r,c,i=32,x;main(s){for(;--i;)F(r))F(c)z=s-!z?z:i)for(s=0,x=-9;x++;)s+=M[x/3-~r][x%3-~c]>i;F(r)puts(""))F(c))putchar(33-!z);}

Try it online!
To avoid having to use two tables (one for current iteration and one for the next), each new crystal is written using the current iteration number, instead of just using 1. This allows for ignoring any crystals from the current iteration when counting neighbours.
A lot of the counting is done backwards for golfing reasons, including the iteration number, which is why we seed the table with 32 in the middle.
The table is made too large so as to have padding, allowing us to count neighbours without having to care about edge-cases.
A more elegant solution is bound to exist. This many loops is hardly ever a good sign.
#define F(x)for(x=64;--x;       To make recurring loops more compact.
                                Goes from 63 down to 1, to skip padding in table.
M[65][65]={[32][32]=32},        Make padded table seeded with first crystal.
r,c,i=32,x,y;main(s){           Misc variables; i holds iteration.
for(;--i;)F(r))F(c)             Loop through table for each iteration.
s==!M[r][c]?M[r][c]=i:0)        Update current cell according to neighbour count
                                found in loop below. We only update if cell is empty
                                (M[r][c] == 0) and if s == 1, combined as shown.
for(s=0,x=r-2;x++<=r;)          Go through 3x3 grid with current cell in center.
for(y=c-2;y++<=c;)
s+=M[x][y]>i;                   If a cell contains a crystal NOT from current gen,
                                increase our neighbour count.

F(r)puts(""))F(c))              Output loop
putchar(33-!M[r][c]);}          Make any non-zero cell a exclamation mark; space otherwise

@ceilingcat Neat one. For some reason I abandonded that avenue too early.

JavaScript (V8), 465 bytes
[w=1414812756,269488144,357569872,285212944,22304e3,286265616,353718608,69648,1414856704,269553680,357650768,17830160,1426150656,286327056,1364280656,0,w,1343229968,1162876240,83886352,1146377472,1414533392,1078284624,327680,1078219860,1414811664,1145062736,89392384,1141130324,1427395664,1073758277,5].map(e=>(Array(32).fill().map(l=>(c.push(1&e?"X":" "),e>>=1),c=[]),c.slice(1).reverse``.concat(c)),o=e=>console.log(e.join``)||e).map(o).reverse``.slice(1).map(o);

Try it online!


text:
Triangles in a tetrahedron
The goal of this challenge is to extend the OEIS sequence A334581.

Number of ways to choose \$3\$ points that form an equilateral triangle from the \$\binom{n+2}{3}\$ points in a regular tetrahedral grid of side length \$n\$.

The sequence begins 0, 0, 4, 24, 84, 224, 516, 1068, ...
Example
For example, if we take side lengths of \$n=4\$, as in the example below, there are \$\binom{4+2}{3} = 20\$ vertices and \$\binom{20}{3} = 1140\$ ways to choose three of them. Of these, there are \$84\$ ways to choose \$3\$ vertices that form an equilateral triangle:

Challenge
This fastest-code challenge will have you compute as many terms of the sequence [A334581(0), A334581(1), ...] that you can in ten minutes, as measured on my machine.

The equilateral triangle isn't necessarily plane-aligned to the tetrahedron's faces, right?

@xnor, that's right. The plane-aligned triangles are somewhat remarkably counted by $4 \binom{n+3}{5}$, and $A334581(6) = 516 > 4 \binom{6+3}{5} = 504$.

Is it me or you're basically asking us to extend every sequence you submit? :)

This year? Just this one and the one about polyominoes on the hyperbolic plane! Some of them have been about extending other people's sequences though.

@Arnauld $4845$ seems to come from $\binom{20}{4}$. Not sure if the bottom number is intended to be $n$ or $3$, though.

Could you post an image of one of the non plane-aligned triangles?  The current animation moves too fast for me to see one.

@Jonah as far as I understand, they only occur in tetrahedrons of, uh, order of at least 6, and the animation has only 4.

The first class of non-planars coming in at $n=6$ appear to follow A059270, and then another class comes in at $n=10$ (maybe other classes then come in at further triangle numbers?) giving us something like $4(\binom{n+3}{5}+\max(0,\frac{(n-5)(n-4)(n-9)}{2})+x(n))$.

So these non-planar cases are essentially what make the problem interesting, correct?

@Jonah IMO they are.

@Jonah, for $n = 6 $, the 12 non-plane-aligned triangles (in barycentric coordinates) are $(0,0,2,3),(0,3,2,0),(3,1,0,1)$ up to symmetries of the tetrahedron.

@AndersKaseorg, the score is based on the time to compute $A(1), \dots, A(n)$, and sharing state is certainly allowed—but pre-seeding terms of the sequence is considered a loophole which is forbidden by default.

Suggest you make the problem statement clearer. I also thought it meant just computing A(n).

Rust, \$A(1), \dotsc, A(1375)\$ in 10 minutes
Unofficial score on Ryzen 7 1800X (8 cores/16 threads).  Build with cargo build --release and run with time target/release/tetrahedron n to compute \$A(1), \dotsc, A(n)\$.
This runs in \$O(n^4)\$ time.  (So to estimate a good value of \$n\$ for your CPU, first time it for some smaller \$n\$, then multiply that \$n\$ by a factor of \$\left(\frac{600\,\mathrm{s}}{t}\right)^{1/4}\$.)
How it works
Any triangle that fits inside a tetrahedron of minimal side \$k \le n\$ may be translated inside a tetrahedron of side \$n\$ in exactly \$\binom{n - k + 3}{3}\$ ways.  This means we only need to find it in one position, leaving six free parameters.  Two of these parameters may be computed from the other four (up to a sign choice) if the triangle is to be equilateral, so we only need to loop over an \$O(n^4)\$ space.
src/main.rs
use rayon::prelude::*;

fn get_counts(n: i64, a0: i64) -> Vec<i64> {
    let mut c = vec![0; n as usize];
    let a0a0 = a0 * a0;
    for a1 in if a0 == 0 { 1 } else { -n + 1 }..n {
        let d = a0a0 + a1 * a1;
        let m = n - a0.abs() - a1.abs();
        for a2 in if m > 0 {
            -n + 2 - (m & 1)..n
        } else {
            -n - m + 2..n + m
        }
        .step_by(2)
        {
            let d = d + a2 * a2;
            let r = 2 * (a0a0 - d);
            if r == 0 {
                continue;
            }
            for b0 in a0..n {
                let pp = d * (3 * d - 4 * (a0a0 + b0 * (b0 - a0)));
                if pp < 0 {
                    break;
                }
                let p = (pp as f64).sqrt() as i64;
                if p * p != pp {
                    continue;
                }
                let q = 2 * a0 * b0 - d;
                let mut check = |p: i64| {
                    let b1r = p * a2 + q * a1;
                    if b1r % r != 0 {
                        return;
                    }
                    let b1 = b1r / r;
                    let b2r = -p * a1 + q * a2;
                    if b2r % r != 0 {
                        return;
                    }
                    let b2 = b2r / r;
                    if (b0, b1, b2) <= (a0, a1, a2) || b0 + b1 + b2 & 1 != 0 {
                        return;
                    }
                    let t = 0.max(a0 + a1 + a2).max(b0 + b1 + b2)
                        + 0.max(-a0 - a1 + a2).max(-b0 - b1 + b2)
                        + 0.max(-a0 + a1 - a2).max(-b0 + b1 - b2)
                        + 0.max(a0 - a1 - a2).max(b0 - b1 - b2);
                    if t >= 2 * n {
                        return;
                    }
                    c[t as usize / 2] += 1;
                };
                check(p);
                if p != 0 {
                    check(-p);
                }
            }
        }
    }
    c
}

fn add_vec(c0: Vec<i64>, c1: Vec<i64>) -> Vec<i64> {
    c0.into_iter().zip(c1).map(|(x0, x1)| x0 + x1).collect()
}

fn main() {
    let n = std::env::args().skip(1).next().expect("missing argument");
    let n = n.parse().expect("not an integer");
    let counts = (0..n)
        .into_par_iter()
        .map(|a0| get_counts(n, a0))
        .reduce(|| vec![0; n as usize], add_vec);
    let (mut d0, mut d1, mut d2, mut d3) = (0, 0, 0, 0);
    for (i, x) in (1..).zip(counts) {
        d3 += x;
        d2 += d3;
        d1 += d2;
        d0 += d1;
        println!("{} {}", i, d0);
    }
}

Cargo.toml
[package]
name = "tetrahedron"
version = "0.1.0"
authors = ["Anders Kaseorg <andersk@mit.edu>"]
edition = "2018"

[dependencies]
rayon = "1.3.0"

Try it online!  (Parallelism removed for TIO.)

C++, all up to 40 in ten minutes
Runs in \$O(n^9)\$ time complexity (fortunately, it seems to be divided by at least 36 and it's also multi-threaded). I tested on Ubuntu 19.10 on AMD Ryzen 5 2600 (12 threads), tested with clang++ -Ofast -march=native -flto -no-pie -fopenmp and ran with timeout 600 ./a.out.
Code:
//#define _GLIBCXX_DEBUG
#include <iostream>
#include <cstring>
#include <complex>
#include <streambuf>
#include <bitset>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <climits>
#include <random>
#include <set>
#include <list>
#include <map>
#include <deque>
#include <stack>
#include <queue>
#include <string>
#include <iomanip>
#include <unordered_set>
#include <thread>

struct pt3
{
    short x, y, z;
    bool operator < (const pt3& rhs) const
    {
        return std::tie(x, y, z) < std::tie(rhs.x, rhs.y, rhs.z);
    }
    pt3 operator - (const pt3& rhs) const
    {
        return {short(x - rhs.x), short(y - rhs.y), short(z - rhs.z)};
    }
    int sqdist() const
    {
        return int(x)*int(x) + int(y)*int(y) + int(z)*int(z);
    }
};
int solve(int n)
{
    //the several lines below took a lot of tinkering-until-it-works
    std::set<pt3> pt3s;
    for(int i = 0; i < n; i++)
    for(int j = 0; j < n; j++)
    for(int k = 0; k < n; k++)
    {
        if(i+j+k >= n) continue;
        pt3 pt { short(i+j), short(j+k), short(i+k) };
        pt3s.insert(pt);
    }
    std::vector<pt3> points; //copy into a vector, they're much faster for this
    for(pt3 el : pt3s) points.push_back(el);

    //printf("n=%d, ps=%d\n", n, points.size());
    int64_t ans = 0;
#pragma omp parallel for schedule(guided) reduction(+:ans)
    for(int i = 0; i < points.size(); i++)
    for(int j = i + 1; j < points.size(); j++)
    for(int k = j + 1; k < points.size(); k++)
    {
        pt3 a = points[i], b = points[j], c = points[k];
        //consider pairwise distances
        pt3 p1 = a-b, p2 = a-c, p3 = b-c; //33% of all time
        int d1 = p1.sqdist(), d2 = p2.sqdist(), d3 = p3.sqdist(); //another 33% of all time
        if(d1 != d2 || d1 != d3) continue;
        ans++;
        //printf("%d %d %d; %d %d %d; %d %d %d\n", p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z);
    }
    return ans;
}
int main()
{
    for(int i = 1;; i++)
    {
        int ans = solve(i);
        printf("n=%d: %d\n", i, ans);
    }
}

Output:
n=1: 0
n=2: 4
n=3: 24
n=4: 84
n=5: 224
n=6: 516
n=7: 1068
n=8: 2016
n=9: 3528
n=10: 5832
n=11: 9256
n=12: 14208
n=13: 21180
n=14: 30728
n=15: 43488
n=16: 60192
n=17: 81660
n=18: 108828
n=19: 142764
n=20: 184708
n=21: 236088
n=22: 298476
n=23: 373652
n=24: 463524
n=25: 570228
n=26: 696012
n=27: 843312
n=28: 1014720
n=29: 1213096
n=30: 1441512
n=31: 1703352
n=32: 2002196
n=33: 2341848
n=34: 2726400
n=35: 3160272
n=36: 3648180
n=37: 4195164
n=38: 4806496
n=39: 5487792
n=40: 6244992

JavaScript (ES7), a(30) in ~50 seconds1
1: when run locally on my laptop
A very simple algorithm.
function count(n) {
  const r0 = (8 / 3) ** 0.5, r1 = 2 / 3, r2 = 3 ** 0.5;
  let cnt = 0;

  for(let z1 = 0; z1 < n; z1++)
  for(let Z1 = z1 * r0,
          y1 = 0; y1 <= z1; y1++)
  for(let Y1 = (y1 - z1 * r1) * r2,
          x1 = 0; x1 <= y1; x1++)
  for(let X1 = 2 * x1 - y1,
          z2 = z1; z2 < n; z2++)
  for(let Z2 = z2 * r0,
          y2 = z2 > z1 ? 0 : y1; y2 <= z2; y2++)
  for(let Y2 = (y2 - z2 * r1) * r2,
          x2 = z2 > z1 || y2 > y1 ? 0 : x1 + 1; x2 <= y2; x2++)
  for(let X2 = 2 * x2 - y2,
          S1 = (X1 - X2) ** 2 + (Y1 - Y2) ** 2 + (Z1 - Z2) ** 2,
          z3 = z2; z3 < n; z3++)
  for(let Z3 = z3 * r0,
          y3 = z3 > z2 ? 0 : y2; y3 <= z3; y3++)
  for(let Y3 = (y3 - z3 * r1) * r2,
          x3 = z3 > z2 || y3 > y2 ? 0 : x2 + 1; x3 <= y3; x3++) {
    let X3 = 2 * x3 - y3,
        S2 = (X1 - X3) ** 2 + (Y1 - Y3) ** 2 + (Z1 - Z3) ** 2;

    if(Math.abs(S1 - S2) < 1e-9) {
      let S3 = (X2 - X3) ** 2 + (Y2 - Y3) ** 2 + (Z2 - Z3) ** 2;

      if(Math.abs(S1 - S3) < 1e-9) {
        cnt++;
      }
    }
  }
  return cnt;
}

Try it online!

I like how both you and the C++ answer have removed indentation of the nested loops to make it less noticeable. He uses two triple-nested loops, but I see you've got a single nonuple-nested loop (I actually had to google what it was called for nine, haha). :D

@KevinCruijssen I always use tabs for indentation (seemingly not preserved by the website), so such triple-nested loops look terrible with conventional indentation (and the point of indentation is therefore unclear) I have also verified that this checks exactly as many nonuples of coordinates as my answer.

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Length of Longest Non-Repeating Substring
Your task is to take as input a single string (or list of characters, list of code points, etc.) and return the length of the longest substring with no character appearing more than once.
Aside: This challenge is similar to Longest Non-Repeating Substring, but without the source restriction ranking submissions by their own longest non-repeating substring.

Assumptions

You may assume that the input contains only lowercase letters and is non-empty (ie. the input will match the regex (a-z)+).

This challenge will use the following definition for substring: "A contiguous sequence of characters contained in the input string"

By "non-repeating" I mean that no letter of the substring is repeated more than once

Examples
If the input is abcdefgabc then the longest substrings with no repeating characters are abcdefg and bcdefga (their positions in the string are [abcdefg]abc and a[bcdefga]bc). The length of these substrings is 7, so the output would be 7.
If the input is abadac then the longest substrings with no repeating characters are bad (a[bad]ac) and dac (aba[dac]) so the output is 3.
If the input is aaaaaa then the longest substring with no repeating characters is a so the output is 1.
If the input is abecdeababcabaa then the longest substrings with no repeating characters are abecd ([abecd]eababcabaa)
and cdeab (abe[cdeab]abcabaa). The output is thus 5.
Test Cases
abcdefgabc -> 7
aaaaaa -> 1
abecdeababcabaa -> 5
abadac -> 3
abababab -> 2
helloworld -> 5
longest -> 7
nonrepeating -> 7
substring -> 8
herring -> 4
abracadabra -> 4
codegolf -> 6
abczyoxpicdabcde -> 10

Scoring
This is code-golf. Shortest answer in bytes for each language wins

I think it would be clearer to say "the longest substring with no character appearing more than once". The "longest non-repeating substring" sounds like it means the longest substring that doesn’t appear elsewhere in the entire string (which would always be the entire string itself!).

@MitchellSpector Thanks, I've updated the first line of the challenge to use that phrasing

@mathjunkie I don't think that's enough, honestly. Throughout the rest of the challenge you still use the misleading wording. Maybe it's just me not paying attention but the wording is saying something and the test cases another thing.

@RGS Hopefully it's clearer now

@mathjunkie it is perfect now; thanks for your time and thanks for the challenge

suggest testcase abczyoxpicdabcde as realised my solution fails for it.

Python 3.8 (pre-release), 51 bytes
f=lambda s:s>''and max(n:=f(s[:-1]),len({*s[~n:]}))

Try it online!

Jelly, 6 bytes
ẆQƑƇṪL

A monadic link accepting a list of characters (or whatever), which yields the length.
Try it online!
How?
ẆQƑƇṪL - Link: list
Ẇ      - all sublists (ordered by length)
   Ƈ   - filter keep those which are:
  Ƒ    -   invariant under:
 Q     -     deduplication
    Ṫ  - tail
     L - length

Julia 1.0, 98 74 73 bytes
~=length
!s=max(.~filter(z->[z...]==z∪z,[s[x:y] for x=1:~s,y=1:~s])...)

Try it online!

-3 bytes thanks to @amelies: replace unique with ∪ (union)
-4 bytes thanks to @amelies: replace collect with splat operator ...
-6 bytes thanks to @amelies: replace in with = in array comprehension
-5 bytes thanks to @amelies: vectorize the reassigned operator (~ => .~)
-2 bytes thanks to @amelies: unary operators don't need parentheses
-4 bytes thanks to @amelies: condense ranges to remove a for statement
-1 byte thanks to @MarcMush: replace ∪(z) with z∪z

Can use .~ instead of length.. Also in->= and collect(z)->[z…]. The answer should get to 83 bytes

80 bytes with also unique->∪

Thanks @amelies for the great suggestions as always! Is there a list of Unicode operators and functions in Julia(∪,∈,÷,etc.)? The docs include the math operators, at least.

not that I'm aware of...I usually implement the solution without them and then use ? in the repl to find possible unicode replacements

one more byte by swapping max with maximum and removing ... and one set of brackets !s=maximum(.~filter(...))

Thanks! Removing the unnecessary parentheses from ~() saved two bytes, and this can be done separately from changing max(x...) with maximum(x).

yes indeed, actually with max is shorter by one

since you're using max, the nested loop can be for x=1:~s,y=1:~s so that the inner one is independent from x declaration. Down to 74 bytes

And one more byte by using z∪z instead of ∪(z)

Wolfram Language (Mathematica), 49 bytes
Tr[1^Last@Select[Subsequences@#,DuplicateFreeQ]]&

Try it online!

C (gcc), 80 bytes
m;f(char*s){int F[128]={},i=0;for(;s[i]*!F[s[i]]++;i++);i=i?i>(m=f(s+1))?i:m:0;}

Try it online!

C (gcc), 83 bytes
An alternate non-recursive answer, although 3 bytes longer.
m;f(char*s){for(m=0;*s;)for(char*t=s++,F[128]={};*t*!F[*t]++;m=m>t++-s?m:t-s);++m;}

Try it online!

Python, 63 bytes
f=lambda s:len({*s})<len(s)and max(f(s[1:]),f(s[:-1]))or len(s)

Try it online!

Haskell, 83 75 bytes
-8 using guarded list comprehension for the length.

import Data.List
f[]=0
f x=sum[1|nub x==x,_<-x]`max`f(tail x)`max`f(init x)

Try it online!

R, 88 bytes
function(l)max(apply(combn(seq(l),2),2,function(v)all(table(l[v[1]:v[2]])<2)*diff(v)))+1

Try it online!
Input is vector of individual letters.
Commented:
max(                              # maximum value of:
  apply(                          # loop over
    combn(seq(l),2)               # all pairs of values in 1:length(l)
                                  # (so all possible start & end indices of contiguous subsets)
    ,2,function(v)                # for each pair assigned to 'v':
      all(table(l[v[1]:v[2]])<2)  # table() counts the different items in each subset v[1]:v[2]
                                  # so: 'all(table()<2)' indicates a nonrepeating subset
      *diff(v)                    # multiply by diff(v) = end-start = subset size -1
  ))+1                            # so finally we have to add 1 to get the subset size

Gaia, 11 bytes
$:u/¦l¦¦_⌉)

Try it online!
I feel like there's probably a byte or two that can be removed...

Husk, 8 bytes
▲mLfS=uQ

Try it online!
▲mLfS=uQ
▲           # maximum value of
 mL         # lengths of
   f   Q    # all contiguous sublists of input that are
    S=u     # equal to themselves without duplicates

Pyth, 7 bytes
le{I#.:

Test suite
Explanation:
le{I#.:  | Full code
le{I#.:Q | with implicit variables
---------+-----------------------------------------------------------------------
     .:Q | All substrings of input
    #    | Filter over
  {I     |  whether the substring is the same as itself without duplicate letters
le       | Length of the last (longest) substring

Vyxal l, 8 bytes
ǎ'¨=U;ÞG

Try it Online!

The filter can be replaced with ~Þu for a byte shave

Japt v2.0a0, 9 bytes
ã Ôæ_¶âÃl

Try it

Retina, 28 bytes
Lw`.+
A`(.).*\1
O^$`
$.&
\G.

Try it online! Link includes test cases. Explanation:
Lw`.+

List all nontrivial substrings.
A`(.).*\1

Delete those which duplicate characters.
O^$`
$.&

Sort in descending order of length.
\G.

Count the first length.
The longest length can also be found using these stages for the same byte count:
%C`.

Take the length of each remaining substring.
N^`

Sort in descending order.
1G`

Keep the first length.

Bash + Core utilities, 46 bytes
sed -E ':a;s/(\w)(\w*)\1/\1\2\n\2\1/;ta'|wc -L

Try the test cases online!

05AB1E, 9 bytes
ŒʒDÙQ}€gà

Try it online!
Explanation
Œ         All sublists
 ʒ        Filter:
  DÙ      Is the uniquified version
    Q}    Equal to the original?
      €g  Map length
        à Maximum

perl -n -Mfeature=say -MList::Util=max, 55 bytes
m;.+(??{$&=~/(.).*\1/||push@&,length$&})(?!);;say max@&

Try it online!
Reads lines from STDIN, writing results to STDOUT.
For each line, it iterates over every sub string (/.+(?!)/ will do that; /(?!)/ will never match). For each sub string, if it doesn't contain a duplicated character (/(.).*\1/ matches if there is a duplicated character), it stores the length of the sub string. We'll print the maximum of those values.

Stax, 11 bytes
éôU)¥¼ó8∩╦z

Run and debug it

Factor, 48 bytes
[ all-subseqs [ all-unique? ] find-last length ]

Try it online!

all-subseqs Get all the substrings of the input from shortest to longest
[ all-unique? ] find-last Find the last one whose elements are unique
length Get its length

Brachylog, 7 bytes
⊇S&sS≠l

Try it online!
Another 7 bytes solution:
sᶠ≠ˢlᵐ⌉

Try it online!
Explanation
Unfortunately, s - substring does not enumerate substrings of consecutive elements in descending order of length (if it did, we would have a 3 byte solution), but rather enumerates based on the indices. Therefore, we first state that we want a ⊇ - sublist, which does not force consecutiveness, but which enumerates from longest to smallest.
⊇S         S is a subset of the input (from longest to smallest)
  &        And
   sS      S is also a substring of consecutive elements of the input
     ≠     All elements of S are different
      l    Output the length of S

The second solution is a more "declarative" approach:
sᶠ         Find all substrings of consecutive elements from the input
  ≠ˢ       Select all substrings with all-different elements
    lᵐ     Map length
      ⌉    Max

JavaScript (ES6), 72 bytes
f=s=>/(.).*\1/.test(s)?Math.max(f(s.slice(1)),f(s.slice(0,-1))):s.length

Try it online!

JavaScript (ES6), 61 bytes
A port of @dingledooper's clever Python answer is 11 bytes shorter.
f=s=>p=s?f(s.slice(1))>(q=new Set(s.slice(0,p+1)).size)?p:q:0

Try it online!

Charcoal, 20 bytes
Ｉ⌈Ｅθ⌊Ｅθ§⌕Ａ⁺✂θκＬθ¹λλ¹

Try it online! Link is to verbose version of code. Works by finding the maximum earliest duplicated character of the string and its nontrivial suffixes. Explanation:
   θ                    Input string
  Ｅ                     Map over characters
      θ                 Input string
     Ｅ                  Map over characters
            θ           Input string
           ✂ κＬθ¹       Substring starting at outer index
          ⁺      λ      Append current character
        ⌕Ａ        λ     Find all matches of the current character
       §           ¹    Take the second
    ⌊                   Take the minimum
 ⌈                      Take the maximum
Ｉ                       Cast to string
                        Implicitly print

T-SQL, 118 bytes
Input is taken from table T (according to the Code Golf rules for SQL): column P represents position and column V represents each character.
SELECT TOP 1B.P-A.P+1FROM T A, T B ORDER BY(SELECT COUNT(*)-COUNT(DISTINCT V)FROM T WHERE T.P>=A.P AND T.P<=B.P),1DESC

DB Fiddle

Ohm v2, 16 bytes
l@€³sÇ;{⁇DU⁼;»l↑

Try it online!
Took me this long to realise » goes before the component. Almost submitted a 17 byte answer using €l;.
Explained
l@€³sÇ;{⁇DU⁼;»l↑
l@                # Push the range [1, len(input)]
  €   ;           # To each number:
   ³sÇ            #   Get all substrings of length number of the input
       {          # Deep flatten that
        ⁇DU⁼;     # And only keep substrings that have all unique characters
             »l   # Push the length of each unique character substring
               ↑  # and get the biggest length

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/


text:
Mowing the Grass
Context
After attempting to program in Grass for the entire morning, you decide to go outside and mow some real grass. The grass can be viewed as a string consisting exclusively of the following characters: wWv. w denotes tall grass which takes \$ 1 \$ unit of energy to mow. W denotes extremely tall grass which takes \$ 2 \$ units of energy to mow. Lastly v denotes short grass which does not need to be mowed.
Task
You decide to mow the grass from left to right (beginning to the end of the string). However, every time you encouter a v (short grass), you stop to take a break to replenish your energy, before carrying on with the mowing. Your task is to calculate the maximum amount of energy expended while mowing. In other words, find the maximum total energy of mowing a patch of grass, that of which does not contain v.
Example
In the example input below, the answer is \$ 8 \$. Although the patch wwwwwww is a longer patch of grass, it only costs \$ 7 \$ units of energy, whereas the optimal patch WWWW expends \$ 2 \times 4 = 8 \$ units of energy.
Input: WwwvWWWWvvwwwwwwwvWwWw
Output: 8

Here is an example Python program -> Try It Online!.
Test Cases
WwwvWWWWvvwwwwwwwvWwWw -> 8
w -> 1
W -> 2
vwww -> 3
vWWW -> 6
v -> 0
vvvvvvv -> 0
vwvWvwvWv -> 2
vWWWWWWWWWWvwwwwwwwwwwwwwwwwwwwwwv -> 21
vWWWWWWWWWWvwwwwwwwwwwwwwwwwwwwv -> 20
vvWvv -> 2

This is code-golf, so the shortest code in bytes wins!

Must we use those 3 specific characters or may we choose any 3?

@Shaggy Only the 3 characters I've mentioned are allowed, otherwise it may not look like grass :P

Mowing the Grass, to stop it from Growing the Mass!

As someone who has actually mowed quite a bit of grass in my life, I'd say this checks out.

Can anyone do it in Grass?

Hmm, v doesn't look much shorter than w.

@Λ̸̸ Maybe a bounty with no deadline for that, similar to https://codegolf.meta.stackexchange.com/a/8219

Python 2, 49 bytes
lambda s:len(max(s.replace("W","ww").split('v')))

Try it online!
Here's how it works:

Replace each W with ww
Split on v's to produce chunks of consecutive w's
Take the max to get the longest one
Get its length

Python 3.8, 55 46 44 bytes
Thanks @xnor for saving 2 bytes!
Thanks @dingledooper for saving 2 bytes!
lambda s,t=0:max((t:=c%2*t+c%5%3)for c in s)

Try it online!
A function that takes in a byte string, and returns the max energy.

Cool trick with eval.
Python 2, 61 bytes
lambda s:max(eval("+".join(s+"v").replace("v","0,")))
w=1;W=2

Try it online!
Transforms the given string by adding + between characters, then replaces v with comma (e.g wWvW -> w+W+0,W).

Wow, that is some crazy eval! For the 3.8 one, it looks like you can do stuff-~t for t+1+stuff, or directly express t:=t+[2,-t,1][ord(c)%3] since it happens the three chars are distinct mod 3.

@xnor Thanks! I was reluctant to go down the mod chain route, but it seems like everything lines up perfectly this time.

Oh wow, your mapping the mod to the value is super clean too.

Is it a default that we can accept a list of integers/bytes when it states "string"?

@JonathanAllan I'm not sure myself, but I've seen people done this before. I think this should be allowed, since Python bytes quacks like a string: gets printed like a string, and supports all string operations. Here is a demonstration.

Jelly,  9  8 bytes
O’%3ṣ0§Ṁ

Try it online!
How?
O’%3ṣ0§Ṁ - Link: list of characters   e.g. "wwwvWvwWww"
O        - ordinals                        [119,119,119,118,87,118,119,87,119,119]
 ’       - decremented                     [118,118,118,117,86,117,118,86,118,118]
  %3     - modulo three                    [1,1,1,0,2,0,1,2,1,1]
    ṣ0   - split at zeros                  [[1,1,1],[2],[1,2,1,1]]
      §  - sums                            [3,2,5]
       Ṁ - maximum                         5

Wow, works out perfectly with modulo

JavaScript (Node.js), 50 bytes
s=>Math.max(...Buffer(s).map(c=>e+=c%5%3||-e,e=0))

Try it online!
Or 42 bytes if we can take a Buffer (or an array of ASCII codes) as input:
s=>Math.max(...s.map(c=>e+=c%5%3||-e,e=0))

Try it online!

K (oK), 19 18 13 bytes
Solution:
|/+/'3!5!"v"\

Try it online!
Explanation:
Might be further golfable...
|/+/'3!5!"v"\ / the solution
         "v"\ / split on "v"
       5!     / modulo 5 (turns "vwW" into 3 4 2)
     3!       / modulo 3 (turns 3 4 2 into 0 1 2)
  +/'         / sum (+/) each (')
|/            / take maximum

The split fails on the single char test cases. Perhaps you should do |/+/'3!5!"v"\(),.

I'd argue that "v" is a char and ,"v" is a string of length 1, which works :)

A fair point, indeed

Charcoal, 12 bytes
Ｉ⌈Ｅ⪪Ｓv⁺Ｌι№ιW

Try it online! Link is to verbose version of code. Explanation:
    Ｓ           Input string
   ⪪ v          Split on literal `v`
  Ｅ             Map over chunks
          ι     Current chunk
         № W    Count of literal `W`
      ⁺         Plus
       Ｌι       Length of current chunk
 ⌈              Maximum
Ｉ               Cast to string for implicit print

Haskell, 51 45 bytes
-6 bytes thanks to Ad Hoc Garf Hunter! Unfortunately all the grass-like operators were already taken :(
g=maximum.scanl(#)0
x#'w'=x+1
x#'W'=x+2
x#_=0

Try it online!
Explanation
scanl is basically a "running total", where for each element of a list, it applies a function to the element and an accumulator, and saves the accumulator at each step. So scanl (+) 0 [1,2,3] gives [0,1,3,6]. We're giving it the function (#) which we've defined to add 1 to the accumulator if the element of the list is 'w', add 2 to the accumulator if it's 'W' or set the accumulator to 0 if it's anything else ('v'). Then we just get the maximum number of the new list, which will be the largest sum we managed to accumulate.
Cool idea I couldn't get to work:
mapping = zip "vwW" [(*0),(+1),(+2)]

Cheers @AdHocGarfHunter :)

We also don't require you to score the g= since maximum.scanl(#)0 is a function on its own.  So if you would like to drop 2 bytes off you may.

Perl 5 + -p0166lF -MList::Util+max, 19 bytes
$\=max$\,@F+y/W//}{
Try it online!
Explanation
-p reads STDIN into $_ breaking on each $/, -0166 sets $/ to v and -l strips $/ from the end of each $_, sets $\ to $/ and enables automatic printing of $\ at the end via print (because $_ will be empty outside of the loop and print outputs $\ at the end of each set of arguments - which default to $_ if none are passed). -F splits the input into an array of chars, stored in @F.
This stores the larger of $\ (initialised to v which is 0 when compared numerically) or @F (which is the array of chars in the input, or the length of the array when used in a numerical context) added to the number of W in the string.
Since $\ is globally scoped, storing it here lasts for each iteration of the script the }{ breaks out of the while (<STDIN>) { loop that -p inserts and terminates it so that instead of the current value of $_ being printed alongside $\, $_ is empty and only $\ is printed.

Retina 0.8.2, 19 17 bytes
-2 bytes thanks to @Neil!
W
ww
S_`v
O^`
\Gw

Try it online!
Replaces W with ww, splits at v, sorts by length, then counts the w's in the largest chunk.

Nice; I've never needed to use S_ before. Tip: \Gw counts the number of ws at the beginning of the buffer. You can use this to help shave 2 bytes off your answer.

@Neil Cool tip, thanks!

PowerShell 6+ for Windows, 48 bytes
port of math junkie's answer for Retina.
$args-creplace'W','ww'-split'v'|% len*|sort -b 1

Try it online!

J, 26 21 bytes
[:>./0+/;.1@,3|2+3&u:

Try it online!

3&u: Change input to unicode int values
3|2+ Add 2 and then mod 3: Now v becomes 0, w becomes 1, and W 2.
0..., Prepend 0
+/;.1 Split by first element (0) and sum the elements of each split chunk
[:>./ Take the max

APL (Dyalog Unicode), 21 bytes
(((⌈/+/¨)×⊆⊢)'vwW'∘⍳)

Try it online! Assumes IO is 0.
How it works:

we convert v, w and W into 0, 1, 2 respectively;
we split on 0s;
we sum each run of non-zeroes;
and find the max of those.

Vyxal, 8 bytes
\v/va›ṠG

Try it Online!
  /      # Split on
\v       # "v"
   v     # For each
    a    # Is each char uppercase?
     ›   # Increment
      Ṡ  # Sum each
       G # Maximum

@DLosc Good idea, having a look...

Yep, k[ does the trick.

Ruby -naFv, 35 bytes
-naFv enables input on STDIN with auto-split on v into the $F global array.
p$F.map{|w|w.gsub(?W){11}.size}.max

Try it online!

C (gcc), 69 \$\cdots\$ 62 60 bytes
Saved 5 bytes thanks to Arnauld!!!
Saved 2 4 btes thanks to dingledooper!!!
e;m;f(char*g){for(e=m=0;*g;m=m>e?m:e)e=*g%2*e+*g++%5%3;e=m;}

Try it online!

05AB1E, 8 bytes
Port of the Jelly answer.
Ç<3%0¡Oà

Try it online!

Jelly, 9 bytes
ṣ”v<”w‘§Ṁ

Try it online!
Explanation
ṣ”v<”w‘§Ṁ  Main Link
ṣ”v        Split on "v"
   <”w     Check if less than "w" ("w" = 0, "W" = 1)
      ‘    Add one to every element
       §   Sum every sublist
        Ṁ  Find the maximum

AWK, 85 82 69 bytes
{FS="v";gsub("W","ww");for(;i<NF;){s=length($++i);m=m>s?m:s;}print m}

Try it online!
Thanks to @dingledooper for cutting off 13 bytes.
The code could be even less, 62 bytes accounting for the field separator flag being a parameter and not part of the actual code.
pretty straightforward: change all W into w, get lenght of words, v counts as field separator. Retain max score with ternary operator.

A couple basic golf ideas for 69 bytes

Thanks @dingledooper, very nice touch! I'm not familiar with the rules, but apparently I can put the -F"v" as argument and further reduce the total number of bytes. Is that a valid move?

Yes you are allowed to add arguments, just make sure you include it in the header. Personally, I rarely use flags, since it seems a bit of a "cheap" golf, but it's up to you :)

C# (Visual C# Interactive Compiler), 69 bytes
Func<string,int>g=s=>s.Replace("W","ww").Split('v').Max(x=>x.Length);

Try it online!
Heavy inspiration from @xnor

Bash, 28 bytes
sed 's,W,ww,g;y/v/\n/'|wc -L

Input from stdin, output to stdout
There might be a better way.
Try it online!

Mathematica, 53 bytes
Max[#~(s=StringCount)~"W"+#~s~_&@StringSplit[#,"v"]]&

More readable, un-golfed code:
Max[StringCount[#,"W"]+StringCount[#,_]&[StringSplit[#,"v"]]]&

StringSplit[#,"v"] takes a string and turns it into a list of strings, seperated by 'v', then add together the length of the string, and the count of 'W's, and take the max value.

Crystal, 57 bytes
def f(a);a.split('v').max_of {|s|s.count('W')+s.size};end

Try it online!

Java (JDK), 62 bytes
s->{int m=0,n=0;for(var c:s)m=(n=c%2*n+c%5%3)>m?n:m;return m;}

Try it online!
After finishing it, it seems to be a port of Noodle9's C answer.

Python 2, 70 bytes
Comprehensive approach.
lambda n:max([sum([2if i=="W"else 1for i in x])for x in n.split('v')])

Try it online!

Rust, 67 bytes
Port of xnor's answer
|s:&str|s.replace("W","ww").split('v').map(str::len).max().unwrap()

Try it online!

Stax, 14 bytes
π├♦.8Σ0└ó?∟X≈Æ

Try it online!

Pip, 13 bytes
MX#*YaR'Wt^'v

Try it online!
Explanation
     a         First command-line argument
      R'W      Replace all occurrences of W
         t     with 10 (arbitrary value--the important thing is, it's two characters)
          ^'v  Split on occurrences of v
    Y          Yank (to enforce precedence)
  #*           Replace each string in the list with its length
MX             Take the maximum

Excel, 101 bytes
=MAX(LEN(SUBSTITUTE(FILTERXML("x"&SUBSTITUTE(A1,"v","x")&"","//b"),"W","ww"))-1)

Link to Spreadsheet

"x"&SUBSTITUTE(A1,"v","x")&"" converts text to
XML containing "x" + the "wW" strings.  The additional "x" prevents
errors.
FILTERXML(~,"//b") converts XML to a vertical array.
SUBSTITUTE(~,"W","ww") change "W" to "ww".
LEN(~)-1 counts the "w"; minus 1 for the leading "x".
MAX(~) return the largest one.

Uiua SBCS, 13 bytes
⬚0/↥⊜/+.◿3-@@

Try it!
⬚0/↥⊜/+.◿3-@@
          -@@  # subtract @ character
        ◿3     # modulo three
    ⊜/+.       # sum groups delimited by zero
⬚0/↥           # maximum using zero as default

YASEPL, 86 bytes
=s=1'=l®1=i=h=c`1!¥i,1}7,"v",2=c`2!}7,"w",3!c+`3!}7,"W",4!c++`4!c}4,h,5!h$c`5!i+}2,l>h

and just for fun,
ibe, 130 bytes
adddQmwwoadddWmwwpadddEmoiadadadadaddsRdfTmRakqdgUmRmYshUmQmwskIakwshUmWmeafImwakeshUmEmrafImeakrsgImOmtskOafOmIaktafYmwssYmTmqasO


text:
Verify a solution for Conway Immobilizer
Background
Conway immobilizer problem is a puzzle that reads like the following:

Three positions, "left," "middle," and "right," are marked on a table.
  Three cards, an ace, a king, and a queen, lie face up in some or all
  three of the positions. If more than one card occupies a given
  position then only the top card is visible, and a hidden card is
  completely hidden; that is, if only two cards are visible then you
  don't know which of them conceals the missing card.
Your goal is to have the cards stacked in the left position with the
  ace on top, the king in the middle, and the queen on the bottom. To do
  this you can move one card at a time from the top of one stack to the
  top of another stack (which may be empty).
The problem is that you have no short-term memory, so you must design
  an algorithm that tells you what to do based only on what is currently
  visible. You can't recall what you've done in the past, and you can't
  count moves. An observer will tell you when you've succeeded. Can you
  devise a policy that will meet the goal in a bounded number of steps,
  regardless of the initial position?

The puzzle has got its name because it's said to have immobilized one solver in his chair for six hours.
The link above gives one possible answer (marked as spoiler for those who want to solve it by themselves):

 If there’s an empty slot, move a card to the right (around the
 corner, if necessary) to fill it. Exception: If the position is
 king-blank-ace or king-ace-blank, place the ace on the king.
If all three cards are visible, move the card to the right of the
 queen one space to the right (again, around the corner if
 necessary). Exception: If the queen is on the left, place the king on
 the queen.

All solutions to the Immobilizer Problem (pdf) uses graph theory to show that there are 14287056546 distinct strategies that solve the problem.
Task
Given a strategy for Conway Immobilizer, determine if the strategy actually solves it, i.e. given any initial configuration, repeating the strategy will eventually place all cards into the winning state.
A strategy (the input) can be in any format that represents a set of pairs current visible state -> next move for every possible current visible state. A visible state represents what is visible on each of three slots (it can be one of A/K/Q or empty). A move consists of two values A, B which represents a move from slot A to slot B.
The input format can be e.g. a list of pairs, a hash table, or even a function, but it should not involve any external information, e.g. you cannot encode a "move" as a function that modifies the full game state (entire stacks of cards). You can use any four distinct values for A/K/Q/empty (the visible state of each slot) and three distinct values for left/middle/right (to represent a move from a slot to another).
Standard code-golf rules apply. The shortest code in bytes wins.
Test cases
Input: the solution above
Output: True

Input: the solution above, but the exception on 'K.A' is removed
       (at 'K.A', move K to the right)
Output: False ('QKA' -> 'K.A' -> 'QKA')

Input: if only one card is visible, move it to the right
       if Q and K are visible, move K on the top of Q
       if K and A are visible, move A on the top of K
       otherwise, move A to the empty slot
Output: False ('..A' where Q, K, A are stacked -> 'A.K' -> '..A')

Just to be clear, can I encode a move as a function that takes a stack of card and only move one of the top cards, without looking at the bottom cards? No external information is involved.

@SurculoseSputum It is not allowed to take a stack of cards, regardless of whether bottom cards are accessed or not.

Is a bad solver at least guaranteed to do only legal moves?

@Arnauld Yes, you can assume that.

JavaScript (ES7),  229 224  213 bytes
Input: a function (the solver) expecting an array of 3 values among 'Q', 'K', 'A' or undefined for empty, and returning a move as a pair [from, to] (0-indexed)
Output: 0 or 1
f=(F,n=162)=>!n--||(g=s=>(s[k=s.join`/`]^=1)?/A,K,/.test(k)|g(s,[x,y]=F(s.map(a=>a[0])),s[y].unshift(s[x].shift())):0)([0,1,2].map(i=>s[n/3**i%3|0].push('QKAKQAAQKQAKKAQAKQ'[3*~~(n/27)+i]),s=[[],[],[]])&&s)&f(F,n)

Try it online!

Charcoal, 120 114 bytes
≔Ｅ³³ＳθＦφＦ⁼Σι³Ｆ⪪”{“±¿U+*～T”³«≔⪪λ¹λ≔Ｅ◧Ｉι³ＥΣμ⊟λλ≔⊟Φθ⬤λ⁼§μπ§∨ξ.⁰ζ≔Ｅλ⮌μη⊞§ηＩ§ζ⁴⊟§ηＩ§ζ³⊞υ⁺λＥη⮌μ»≔⊟υθＷΦυ¬№Ｅυ…μ³✂κ³≔⁻υιυ¬υ

Try it online! Link is to verbose version of code. Outputs a Charcoal boolean, i.e. - for verified, nothing for failed. Explanation:
≔Ｅ³³Ｓθ

Read in the 33 instructions, comprising strings of 5 characters, the three visible cards and the source and destination of the move (0-indexed).
ＦφＦ⁼Σι³

Loop over all 3-digit numbers with a digit sum of 3.
Ｆ⪪”{“±¿U+*～T”³«

Loop over the permutations of AKQ by taking a compressed string of all six permutations and splitting it into substrings of length 3.
≔⪪λ¹λ≔

Split the AKQ into an array.
Ｅ◧Ｉι³ＥΣμ⊟λλ

Move the AKQ into one of three lists depending on the digits of the outer loop variable. For instance, if the variable is 111 then the resulting list is a list of lists of reverse of the array, while if it is 300 then the resulting list has the reverse of the array as its first element and two empty lists. This represents the real positions of the three cards.
≔⊟Φθ⬤λ⁼§μπ§∨ξ.⁰ζ

Calculate the visible cards and find the move for that set of cards.
≔Ｅλ⮌μη

Make a reversed copy of the card positions.
⊞§ηＩ§ζ⁴⊟§ηＩ§ζ³

Perform the move on the copy. The reversal, in addition to resulting in a deep copy, also allows the moved card to be popped and pushed.
⊞υ⁺λＥη⮌μ

Unreverse the move result and save the cards and the move result to an empty list.
»≔⊟υθ

Remove the winning position from the list of all card positions.
ＷΦυ¬№Ｅυ…μ³✂κ³

Repeat while there are positions whose move reaches a winnable position.
≔⁻υιυ

Remove all of the now winnable positions.
¬υ

Are there no unwinnable positions left?


text:
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.
Task
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 code-golf, 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?

"your output has to be non-deterministic" - does employing pseudo-random numbers count as being non-deterministic?

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

@Λ̸̸ Yes, unless you use the exact same seed for the PRNG every time.

@sporeball Is being capable of producing the ending sequence the only requirement for non-determinism? That still leaves a lot of room between that and uniform randomness.

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

@sporeball Could you please clarify this in the challenge?

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

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

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.

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.

@KevinCruijssen wow, I don't know how I missed that. Thanks!

Oh, and another -2 by changing ¯5.£J30bQ to ¯J30bÅ¿. :) (PS: My name isn't Keven ;p)

@KevinCruijssen aw h*ck. Sorry about that!

Np, it isn't the first time my name is misspelled like that, haha ;)

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.

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  might be interesting to read through.

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! :)

Thanks, Kevin, for being so helpful! I'm admittedly not good with lambdas yet but learning new things everyday ;)

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:

The s=new Random().Next()%2<1?"zun\n":"doko\n" could be placed directly inside the Console.Write(s=new Random().Next()%2<1?"zun\n":"doko\n");. You can also use that k.Contains("zzzzd") as a condition for your now infinite loop to end, so you can save on both that if, break; and some brackets and print the final line after the loop, like this: ()=>{string s,k="";for(;!k.Contains("zzzzd");){Console.Write(s=new Random().Next()%2<1?"zun\n":"doko\n");k+=s[0];}Console.Write("ki-yo-shi!");}.

After that, you can place the k+=s[0] inside the loop ()=>{string s,k="";for(;!k.Contains("zzzzd");k+=s[0])Console.Write(s=new Random().Next()%2<1?"zun\n":"doko\n");Console.Write("ki-yo-shi!");} to save some more bytes on brackets. And finally, by using a temp-string which we return at the end, you can save on both  Console.Write as well as the using System: ()=>{string s,k="",r=k;for(;!k.Contains("zzzzd");k+=s[0])r+=s=new System.Random().Next()%2<1?"zun\n":"doko\n";return r+"ki-yo-shi!";}.

And one last thing, is ()=> to o=>() (by using an empty unused parameter). So that makes it 132 bytes in the end. Sorry for all the comments, but I wanted to explain what I've golfed instead of just giving you this 132 byter, otherwise you won't be able to learn from it. :) If you have any questions, feel free to ask. And good luck with other challenges.

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!

@KevinCruijssen The rules say only a delimiter is necessary, so | should be fine. On that note I then saved it two more bytes, putting it to 130 bytes. Then per monicareinstate's suggestion, we can get 119 bytes :D Try it online!

I just noticed that the variable r can be simply removed now.

@loohhoo Ah, you're indeed right. Somehow read past that rule. In that case | is indeed fine. :)

Pyth, 44 42 40 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, 56 48 47 46 bytes
Solution:
{x,1?$`zun`doko}/[75-2/:<-5#;()],,"ki-yo-shi!"

Example:
q)k){x,1?$`zun`doko}/[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?$`zun`doko}/[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
     $`zun`doko                                / 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

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.

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

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 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 :)

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.

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.

@Bubbler I don't feel like changing it, because I'm in a hurry to answer other new questions.

My first attempt at a golf, and using a language not remotely suited for golfing, no less! I can't think of any ways to shrink this because Lua is a verbose, spacey language. But I still love her anyways.
I should give a heads-up: when the Lua interpreter starts, its pRNG seed seems to always default to the same value (rather than polling /dev/random or something), so running it once will always provide the same result. TIO, therefore, creates the same output repeatedly. This will change when 5.4 is released.
Credits to manatwork for a 22% reduction over four changes.
Lua, 132 123 107 104 103 bytes
z=0repeat if math.random(2)<2then print"zun"z=z+1 else w=z>3z=0print"doko"end until w print"ki-yo-shi!"

Try it online!
Explanation, because why not?
z=0                           --set a variable to count consecutive "zun"s
repeat                        --loop until we hit the target circumstance
    if math.random(2)<2 then  --pick 1 or 2: 1 = "zun", 2 = "doko"
        print"zun"            --print "zun"
        z=z+1                 --increment the "zun" counter
    else                      --when the pRNG lands 2
        w=z>3                 --after 4 consecutive "zun"s, w will be false
        z=0                   --reset the counter
        print"doko"           --print doko
    end
until w                       --loop kills when w is defined (as true)
                              --execution only gets here if we succeed...
print"ki-yo-shi!"             --...so print "ki-yo-shi!" and halt

in Lua only false and nil are falsy values, so replace true with a single digit number. As there will be no execution beyond ==4, just make it >3. For the same reason resetting z doesn't need to be in the else block, can be anywhere after the then block's end. Try it online!

Can't believe I forgot about truthiness, as that's one of my favorite parts of the language. Thanks!

The if only for that single break is too long. Better save the z>3 condition's result in a variable and use it to control the loop. Try it online!

Add another twist to my previous suggestion and use a repeat .. until loop, so the loop control variable doesn't have to be initialized before the loop. Try it online!

I was just starting to think a repeat-until loop would be smarter, but you beat me to it

And a small last one: as the parser doesn't like digits touching the end keyword, move the print last as the closing " can touch end. Try it online!

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!'

Ruby, 71...59 57 bytes
-4 bytes thanks to @ValueInk!

$/+=p %w[zun doko].sample until$/=~/n.{9}d/;p'ki-yo-shi!'

Try it online!
Stores output history by appending zun or doko to the string $/ (the predefined input record separator). Halts when the string contains an n, followed by exactly 9 characters, followed by a d. This can only occur when the string ends with zunzunzunzundoko.

59 bytes by inlining the p and tweaking the regex

@ValueInk Thanks... annoyed I missed that regex! Saved 2 more bytes by getting rid of the no-longer-needed f=.

Retina, 50 bytes
/(n¶.+){4}o$/^+?(`\z
zun¶
)`\z
doko¶
\z
ki-yo-shi!

Try it online! Explanation:
/(n¶.+){4}o$/^+

Repeat until the buffer ends with 4 lines ending with n and a line ending in o. Note that the $ anchor works like \Z or ¶?\z, so I don't need to match the trailing newline explicitly.
?(`
)`

Execute one of the inner stages chosen randomly.
\z
zun¶
\z
doko¶

Append either zun or doko. Note that I can't use $ here as I only want to match the very end and the $ would match before the final newline as well as after it.
\z
ki-yo-shi!

Finally append ki-yo-shi!.

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!

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

@Arnauld A simple but very interesting golf: $\mod{2^n}$ is the same as masking the $n$ least-significant bits - thanks! :-)

What about m;r;f() -> f(m,r)?

@tsh Nice one - thanks! :-)

Java 10, 148 141 137 136 125 123 112 110 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
  for(int k=3;            //  Bit-mask integer
      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 Lol, not sure how I misread that, but thanks for noticing I guess. ;)

112 bytes, by porting Arnauld's answer.

@OlivierGrégoire Thanks. And another -2 by only doing j=2 once. :)

Good catch! :-)

@OlivierGrégoire Oh, and another -3 by porting loohhoo's C# a third time, rofl: 107 bytes.

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!".

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.

@EasyasPi fixed it! Sorry!

Does this actually output "zun" and "doko"?

@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!

@EasyasPi Fixed the script, now it outputs 'zun' and 'doko'! :)

R 161 143 69 bytes
An initial & fairly clumsy attempt at a code golf challenge involving text!, and defining
b=c("doko","zun")
as a header.
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)

when adding
a="ki-yo-shi!"

to the header.
Try it online!

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!".

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

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"
          adrcs r0, .Lzun
          // Otherwise, set it to "doko"
          adrcc r0, .Ldoko
        // Add to the bitmask using adcs.
        // r1 = r1 * 2 + (rand() & 1)
        adcs    r1, r1
        // 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!"
        adr     r0, .Lkiyoshi
        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

With comments
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
    .adok.pXX.z
    # 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...

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

Try it Online!
-7 thanks to lyxal.

37 bytes, ironically tying with my 05AB1E answer

and 36 bytes tying with Jelly

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!
Uses the bitmask setup.
# Initialize with FALSE. We will coerce it to
# Number later, just get some form of 0.
; = mask FALSE
# loop while mask != 30
; WHILE( - 30
          # chaining ftw
          ; OUTPUT
             : IF (= x % RAND 2){"zun"}{"doko"}
          # mask = ((mask << 1) | x) % 32
          : = mask % (+ x (* 2 mask)) 32
        ) {
    : NOP # we use "x" in the golfed code.
}
: OUTPUT "ki-yo-shi!"