home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-385-Vol-1of3.iso / m / me_cd25.zip / MUTT2.ZIP / GOMOKU.MUT < prev    next >
Lisp/Scheme  |  1992-11-09  |  44KB  |  1,347 lines

  1. ;;   Once installed and compiled, the program is invoked with 'M-x gomoku'
  2. ;; and 'C-h m' (the well-known describe-mode) will list all key bindings
  3. ;; provided to the player.  Have fun.
  4.  
  5. ;;; Gomoku game between you and GNU Emacs.  Last modified on 13 Sep 1988
  6. ;;; Converted to Mutt 9/88 C Durland
  7. ;;;
  8. ;;; Written by Ph. Schnoebelen (phs@lifia.imag.fr), 1987, 1988
  9. ;;; with precious advices from J.-F. Rit.
  10. ;;; This has been tested with GNU Emacs 18.50.
  11. ;;;
  12. ;;; This software is distributed 'as is', without warranties of any
  13. ;;; kind, but all comments, suggestions and bug reports are welcome.
  14.  
  15.  
  16. ;; RULES:
  17. ;;
  18. ;; Gomoku is a game played between two players on a rectangular board.    Each
  19. ;; player, in turn, marks a free square of its choice. The winner is the first
  20. ;; one to mark five contiguous squares in any direction (horizontally,
  21. ;; vertically or diagonally).
  22. ;;
  23. ;; I have been told that, in "The TRUE Gomoku", some restrictions are made
  24. ;; about the squares where one may play, or else there is a known forced win
  25. ;; for the first player. This program has no such restriction, but it does not
  26. ;; know about the forced win, nor do I.     Furthermore, you probably do not know
  27. ;; it yourself :-).
  28.  
  29.  
  30. ;; HOW TO INSTALL:
  31. ;;
  32. ;; There is nothing specific w.r.t. installation: just put this file in the
  33. ;; lisp directory and add an autoload for command gomoku in site-init.el. If
  34. ;; you don't want to rebuild Emacs, then every single user interested in
  35. ;; Gomoku will have to put the autoload command in its .emacs file.  Another
  36. ;; possibility is to define in your .emacs some command using (require
  37. ;; 'gomoku).
  38. ;;
  39. ;; The most important thing is to BYTE-COMPILE gomoku.el because it is
  40. ;; important that the code be as fast as possible.
  41. ;;
  42. ;; There are two main places where you may want to customize the program: key
  43. ;; bindings and board display. These features are commented in the code. Go
  44. ;; and see.
  45.  
  46.  
  47. ;; HOW TO USE:
  48. ;;
  49. ;; Once this file has been installed, the command "M-x gomoku" will display a
  50. ;; board, the size of which depends on the size of the current window. The
  51. ;; size of the board is easily modified by giving numeric arguments to the
  52. ;; gomoku command and/or by customizing the displaying parameters.
  53. ;;
  54. ;; Emacs plays when it is its turn. When it is your turn, just put the cursor
  55. ;; on the square where you want to play and hit RET, or X, or whatever key you
  56. ;; bind to the command gomoku-human-plays. When it is your turn, Emacs is
  57. ;; idle: you may switch buffers, read your mail, ... Just come back to the
  58. ;; *Gomoku* buffer and resume play.
  59.  
  60.  
  61. ;; ALGORITHM:
  62. ;;
  63. ;; The algorithm is briefly described in section "THE SCORE TABLE". Some
  64. ;; parameters may be modified if you want to change the style exhibited by the
  65. ;; program.
  66.  
  67.  
  68. (include me2.h)
  69. (include mod.mut)
  70. (include random.mut)
  71. (include max.mut)
  72. (include min.mut)
  73.  
  74. ;;;
  75. ;;; GOMOKU MODE AND KEYMAP.
  76. ;;;
  77.  
  78. (include nomunge.mut)
  79.  
  80. (defun create-gomoku-mode-map
  81. {
  82.   (buffer-nomunge)
  83.  
  84.   ;; Key bindings for cursor motion. Arrow keys are just "function"
  85.   ;; keys, see below.
  86.   (bind-local-key "gomoku-move-nw"    "y")        ; Y
  87.   (bind-local-key "gomoku-move-ne"    "u")        ; U
  88.   (bind-local-key "gomoku-move-sw"    "b")        ; B
  89.   (bind-local-key "gomoku-move-se"    "n")        ; N
  90.   (bind-local-key "gomoku-move-left"    "h")        ; H
  91.   (bind-local-key "gomoku-move-right"    "l")        ; L
  92.   (bind-local-key "gomoku-move-down"    "j")        ; J
  93.   (bind-local-key "gomoku-move-up"    "k")        ; K
  94.   (bind-local-key "gomoku-move-down"    "C-n")        ; C-N
  95.   (bind-local-key "gomoku-move-down"    "F-D")        ; down arrow
  96.   (bind-local-key "gomoku-move-up"    "C-p")        ; C-P
  97.   (bind-local-key "gomoku-move-up"    "F-C")        ; up arrow
  98.   (bind-local-key "gomoku-move-right"    "C-f")        ; C-F
  99.   (bind-local-key "gomoku-move-right"    "F-E")        ; right arrow
  100.   (bind-local-key "gomoku-move-left"    "C-b")        ; C-B
  101.   (bind-local-key "gomoku-move-left"    "F-F")        ; left arrow
  102.  
  103.   ;; Key bindings for entering Human moves.
  104.   (bind-local-key  "gomoku-human-plays"        "X")    ; X
  105.   (bind-local-key  "gomoku-human-plays"        "x")    ; x
  106.   (bind-local-key  "gomoku-human-plays"        "C-m")    ; RET
  107. ; (bind-local-key  "gomoku-human-plays"        "C-Xp")    ; C-C P
  108.   (bind-local-key  "gomoku-human-resigns"    "C-Xr")    ; C-C R
  109.   (bind-local-key  "gomoku-emacs-plays"        "C-Xe")    ; C-C E
  110. ; (bind-local-key  "gomoku-human-takes-back"    "C-cb")    ; C-C B
  111. })
  112.  
  113.  
  114. ;;    Major mode for playing Gomoku against Emacs.  You and Emacs play in
  115. ;; turn by marking a free square.  You mark it with X and Emacs marks it
  116. ;; with O.  The winner is the first to get five contiguous marks
  117. ;; horizontally, vertically or in diagonal.  You play by moving the cursor
  118. ;; over the square you choose and hitting RET, x, ..  or whatever has been
  119. ;; set locally.
  120.  
  121. ;; Other useful commands:
  122. ;;   C-c r Indicate that you resign.
  123. ;;   C-c t Take back your last move.
  124. ;;   C-c e Ask for Emacs to play (thus passing).
  125.  
  126. (defun gomoku-mode
  127. {
  128. ;  (setq major-mode 'gomoku-mode    mode-name "Gomoku")
  129.  
  130.   (clear-modes)
  131.   (major-mode "Gomoku")
  132.   (gomoku-display-statistics)
  133.   (create-gomoku-mode-map)
  134. })
  135.  
  136. ;;;
  137. ;;; THE BOARD.
  138. ;;;
  139.  
  140. ;;   The board is a rectangular grid.  We code empty squares with 0, X's
  141. ;; with 1 and O's with 6.  The rectangle is recorded in a one dimensional
  142. ;; vector containing padding squares (coded with -1).  These squares allow
  143. ;; us to detect when we are trying to move out of the board.  We denote a
  144. ;; square by its (X,Y) coords, or by the INDEX corresponding to them in the
  145. ;; vector.  The leftmost topmost square has coords (1,1) and index
  146. ;; gomoku-board-width + 2.  Similarly, vectors between squares may be given
  147. ;; by two DX, DY coords or by one DEPL (the difference between indexes).
  148.  
  149. (const gomoku-max-vector-length 4000)
  150.  
  151.   ;; Number of columns on the Gomoku board.
  152. (int gomoku-board-width)
  153.  
  154.   ;; Number of lines on the Gomoku board.
  155. (int gomoku-board-height)
  156.  
  157.   ;; Vector recording the actual state of the Gomoku board.
  158. (array int gomoku-board gomoku-max-vector-length)
  159.  
  160.   ;; Length of gomoku-board vector.
  161. (int gomoku-vector-length)
  162.  
  163.   ;; After how many moves will Emacs offer a draw ?
  164.   ;; This is usually set to 70% of the number of squares.
  165. (int gomoku-draw-limit)
  166.  
  167.   ;; Translate X, Y cartesian coords into the corresponding board index.
  168. (defun gomoku-xy-to-index (int x y) { (+ (* y gomoku-board-width) x y) })
  169.  
  170.   ;; Return corresponding x-coord of board INDEX.
  171. (defun gomoku-index-to-x (int index) { (mod index (+ 1 gomoku-board-width)) })
  172.  
  173.   ;; Return corresponding y-coord of board INDEX.
  174. (defun gomoku-index-to-y (int index) { (/ index (+ 1 gomoku-board-width)) })
  175.  
  176.   ;; Create the gomoku-board vector and fill it with initial values.
  177. (defun gomoku-init-board
  178. {
  179.   (int i ii)
  180.  
  181. ;(setq gomoku-board (make-vector gomoku-vector-length 0))
  182.     ;; Every square is 0 (i.e. empty) except padding squares:
  183.  
  184.   (i gomoku-vector-length) (while (!= 0 (-= i 1)) (gomoku-board i 0))
  185.  
  186.   (i 0) (ii (- gomoku-vector-length 1))
  187.   (while (<= i gomoku-board-width)    ; The squares in [0..width] and in
  188.   {
  189.     (gomoku-board i  -1)        ;    [length - width - 1..length - 1]
  190.     (gomoku-board ii -1)        ;    are padding squares.
  191.     (+= i 1)(-= ii 1)
  192.   })
  193.  
  194.   (i 0)
  195.   (while (< i gomoku-vector-length)
  196.   {
  197.     (gomoku-board i -1)        ; and also all k*(width+1)
  198.     (+= i gomoku-board-width 1)
  199.   })
  200. })
  201.  
  202. ;;;
  203. ;;; THE SCORE TABLE.
  204. ;;;
  205.  
  206. ;; Every (free) square has a score associated to it, recorded in the
  207. ;; GOMOKU-SCORE-TABLE vector. The program always plays in the square having
  208. ;; the highest score.
  209.  
  210.   ;; Vector recording the actual score of the free squares.
  211. (array int gomoku-score-table gomoku-max-vector-length)
  212.  
  213.  
  214. ;; The key point about the algorithm is that, rather than considering
  215. ;; the board as just a set of squares, we prefer to see it as a "space" of
  216. ;; internested 5-tuples of contiguous squares (called qtuples).
  217. ;;
  218. ;; The aim of the program is to fill one qtuple with its O's while preventing
  219. ;; you from filling another one with your X's. To that effect, it computes a
  220. ;; score for every qtuple, with better qtuples having better scores. Of
  221. ;; course, the score of a qtuple (taken in isolation) is just determined by
  222. ;; its contents as a set, i.e. not considering the order of its elements. The
  223. ;; highest score is given to the "OOOO" qtuples because playing in such a
  224. ;; qtuple is winning the game. Just after this comes the "XXXX" qtuple because
  225. ;; not playing in it is just loosing the game, and so on. Note that a
  226. ;; "polluted" qtuple, i.e. one containing at least one X and at least one O,
  227. ;; has score zero because there is no more any point in playing in it, from
  228. ;; both an attacking and a defending point of view.
  229. ;;
  230. ;; Given the score of every qtuple, the score of a given free square on the
  231. ;; board is just the sum of the scores of all the qtuples to which it belongs,
  232. ;; because playing in that square is playing in all its containing qtuples at
  233. ;; once. And it is that function which takes into account the internesting of
  234. ;; the qtuples.
  235. ;;
  236. ;; This algorithm is rather simple but anyway it gives a not so dumb level of
  237. ;; play. It easily extends to "n-dimensional Gomoku", where a win should not
  238. ;; be obtained with as few as 5 contiguous marks: 6 or 7 (depending on n !)
  239. ;; should be preferred.
  240.  
  241.  
  242. ;; Here are the scores of the nine "non-polluted" configurations.  Tuning
  243. ;; these values will change (hopefully improve) the strength of the program
  244. ;; and may change its style (rather aggressive here).
  245.  
  246. (const nil-score      7)    ; Score of an empty qtuple.
  247. (const Xscore         15)    ; Score of a qtuple containing one X.
  248. (const XXscore        400)    ; Score of a qtuple containing two X's.
  249. (const XXXscore           1800)    ; Score of a qtuple containing three X's.
  250. (const XXXXscore     100000)    ; Score of a qtuple containing four X's.
  251. (const Oscore         35)    ; Score of a qtuple containing one O.
  252. (const OOscore        800)    ; Score of a qtuple containing two O's.
  253. (const OOOscore          15000)    ; Score of a qtuple containing three O's.
  254. (const OOOOscore     800000)    ; Score of a qtuple containing four O's.
  255.  
  256. ;; These values are not just random: if, given the following situation:
  257. ;;
  258. ;;              . . . . . . . O .
  259. ;;              . X X a . . . X .
  260. ;;              . . . X . . . X .
  261. ;;              . . . X . . . X .
  262. ;;              . . . . . . . b .
  263. ;;
  264. ;; you want Emacs to play in "a" and not in "b", then the parameters must
  265. ;; satisfy the inequality:
  266. ;;
  267. ;;           6 * XXscore > XXXscore + XXscore
  268. ;;
  269. ;; because "a" mainly belongs to six "XX" qtuples (the others are less
  270. ;; important) while "b" belongs to one "XXX" and one "XX" qtuples.  Other
  271. ;; conditions are required to obtain sensible moves, but the previous example
  272. ;; should illustrate the point. If you manage to improve on these values,
  273. ;; please send me a note. Thanks.
  274.  
  275.  
  276. ;; As we choosed values 0, 1 and 6 to denote empty, X and O squares, the
  277. ;; contents of a qtuple is uniquely determined by the sum of its elements and
  278. ;; we just have to set up a translation table.
  279.  
  280. ;(defconst gomoku-score-trans-table
  281. ;  (vector nil-score Xscore XXscore XXXscore XXXXscore 0
  282. ;      Oscore    0       0       0        0          0
  283. ;      OOscore   0       0       0        0          0
  284. ;      OOOscore  0       0       0        0          0
  285. ;      OOOOscore 0       0       0        0          0
  286. ;      0)
  287.  
  288.   ;; Vector associating qtuple contents to their score.
  289. (array int gomoku-score-trans-table 31)
  290. (defun gomoku-init-score-trans-table
  291. {
  292.   (gomoku-score-trans-table 0  nil-score)
  293.   (gomoku-score-trans-table 1  Xscore)
  294.   (gomoku-score-trans-table 2  XXscore)
  295.   (gomoku-score-trans-table 3  XXXscore)
  296.   (gomoku-score-trans-table 4  XXXXscore)
  297.   (gomoku-score-trans-table 6  Oscore)
  298.   (gomoku-score-trans-table 12 OOscore)
  299.   (gomoku-score-trans-table 18 OOOscore)
  300.   (gomoku-score-trans-table 24 OOOOscore)
  301. })
  302.  
  303. ;; If you do not modify drastically the previous constants, the only way for a
  304. ;; square to have a score higher than OOOOscore is to belong to a "OOOO"
  305. ;; qtuple, thus to be a winning move. Similarly, the only way for a square to
  306. ;; have a score between XXXXscore and OOOOscore is to belong to a "XXXX"
  307. ;; qtuple. We may use these considerations to detect when a given move is
  308. ;; winning or loosing.
  309.  
  310.   ;; Threshold score beyond which an emacs move is winning.
  311. (const gomoku-winning-threshold OOOOscore)
  312.  
  313.   ;; Threshold score beyond which a human move is winning.
  314. (const gomoku-loosing-threshold XXXXscore)
  315.  
  316.   ;; Compute index of free square with highest score, or nil if none.
  317. (defun gomoku-strongest-square
  318. {
  319.   ;; We just have to loop other all squares. However there are two problems:
  320.   ;; 1/ The SCORE-TABLE only gives correct scores to free squares. To speed
  321.   ;;    up future searches, we set the score of padding or occupied squares
  322.   ;;    to -1 whenever we meet them.
  323.   ;; 2/ We want to choose randomly between equally good moves.
  324.  
  325.   (int score score-max)
  326.   (int count square end best-square)
  327.  
  328.   (score-max 0)
  329.   (count   0)                ; Number of equally good moves
  330.   (square  (gomoku-xy-to-index 1 1))    ; First square
  331.   (end       (gomoku-xy-to-index gomoku-board-width gomoku-board-height))
  332.   (while (<= square end)
  333.   {
  334.     (cond
  335.     ;; If score is lower (i.e. most of the time), skip to next:
  336.       (< (gomoku-score-table square) score-max) ()
  337.     ;; If score is better, beware of non free squares:
  338.       (> (score (gomoku-score-table square)) score-max)
  339.         (if (== 0 (gomoku-board square))    ; is it free ?
  340.       {
  341.         (count 1)                ; yes: take it !
  342.         (best-square square)
  343.         (score-max   score)
  344.       }
  345.       (gomoku-score-table square -1)    ; no: kill it !
  346.     )
  347.     ;; If score is equally good, choose randomly. But first check freeness:
  348.       (!= 0 (gomoku-board square)) (gomoku-score-table square -1)
  349.       (== count (random-number (+= count 1)))
  350.     { (best-square square)(score-max score) }
  351.     )
  352.     (+= square 1)    ; try next square
  353.   })
  354.   best-square
  355. })
  356.  
  357.   ;; Return a random integer between 0 and N-1 inclusive.
  358. (defun random-number (n) { (mod (rand) n) })
  359.  
  360. ;;;
  361. ;;; INITIALIZING THE SCORE TABLE.
  362. ;;;
  363.  
  364. ;; At initialization the board is empty so that every qtuple amounts for
  365. ;; nil-score. Therefore, the score of any square is nil-score times the number
  366. ;; of qtuples that pass through it. This number is 3 in a corner and 20 if you
  367. ;; are sufficiently far from the sides. As computing the number is time
  368. ;; consuming, we initialize every square with 20*nil-score and then only
  369. ;; consider squares at less than 5 squares from one side. We speed this up by
  370. ;; taking symmetry into account.
  371. ;; Also, as it is likely that successive games will be played on a board with
  372. ;; same size, it is a good idea to save the initial SCORE-TABLE configuration.
  373.  
  374.   ;; Recorded initial value of previous score table.
  375. ;(??? gomoku-saved-score-table)
  376.  
  377.   ;; Recorded value of previous board width.
  378. (int gomoku-saved-board-width)
  379.  
  380.   ;; Recorded value of previous board height.
  381. (int gomoku-saved-board-height)
  382.  
  383.  
  384.   ;; Create the score table vector and fill it with initial values.
  385. (defun gomoku-init-score-table
  386. {
  387.   (int i j maxi maxj maxi2 maxj2)
  388.  
  389. ;  (if (and gomoku-saved-score-table    ; Has it been stored last time ?
  390. ;       (= gomoku-board-width  gomoku-saved-board-width)
  391. ;       (= gomoku-board-height gomoku-saved-board-height))
  392. ;      (setq gomoku-score-table (copy-sequence gomoku-saved-score-table))
  393.       ;; No, compute it:
  394.  
  395. ;
  396. ;(setq gomoku-score-table
  397. ;        (make-vector gomoku-vector-length (* 20 nil-score)))
  398.   (i 0)
  399.   (while (< i gomoku-vector-length)
  400.     { (gomoku-score-table i (* 20 nil-score)) (+= i 1) })
  401.  
  402.   (maxi  (/ (+ 1 gomoku-board-width) 2))
  403.   (maxj  (/ (+ 1 gomoku-board-height) 2))
  404.   (maxi2 (min 4 maxi))
  405.   (maxj2 (min 4 maxj))
  406.     ;; We took symmetry into account and could use it more if the board
  407.     ;; would have been square and not rectangular !
  408.     ;; In our case we deal with all (i,j) in the set [1..maxi2]*[1..maxj] U
  409.     ;; [maxi2+1..maxi]*[1..maxj2]. Maxi2 and maxj2 are used because the
  410.     ;; board may well be less than 8 by 8 !
  411.   (i 1)
  412.   (while (<= i maxi2)
  413.   {
  414.     (j 1)
  415.     (while (<= j maxj) { (gomoku-init-square-score i j) (+= j 1) })
  416.     (+= i 1)
  417.   })
  418.   (while (<= i maxi)
  419.   {
  420.     (j 1)
  421.     (while (<= j maxj2) { (gomoku-init-square-score i j) (+= j 1) })
  422.     (+= i 1)
  423.   })
  424. ;(setq gomoku-saved-score-table  (copy-sequence gomoku-score-table)
  425. ;        gomoku-saved-board-width  gomoku-board-width
  426. ;        gomoku-saved-board-height gomoku-board-height)
  427. })
  428.  
  429.   ;; Return the number of qtuples containing square I,J.
  430. (defun gomoku-nb-qtuples (int i j)
  431. {
  432.   ;; This function is complicated because we have to deal
  433.   ;; with ugly cases like 3 by 6 boards, but it works.
  434.   ;; If you have a simpler (and correct) solution, send it to me. Thanks !
  435.  
  436.   (int left right up down)
  437.  
  438.   (left  (min 4 (- i 1)))
  439.   (right (min 4 (- gomoku-board-width i)))
  440.   (up    (min 4 (- j 1)))
  441.   (down  (min 4 (- gomoku-board-height j)))
  442.   (+ -12
  443.      (min (max (+ left right) 3) 8)
  444.      (min (max (+ up down) 3) 8)
  445.      (min (max (+ (min left up) (min right down)) 3) 8)
  446.      (min (max (+ (min right up) (min left down)) 3) 8))
  447. })
  448.  
  449.   ;; Give initial score to square I,J and to its mirror images.
  450. (defun gomoku-init-square-score (int i j)
  451. {
  452.   (int ii jj)(int sc)
  453.  
  454.   (ii (+ 1 (- gomoku-board-width i)))
  455.   (jj (+ 1 (- gomoku-board-height j)))
  456.   (sc (* (gomoku-nb-qtuples i j) (gomoku-score-trans-table 0)))
  457.   (gomoku-score-table (gomoku-xy-to-index i  j)  sc)
  458.   (gomoku-score-table (gomoku-xy-to-index ii j)     sc)
  459.   (gomoku-score-table (gomoku-xy-to-index i  jj) sc)
  460.   (gomoku-score-table (gomoku-xy-to-index ii jj) sc)
  461. })
  462.  
  463. ;;;
  464. ;;; MAINTAINING THE SCORE TABLE.
  465. ;;;
  466.  
  467. ;; We do not provide functions for computing the SCORE-TABLE given the
  468. ;; contents of the BOARD. This would involve heavy nested loops, with time
  469. ;; proportional to the size of the board. It is better to update the
  470. ;; SCORE-TABLE after each move. Updating needs not modify more than 36
  471. ;; squares: it is done in constant time.
  472.  
  473.   ;; Update score table after SQUARE received a DVAL increment.
  474. (defun gomoku-update-score-table (int square dval)
  475. {
  476.   ;; The board has already been updated when this function is called.
  477.   ;; Updating scores is done by looking for qtuples boundaries in all four
  478.   ;; directions and then calling update-score-in-direction.
  479.   ;; Finally all squares received the right increment, and then are up to
  480.   ;; date, except possibly for SQUARE itself if we are taking a move back for
  481.   ;; its score had been set to -1 at the time.
  482.  
  483.   (int x y imin jmin imax jmax)
  484.  
  485.   (x (gomoku-index-to-x square))
  486.   (y (gomoku-index-to-y square))
  487.   (imin (max -4 (- 1 x)))
  488.   (jmin (max -4 (- 1 y)))
  489.   (imax (min 0 (- gomoku-board-width x 4)))
  490.   (jmax (min 0 (- gomoku-board-height y 4)))
  491.   (gomoku-update-score-in-direction imin imax square 1 0 dval)
  492.   (gomoku-update-score-in-direction jmin jmax square 0 1 dval)
  493.   (gomoku-update-score-in-direction
  494.     (max imin jmin) (min imax jmax) square 1 1 dval)
  495.   (gomoku-update-score-in-direction
  496.     (max (- 1 y) -4 (- x gomoku-board-width))
  497.     (min 0 (- x 5) (- gomoku-board-height y 4))
  498.     square -1 1 dval)
  499. })
  500.  
  501.   ;; Update scores for all squares in the qtuples starting between the
  502.   ;;   LEFTth square and the RIGHTth after SQUARE, along the DX, DY
  503.   ;;   direction, considering that DVAL has been added on SQUARE.
  504. (defun gomoku-update-score-in-direction (int left right sq dx dy dval)
  505. {
  506.   ;; We always have LEFT <= 0, RIGHT <= 0 and DEPL > 0 but we may very well
  507.   ;; have LEFT > RIGHT, indicating that no qtuple contains SQUARE along that
  508.   ;; DX,DY direction.
  509.  
  510.   (int depl square square0 square1 square2 count)
  511.   (int delta)
  512.  
  513.   (square sq)
  514.   (if (> left right) (done))        ; Quit
  515.   (depl    (gomoku-xy-to-index dx dy))
  516.   (square0 (+ square (* left depl)))
  517.   (square1 (+ square (* right depl)))
  518.   (square2 (+ square0 (* 4 depl)))
  519.       ;; Compute the contents of the first qtuple:
  520.   (square square0)
  521.   (count  0)
  522.   (while (<= square square2)
  523.     { (+= count (gomoku-board square)) (+= square depl) })
  524.   (while (<= square0 square1)
  525.   {
  526.     ;; Update the squares of the qtuple beginning in SQUARE0 and ending
  527.     ;; in SQUARE2.
  528.     (delta (- (gomoku-score-trans-table count)
  529.           (gomoku-score-trans-table (- count dval))))
  530.     (if (!= 0 delta)        ; or else nothing to update
  531.     {
  532.       (square square0)
  533.       (while (<= square square2)
  534.       {
  535.     (if (== 0 (gomoku-board square))     ; only for free squares
  536.       (gomoku-score-table square (+ (gomoku-score-table square) delta)))
  537.     (+= square depl)
  538.       })
  539.     })
  540.     ;; Then shift the qtuple one square along DEPL, this only requires
  541.     ;; modifying SQUARE0 and SQUARE2.
  542.     (+= square2 depl)
  543.     (+= count (- (gomoku-board square2) (gomoku-board square0)) )
  544.     (+= square0 depl)
  545.   })
  546. })
  547.  
  548. ;;;
  549. ;;; GAME CONTROL.
  550. ;;;
  551.  
  552. ;; Several variables are used to monitor a game, including a GAME-HISTORY (the
  553. ;; list of all (SQUARE . PREVSCORE) played) that allows to take moves back
  554. ;; (anti-updating the score table) and to compute the table from scratch in
  555. ;; case of an interruption.
  556.  
  557.   ;; Non-nil if a game is in progress.
  558. (bool gomoku-game-in-progress)
  559.  
  560.   ;; Number of moves already played in current game.
  561. (int gomoku-number-of-moves)
  562.  
  563.   ;; Number of moves already played by human in current game.
  564. (int gomoku-number-of-human-moves)
  565.  
  566.   ;; Non-nil if Emacs played first.
  567. (bool gomoku-emacs-played-first)
  568.  
  569.   ;; Non-nil if Human took back a move during the game.
  570. (bool gomoku-human-took-back)
  571.  
  572.   ;; Non-nil if Human refused Emacs offer of a draw.
  573. (bool gomoku-human-refused-draw)
  574.  
  575.   ;; This is used to detect interruptions. Hopefully, it should not be needed.
  576.   ;; Non-nil if Emacs is in the middle of a computation.
  577. (bool gomoku-emacs-is-computing)
  578.  
  579.  
  580.   ;; Initialize a new game on an N by M board.
  581. (defun gomoku-start-game (int n m)
  582. {
  583.   (gomoku-emacs-is-computing TRUE)    ; Raise flag
  584.   (gomoku-game-in-progress TRUE)
  585.   (gomoku-board-width  n) (gomoku-board-height m)
  586.   (gomoku-vector-length (+ 1 (* (+ m 2) (+ 1 n))))
  587. (if (<= gomoku-max-vector-length gomoku-vector-length)
  588. (error "Board too big"))
  589.   (gomoku-draw-limit (/ (* 7 n m) 10))
  590.   (gomoku-number-of-moves 0)
  591.   (gomoku-number-of-human-moves 0)
  592.   (gomoku-emacs-played-first TRUE)
  593.   (gomoku-human-took-back    FALSE)
  594.   (gomoku-human-refused-draw FALSE)
  595.   (gomoku-init-display n m)        ; Display first: the rest takes time
  596.   (gomoku-init-score-trans-table)
  597.   (gomoku-init-score-table)        ; INIT-BOARD requires that the score
  598.   (gomoku-init-board)            ;   table be already created.
  599.   (gomoku-emacs-is-computing FALSE)
  600. })
  601.  
  602.   ;; Go to SQUARE, play VAL and update everything.
  603. (defun gomoku-play-move (int square val) ; &optional dont-update-score
  604. {
  605.   (gomoku-emacs-is-computing TRUE)    ; Raise flag
  606.   (cond
  607.     (== 1 val)            ; a Human move
  608.     (gomoku-number-of-human-moves (+ 1 gomoku-number-of-human-moves))
  609.     (== 0 gomoku-number-of-moves)    ; an Emacs move. Is it first ?
  610.     (gomoku-emacs-played-first TRUE)
  611.   )
  612. ;  (setq gomoku-game-history
  613. ;    (cons (cons square (aref gomoku-score-table square))
  614. ;          gomoku-game-history)
  615.  
  616.   (+= gomoku-number-of-moves 1)
  617.  
  618.   (gomoku-plot-square square val)
  619.   (gomoku-board square val)    ; *BEFORE* UPDATE-SCORE !
  620.   (gomoku-update-score-table square val) ; previous val was 0: dval = val
  621.   (gomoku-score-table square -1)
  622.   (gomoku-emacs-is-computing FALSE)
  623. })
  624.  
  625.   ;; Take back last move and update everything.
  626. (defun gomoku-take-back
  627. {
  628. ;  (setq gomoku-emacs-is-computing t)
  629. ;  (let* ((last-move (car gomoku-game-history))
  630. ;     (square (car last-move))
  631. ;     (oldval (aref gomoku-board square)))
  632. ;    (if (= 1 oldval)
  633. ;    (setq gomoku-number-of-human-moves (1- gomoku-number-of-human-moves)))
  634. ;    (setq gomoku-game-history     (cdr gomoku-game-history)
  635. ;      gomoku-number-of-moves (1- gomoku-number-of-moves))
  636. ;    (gomoku-plot-square square 0)
  637. ;    (aset gomoku-board square 0)    ; *BEFORE* UPDATE-SCORE !
  638. ;    (gomoku-update-score-table square (- oldval))
  639. ;    (aset gomoku-score-table square (cdr last-move)))
  640. ;  (setq gomoku-emacs-is-computing nil))
  641. })
  642.  
  643. ;;;
  644. ;;; SESSION CONTROL.
  645. ;;;
  646.  
  647.   ;; Number of games already won in this session.
  648. (int gomoku-number-of-wins)
  649.  
  650.   ;; Number of games already lost in this session.
  651. (int gomoku-number-of-losses)
  652.  
  653.   ;; Number of games already drawn in this session.
  654. (int gomoku-number-of-draws)
  655.  
  656.  
  657. (const
  658.   emacs-won     1
  659.   human-won     2
  660.   nobody-won     3
  661.   draw-agreed     4
  662.   human-resigned 5
  663.   crash-game     6
  664. )
  665.  
  666.   ;; Terminate the current game with RESULT.
  667. (defun gomoku-terminate-game (int result)
  668. {
  669.   (string message)
  670.  
  671.   (switch result
  672.     emacs-won
  673.     {
  674.       (gomoku-number-of-wins (+ 1 gomoku-number-of-wins))
  675.       (message
  676.         (cond
  677.       (< gomoku-number-of-moves 20) "This was a REALLY QUICK win."
  678.       gomoku-human-refused-draw
  679.         "I won... Too bad you refused my offer of a draw !"
  680.       gomoku-human-took-back
  681.         "I won... Taking moves back will not help you !"
  682.       (not gomoku-emacs-played-first)
  683.         "I won... Playing first did not help you much !"
  684.       (and (== 0 gomoku-number-of-losses)
  685.            (== 0 gomoku-number-of-draws)
  686.            (> gomoku-number-of-wins 1))
  687.            "I'm becoming tired of winning..."
  688.       TRUE "I won."
  689.     )
  690.       )
  691.     }
  692.     human-won
  693.     {
  694.       (gomoku-number-of-losses (+ 1 gomoku-number-of-losses))
  695.       (message
  696.         (cond
  697.       gomoku-human-took-back
  698.         "OK, you won this one. I, for one, never take my moves back..."
  699.       gomoku-emacs-played-first "OK, you won this one... so what ?"
  700.       TRUE
  701.         "OK, you won this one. Now, let me play first just once."
  702.     )
  703.       )
  704.     }
  705.     human-resigned
  706.     {
  707.       (gomoku-number-of-wins (+ 1 gomoku-number-of-wins))
  708.       (message "So you resign... That's just one more win for me.")
  709.     }
  710.     nobody-won
  711.     {
  712.       (gomoku-number-of-draws (+ 1 gomoku-number-of-draws))
  713.       (message
  714.         (cond
  715.       gomoku-human-took-back
  716.         "This is a draw. I, for one, never take my moves back..."
  717.       gomoku-emacs-played-first "This is a draw... Just chance, I guess."
  718.       TRUE "This is a draw. Now, let me play first just once."
  719.     )
  720.       )
  721.     }
  722.     draw-agreed
  723.     {
  724.       (gomoku-number-of-draws (+ 1 gomoku-number-of-draws))
  725.       (message
  726.         (cond
  727.       gomoku-human-took-back
  728.         "Draw agreed. I, for one, never take my moves back..."
  729.       gomoku-emacs-played-first "Draw agreed. You were lucky."
  730.       TRUE "Draw agreed. Now, let me play first just once."
  731.     )
  732.       )
  733.     }
  734.     crash-game
  735.       (message "Sorry, I have been interrupted and cannot resume that game...")
  736.   )
  737.   (gomoku-display-statistics)
  738.   (msg message)
  739.   (gomoku-game-in-progress FALSE)
  740. })
  741.  
  742.   ;; What to do when Emacs detects it has been interrupted.
  743. (defun gomoku-crash-game
  744. {
  745.   (gomoku-emacs-is-computing FALSE)
  746.   (gomoku-terminate-game crash-game)
  747. ;  (sit-for 4)                ; Let's see the message
  748.   (gomoku-prompt-for-other-game)
  749. })
  750.  
  751. ;;;
  752. ;;; INTERACTIVE COMMANDS.
  753. ;;;
  754.  
  755. (defun error (string error-message)
  756. {
  757.   (msg error-message)(halt)
  758. })
  759.  
  760. ;; Start a Gomoku game between you and Emacs.
  761. ;; If a game is in progress, this command allows you to resume it.
  762. ;; If optional arguments N and M are given, an N by M board is used.
  763. ;; You and Emacs play in turn by marking a free square.  You mark it with X
  764. ;;   and Emacs marks it with O.  The winner is the first to get five
  765. ;;   contiguous marks horizontally, vertically or in diagonal.
  766. ;; You play by moving the cursor over the square you choose and hitting RET,
  767. ;;   x, ..  or whatever has been set locally.
  768. (defun gomoku
  769. {
  770.   (int n m max-width max-height)
  771.  
  772.   (n 0)(m 0)
  773.   (if (arg-flag)
  774.     {
  775.       (n (convert-to NUMBER (ask "Gomoku board width: ")))
  776.       (m (convert-to NUMBER (ask "Gomoku board height: ")))
  777.     }
  778.     (if (!= 0 (nargs)) { (n (arg 0)) (m (arg 1)) })
  779.   )
  780.  
  781.   (gomoku-switch-to-window)
  782.  
  783.   (cond
  784.     gomoku-emacs-is-computing (gomoku-crash-game) ; ???
  785.     (not gomoku-game-in-progress)
  786.     {
  787.       (max-width (gomoku-max-width)) (max-height (gomoku-max-height))
  788.       (if (== 0 n) (n max-width))
  789.       (if (== 0 m) (m max-height))
  790.       (cond
  791.         (< n 1) (error "I need at least 1 column")
  792.     (< m 1) (error "I need at least 1 row")
  793.     (> n max-width)
  794.       (error (concat "I cannot display " n " columns in that window"))
  795.       )
  796.       (if (and (> m max-height)
  797.            (!= m gomoku-saved-board-height)
  798.            (not (yesno "Do you really want " m " rows")))
  799.       (m max-height))
  800.       (msg "One moment, please...")
  801.       (gomoku-start-game n m)
  802.       (if (yesno "Do you allow me to play first")
  803.     (gomoku-emacs-plays)
  804.     (gomoku-prompt-for-move))
  805.     }
  806.     (yesno "Shall we continue our game") (gomoku-prompt-for-move)
  807.     TRUE (gomoku-human-resigns)
  808.   )
  809. })
  810.  
  811.   ;; Compute Emacs next move and play it.
  812. (defun gomoku-emacs-plays
  813. {
  814.   (int square) (int score)
  815.  
  816. ;  (gomoku-switch-to-window)
  817.   (cond
  818.     gomoku-emacs-is-computing (gomoku-crash-game)
  819.     (not gomoku-game-in-progress) (gomoku-prompt-for-other-game)
  820.     TRUE
  821.     {
  822.       (msg "Let me think...")
  823.       (square (gomoku-strongest-square))
  824.       (cond
  825.         (== 0 square) (gomoku-terminate-game nobody-won)
  826.     TRUE
  827.     {
  828.       (score (gomoku-score-table square))
  829.       (gomoku-play-move square 6)
  830.       (cond
  831.         (>= score gomoku-winning-threshold)
  832.         {
  833.           (gomoku-find-filled-qtuple square 6)
  834.           (gomoku-cross-winning-qtuple)
  835.           (gomoku-terminate-game emacs-won)
  836.         }
  837.         (== 0 score) (gomoku-terminate-game nobody-won)
  838.         (and (> gomoku-number-of-moves gomoku-draw-limit)
  839.          (not gomoku-human-refused-draw)
  840.          (gomoku-offer-a-draw))
  841.            (gomoku-terminate-game draw-agreed)
  842.         TRUE (gomoku-prompt-for-move)
  843.       )
  844.     }
  845.       )
  846.     }
  847.   )
  848. })
  849.  
  850.   ;; Signal to the Gomoku program that you have played.
  851.   ;; You must have put the cursor on the square where you want to play.
  852.   ;; If the game is finished, this command requests for another game.
  853. (defun gomoku-human-plays
  854. {
  855.   (int square) (int score)
  856.  
  857.   (gomoku-switch-to-window)
  858.   (cond
  859.     gomoku-emacs-is-computing (gomoku-crash-game)
  860.     (not gomoku-game-in-progress) (gomoku-prompt-for-other-game)
  861.     TRUE
  862.     {
  863.       (square (gomoku-point-square))
  864.       (cond
  865.         (== 0 square) (error "Your point is not on a square. Retry !")
  866.     (!= 0 (gomoku-board square))
  867.       (error "Your point is not on a free square. Retry !")
  868.     TRUE
  869.     {
  870.       (score (gomoku-score-table square))
  871.       (gomoku-play-move square 1)
  872.       (cond
  873.         (and (>= score gomoku-loosing-threshold)
  874.             ;; Just testing SCORE > THRESHOLD is not enough for
  875.             ;; detecting wins, it just gives an indication that
  876.             ;; we confirm with GOMOKU-FIND-FILLED-QTUPLE.
  877.          (gomoku-find-filled-qtuple square 1))
  878.           {
  879.             (gomoku-cross-winning-qtuple)
  880.             (gomoku-terminate-game human-won)
  881.           }
  882.         TRUE (gomoku-emacs-plays)
  883.       )
  884.     }
  885.       )
  886.     }
  887.   )
  888. })
  889.  
  890.   ;; Signal to the Gomoku program that you wish to take back your last move.
  891. (defun gomoku-human-takes-back
  892. {
  893. (msg "Take back not implemented yet")
  894. ;  (gomoku-switch-to-window)
  895. ;  (cond
  896. ;   (gomoku-emacs-is-computing
  897. ;    (gomoku-crash-game))
  898. ;   ((not gomoku-game-in-progress)
  899. ;    (message "Too late for taking back...")
  900. ;    (sit-for 4)
  901. ;    (gomoku-prompt-for-other-game))
  902. ;   ((zerop gomoku-number-of-human-moves)
  903. ;    (message "You have not played yet... Your move ?"))
  904. ;   (t
  905. ;    (message "One moment, please...")
  906.     ;; It is possible for the user to let Emacs play several consecutive
  907.     ;; moves, so that the best way to know when to stop taking back moves is
  908.     ;; to count the number of human moves:
  909. ;    (setq gomoku-human-took-back t)
  910. ;    (let ((number gomoku-number-of-human-moves))
  911. ;      (while (= number gomoku-number-of-human-moves)
  912. ;    (gomoku-take-back)))
  913. ;    (gomoku-prompt-for-move))))
  914. })
  915.  
  916.   ;; Signal to the Gomoku program that you may want to resign.
  917. (defun gomoku-human-resigns
  918. {
  919.   (gomoku-switch-to-window)
  920.   (cond
  921.     gomoku-emacs-is-computing (gomoku-crash-game)
  922.     (not gomoku-game-in-progress) (msg "There is no game in progress")
  923.     (yesno "You mean, you resign") (gomoku-terminate-game human-resigned)
  924.     (yesno "You mean, we continue") (gomoku-prompt-for-move)
  925.     TRUE (gomoku-terminate-game human-resigned)    ; OK. Accept it
  926.   )
  927. })
  928.  
  929. ;;;
  930. ;;; PROMPTING THE HUMAN PLAYER.
  931. ;;;
  932.  
  933.   ;; Display a message asking for Human's move.
  934. (defun gomoku-prompt-for-move
  935. {
  936.   (msg
  937.     (if (== 0 gomoku-number-of-human-moves)
  938.     "Your move ? (move to a free square and hit X, RET ...)"
  939.     "Your move ?"))
  940.   ;; This may seem silly, but if one omits the following line (or a similar
  941.   ;; one), the cursor may very well go to some place where POINT is not.
  942. ;???  (save-excursion (set-buffer (other-buffer))))
  943. })
  944.  
  945.   ;; Ask for another game, and start it.
  946. (defun gomoku-prompt-for-other-game
  947. {
  948.   (if (yesno "Another game")
  949.     (gomoku gomoku-board-width gomoku-board-height)
  950.     (msg "Chicken !"))
  951. })
  952.  
  953.   ;; Offer a draw and return T if Human accepted it.
  954. (defun gomoku-offer-a-draw
  955. {
  956.   (if (yesno "I offer you a draw. Do you accept it")
  957.     (gomoku-human-refused-draw TRUE)
  958.     FALSE)
  959. })
  960.  
  961. ;;;
  962. ;;; DISPLAYING THE BOARD.
  963. ;;;
  964.  
  965. ;; You may change these values if you have a small screen or if the squares
  966. ;; look rectangular, but spacings SHOULD be at least 2 (MUST BE at least 1).
  967.  
  968.   ;; Horizontal spacing between squares on the Gomoku board.
  969. (const gomoku-square-width 4)
  970.  
  971.   ;; Vertical spacing between squares on the Gomoku board.
  972. (const gomoku-square-height 2)
  973.  
  974.   ;; Number of columns between the Gomoku board and the side of the window.
  975. (const gomoku-x-offset 3)
  976.  
  977.   ;; Number of lines between the Gomoku board and the top of the window.
  978. (const gomoku-y-offset 1)
  979.  
  980.  
  981.   ;; Largest possible board width for the current window.
  982. (defun gomoku-max-width
  983. {
  984.   (+ 1 (/ (- (screen-width) gomoku-x-offset gomoku-x-offset 1)
  985.      gomoku-square-width))
  986. })
  987.  
  988.   ;; Largest possible board height for the current window.
  989. (defun gomoku-max-height
  990. {
  991.   (+ 1 (/ (- (window-height -1) gomoku-y-offset gomoku-y-offset 1)
  992.      ;; 2 instead of 1 because WINDOW-HEIGHT includes the mode line !
  993.      gomoku-square-height))
  994. })
  995.  
  996.  ;; Return the board column where point is, or nil if it is not a board column.
  997. (defun gomoku-point-x
  998. {
  999.   (int col)
  1000.  
  1001.   (col (- (current-column) gomoku-x-offset 1))
  1002.   (if (and (>= col 0)
  1003.        (== 0 (mod col gomoku-square-width))
  1004.        (<= (col (+ 1 (/ col gomoku-square-width))) gomoku-board-width))
  1005.     col
  1006.     0)
  1007. })
  1008.  
  1009.   ;; Return the board row where point is, or nil if it is not a board row.
  1010. (defun gomoku-point-y
  1011. {
  1012.   (int row)
  1013.   (int buffer-size dot lines buffer-row wasted char-at-dot)    ;; BufferInfo
  1014.   
  1015.   (buffer-stats -1 (loc buffer-size))
  1016.  
  1017.   (row (- (buffer-row) gomoku-y-offset 1))
  1018.   (if (and (>= row 0)
  1019.       (== 0 (mod row gomoku-square-height))
  1020.       (<= (row (+ 1 (/ row gomoku-square-height))) gomoku-board-height))
  1021.     row
  1022.     0)
  1023. })
  1024.  
  1025.   ;; Return the index of the square point is on, or nil if not on the board.
  1026. (defun gomoku-point-square
  1027. {
  1028.   (int x y)
  1029.  
  1030.   (if (and (!= 0 (x (gomoku-point-x)))(!= 0 (y (gomoku-point-y))))
  1031.     (gomoku-xy-to-index x y)
  1032.     0)
  1033. })
  1034.  
  1035.   ;; Move point to square number INDEX.
  1036. (defun gomoku-goto-square (int index)
  1037.   { (gomoku-goto-xy (gomoku-index-to-x index) (gomoku-index-to-y index)) })
  1038.  
  1039.   ;; Move point to square at X, Y coords.
  1040. (defun gomoku-goto-xy (int x y)
  1041. {
  1042.   (goto-line (+ 1 gomoku-y-offset (* gomoku-square-height (- y 1))))
  1043.   (current-column (+ 1 gomoku-x-offset (* gomoku-square-width (- x 1))))
  1044. })
  1045.  
  1046.   ;; Draw 'X', 'O' or '.' on SQUARE (depending on VALUE), leave point there.
  1047. (defun gomoku-plot-square (int square value)
  1048. {
  1049.   (gomoku-goto-square square)
  1050.   (gomoku-put-char (cond (== value 1) "X"
  1051.              (== value 6) "O"
  1052.              TRUE          "."))
  1053.   (update)    ; Display NOW
  1054. })
  1055.  
  1056.   ;; Draw CHAR on the Gomoku screen.
  1057. (defun gomoku-put-char (string char)
  1058. {
  1059.   (insert-text char)
  1060.   (delete-character)
  1061.   (previous-character)
  1062. })
  1063.  
  1064. (const BLANKS "          ")
  1065.  
  1066.   ;; Display an N by M Gomoku board.
  1067. (defun gomoku-init-display (int n m)
  1068. {
  1069.   (int i j)
  1070.   (string row)
  1071.  
  1072.   (clear-buffer)
  1073.     ;; We do not use gomoku-plot-square which would be too slow for
  1074.     ;; initializing the display. Rather we build STRING1 for lines where
  1075.     ;; board squares are to be found, and STRING2 for empty lines. STRING1 is
  1076.     ;; like STRING2 except for dots every DX squares. Empty lines are filled
  1077.     ;; with spaces so that cursor moving up and down remains on the same
  1078.     ;; column.
  1079.   (row (concat (extract-elements BLANKS 0 gomoku-x-offset) "."))
  1080.   (j 0)(while (< (+= j 1) n)
  1081.     (row (concat row 
  1082.     (extract-elements BLANKS 0 (- gomoku-square-width 1)) ".")))
  1083.   (j 0)
  1084.   (arg-prefix gomoku-y-offset)(newline)
  1085.   (while (<= (+= j 1) m)
  1086.   {
  1087.     (insert-text row)
  1088.     (arg-prefix gomoku-square-height)(newline)
  1089.   })
  1090.   (beginning-of-buffer)
  1091.  
  1092.   (gomoku-goto-xy (/ (+ 1 n) 2) (/ (+ 1 m) 2)) ; center of the board
  1093.   (update)                ; Display NOW
  1094. })
  1095.  
  1096.   ;; Obnoxiously display some statistics about previous games in mode line.
  1097. (defun gomoku-display-statistics
  1098. {
  1099.   ;; We store this string in the mode-line-process local variable.
  1100.   ;; This is certainly not the cleanest way out ...
  1101. ;  (setq mode-line-process
  1102. ;    (cond
  1103. ;     ((not (zerop gomoku-number-of-draws))
  1104. ;      (format ": Won %d, lost %d, drew %d"
  1105. ;          gomoku-number-of-wins
  1106. ;          gomoku-number-of-losses
  1107. ;          gomoku-number-of-draws))
  1108. ;     ((not (zerop gomoku-number-of-losses))
  1109. ;      (format ": Won %d, lost %d"
  1110. ;          gomoku-number-of-wins
  1111. ;          gomoku-number-of-losses))
  1112. ;     ((zerop gomoku-number-of-wins)
  1113. ;      "")
  1114. ;     ((= 1 gomoku-number-of-wins)
  1115. ;      ": Already won one")
  1116. ;     (t
  1117. ;      (format ": Won %d in a row"
  1118. ;          gomoku-number-of-wins))))
  1119.   ;; Then a (standard) kludgy line will force update of mode line.
  1120. ;  (set-buffer-modified-p (buffer-modified-p)))
  1121. })
  1122.  
  1123.   ;; Find or create the Gomoku buffer, and display it.
  1124. (defun gomoku-switch-to-window
  1125. {
  1126.   (int b)
  1127.  
  1128.   (if (== (current-buffer) (b (attached-buffer "*Gomoku*"))) (done))
  1129.   (if (!= -2 b)
  1130.     {        ; Buffer exists: no problem.
  1131.       (switch-to-buffer "*Gomoku*")
  1132.     }
  1133.     {
  1134.       (if gomoku-game-in-progress
  1135.      (gomoku-crash-game))        ; Buffer has been killed or something
  1136.       (switch-to-buffer "*Gomoku*")    ; Anyway, start anew.
  1137.       (buffer-flags (attached-buffer "*Gomoku*") BFFoo)
  1138.       (gomoku-mode)
  1139.     }
  1140.   )
  1141. ;  (arg-prefix 1000)(scroll-up)(update)
  1142. })
  1143.  
  1144. ;;;
  1145. ;;; CROSSING WINNING QTUPLES.
  1146. ;;;
  1147.  
  1148. ;; When someone succeeds in filling a qtuple, we draw a line over the five
  1149. ;; corresponding squares. One problem is that the program does not know which
  1150. ;; squares ! It only knows the square where the last move has been played and
  1151. ;; who won. The solution is to scan the board along all four directions.
  1152.  
  1153.   ;; First square of the winning qtuple.
  1154. (int gomoku-winning-qtuple-beg)
  1155.  
  1156.   ;; Last square of the winning qtuple.
  1157. (int gomoku-winning-qtuple-end)
  1158.  
  1159.   ;; Direction of the winning qtuple (along the X axis).
  1160. (int gomoku-winning-qtuple-dx)
  1161.  
  1162.   ;; Direction of the winning qtuple (along the Y axis).
  1163. (int gomoku-winning-qtuple-dy)
  1164.  
  1165.  
  1166.   ;; Return T if SQUARE belongs to a qtuple filled with VALUEs.
  1167. (defun gomoku-find-filled-qtuple (int square value)
  1168. {
  1169.   (or (gomoku-check-filled-qtuple square value 1 0)
  1170.       (gomoku-check-filled-qtuple square value 0 1)
  1171.       (gomoku-check-filled-qtuple square value 1 1)
  1172.       (gomoku-check-filled-qtuple square value -1 1))
  1173. })
  1174.  
  1175.   ;; Return T if SQUARE belongs to a qtuple filled  with VALUEs along DX, DY.
  1176.   ;; And record it in the WINNING-QTUPLE-... variables.
  1177. (defun gomoku-check-filled-qtuple (int square value dx dy)
  1178. {
  1179.   (int a b left right depl a+4)
  1180.  
  1181.   (a 0) (b 0)
  1182.   (left square) (right square)
  1183.   (depl (gomoku-xy-to-index dx dy))
  1184.   (while
  1185.     (and (> a -4)        ; stretch tuple left
  1186.      (== value (gomoku-board (-= left depl))))
  1187.     (-= a 1))
  1188.   (a+4 (+ a 4))
  1189.   (while
  1190.     (and (< b a+4)        ; stretch tuple right
  1191.      (== value (gomoku-board (+= right depl))))
  1192.     (+= b 1))
  1193.   (if (== b a+4)            ; tuple length = 5 ?
  1194.     {
  1195.       (gomoku-winning-qtuple-beg (+ square (* a depl)))
  1196.       (gomoku-winning-qtuple-end (+ square (* b depl)))
  1197.       (gomoku-winning-qtuple-dx dx)
  1198.       (gomoku-winning-qtuple-dy dy)
  1199.       TRUE
  1200.     }
  1201.     FALSE)
  1202. })
  1203.  
  1204.   ;; Cross winning qtuple, as found by gomoku-find-filled-qtuple.
  1205. (defun gomoku-cross-winning-qtuple
  1206. {
  1207.   (gomoku-cross-qtuple gomoku-winning-qtuple-beg
  1208.                gomoku-winning-qtuple-end
  1209.                gomoku-winning-qtuple-dx
  1210.                gomoku-winning-qtuple-dy)
  1211. })
  1212.  
  1213.   ;; Cross every square between SQUARE1 and SQUARE2 in the DX, DY direction.
  1214. (defun gomoku-cross-qtuple (int sq1 square2 dx dy)
  1215. {
  1216.   (int depl n col square1)
  1217.  
  1218.   (square1 sq1)
  1219.   (set-mark)            ; Not moving point from last square
  1220.   (depl (gomoku-xy-to-index dx dy))
  1221.       ;; WARNING: this function assumes DEPL > 0 and SQUARE2 > SQUARE1
  1222.   (while (not (== square1 square2))
  1223.   {
  1224.     (gomoku-goto-square square1)
  1225.     (+= square1 depl)
  1226.     (cond
  1227.       (and (== dx 1) (== dy 0))        ; Horizontal
  1228.       {
  1229.     (n 1)
  1230.     (while (< n gomoku-square-width)
  1231.     {
  1232.       (+= n 1)
  1233.       (next-character)
  1234.       (gomoku-put-char "-")
  1235.     })
  1236.       }
  1237.       (and (== dx 0) (== dy 1))        ; Vertical
  1238.       {
  1239.     (n 1)(col (current-column))
  1240.     (while (< n gomoku-square-height)
  1241.     {
  1242.       (+= n 1)
  1243.       (forward-line 1)
  1244.       (to-col col)
  1245.       (insert-text "|")
  1246.     })
  1247.       }
  1248.       (and (== dx -1) (== dy 1))    ; 1st Diagonal
  1249.       {
  1250.     (arg-prefix (/ gomoku-square-width 2))(previous-character)
  1251.     (col (current-column))
  1252.     (forward-line (/ gomoku-square-height 2))
  1253.     (to-col col)
  1254.     (insert-text "/")
  1255.       }
  1256.       (and (== dx 1) (== dy 1))        ; 2nd Diagonal
  1257.       {
  1258.     (next-character (/ gomoku-square-width 2))
  1259.     (col (current-column))
  1260.     (forward-line (/ gomoku-square-height 2))
  1261.     (to-col col)
  1262.     (insert-text "\\")
  1263.       }
  1264.     )
  1265.   })
  1266.   (swap-marks)
  1267.   (update)                ; Display NOW
  1268. })
  1269.  
  1270. ;;;
  1271. ;;; CURSOR MOTION.
  1272. ;;;
  1273.   ;; Move point backward one column on the Gomoku board.
  1274. (defun gomoku-move-left
  1275. {
  1276.   (int x)
  1277.  
  1278.   (x (gomoku-point-x))
  1279.   (arg-prefix
  1280.     (cond
  1281.       (== 0 x) 1
  1282.       (> x 1) gomoku-square-width
  1283.       TRUE 0
  1284.     ))
  1285.   (previous-character)
  1286. })
  1287.  
  1288.   ;; Move point forward one column on the Gomoku board.
  1289. (defun gomoku-move-right
  1290. {
  1291.   (int x)
  1292.  
  1293.   (x (gomoku-point-x))
  1294.   (arg-prefix
  1295.     (cond
  1296.       (== x 0) 1
  1297.       (< x gomoku-board-width) gomoku-square-width
  1298.       TRUE 0
  1299.     ))
  1300.   (next-character)
  1301. })
  1302.  
  1303.   ;; Move point down one row on the Gomoku board.
  1304. (defun gomoku-move-down
  1305. {
  1306.   (int x y)
  1307.  
  1308.   (y (gomoku-point-y))(x (current-column))
  1309.   
  1310.   (forward-line
  1311.     (cond
  1312.       (== 0 y) 1
  1313.       (< y gomoku-board-height) gomoku-square-height
  1314.       TRUE 0
  1315.     ))
  1316.   (current-column x)
  1317. })
  1318.  
  1319.   ;; Move point up one row on the Gomoku board.
  1320. (defun gomoku-move-up
  1321. {
  1322.   (int x y)
  1323.  
  1324.   (y (gomoku-point-y))(x (current-column))
  1325.  
  1326.   (forward-line
  1327.     (- 0
  1328.       (cond
  1329.         (== 0 y) 1
  1330.     (> y 1) gomoku-square-height
  1331.     TRUE 0
  1332.       )))
  1333.   (current-column x)
  1334. })
  1335.  
  1336.   ;; Move point North East on the Gomoku board.
  1337. (defun gomoku-move-ne { (gomoku-move-up) (gomoku-move-right) })
  1338.  
  1339.   ;; Move point South East on the Gomoku board.
  1340. (defun gomoku-move-se { (gomoku-move-down) (gomoku-move-right) })
  1341.  
  1342.   ;; Move point North West on the Gomoku board.
  1343. (defun gomoku-move-nw { (gomoku-move-up) (gomoku-move-left) })
  1344.  
  1345.   ;; Move point South West on the Gomoku board.
  1346. (defun gomoku-move-sw { (gomoku-move-down) (gomoku-move-left) })
  1347.