(module utils mzscheme
  ; See boardsig.ss for the core utilities.
  (require "board-size.ss"
           "board.ss")
  
  (provide
   n-times      ; call a procedure with each number in [0, n]
   n-map        ; call a procedure with each number in [0, n] to make a list
   print-board  ; pretty-prints a board
   find-winner  ; checks a board to see if there's a winner
   other-player ; x -> o   or  o -> x

   pick-best    ; takes a list of (cons <num> <val>) and returns pair
                ;  with the biggest <num>, randomly chossing among
                ;  equals
   )
   
  ; Call f with each number in [0, n]
  (define (n-times n f)
    (let loop ([i 0])
      (unless (= i n)
        (f i)
        (loop (add1 i)))))
  
  ; Call f with each number in [0, n] to make a list
  (define (n-map n f)
    (let loop ([i 0])
      (if (= i n)
          null
          (cons (f i)
                (loop (add1 i))))))
  
  
  ; Pretty-prints the board
  (define print-board
    (case-lambda
      [(b) (print-board b (current-output-port))]
      [(b port)
       (let ([n (current-board-size)])
         (n-times n (lambda (j)
                      (n-times n (lambda (i)
                                   (fprintf port "~a " (let ([v (board-cell b i j)])
                                                         (cond
                                                           [(eq? v none) '-]
                                                           [(eq? v x) 'x]
                                                           [(eq? v o) 'o])))))
                      (newline port))))]))
  
  ; Given a player (board cell value), get the other one
  (define (other-player as-player)
    (if (eq? as-player x)
        o
        x))
  
  ; See if the board has a winner; returns o, x, or #f
  (define (find-winner board)
    (let ([n (current-board-size)])
      (let ([row-wins-x (make-vector n 1)]
            [row-wins-o (make-vector n 1)]
            [col-wins-x (make-vector n 1)]
            [col-wins-o (make-vector n 1)])
        (n-map n (lambda (i)
                   (n-map n (lambda (j)
                              (let ([v (board-cell board i j)])
                                (cond
                                  [(eq? v x) (vector-set! col-wins-o i 0)
                                   (vector-set! row-wins-o j 0)]
                                  [(eq? v o) (vector-set! col-wins-x i 0)
                                   (vector-set! row-wins-x j 0)]
                                  [else (vector-set! col-wins-o i 0)
                                        (vector-set! row-wins-o j 0)
                                        (vector-set! col-wins-x i 0)
                                        (vector-set! row-wins-x j 0)]))))))
        (let ([o-wins (+ (apply + (vector->list row-wins-o))
                         (apply + (vector->list col-wins-o)))]
              [x-wins (+ (apply + (vector->list row-wins-x))
                         (apply + (vector->list col-wins-x)))])
          (cond
            [(= o-wins x-wins) #f]
            [(> o-wins x-wins) o]
            [else x])))))

  ; Compare the values in goodness and pick the biggest element.
  ; Takes a non-empty list of (cons <goodness> <choice>)
  ; If <goodness> is a pair, look at the car.
  ; Return (cons <goodness> <choice>).
  (define (pick-best choices)
    (let loop ([l (cdr choices)]
               [goodness (let ([g (caar choices)])
                           (if (pair? g)
                               (car g)
                               g))]
               [result (car choices)])
      (if (null? l)
          result
          (let ([v (let ([v (caar l)])
                     (if (pair? v)
                         (car v)
                         v))])
            (cond
	      [(> v goodness) (loop (cdr l) v (car l))]
	      [(and (= v goodness)
		    ; pick randomly
		    (zero? (random 2)))
	       (loop (cdr l) v (car l))]
	      [else (loop (cdr l) goodness result)]))))))
