############################################################################
#
#	File:     bj.icn
#
#	Subject:  Program to play blackjack game
#
#	Author:   Chris Tenaglia (modified by Richard L. Goerwitz)
#
#	Date:     December 12, 1995
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Version:  1.7
#
############################################################################
#  
#  Simple but fun blackjack game.  The original version was for an ANSI
#  screen.  This version has been modified to work with the UNIX termcap
#  database file.
#
############################################################################
#
#  Links: itlib, random
#
#  Requires: UNIX
#
############################################################################

link itlib
link random

global deck, message, lookup,
       user_money,  host_money,
       user_hand,   host_hand

procedure main(param)
  local bonus, user_points, host_points
  user_money := integer(param[1]) | 3 ; host_money := user_money
  write(screen("cls"))
#  Most terminals don't do oversize characters like this.
#  write(screen("cls"),"               ",screen("top"),screen("hinv"),
#        "BLACK JACK",screen("norm"))
#  write("               ",screen("bot"),screen("hinv"),
#        "BLACK JACK",screen("norm"))
  write(screen("high"),"  ---- BLACK JACK ----",screen("norm"))
  bonus := 0
  repeat
    {
    if not any('y',(map(input(at(1,3) || "  " || screen("under") ||
                   "Play a game? y/n :"|| screen("norm") || " " ||
                   screen("eeol")))[1])) then break
    every writes(at(1,3|4),screen("eeos"))
    display_score()
    deck    := xshuffle()
    message := ""
    user_hand := []          ; host_hand := []
    put(user_hand,pop(deck)) ; put(host_hand,pop(deck))
    put(user_hand,pop(deck)) ; put(host_hand,pop(deck))
    user_points := first(host_hand[1])
    if user_points > 21 then
      {
      writes(at(1,13),user_points," points. You went over. You lose.")
      user_money -:= 1 ; host_money +:= 1 + bonus ; bonus := 0
      display_score()
      next
      }
    display_host(2)
    host_points := second(user_points)
    if host_points > 21 then
      {
      writes(at(48,22), right(host_points || " points. " || 
        (&host ? tab(find(" ")|0)) || " went over.", 28))
      writes(at(1,13),screen("hiblink"),"You win.",screen("norm"))
      host_money -:= 1 ; user_money +:= 1 + bonus ; bonus := 0
      display_score()
      next
      }
    if host_points = user_points then
      {
      writes(at(1,22),screen("hiblink"),"It's a draw at ",user_points,
            ". The ANTY goes to bonus.",screen("norm"))
      bonus +:= 2 ; host_money -:= 1 ; user_money -:= 1
      display_score()
      next
      }
    writes(at(1,12),user_points," points for user.")
    writes(at(1,14),host_points," points for ",&host ? tab(find(" ")|0))
    if user_points < host_points then
      {
      write(at(1,22),screen("hiblink"),&host ? tab(find(" ")|0)," wins.",
            screen("norm"),screen("eeol"))
      user_money -:= 1 ; host_money +:= 1 + bonus ; bonus := 0
      display_score()
      next
      } else {
      writes(at(1,12),screen("hiblink"),"You win.",screen("norm"),
	     screen("eeol"))
      user_money +:= 1 + bonus ; host_money -:= 1 ; bonus := 0
      display_score()
      next
      }
    }
  write(screen("clear"))
  end

#
# THIS PROCEDURE ALLOWS THE USER TO PLAY AND TAKE HITS
#
procedure first(host_card)
  local points

  display_user()
  display_host(1)
  points := value(user_hand)   # just in case
  writes(at(1,9),"(",points,") ")
  repeat
    if any('hy',map(input(at(1,23) || "Hit ? y/n : " || screen("eeol")))) then
      {
      put(user_hand,pop(deck))
      display_user()
      if (points := value(user_hand)) > 21 then return points
      writes(at(1,9),"(",points,") ")
      } else break
  (points > 0) | (points := value(user_hand))
  writes(at(1,9),"(",points,") ")
  write(at(55,11),right("You stay with "||points,20))
  return points
  end

#
# THIS SECOND PROCEDURE IS THE HOST PLAYING AGAINST THE USER
#
procedure second(ceiling)
  local stop_at, points

  static limits
  initial limits := [14,14,15,15,19,16,17,18]
  stop_at := ?limits ; points := 0
  until (points := value(host_hand)) > stop_at do
    {
    if points > ceiling then return points
    writes(at(1,19),"(",points,") ")
#    write(at(1,22),screen("eeol"),&host," will take a hit.",screen("eeol"))
    write(at(1,22),screen("eeol"),&host ? tab(find(" ")|0),
	  " will take a hit.",screen("eeol"))
    put(host_hand,pop(deck))
    display_host(2)
    }
  (points > 0) | (points := value(host_hand))
  writes(at(1,19),"(",points,") ")
  return points
  end

#
# THIS ROUTINE DISPLAYS THE CURRENT SCORE
#
procedure display_score()
  writes(screen("nocursor"))
  writes(screen("dim"),at(1,7),"Credits",screen("norm"))
  writes(screen("high"),at(1,8),right(user_money,7),screen("norm"))
  writes(screen("dim"),at(1,17),"Credits",screen("norm"))
  writes(screen("high"),at(1,18),right(host_money,7),screen("norm"))
  end
#
# THIS PROCEDURE EVALUATES THE POINTS OF A HAND. IT TRIES TO MAKE THEM
# AS HIGH AS POSSIBLE WITHOUT GOING OVER 21.
#
procedure value(sample)
  local hand, possible, sum, card, i, best_score, gone_over_score, score

  hand     := copy(sample)
  possible := []
  repeat
    {
    sum := 0
    every card := !hand do sum +:= lookup[card[1]]
    put(possible,sum)
    if not ("A" == (!hand)[1]) then break else
      every i := 1 to *hand do {
        if hand[i][1] == "A" then {
          hand[i][1] := "a"
          break
        }  
      }
    }
  best_score := 0
  gone_over_score := 100
  every score := !possible do {
    if score > 21
    then gone_over_score >:= score
    else best_score <:= score
  }
  return (0 ~= best_score) | gone_over_score
  end

#
# THIS ROUTINE DISPLAYS THE USER HAND AND STATUS
#
procedure display_user()
  local x, y, card

  writes(screen("nocursor"),at(1,6),screen("hinv"),"USER",screen("norm"))
  x := 10 ; y := 4
  every card := !user_hand do
    {
    display(card,x,y)
    x +:= 7
    }
  end

#
# THIS ROUTINE DISPLAYS THE HOST HAND AND STATUS
#
procedure display_host(flag)
  local x, y, card

  writes(screen("nocursor"),at(1,16),screen("hinv"),
	 &host ? tab(find(" ")|0),screen("norm"))
  x := 10 ; y := 14 ; /flag := 0
  every card := !host_hand do
    {
    if (flag=1) & (x=10) then card := "XX"
    display(card,x,y)
    x +:= 7
    }
  end

#
# THIS ROUTINE DISPLAYS A GIVEN CARD AT A GIVEN X,Y SCREEN LOCATION
#
procedure display(card,x,y)
    local all, j, shape

    all := [] ; j := y
    if find(card[2],"CS") then card := screen("hinv") || card || screen("norm")
#    shape := [at(x,(j+:=1)) || screen("gchar") || "lqqqqqqqk"]
    shape := [at(x,(j+:=1)) || screen("inv") || "         " || screen("norm")]
    put(shape,at(x,(j+:=1)) || screen("inv") || " " || screen("norm") ||
	" " || card || "    " || screen("inv") || " " || screen("norm"))
    put(shape,at(x,(j+:=1)) || screen("inv") || " " || screen("norm") ||
	"       " || screen("inv") || " " || screen("norm"))
    put(shape,at(x,(j+:=1)) || screen("inv") || " " || screen("norm") ||
	"       " || screen("inv") || " " || screen("norm"))
    put(shape,at(x,(j+:=1)) || screen("inv") || " " || screen("norm") ||
	"       " || screen("inv") || " " || screen("norm"))
#    put(shape,at(x,(j+:=1)) || "x       x")
#    put(shape,at(x,(j+:=1)) || "x       x")
    put(shape,at(x,(j+:=1)) || screen("inv") || " " || screen("norm") ||
	"    " || card || " " || screen("inv") || " " || screen("norm"))
#    put(shape,at(x,(j+:=1)) || "mqqqqqqqj" || screen("nchar"))
    put(shape,at(x,(j+:=1)) || screen("inv") || "         " || screen("norm"))
    put(all,shape)
    x +:= 14
  while shape := pop(all) do every writes(!shape)
  end

#
# THIS ROUTINE SHUFFLES THE CARD DECK
#
procedure xshuffle()
  static faces, suits
  local cards, i
  initial {
          randomize()
          faces   := ["2","3","4","5","6","7","8","9","T","J","Q","K","A"]
          suits   := ["D","H","C","S"]
          lookup  := table(0)
          every i := 2 to 9 do insert(lookup,string(i),i)
          insert(lookup,"T",10)
          insert(lookup,"J",10)
          insert(lookup,"Q",10)
          insert(lookup,"K",10)
          insert(lookup,"A",11)
          insert(lookup,"a",1)
          }
  cards   := []
  every put(cards,!faces || !suits)
  every i := *cards to 2 by -1 do cards[?i] :=: cards[i]
  return cards
  end

#
# THIS ROUTINE PARSES A STRING WITH RESPECT TO SOME DELIMITER
#
procedure parse(line,delims)
  local tokens

  static chars
  chars  := &cset -- delims
  tokens := []
  line ? while tab(upto(chars)) do put(tokens,tab(many(chars)))
  return tokens
  end

#
# THIS ROUTINE PROMPTS FOR INPUT AND RETURNS A STRING
#
procedure input(prompt)
  writes(screen("cursor"),prompt)
  return read()
  end


#
# THIS ROUTINE SETS THE VIDEO OUTPUT ATTRIBUTES FOR VT102 OR LATER
# COMPATIBLE TERMINALS.
#
procedure screen(attr)
  initial if getval("ug"|"mg"|"sg") > 0 then
    er("screen","oops, magic cookie terminal!",34)
  return {
    case attr of
      {
      "cls"  : getval("cl")
      "clear": getval("cl")
      # HIGH INTENSITY & INVERSE
      "hinv" : (getval("md") | "") || getval("so")
      "norm" : (getval("se") | "") || (getval("me") | "") || (getval("ue")|"")
      # LOW INTENSITY VIDEO
      "dim"  : getval("mh"|"me"|"se")
      "blink": getval("mb"|"md"|"so")
      # HIGH INTENSITY BLINKING
      "hiblink": (getval("md") | "") || getval("mb") | getval("so")
      "under": getval("us"|"md"|"so")
      "high" : getval("md"|"so"|"ul")
      "inv"  : getval("so"|"md"|"ul")
      # ERASE TO END OF LINE
      "eeol" : getval("ce")
      # ERASE TO START OF LINE
      "esol" : getval("cb")
      # ERASE TO END OF SCREEN
      "eeos" : getval("cd")
      # MAKE CURSOR INVISIBLE
      "cursor": getval("vi"|"CO") | ""
      # MAKE CURSOR VISIBLE
      "nocursor": getval("ve"|"CF") | ""
#      # START ALTERNATE FONT      <- very non-portable
#      "gchar": getval("as") | ""
#      # END ALTERNATE FONT
#      "nchar": getval("ae") | ""
#      "light": return "\e[?5h"     # LIGHT COLORED SCREEN
#      "dark" : return "\e[?5l"     # DARK  COLORED SCREEN
#      "80"   : return "\e[?3l"     # 80    COLUMNS ON SCREEN
#      "132"  : return "\e[?3h"     # 132   COLUMNS ON SCREEN
#      "smooth": return "\e[?4h"    # SMOOTH SCREEN SCROLLING
#      "jump" : return "\e[?4l"     # JUMP   SCREEN SCROLLING
      default : er("screen",attr||" is just too weird for most terminals",34)
      } | er("screen","I just can't cope with your terminal.",35)
    }
  end

#
# THIS ROUTINE SETS THE CURSOR TO A GIVEN X (COL) Y(ROW) SCREEN LOCATION
#
procedure at(x,y)
#  return "\e[" || y || ";" || x || "f"
  return igoto(getval("cm"),x,y)
  end

