# a948ad4413e4485617facfcca0ae72de23872523acc7977a4afbd66c6560bd61 Elan 1.6.0 guest default_profile valid

main
  # Simulate dealer's probability of bust from first card
  variable dealer set to createDealer(100000)
  variable totalBust set to 0
  each rank in rankValue.keys()
    let dfc be new Card() with rank set to rank
    variable bust set to 0
    for g from 1 to 100000 step 1
      call dealer.newHand(dfc)
      call dealer.playHandOut()
      let outcome be dealer.hand.status
      if outcome is Status.bust then
        set bust to bust + 1
      end if
    end for
    print "Dealer face card: {rank} Dealer bust %: {(bust/1000).floor()}"
    set totalBust to totalBust + bust
  end each
end main

function createDealer(startPoints as Int) returns Dealer
  return new Dealer(startPoints)
end function

procedure updateScores(dealer as Dealer, player as Player, outcome as Outcome)
  if outcome is Outcome.winDouble then
    call player.changeScoreBy(2)
    call dealer.changeScoreBy(-2)
  else if outcome is Outcome.win then
    call player.changeScoreBy(1)
    call dealer.changeScoreBy(-1)
  else if outcome is Outcome.lose then
    call player.changeScoreBy(-1)
    call dealer.changeScoreBy(1)
  end if
end procedure

function determineOutcome(dealer as Dealer, player as Player) returns Outcome
  let d be dealer.hand.status
  let dTot be dealer.hand.total
  let p be player.hand.status
  let pTot be player.hand.total
  let bust be Status.bust
  let bj be Status.blackjack
  let win be Outcome.win
  let winDouble be Outcome.winDouble
  let lose be Outcome.lose
  let draw be Outcome.draw
  return if p is bust then lose else if d is bust then win else if (d is bj) and (p is bj) then draw else if p is bj then winDouble else if d is bj then lose else if pTot > dTot then win else if pTot < dTot then lose else draw
end function

function dealCard(random as Float) returns Card
  let number be (random*52).floor()
  let rank be rankValue.keys()[number div 4]
  let suit be number mod 4
  return new Card() with rank set to rank, suit set to suit
end function

interface Player
  abstract property hand as Hand

  abstract procedure changeScoreBy(amount as Int)

end interface

abstract class Automated inherits Player
  property hand as Hand

  property score as Int

  property dealerFaceCard as Card

  property active as Boolean

  abstract function getAction() returns Action

  procedure nextMove()
    if getAction() is Action.draw then
      call property.hand.draw()
    else
      call property.hand.stand()
    end if
  end procedure

  procedure playHandOut()
    call start()
    while property.hand.status is Status.playing
      call nextMove()
    end while
  end procedure

  procedure start()
    set property.active to true
  end procedure

  procedure changeScoreBy(amount as Int)
    set property.score to property.score + amount
  end procedure

end class

class Dealer inherits Automated
  constructor(startingPoints as Int)
    set property.score to startingPoints
  end constructor

  procedure newHand(faceCard as Card)
    set property.dealerFaceCard to faceCard
    set property.active to false
    set property.hand to new Hand()
    call property.hand.addCard(faceCard)
    call property.hand.draw()
  end procedure

  function getAction() returns Action
    return if property.hand.total < 17 then Action.draw else Action.stand
  end function

  function asHtml() returns String
    return if property.active then "Dealer: {property.hand.asHtml()}" else "Dealer: {property.dealerFaceCard.asHtml()} {unicode(9633)}"
  end function

end class

class AutomatedPlayer inherits Automated
  constructor(name as String, decisionFunc as Func<of Hand, Card => Action>, startingPoints as Int)
    set property.name to name
    set property.decisionFunc to ref decisionFunc
    set property.score to startingPoints
  end constructor

  property name as String

  property decisionFunc as Func<of Hand, Card => Action>

  procedure newHand(faceCard as Card)
    set property.dealerFaceCard to faceCard
    set property.active to false
    set property.hand to new Hand()
    call property.hand.draw()
    call property.hand.draw()
  end procedure

  function getAction() returns Action
    return decisionFunc(property.hand, property.dealerFaceCard)
  end function

  function asHtml() returns String
    return "{property.name}: {property.hand.asHtml()}"
  end function

end class

class Hand
  property softAce as Boolean

  property cards as List<of Card>

  property count as Int

  property total as Int

  property status as Status

  function isPairOf8s() returns Boolean
    return (property.count is 2) and (property.cards[0].value() is 8) and (property.cards[1].value() is 8)
  end function

  procedure setStatus(status as Status)
    set property.status to status
  end procedure

  procedure stand()
    set property.status to Status.standing
  end procedure

  procedure draw()
    set property.status to Status.playing
    let card be dealCard(random())
    call addCard(card)
  end procedure

  procedure addCard(card as Card)
    call property.cards.append(card)
    set property.count to property.count + 1
    if card.isAce() then
      call addAce()
    else
      set property.total to property.total + card.value()
    end if
    if property.total > 21 then
      call useUpSoftAceIfPossible()
    else if card.isAce() then
      set property.softAce to true
    end if
    if (property.count is 2) and (property.total is 21) and property.softAce then
      set property.status to Status.blackjack
    else if property.total > 21 then
      set property.status to Status.bust
    else if property.total is 21 then
      set property.status to Status.standing
    end if
  end procedure

  procedure addAce()
    if property.softAce then
      set property.total to property.total + 1
    else
      set property.total to property.total + 11
      set property.softAce to true
    end if
  end procedure

  private procedure useUpSoftAceIfPossible()
    if property.softAce then
      set property.total to property.total - 10
      set property.softAce to false
    else
      set property.status to Status.bust
    end if
  end procedure

  function asHtml() returns String
    variable str set to ""
    each card in property.cards
      set str to str + card.asHtml() + " "
    end each
    set str to "{str}({property.total}) {property.status}"
    return str
  end function

end class

record Card
  property suit as Int

  property rank as String

  function value() returns Int
    return rankValue[property.rank]
  end function

  function isAce() returns Boolean
    return property.rank is "A"
  end function

  function asHtml() returns String
    let blackSuit be (property.suit is 0) or (property.suit is 3)
    let colour be if blackSuit then "black" else "red"
    let tag be htmlEscChars[property.suit]
    return "{property.rank}<span style='color: {colour}'>{tag}</span>"
  end function

end record

enum Action stand, draw

enum Outcome undecided, lose, draw, win, winDouble

enum Status pending, playing, standing, blackjack, bust

constant rankValue set to {"2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":10, "Q":10, "K":10, "A":11}

constant htmlEscChars set to {"&clubs;", "&diams;", "&hearts;", "&spades;"}
