-
Notifications
You must be signed in to change notification settings - Fork 0
/
tictactoe.org~
351 lines (314 loc) · 10.9 KB
/
tictactoe.org~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
;;; ticTacToe.el
;;; A program to play the game Tic Tac Toe.
;;; COPYRIGHT 2017 ben rudgers
;;; LICENSE GPL version 3 or later
;;;
;;; INSTRUCTIONS
;;; 1. Open this file in emacs
;;; 2. M-x ielm to start IELM
;;; 3. C-x b RET to return to source file
;;; 4. M-x eval-buffer to evaluate source file
;;; 5. C-x 3 to split windows
;;; 6. C-x b RET to put IELM in window
;;; 7. (setup) at the ELISP> prompt
;;; 8. C-x o to return to the code window
;;; 9. C-x b tictactoe to bring up the display window
;;; 10. C-x o to return to the IELM window
;;; 11. (tictactoe-play) at ELISP> prompt to play game
;;;
;;; GAME DESIGN BACKGROUND
;;; Adpated from *Artificial Intelligence: A Modern Approach*, Russell
;;; and Norvig, 1995. [pages 123-124]
;;;
;;; A game consists of:
;;;
;;; * |Initial State| which consists of a board position and an indication
;;; of whose move it is.
;;; * A set of |Terminal States|.
;;; * A set of |Operators| which define the legal moves a player can make.
;;; * A |Terminal Test| which determines when the game is over.
;;; * A |Utility Function| which assigns a numeric value to each Terminal
;;; State of the game.
;;;
;;; INTERFACE DESIGN BACKGROUND
;;; Gameplay is displayed in a new buffer named "tictactoe", but
;;; commands are entered via the mini-buffer in the IELM window.
;;; The overall exercise included learning a bit more about Emacs
;;; in general, and Emacs Lisp in particular. The implementaion
;;; reflects the fact that this is the first time trying to write
;;; code for (rather than in) the Emacs environment
;;; and proves that "How hard could it be?"
;;; is not often a question that produces the expected answer.
;;; The tutorial: Emacs Lisp Animanation
;;; http://dantorop.info/project/emacs-animation/ was invaluable.
;;; IMPORTS
(require 'cl)
;;; INITIAL STATE
(defconst board-size 9
"A BOARD consists of 9 squares.
A board's squares are arranged in a 3x3 grid.
0 | 1 | 2
-----------
3 | 4 | 5
-----------
6 | 7 | 8
Each square has a contents. A SQUARE contents is one of:
empty-square | player-1-square | player-2-square.")
(defconst empty-square 0
"A zero value represents an empty (unmarked) square.")
(defconst player-1-square 1
"The value 1 represents a square marked by player-1.")
(defconst player-2-square -1
"The value -1 represents a square marked by player-2")
(defun make-empty-board ()
"Returns an empty tictactoe board.
Example: () -> (0 0 0 0 0 0 0 0 0)"
(make-list board-size empty-square))
;;; Board -> List(List : Squares)
(defun get-rows (board)
"Returns a list of board rows represented as lists of their square's contents.
Example: (1 0 -1 0 1 -1 0 0 1) -> ((1 0 -1)(0 1 -1)(0 0 1))"
(list
(list (nth 0 board)
(nth 1 board)
(nth 2 board))
(list (nth 3 board)
(nth 4 board)
(nth 5 board))
(list (nth 6 board)
(nth 7 board)
(nth 8 board))))
;;; Board -> List(List : Squares)
(defun get-columns (board)
"Returns a list of board columns represented as lists of their square's contents.
Example: (1 0 -1 0 1 -1 0 0 1) -> ((1 0 0)(0 1 0)(-1 -1 1))"
(list
(list (nth 0 board)
(nth 3 board)
(nth 6 board))
(list (nth 1 board)
(nth 4 board)
(nth 7 board))
(list (nth 2 board)
(nth 5 board)
(nth 8 board))))
;;; Board -> List(List : Squares)
(defun get-diagonals (board)
"Returns a list of board diagnonals represented as lists of their square's contents.
Example: (1 0 -1 0 1 -1 0 0 1) -> ((1 1 1)(-1 1 0))"
(list
(list (nth 0 board)
(nth 4 board)
(nth 8 board))
(list (nth 2 board)
(nth 4 board)
(nth 6 board))))
;;; A PLAYER is one of
;;; player-1 | player-2
(defconst player-1 #'(lambda (square) (= square player-1-square))
"Player-1 is a function that returns true for squares marked by player-1")
(defconst player-2 #'(lambda (square) (= square player-2-square))
"Player-1 is a function that returns true for squares marked by player-2")
;;; TERMINAL STATES
;;; A finished game is one of:
;;; drawn-game | player-1-wins | player-2-wins
(defun map-player-squares (player get-squares board)
"A utility function. Given a board representation, maps true to the squares marked by a player.
Player (Board -> List(List : Squares)) Board -> List(List : Boolean)
Example:
(map-player-squares player-1
#'get-diagonals
'(1 0 -1 0 1 -1 0 0 1))
-> ((t t t)
(nil t nil))
"
(mapcar #'(lambda (x)
(mapcar player x))
(funcall get-squares board)))
(defun winning-squares (map)
"A utility function. Given a mapping of true to a player's squares over a board representation returns true if there is a winning condtion.
List(List : Boolean) -> List Boolean
Example:
(winning-squares '((t t t)(nil t nil))) -> t"
(mapcar #'(lambda (list)
(every #'identity list))map))
;;; Player Board -> Boolean
(defun winner-p (player board)
"Returns true if the player has won.
Example: (winner-p player-1 '(1 0 -1 0 1 -1 0 0 1)) -> t"
(let
((rows
(map-player-squares player
#'get-rows
board))
(columns
(map-player-squares player
#'get-columns
board))
(diagonals
(map-player-squares player
#'get-diagonals
board)))
(or (some #'identity
(winning-squares rows))
(some #'identity
(winning-squares columns))
(some #'identity
(winning-squares diagonals)))))
;;; Board -> Boolean
(defun all-squares-filled-p (board)
"Utility Function. Returns true if no squares are empty.
Example: (all-squares-filled-p '(1 0 -1 0 1 -1 0 0 1))) -> nil"
(not (some #'zerop board)))
;;; TERMINAL TEST
(defun game-over-p (board)
"Example: (game-over-p '(1 0 -1 0 1 -1 0 0 1))) -> 'player-one-wins
Example: (game-over-p (make-empty-board)) -> nil"
(cond
((winner-p player-1 board) 'player-1-wins)
((winner-p player-2 board) 'player-2-wins)
((all-squares-filled-p board) 'draw)))
;;; OPERATORS
(defun player-1-choose-square (board)
"Board -> Board"
(insert "Status: It is Player-1's turn\n")
(let* ((empty-squares (find-empty-squares board))
(message (concat "Player-1 choose square: "
(prin1-to-string empty-squares)
" : "))
(choice (read-string message)))
(setf (nth (read choice) board) player-1-square))
board)
(defun player-2-choose-square (board)
"Board -> Board"
(insert "Status: It is Player-2's turn\n")
(let ((empty-squares (find-empty-squares board)))
(setf
(nth (ttt:simple-reflex-agent board) board)
player-2-square))
board)
(defun find-empty-squares (board)
"Utility function. Returns a list of indexes to a board's empty squares.
Board -> List:number[0-8]
Example: (find-empty-squares (make-empty-board))
-> (0 1 2 3 4 5 6 7 8)
Example: (find-empty-squares '(1 0 -1 0 1 -1 0 0 1)
-> (1 3 6 7)"
(let ((i 0)
(acc))
(dolist (element board acc)
(if (= 0 (nth i board))
(push i acc))
(setq i (+ i 1)))
(reverse acc)))
;;; GAME LOOP
(defun tictactoe-play ()
(let ((game-outcome (tictactoe-main (make-empty-board))))
(cond
((eq game-outcome 'player-1-wins)
(insert "Game Over: Player-1 Wins"))
((eq game-outcome 'player-2-wins)
(insert "Game Over: Player-2 Wins"))
((eq game-outcome 'draw)
(insert "Game Over: It is a draw")))
game-outcome))
(defun tictactoe-main (board)
(board->text board)
(if (game-over-p board)
(game-over-p board)
(let
((board-sum (apply #'+ board)))
(cond
((= board-sum 0)
(tictactoe-main (player-1-choose-square board)))
((= board-sum 1)
(tictactoe-main (player-2-choose-square board)))))))
;;; USER INTERFACE
(defun square->text (square index)
"Utility function. Converts a square to the correct text value."
(cond
((eq square -1) " o ")
((eq square 1) " x ")
(t (concat " " (prin1-to-string index) " "))))
(defun row->text (row i)
"Utility function. Converts board row to its text representation"
(concat
(square->text (nth 0 row) i)
"|"
(square->text (nth 1 row) (+ i 1))
"|"
(square->text (nth 2 row) (+ i 2))))
(defun board->text (board)
"Utility function. Converts a board to its text representation."
(let* ((brd (get-rows board))
(separator "\n-----------\n")
(row1 (row->text (nth 0 brd) 0))
(row2 (row->text (nth 1 brd) 3))
(row3 (row->text (nth 2 brd) 6)))
(erase-buffer)
(insert "Playing TicTacToe\n\n")
(insert row1)
(insert separator)
(insert row2)
(insert separator)
(insert row3)
(insert "\n\n")))
(defun setup ()
(get-buffer-create "tictactoe")
(set-buffer "tictactoe"))
;;; AGENTS
(defun ttt:simple-reflex-agent (board)
"precept -> action
A precept is a board.
An action is the label of a square.
The agent prefers winning squares over other squares.
The agent prefers blocking squares over other squares.
The agent prefers the center square over other squares.
The agent prefers corner squares over other squares."
(let* ((precept (find-empty-squares board))
(*player* player-2)
(*other-player* player-1)
(*player-square* player-2-square)
(*other-player-square* player-1-square)
(expansion (expand board precept *player-square*))
(winner (find-winner expansion precept *player*))
(center 4)
(corner0 0)
(corner2 2)
(corner6 6)
(corner8 8))
(if winner
winner
(let*
((other-expansion (expand board
precept
*other-player-square*))
(block (find-winner other-expansion
precept
*other-player*)))
(cond
(block block)
((memq center precept) center)
((memq corner0 precept) corner0)
((memq corner2 precept) corner2)
((memq corner6 precept) corner6)
((memq corner8 precept) corner8)
(t (first precept)))))))
(defun expand (board empty-squares player-square)
(let ((new-board (copy-list board)))
(cond
((null empty-squares) nil)
(t (setf (nth (first empty-squares) new-board)
player-square)
(cons new-board
(expand board
(rest empty-squares)
player-square))))))
(defun find-winner (expansion empty-squares player)
(cond
((null expansion) nil)
((winner-p player (first expansion))
(first empty-squares))
(t (find-winner (rest expansion)
(rest empty-squares)
player))))