1 # Chessboard program: you type in moves in algebraic notation, and it'll
  2 # display the position after each move.
  3 
  4 def main [
  5   local-scope
  6   open-console  # take control of screen, keyboard and mouse
  7   clear-screen 0/screen  # non-scrolling app
  8 
  9   # The chessboard function takes keyboard and screen objects as 'ingredients'.
 10   #
 11   # In Mu it is good form (though not required) to explicitly state what
 12   # hardware a function needs.
 13   #
 14   # Here the console and screen are both 0, which usually indicates real
 15   # hardware rather than a fake for testing as you'll see below.
 16   chessboard 0/screen, 0/console
 17 
 18   close-console  # clean up screen, keyboard and mouse
 19 ]
 20 
 21 ## But enough about Mu. Here's what it looks like to run the chessboard program.
 22 
 23 scenario print-board-and-read-move [
 24   local-scope
 25   trace-until 100/app
 26   # we'll make the screen really wide because the program currently prints out a long line
 27   assume-screen 120/width, 20/height
 28   # initialize keyboard to type in a move
 29   assume-console [
 30   ¦ type [a2-a4
 31 ]
 32   ]
 33   run [
 34   ¦ screen:&:screen, console:&:console <- chessboard screen:&:screen, console:&:console
 35   ¦ # icon for the cursor
 36   ¦ cursor-icon:char <- copy 9251/␣
 37   ¦ screen <- print screen, cursor-icon
 38   ]
 39   screen-should-contain [
 40   #            1         2         3         4         5         6         7         8         9         10        11
 41   #  012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
 42   ¦ .Stupid text-mode chessboard. White pieces in uppercase; black pieces in lowercase. No checking for legal moves.         .
 43   ¦ .                                                                                                                        .
 44   ¦ .8 | r n b q k b n r                                                                                                     .
 45   ¦ .7 | p p p p p p p p                                                                                                     .
 46   ¦ .6 |                                                                                                                     .
 47   ¦ .5 |                                                                                                                     .
 48   ¦ .4 | P                                                                                                                   .
 49   ¦ .3 |                                                                                                                     .
 50   ¦ .2 |   P P P P P P P                                                                                                     .
 51   ¦ .1 | R N B Q K B N R                                                                                                     .
 52   ¦ .  +----------------                                                                                                     .
 53   ¦ .    a b c d e f g h                                                                                                     .
 54   ¦ .                                                                                                                        .
 55   ¦ .Type in your move as <from square>-<to square>. For example: 'a2-a4'. Then press <enter>.                               .
 56   ¦ .                                                                                                                        .
 57   ¦ .Hit 'q' to exit.                                                                                                        .
 58   ¦ .                                                                                                                        .
 59   ¦ .move: ␣                                                                                                                 .
 60   ¦ .                                                                                                                        .
 61   ¦ .                                                                                                                        .
 62   ]
 63 ]
 64 
 65 ## Here's how 'chessboard' is implemented.
 66 
 67 type board = &:@:&:@:char  # a 2-D array of arrays of characters
 68 
 69 def chessboard screen:&:screen, console:&:console -> screen:&:screen, console:&:console [
 70   local-scope
 71   load-ingredients
 72   board:board <- initial-position
 73   # hook up stdin
 74   stdin-in:&:source:char, stdin-out:&:sink:char <- new-channel 10/capacity
 75   start-running send-keys-to-channel, console, stdin-out, screen
 76   # buffer lines in stdin
 77   buffered-stdin-in:&:source:char, buffered-stdin-out:&:sink:char <- new-channel 10/capacity
 78   start-running buffer-lines, stdin-in, buffered-stdin-out
 79   {
 80   ¦ print screen, [Stupid text-mode chessboard. White pieces in uppercase; black pieces in lowercase. No checking for legal moves.
 81 ]
 82   ¦ cursor-to-next-line screen
 83   ¦ print-board screen, board
 84   ¦ cursor-to-next-line screen
 85   ¦ print screen, [Type in your move as <from square>-<to square>. For example: 'a2-a4'. Then press <enter>.
 86 ]
 87   ¦ cursor-to-next-line screen
 88   ¦ print screen [Hit 'q' to exit.
 89 ]
 90   ¦ {
 91   ¦ ¦ cursor-to-next-line screen
 92   ¦ ¦ screen <- print screen, [move: ]
 93   ¦ ¦ m:&:move, quit:bool, error:bool <- read-move buffered-stdin-in, screen
 94   ¦ ¦ break-if quit, +quit
 95   ¦ ¦ buffered-stdin-in <- clear buffered-stdin-in  # cleanup after error. todo: test this?
 96   ¦ ¦ loop-if error
 97   ¦ }
 98   ¦ board <- make-move board, m
 99   ¦ screen <- clear-screen screen
100   ¦ loop
101   }
102   +quit
103 ]
104 
105 ## a board is an array of files, a file is an array of characters (squares)
106 
107 def new-board initial-position:&:@:char -> board:board [
108   local-scope
109   load-ingredients
110   # assert(length(initial-position) == 64)
111   len:num <- length *initial-position
112   correct-length?:bool <- equal len, 64
113   assert correct-length?, [chessboard had incorrect size]
114   # board is an array of pointers to files; file is an array of characters
115   board <- new {(address array character): type}, 8
116   col:num <- copy 0
117   {
118   ¦ done?:bool <- equal col, 8
119   ¦ break-if done?
120   ¦ file:&:@:char <- new-file initial-position, col
121   ¦ *board <- put-index *board, col, file
122   ¦ col <- add col, 1
123   ¦ loop
124   }
125 ]
126 
127 def new-file position:&:@:char, index:num -> result:&:@:char [
128   local-scope
129   load-ingredients
130   index <- multiply index, 8
131   result <- new character:type, 8
132   row:num <- copy 0
133   {
134   ¦ done?:bool <- equal row, 8
135   ¦ break-if done?
136   ¦ square:char <- index *position, index
137   ¦ *result <- put-index *result, row, square
138   ¦ row <- add row, 1
139   ¦ index <- add index, 1
140   ¦ loop
141   }
142 ]
143 
144 def print-board screen:&:screen, board:board -> screen:&:screen [
145   local-scope
146   load-ingredients
147   row:num <- copy 7  # start printing from the top of the board
148   space:char <- copy 32/space
149   # print each row
150   {
151   ¦ done?:bool <- lesser-than row, 0
152   ¦ break-if done?
153   ¦ # print rank number as a legend
154   ¦ rank:num <- add row, 1
155   ¦ print screen, rank
156   ¦ print screen, [ | ]
157   ¦ # print each square in the row
158   ¦ col:num <- copy 0
159   ¦ {
160   ¦ ¦ done?:bool <- equal col:num, 8
161   ¦ ¦ break-if done?
162   ¦ ¦ f:&:@:char <- index *board, col
163   ¦ ¦ c:char <- index *f, row
164   ¦ ¦ print screen, c
165   ¦ ¦ print screen, space
166   ¦ ¦ col <- add col, 1
167   ¦ ¦ loop
168   ¦ }
169   ¦ row <- subtract row, 1
170   ¦ cursor-to-next-line screen
171   ¦ loop
172   }
173   # print file letters as legend
174   print screen, [  +----------------]
175   cursor-to-next-line screen
176   print screen, [    a b c d e f g h]
177   cursor-to-next-line screen
178 ]
179 
180 def initial-position -> board:board [
181   local-scope
182   # layout in memory (in raster order):
183   #   R P _ _ _ _ p r
184   #   N P _ _ _ _ p n
185   #   B P _ _ _ _ p b
186   #   Q P _ _ _ _ p q
187   #   K P _ _ _ _ p k
188   #   B P _ _ _ _ p B
189   #   N P _ _ _ _ p n
190   #   R P _ _ _ _ p r
191   initial-position:&:@:char <- new-array 82/R, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 114/r, 78/N, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 110/n, 66/B, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 98/b, 81/Q, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 113/q, 75/K, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 107/k, 66/B, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 98/b, 78/N, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 110/n, 82/R, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 114/r
192 #?       82/R, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 114/r,
193 #?       78/N, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 110/n,
194 #?       66/B, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 98/b, 
195 #?       81/Q, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 113/q,
196 #?       75/K, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 107/k,
197 #?       66/B, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 98/b,
198 #?       78/N, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 110/n,
199 #?       82/R, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 114/r
200   board <- new-board initial-position
201 ]
202 
203 scenario printing-the-board [
204   local-scope
205   board:board <- initial-position
206   assume-screen 30/width, 12/height
207   run [
208   ¦ screen:&:screen <- print-board screen:&:screen, board
209   ]
210   screen-should-contain [
211   #  012345678901234567890123456789
212   ¦ .8 | r n b q k b n r           .
213   ¦ .7 | p p p p p p p p           .
214   ¦ .6 |                           .
215   ¦ .5 |                           .
216   ¦ .4 |                           .
217   ¦ .3 |                           .
218   ¦ .2 | P P P P P P P P           .
219   ¦ .1 | R N B Q K B N R           .
220   ¦ .  +----------------           .
221   ¦ .    a b c d e f g h           .
222   ¦ .                              .
223   ¦ .                              .
224   ]
225 ]
226 
227 ## data structure: move
228 
229 container move [
230   # valid range: 0-7
231   from-file:num
232   from-rank:num
233   to-file:num
234   to-rank:num
235 ]
236 
237 # prints only error messages to screen
238 def read-move stdin:&:source:char, screen:&:screen -> result:&:move, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
239   local-scope
240   load-ingredients
241   from-file:num, quit?:bool, error?:bool <- read-file stdin, screen
242   return-if quit?, 0/dummy
243   return-if error?, 0/dummy
244   # construct the move object
245   result:&:move <- new move:type
246   *result <- put *result, from-file:offset, from-file
247   from-rank:num, quit?, error? <- read-rank stdin, screen
248   return-if quit?, 0/dummy
249   return-if error?, 0/dummy
250   *result <- put *result, from-rank:offset, from-rank
251   error? <- expect-from-channel stdin, 45/dash, screen
252   return-if error?, 0/dummy, 0/quit
253   to-file:num, quit?, error? <- read-file stdin, screen
254   return-if quit?, 0/dummy
255   return-if error?, 0/dummy
256   *result <- put *result, to-file:offset, to-file
257   to-rank:num, quit?, error? <- read-rank stdin, screen
258   return-if quit?, 0/dummy
259   return-if error?, 0/dummy
260   *result <- put *result, to-rank:offset, to-rank
261   error? <- expect-from-channel stdin, 10/newline, screen
262   return-if error?, 0/dummy, 0/quit
263 ]
264 
265 # valid values for file: 0-7
266 def read-file stdin:&:source:char, screen:&:screen -> file:num, quit:bool, error:bool, stdin:&:source:char, screen:&:screen [
267   local-scope
268   load-ingredients
269   c:char, eof?:bool, stdin <- read stdin
270   return-if eof?, 0/dummy, 1/quit, 0/error
271   q-pressed?:bool <- equal c, 81/Q
272   return-if q-pressed?, 0/dummy, 1/quit, 0/error
273   q-pressed? <- equal c, 113/q
274   return-if q-pressed?, 0/dummy, 1/quit, 0/error
275   empty-fake-keyboard?:bool <- equal c, 0/eof
276   return-if empty-fake-keyboard?, 0/dummy, 1/quit, 0/error
277   {
278   ¦ newline?:bool <- equal c, 10/newline
279   ¦ break-unless newline?
280   ¦ print screen, [that's not enough]
281   ¦ return 0/dummy, 0/quit, 1/error
282   }
283   file:num <- subtract c, 97/a
284   # 'a' <= file <= 'h'
285   {
286   ¦ above-min:bool <- greater-or-equal file, 0
287   ¦ break-if above-min
288   ¦ print screen, [file too low: ]
289   ¦ print screen, c
290   ¦ cursor-to-next-line screen
291   ¦ return 0/dummy, 0/quit, 1/error
292   }
293   {
294   ¦ below-max:bool <- lesser-than file, 8
295   ¦ break-if below-max
296   ¦ print screen, [file too high: ]
297   ¦ print screen, c
298   ¦ return 0/dummy, 0/quit, 1/error
299   }
300   return file, 0/quit, 0/error
301 ]
302 
303 # valid values for rank: 0-7
304 def read-rank stdin:&:source:char, screen:&:screen -> rank:num, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
305   local-scope
306   load-ingredients
307   c:char, eof?:bool, stdin <- read stdin
308   return-if eof?, 0/dummy, 1/quit, 0/error
309   q-pressed?:bool <- equal c, 81/Q
310   return-if q-pressed?, 0/dummy, 1/quit, 0/error
311   q-pressed? <- equal c, 113/q
312   return-if q-pressed?, 0/dummy, 1/quit, 0/error
313   empty-fake-keyboard?:bool <- equal c, 0/eof
314   return-if empty-fake-keyboard?, 0/dummy, 1/quit, 0/error
315   {
316   ¦ newline?:bool <- equal c, 10  # newline
317   ¦ break-unless newline?
318   ¦ print screen, [that's not enough]
319   ¦ return 0/dummy, 0/quit, 1/error
320   }
321   rank:num <- subtract c, 49/'1'
322   # assert'1' <= rank <= '8'
323   {
324   ¦ above-min:bool <- greater-or-equal rank, 0
325   ¦ break-if above-min
326   ¦ print screen, [rank too low: ]
327   ¦ print screen, c
328   ¦ return 0/dummy, 0/quit, 1/error
329   }
330   {
331   ¦ below-max:bool <- lesser-or-equal rank, 7
332   ¦ break-if below-max
333   ¦ print screen, [rank too high: ]
334   ¦ print screen, c
335   ¦ return 0/dummy, 0/quit, 1/error
336   }
337   return rank, 0/quit, 0/error
338 ]
339 
340 # read a character from the given channel and check that it's what we expect
341 # return true on error
342 def expect-from-channel stdin:&:source:char, expected:char, screen:&:screen -> result:bool, stdin:&:source:char, screen:&:screen [
343   local-scope
344   load-ingredients
345   c:char, eof?:bool, stdin <- read stdin
346   return-if eof? 1/true
347   {
348   ¦ match?:bool <- equal c, expected
349   ¦ break-if match?
350   ¦ print screen, [expected character not found]
351   }
352   result <- not match?
353 ]
354 
355 scenario read-move-blocking [
356   local-scope
357   assume-screen 20/width, 2/height
358   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
359   read-move-routine:num/routine <- start-running read-move, source, screen:&:screen
360   run [
361   ¦ # 'read-move' is waiting for input
362   ¦ wait-for-routine-to-block read-move-routine
363   ¦ read-move-state:num <- routine-state read-move-routine
364   ¦ waiting?:bool <- not-equal read-move-state, 2/discontinued
365   ¦ assert waiting?, [ 
366 F read-move-blocking: routine failed to pause after coming up (before any keys were pressed)]
367   ¦ # press 'a'
368   ¦ sink <- write sink, 97/a
369   ¦ restart read-move-routine
370   ¦ # 'read-move' still waiting for input
371   ¦ wait-for-routine-to-block read-move-routine
372   ¦ read-move-state <- routine-state read-move-routine
373   ¦ waiting? <- not-equal read-move-state, 2/discontinued
374   ¦ assert waiting?, [ 
375 F read-move-blocking: routine failed to pause after rank 'a']
376   ¦ # press '2'
377   ¦ sink <- write sink, 50/'2'
378   ¦ restart read-move-routine
379   ¦ # 'read-move' still waiting for input
380   ¦ wait-for-routine-to-block read-move-routine
381   ¦ read-move-state <- routine-state read-move-routine
382   ¦ waiting? <- not-equal read-move-state, 2/discontinued
383   ¦ assert waiting?, [ 
384 F read-move-blocking: routine failed to pause after file 'a2']
385   ¦ # press '-'
386   ¦ sink <- write sink, 45/'-'
387   ¦ restart read-move-routine
388   ¦ # 'read-move' still waiting for input
389   ¦ wait-for-routine-to-block read-move-routine
390   ¦ read-move-state <- routine-state read-move-routine
391   ¦ waiting? <- not-equal read-move-state, 2/discontinued
392   ¦ assert waiting?, [ 
393 F read-move-blocking: routine failed to pause after hyphen 'a2-']
394   ¦ # press 'a'
395   ¦ sink <- write sink, 97/a
396   ¦ restart read-move-routine
397   ¦ # 'read-move' still waiting for input
398   ¦ wait-for-routine-to-block read-move-routine
399   ¦ read-move-state <- routine-state read-move-routine
400   ¦ waiting? <- not-equal read-move-state, 2/discontinued
401   ¦ assert waiting?, [ 
402 F read-move-blocking: routine failed to pause after rank 'a2-a']
403   ¦ # press '4'
404   ¦ sink <- write sink, 52/'4'
405   ¦ restart read-move-routine
406   ¦ # 'read-move' still waiting for input
407   ¦ wait-for-routine-to-block read-move-routine
408   ¦ read-move-state <- routine-state read-move-routine
409   ¦ waiting? <- not-equal read-move-state, 2/discontinued
410   ¦ assert waiting?, [ 
411 F read-move-blocking: routine failed to pause after file 'a2-a4']
412   ¦ # press 'newline'
413   ¦ sink <- write sink, 10  # newline
414   ¦ restart read-move-routine
415   ¦ # 'read-move' now completes
416   ¦ wait-for-routine-to-block read-move-routine
417   ¦ read-move-state <- routine-state read-move-routine
418   ¦ completed?:bool <- equal read-move-state, 1/completed
419   ¦ assert completed?, [ 
420 F read-move-blocking: routine failed to terminate on newline]
421   ¦ trace 1, [test], [reached end]
422   ]
423   trace-should-contain [
424   ¦ test: reached end
425   ]
426 ]
427 
428 scenario read-move-quit [
429   local-scope
430   assume-screen 20/width, 2/height
431   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
432   read-move-routine:num <- start-running read-move, source, screen:&:screen
433   run [
434   ¦ # 'read-move' is waiting for input
435   ¦ wait-for-routine-to-block read-move-routine
436   ¦ read-move-state:num <- routine-state read-move-routine
437   ¦ waiting?:bool <- not-equal read-move-state, 2/discontinued
438   ¦ assert waiting?, [ 
439 F read-move-quit: routine failed to pause after coming up (before any keys were pressed)]
440   ¦ # press 'q'
441   ¦ sink <- write sink, 113/q
442   ¦ restart read-move-routine
443   ¦ # 'read-move' completes
444   ¦ wait-for-routine-to-block read-move-routine
445   ¦ read-move-state <- routine-state read-move-routine
446   ¦ completed?:bool <- equal read-move-state, 1/completed
447   ¦ assert completed?, [ 
448 F read-move-quit: routine failed to terminate on 'q']
449   ¦ trace 1, [test], [reached end]
450   ]
451   trace-should-contain [
452   ¦ test: reached end
453   ]
454 ]
455 
456 scenario read-move-illegal-file [
457   local-scope
458   assume-screen 20/width, 2/height
459   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
460   read-move-routine:num <- start-running read-move, source, screen:&:screen
461   run [
462   ¦ # 'read-move' is waiting for input
463   ¦ wait-for-routine-to-block read-move-routine
464   ¦ read-move-state:num <- routine-state read-move-routine
465   ¦ waiting?:bool <- not-equal read-move-state, 2/discontinued
466   ¦ assert waiting?, [ 
467 F read-move-illegal-file: routine failed to pause after coming up (before any keys were pressed)]
468   ¦ sink <- write sink, 50/'2'
469   ¦ restart read-move-routine
470   ¦ wait-for-routine-to-block read-move-routine
471   ]
472   screen-should-contain [
473   ¦ .file too low: 2     .
474   ¦ .                    .
475   ]
476 ]
477 
478 scenario read-move-illegal-rank [
479   local-scope
480   assume-screen 20/width, 2/height
481   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
482   read-move-routine:num <- start-running read-move, source, screen:&:screen
483   run [
484   ¦ # 'read-move' is waiting for input
485   ¦ wait-for-routine-to-block read-move-routine
486   ¦ read-move-state:num <- routine-state read-move-routine
487   ¦ waiting?:bool <- not-equal read-move-state, 2/discontinued
488   ¦ assert waiting?, [ 
489 F read-move-illegal-rank: routine failed to pause after coming up (before any keys were pressed)]
490   ¦ sink <- write sink, 97/a
491   ¦ sink <- write sink, 97/a
492   ¦ restart read-move-routine
493   ¦ wait-for-routine-to-block read-move-routine
494   ]
495   screen-should-contain [
496   ¦ .rank too high: a    .
497   ¦ .                    .
498   ]
499 ]
500 
501 scenario read-move-empty [
502   local-scope
503   assume-screen 20/width, 2/height
504   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
505   read-move-routine:num <- start-running read-move, source, screen:&:screen
506   run [
507   ¦ # 'read-move' is waiting for input
508   ¦ wait-for-routine-to-block read-move-routine
509   ¦ read-move-state:num <- routine-state read-move-routine
510   ¦ waiting?:bool <- not-equal read-move-state, 2/discontinued
511   ¦ assert waiting?, [ 
512 F read-move-empty: routine failed to pause after coming up (before any keys were pressed)]
513   ¦ sink <- write sink, 10/newline
514   ¦ sink <- write sink, 97/a
515   ¦ restart read-move-routine
516   ¦ wait-for-routine-to-block read-move-routine
517   ]
518   screen-should-contain [
519   ¦ .that's not enough   .
520   ¦ .                    .
521   ]
522 ]
523 
524 def make-move board:board, m:&:move -> board:board [
525   local-scope
526   load-ingredients
527   from-file:num <- get *m, from-file:offset
528   from-rank:num <- get *m, from-rank:offset
529   to-file:num <- get *m, to-file:offset
530   to-rank:num <- get *m, to-rank:offset
531   from-f:&:@:char <- index *board, from-file
532   to-f:&:@:char <- index *board, to-file
533   src:char/square <- index *from-f, from-rank
534   *to-f <- put-index *to-f, to-rank, src
535   *from-f <- put-index *from-f, from-rank, 32/space
536 ]
537 
538 scenario making-a-move [
539   local-scope
540   assume-screen 30/width, 12/height
541   board:board <- initial-position
542   move:&:move <- new move:type
543   *move <- merge 6/g, 1/'2', 6/g, 3/'4'
544   run [
545   ¦ board <- make-move board, move
546   ¦ screen:&:screen <- print-board screen:&:screen, board
547   ]
548   screen-should-contain [
549   #  012345678901234567890123456789
550   ¦ .8 | r n b q k b n r           .
551   ¦ .7 | p p p p p p p p           .
552   ¦ .6 |                           .
553   ¦ .5 |                           .
554   ¦ .4 |             P             .
555   ¦ .3 |                           .
556   ¦ .2 | P P P P P P   P           .
557   ¦ .1 | R N B Q K B N R           .
558   ¦ .  +----------------           .
559   ¦ .    a b c d e f g h           .
560   ¦ .                              .
561   ]
562 ]