;; prisond.scm: iterated prisoner's dilemma simulation
;; by matt
;; this program is in the public domain

(define strategies '())

(define add-strategy
  (lambda (x y)
    (set! strategies (cons (cons x y) strategies))))

(define angel
  (lambda (x y) 0))

(define devil
  (lambda (x y) 1))

(define tat-for-tit
  (lambda (x y)
    (if (null? x)
        1
        (if (= (car x) 0)
            1
            0))))

(define tit-for-tat
  (lambda (x y)
    (if (null? x)
        0
        (car x))))

(define time-machine
  (lambda (x y)
    (if (> 3 (length x))
        0
        (caddr x))))

(define random-choice
  (lambda (x y)
    (random 2)))

(define grudger
  (lambda (x y)
    (if (memq 1 x)
        1
        0)))

(define angry-tit-for-tat
  (lambda (x y)
    (if (null? x)
        1
        (car x))))

(define apl
  (lambda (x y)
    (if (null? x)
        1
        (if (= ( car y) 0)
            1
            0))))

(define forgiving-grudge
  (lambda (x y) (let* (
    (defection-count (length (filter (lambda (m) (= m 1)) x)))
    (result (if (> defection-count 3) 1 0))
  ) result)))

(define (take n xs)
  (let loop ((n n) (xs xs) (zs (list)))
    (if (or (zero? n) (null? xs))
        (reverse zs)
        (loop (- n 1) (cdr xs)
              (cons (car xs) zs)))))

(define (zip . xss) (apply map list xss))

(define actually-forgiving-grudge
  (lambda (x y) (let* (
    (defection-count (length (filter (lambda (m) (= m 1)) x)))
    ; TODO: tune 1.8 there, or maybe entirely switch out formula
    (lookback (+ 1 (inexact->exact (floor (expt 1.8 defection-count)))))
    ; should maybe be (zip (append x '(-1)) (cons -1 y)) - current version compares actions at the same time, but it may not be "betrayal" unless the defecting by the opponent comes AFTER our cooperating
    ; this does seem to worsen its score so maybe not
    (result (if (member '(1 0) (take lookback (zip x y))) 1 0))
  ) result)))

(define apiomemetics
  (lambda (x y) (random-seed 334278294) ; NOTE TO SELF: 3227883998 (0/2) or 2324865786 (48/50) 
    (if (null? x)
        (begin 0)
        (if (> (length x) 93)
            1
            (car x)))))

(define meapiometics
  (lambda (x y)
    (if (null? x)
        0
        (if (> (length x) 97)
            1
            (car x)))))

(define prisond
  (lambda (x y)
    (if (= x y)
        (if (= x 1)
            '(-2 -2)
            '(-1 -1))
        (if (= x 1)
            '(0 -3)
            '(-3 0)))))

(define iter-prisond
  (lambda (x y z)
    (define scores '(0 0))
    (define moves-x '())
    (define moves-y '())
    (define current-moves '())
    (define helper
      (lambda (x y z)
        (if (= z 0)
            scores
            (begin
              (set! current-moves (list (x moves-x moves-y) (y moves-y moves-x)))
              (set! moves-x (cons (cadr current-moves) moves-x))
              (set! moves-y (cons (car current-moves) moves-y))
              (set! scores (map + scores (prisond (car current-moves) (cadr current-moves))))
              (helper x y (- z 1)))))) (helper x y z)))

(define get-strategy-scores
  (lambda (x)
    (define score 0)
    (define iters (+ 100 (random 50)))
    (define helper
      (lambda (y)
        (if (eqv? (car x) (car y))
            0
            (set! score (+ score (car (iter-prisond (cdr x) (cdr y) iters)))))))
    (map helper strategies)
    score))

(define get-repeated-score
  (lambda (strategy accumulator counter)
    (if (= counter 0) accumulator 
      (get-repeated-score strategy 
        (+ accumulator (get-strategy-scores strategy))
        (- counter 1)))))

(define get-all-scores
  (lambda ()
    (define helper
      (lambda (x)
        (write (list (car x) (get-repeated-score x 0 50)))
        (newline)))
    (map helper strategies)))

(add-strategy 'angel angel)
(add-strategy 'devil devil)
(add-strategy 'tit-for-tat tit-for-tat)
(add-strategy 'tat-for-tit tat-for-tit)
(add-strategy 'time-machine time-machine)
(add-strategy 'random-choice random-choice)
(add-strategy 'grudger grudger)
(add-strategy 'angry-tit-for-tat angry-tit-for-tat)
(add-strategy 'apl apl)
(add-strategy 'meapiometics meapiometics)
(add-strategy 'apiomemetics apiomemetics)
(add-strategy 'forgiving-grudge forgiving-grudge)
(add-strategy 'actually-forgiving-grudge actually-forgiving-grudge)

(random-seed (time-second (current-time)))

(get-all-scores)
(exit)
