import "listUtil"
print " "*31 + "Black Jack"
print " "*15 + "Creative Computing  Morristown, New Jersey"
print; print; print

fna = function(q); return q - 11*(q >= 22); end function
hands = [[]]*15		// hands(i,j) is the jth card in hand i (P)
handValue = [0]*15	// total value of each hand (Q)
deck = []			// the deck being dealt from (C)
playerMoney = [0]*8	// total $ for each player (T)
roundWinnings = [0]*7 // total $ won/lost this hand for each player (S)
betPerHand = [0]*15	// bet for each hand (B)

cardNames = " A 2 3 4 5 6 7 8 9 10 J Q K".split

reshuffle = function
	print "Reshuffling"
	globals.deck = range(1,13)*4
	deck.shuffle
end function
		
// Function to draw a card from the deck (reshuffling if needed)
getCard = function
	if not deck then reshuffle
	return deck.pop
end function

// Function to get a name for the given card, preceded by "a" or "an"
a = function(cardNum)
	article = "a" + "n" * (cardNum == 1 or cardNum == 8)
	return article + " " + cardNames[cardNum]
end function

// Function to evaluate the given hand. Total is usually put into
// handValue(handNum).  Totals have the following meaning:
//      2-10...Hard 2-10
//     11-21...Soft 11-21
//     22-32...Hard 11-21
// 33+ or -1...Busted
evalHand = function(handNum)
	result = 0
	for card in hands[handNum]
		result = addCardToTotal(result, card)
	end for
	return result
end function

// Function to add a card into a total hand value.
addCardToTotal = function(total, card)
	if total < 0 then return total	// (already busted)
	x1 = card; if x1 > 10 then x1 = 10
	q1 = total + x1
	if total < 11 then
		if card == 1 then return total + 11	// (ace)
		return q1 + 11 * (q1 >= 11)
	end if
	total = q1 + (total <= 21 and q1 > 21)
	if total >= 33 then total = -1
	return total
end function

// Print one of those totals as it should appear to the user.
displayTotal = function(total)
	return total - 11 * (total >= 22)
end function

// Get a yes/no response from the user
getYesNo = function(prompt)
	while true
		inp = input(prompt).upper
		if inp and (inp[0] == "Y" or inp[0] == "N") then return inp[0]
	end while
end function

// Get a number, within a given range, from the user
getNumber = function(prompt, minVal=0, maxVal=500)
	while true
		result = input(prompt).val
		if result == floor(result) and minVal <= result <= maxVal then return result
		print "Please enter a number from " + minVal + " to " + maxVal
	end while
end function

// Get one of a list of one-letter (uppercase) options.
getOption = function(prompt, options)
	while true
		result = input(prompt).upper
		if result and options.indexOf(result[0]) != null then return result[0]
		print "Type " + options[:-1].join(", ") + " or " + options[-1] + " please"
	end while
end function

getBets = function
	print "Bets:"
	for i in range(0, numPlayers-1)
		betPerHand[i] = getNumber("# " + (i+1) + "? ", 1, 500)
	end for
end function

playOneRound = function
	globals.roundWinnings = [0] * numPlayers
	if deck.len < (numPlayers+1) * 2 then reshuffle
	getBets
	print "PLAYER ", ""
	for i in range(0, numPlayers-1)
		print i+1, "     "
		hands[i] = []
	end for
	print "DEALER"
	hands[numPlayers] = []
	for row in [1,2]
		print "     ", ""
		for i in range(0, numPlayers)
			hands[i].push getCard
			if row == 1 or i < numPlayers then 
				print "  " + (cardNames[hands[i][-1]] + " ")[:2], "  "
			end if
		end for
		print
	end for
	dealerCard0 = hands[numPlayers][0]
	dealerCard1 = hands[numPlayers][1]
	// Test for insurance
	if dealerCard0 == 1 and getYesNo("Any insurance? ") == "Y" then
		print "Insurance Bets"
		for i in range(0, numPlayers-1)
			insurance = getNumber("# " + (i+1) + "? ", 0, betPerHand[i]/2)
			roundWinnings[i] = insurance * (3 * (dealerCard1 >= 10) - 1)
		end for
	end if
	// Test for dealer blackjack
	if (dealerCard0==1 and dealerCard1 > 9) or
	  (dealerCard0 > 9 and dealerCard1==1) then
		print; print "Dealer has " + a(dealerCard1) + " in the hole for Blackjack"
		for i in range(0, numPlayers)
			handValue[i] = evalHand(i)
		end for
	else
		// no dealer blackjack
		if dealerCard0 == 1 or dealerCard0 >= 10 then
			print; print "No dealer Blackjack."
		end if
		// now play the hands
		for i in range(0, numPlayers-1)
			playHand i
		end for
		handValue[numPlayers] = evalHand(numPlayers)	// (evaluate dealer hand)
		// Test for playing the dealer's hand... we only do so if
		// there are any player hands with cards left.
		anyLeft = false
		for i in range(0, numPlayers-1)
			if hands[i] or hands[i+8] then anyLeft = true
		end for
		if not anyLeft then
			print "Dealer had " + a(hands[numPlayers][1]) + " concealed."
		else
			// Play dealer's hand.
			dispTotal = displayTotal(handValue[numPlayers])
			print "Dealer has " + a(hands[numPlayers][1]) + " concealed" +
			  " for a total of " + dispTotal + "."
			while handValue[numPlayers] > 0 and dispTotal <= 16
				card = getCard
				if hands[numPlayers].len == 2 then print "Draws  ", ""
				print cardNames[card], "    "
				hands[numPlayers].push card
				handValue[numPlayers] = evalHand(numPlayers)
				dispTotal = displayTotal(handValue[numPlayers])
			end while
			if hands[numPlayers].len > 2 then
				if handValue[numPlayers] < 0 then print "  ---Busted" else print "  ---Total is " + dispTotal
			end if
			print
		end if
	end if
	tallyResults
end function

playHand = function(handNum, prompt=null, allowSplit=true)
	if not prompt then prompt = "Player " + (handNum % 8 + 1)
	while hands[handNum]
		options = ["H", "S", "D"] + ["/"] * allowSplit
		choice = getOption(prompt + "? ", options)
		if choice == "S" then			// player wants to stand
			handValue[handNum] = evalHand(handNum)
			if handValue[handNum] == 21 and hands[handNum].len == 2 then
				print "Blackjack"
				roundWinnings[handNum] += 1.5 * betPerHand[handNum]
				betPerHand[handNum] = 0
				discardHand handNum
			else
				print "Total is " + displayTotal(handValue[handNum])
			end if
			break
		else if choice == "D" or choice == "H" then		// hit or double down
			handValue[handNum] = evalHand(handNum)
			if choice == "D" then betPerHand[handNum] *= 2
			card = getCard
			print "Received " + a(card), "   "
			hands[handNum].push card
			handValue[handNum] = evalHand(handNum)
			if handValue[handNum] < 0 then
				print "...Busted"
				discardHand handNum
				roundWinnings[handNum] = -betPerHand[handNum]
				betPerHand[handNum] = 0
			end if
			prompt = "Hit"
			if choice == "D" then; print; break; end if
		else if choice == "/" then		// split
			card1 = hands[handNum][0]; if card1 > 10 then card1 = 10
			card2 = hands[handNum][1]; if card2 > 10 then card2 = 10
			if card1 != card2 then
				print "Splitting not allowed."
				continue
			end if
			hand2 = handNum + 8
			hands[hand2] = [hands[handNum].pop]
			betPerHand[hand2] = betPerHand[handNum]
			card = getCard
			print "First hand receives " + a(card)
			hands[handNum].push card
			card = getCard
			print "Second hand receives " + a(card)
			hands[hand2].push card
			if card1 != 1 then
				// Now play the two hands
				playHand handNum, "Hand 1", false
				playHand hand2, "Hand 2", false
			end if
			break
		end if
		allowSplit = false
	end while
end function

discardHand = function(handNum)
	hands[handNum] = []
	handValue[handNum] = 0
end function

tallyResults = function
	dealerTotal = displayTotal(evalHand(numPlayers))
	for i in range(0, numPlayers-1)
		playerHandATotal = displayTotal(evalHand(i))
		playerHandBTotal = displayTotal(evalHand(i+8))
		// calculate roundWinnings[i], which is the $ won/lost for player i
		roundWinnings[i] = roundWinnings[i] + betPerHand[i]*sign(playerHandATotal - dealerTotal) + betPerHand[i+8]*sign(playerHandBTotal - dealerTotal)
		betPerHand[i+8] = 0
		s = "Player " + (i+1) + " "
		s += ["loses", "pushes", "wins"][sign(roundWinnings[i])+1]		
		if roundWinnings[i] != 0 then s += " " + abs(roundWinnings[i])
		playerMoney[i] += roundWinnings[i]
		playerMoney[numPlayers] -= roundWinnings[i]
		s = (s + " "*25)[:25] + "Total = " + playerMoney[i]
		print s
		discardHand i
		discardHand i+8
	end for
	print "Dealer's total = " + playerMoney[numPlayers]
	print
end function

// Main program starts here

if getYesNo("Do you want instructions? ") == "Y" then
	print "This is the game of 21. As many as 7 players may play the"
	print "game. On each deal, bets will be asked for, and the"
	print "players' bets should be typed in. The cards will then be"
	print "dealt, and each player in turn plays his hand. The"
	print "first response should be either 'D', indicating that the"
	print "player is doubling down, 'S', indicating that he is"
	print "standing, 'H', indicating he wants another card, or '/',"
	print "indicating that he wants to split his cards. After the"
	print "initial response, all further responses should be 'S' or"
	print "'H', unless the cards were split, in which case doubling"
	print "down is again permitted. In order to collect for"
	print "blackjack, the initial response should be 'S'."
	print
end if
numPlayers = getNumber("Number of players? ", 1, 7)
print
// main loop!
while true
	playOneRound
end while
