############################################################################
#
#	File:     snake.icn
#
#	Subject:  Program to play the snake game
#
#	Author:   Richard L. Goerwitz
#
#	Date:     March 26, 2002
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Version:  1.9
#
############################################################################
#  
#      While away the idle moments watching the snake eat blank squares
#  on your screen.  Snake has only one (optional) argument -
#
#      usage:  snake [character]
#
#  where "character" represents a single character to be used in drawing
#  the snake.  The default is an "o."  In order to run snake, your ter-
#  minal must have cursor movement capability, and must be able to do re-
#  verse video.
#
#      I wrote this program to test itlib.icn, iscreen.icn, and some
#  miscellaneous utilities I wrote.  It clears the screen, moves the cur-
#  sor to arbitrary squares on the screen, changes video mode, and in
#  general exercises the terminal capability database on the target ma-
#  chine.
#
############################################################################
#
#  Bugs:  Most magic cookie terminals just won't work.  Terminal really
#  needs reverse video (it will work without, but won't look as cute).
#
############################################################################
#
#  Requires:  UNIX (MS-DOS is okay, if you replace itlib with itlibdos.icn)
#
############################################################################
#
#  Links: itlib, iscreen, random
#
############################################################################

link itlib
link iscreen
link random

global max_l, max_w, snake_char

record wholething(poop,body)

procedure main(a)

    local snake, limit, sl, sw, CM, x, r, leftbehind

    randomize()

    if not (getval("so"),  CM := getval("cm"))
    then stop("snake:  Your terminal is too stupid to run me.  Sorry.")
    clear(); Kludge() # if your term likes it, use emphasize(); clear()
    # Decide how much space we have to operate in.
    max_l := getval("li")-2             # global
    max_w := getval("co")-1             # global
    # Determine the character that will be used to represent the snake.
    snake_char := (\a[1])[1] | "o"

    # Make the head.
    snake := []; put(snake,[?(max_l-1)+1, ?(max_w-1)+1])
    # Make the body, displaying it as it grows.
    every x := 2 to 25 do {
	display(,snake)
	put(snake,findnext(snake[x-1],snake))
    }

    # Begin "eating" all the standout mode spaces on the screen.
    repeat {
	r := makenew(snake)
	leftbehind := r.poop
	snake := r.body
	display(leftbehind,snake) | break
    }

    # Shrink the snake down to nothing, displaying successively smaller bits.
    while leftbehind := get(snake)
    do display(leftbehind,snake)

    iputs(igoto(getval("cm"), 1, getval("li")-1))
    normal()
    
end 



procedure findnext(L, snake)

    local i, j, k, op, l
    static sub_lists
    initial {
	sub_lists := [[1,2,3], [1,3,2], [3,2,1], [3,1,2], [2,1,3], [2,3,1]]
    }
    # global max_l, max_w

    i := L[1]; j := L[2]	# for clarity, use i, j (not l[i|j])

    # L is the last snake segment; find k and l, such that k and l are
    # valid line and column numbers differing from l[1] and l[2] by no
    # more than 1, respectively.  Put simply:  Create a new segment
    # [k, l] adjacent to the last one (L).

    op := (different | Null) &
    (k := max_l+1 > [i,i+1,i-1][!sub_lists[?6]]) > 1 &
	(l := max_w+1 > [j,j+1,j-1][!sub_lists[?6]]) > 1 &
	op([k, l], snake)

    return [k, l]

end



procedure different(l,snake)

    local bit
    (l[1] = (bit := !\snake)[1], l[2] = bit[2]) & fail
    return

end



procedure Null(a[])
    return
end



procedure display(lb,snake)

    local last_segment, character
    static CM
    initial CM := getval("cm")

    # Change the mode of the square just "vacated" by the moving snake.
    if *snake = 0 | different(\lb,snake) then {
	iputs(igoto(CM, lb[2], lb[1]))
	normal()
	writes(" ")
    }

    if last_segment := (0 ~= *snake) then {
	# Write the last segment (which turns out to be the snakes head!).
	iputs(igoto(CM, snake[last_segment][2], snake[last_segment][1]))
	emphasize(); writes(snake_char)  # snake_char is global
    }

    # Check to see whether we've eaten every edible square on the screen.
    if done_yet(lb)
    then fail
    else return

end



procedure makenew(snake)
    local leftbehind, i

    # Move each constituent list up one position in snake, discard
    # the first element, and tack a new one onto the end.

    every i := 1 to *snake - 1 do
	snake[i] :=: snake[i+1]
    leftbehind := copy(snake[i+1])
    snake[i+1] := findnext(snake[i],snake)
    return wholething(leftbehind,snake)
    
end



procedure the_same(l1, l2)

    if l1[1] = l2[1] & l1[2] = l2[2]
    then return else fail

end



procedure done_yet(l)
    local i, j

    # Check to see if we've eaten every edible square on the screen.
    # It's easy for snake to screw up on this one, since somewhere
    # along the line most terminal/driver/line combinations will con-
    # spire to drop a character somewhere along the line.

    static square_set
    initial {

	square_set := set()
	every i := 2 to max_l do {
	    every j := 2 to max_w do {
		insert(square_set, i*j)
	    }
	}
    }

    /l & fail
    delete(square_set, l[1]*l[2])
    if *square_set = 0 then return
    else fail

end



procedure Kludge()
    local i

    # Horrible way of clearing the screen to all reverse-video, but
    # the only apparent way we can do it "portably" using the termcap
    # capability database.

    iputs(igoto(getval("cm"),1,1))
    if getval("am") then {
	emphasize()
        every 1 to (getval("li")-1) * getval("co") do
	    writes(" ")
    }
    else {
	every i := 1 to getval("li")-1 do {
	    iputs(igoto(getval("cm"), 1, i))
	    emphasize()
	    writes(repl(" ",getval("co")))
	}
    }
    iputs(igoto(getval("cm"),1,1))

end
