; Tic-Tac-Toe

(define result #f)

(define (init-board)
	(cls) (set-pattern #t)
	(move 25 0) (rect 28 81 0)
	(move 52 0) (rect 55 81 0)
	(move 0 25) (rect 81 28 0)
	(move 0 52) (rect 81 55 0)
	(list 0 0 0 0 0 0 0 0 0))

(define (mark who where)
   (let ((x (* 27 
		(remainder where 3)))
	(y (* 27
		(quotient where 3))))
  (cond (who ; Human: draw O
	(move (+ x 2) (+ y 2))
	(set-pattern #t)
	(rect (+ x 24) (+ y 24) 11)
	(move (+ x 4) (+ y 4))
	(set-pattern #f)
	(rect (+ x 22) (+ y 22) 9))
    (else ; Pilot: draw X
	(set-pattern #t)
	(move (+ x 2) (+ y 2))
	(draw (+ x 23) (+ y 23))
	(move (+ x 3) (+ y 2))
	(draw (+ x 24) (+ y 23))
	(move (+ x 23) (+ y 2))
	(draw (+ x 2) (+ y 23))
	(move (+ x 23) (+ y 3))
	(draw (+ x 2) (+ y 24))))))

;;; get user's move
(define (human bo)
  (move 90 36) 
  (text "Tap a square") 
  (let ((p (wait-pen)))
    (let ((x (car p)) 
	    (y (cdr p)))
      (if (> x 81) 
	(result "Player resigns")
	(let ((mv (+ (quotient x 27)
		(* 3 (quotient y 27)))))
	(cond ((memq mv 
			(poss-moves bo))
	  	(mark #t mv)
		(do-move 1 mv bo))
	(else (human bo))))))))

(define (index val)
  (lambda (bo)
   (letrec ((pm (lambda (b m x)
     (cond ((null? b) m)
	((eq? (car b) val)
	    (pm (cdr b) (cons x m)
		    (+ x 1)))
	(else (pm (cdr b) m (+ x 1))))
	)))
  (pm bo '() 0))))

(define (poss-moves bo) 
	((index 0) bo)) 

(define (do-move who where board)
  (if (eq? where 0)
	(cons who (cdr board))
	(cons (car board)
	   (do-move who (- where 1) 
		(cdr board)))))

(define (subset s1 s2)
   (cond ((null? s1) #t)
	((null? s2) #f)
	(else (let
		((d (- (car s1) (car s2))))
	   (cond ((> d 0) #f)
		((< d 0)
		   (subset s1 (cdr s2)))
		(else
		   (subset (cdr s1) (cdr s2))
))))))

(define (evaluate bo)
  (letrec (
	(any-subset (lambda (s ls)
	  (if (null? ls) #f
	     (or (subset  (car ls) s)
		(any-subset s (cdr ls))))))
	(winner (lambda (ps)
	  (any-subset ps 
		'((2 1 0) (5 4 3) (8 7 6) 
		(6 3 0) (7 4 1) (8 5 2)
		(8 4 0) (6 4 2)))))
	(crs (lambda (l1 l2)
		(if (null? l1) 0
		(+ (* (car l1) (car l2))
		  (crs (cdr l1) (cdr l2)))))))
  (let ((me ((index -1) bo))
	(you ((index 1) bo)))
	(cond ((and (>= (length you) 3)
			 (winner you)) 1000)
		((and (>= (length me) 3)
			(winner me)) -1000)
	(else (crs bo '(2 1 2 1 5 1 2 1 2)))
))))

(define (extend who) 
  (lambda (bo)
    (map (lambda (m)
	(let ((nb (do-move who m bo)))
	  (list nb m (evaluate nb))))
	(poss-moves bo))))

(define (find p l)
  (letrec ((f (lambda (l m)
    (cond ((null? l)  m)
	((p m (car l))
		(f (cdr l) m))
	(else (f (cdr l) (car l)))))))
  (f (cdr l) (car l))))
 
(define (not-loose p)
  (or (eq? (caddr p) -1000)
    (not (lost ((extend 1) (car p))))))

(define (lost ps)
  (if (null? ps) #f
	(or (eq? (caddr (car ps)) 1000)
		(lost (cdr ps)))))

(define (stupid bo)
   (let ((moves (poss-moves bo)))
      (list-ref moves
	(random (length moves)))))

(define (static bo)
  (cadr (find (lambda (a b)
		(< (caddr a) (caddr b)))
	((extend -1) bo))))

(define (smart bo)
  (let ((ps (filter not-loose
		 ((extend -1) bo))))
    (if (null? ps)
	(result "Pilot resigns")
	(cadr (find (lambda (a b)
	     (< (caddr a) (caddr b)))
	ps)))))

(define (pilot algo)
  (lambda (bo)
    (move 90 36)
    (set-pattern #t) 
    (text "I'm thinking...")
    (let ((mv (algo bo)))
 	(mark #f mv)
	(do-move -1 mv bo))))

(define (ttt who algo)
  (letrec ((go (lambda (bo who)
    (let ((new
      ((if who human (pilot algo)) bo)))
      (let ((v (evaluate new)))
	(cond ((eq? v 1000) 
			(result "You win!"))
		((eq? v -1000)
			(result "Pilot wins!"))
		((null? (poss-moves new))
			(result "Draw!"))
		(else (go new (not who))))
    )))))
  (own-gui #t)
  (message
    (call/cc (lambda (k)
	(set! result k)
	(go  (init-board) who))))
  (own-gui #f)))
