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