# 3d20308b230c648900712e6d33181fc292b000310973177961210bd3b06c6546 Elan 1.6.0 guest default_profile valid

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

[imported] function createHumanPlayer(startPoints as Int) returns HumanPlayer
  return new HumanPlayer(startPoints)
end function

[imported] function createPlayerA(startPoints as Int) returns AutomatedPlayer
  return new AutomatedPlayer("Player A", ref getActionForPlayerA, startPoints)
end function

[imported] function createPlayerB(startPoints as Int) returns AutomatedPlayer
  return new AutomatedPlayer("Player B", ref getActionForPlayerB, startPoints)
end function

[imported] procedure playAllHands(dealer as Dealer, playerA as AutomatedPlayer, playerB as AutomatedPlayer)
  let dfc be dealCard(random())
  call playerA.newHand(dfc)
  call playerA.playHandOut()
  call playerB.newHand(dfc)
  call playerB.playHandOut()
  let statusB be playerB.hand.status
  call dealer.newHand(dfc)
  call dealer.playHandOut()
  let outcomeA be determineOutcome(dealer, playerA)
  call updateScores(dealer, playerA, outcomeA)
  let outcomeB be determineOutcome(dealer, playerB)
  call updateScores(dealer, playerB, outcomeB)
end procedure

[imported] 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

[imported] 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

[imported] 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

[imported] interface Player
  abstract property hand as Hand

  abstract procedure changeScoreBy(amount as Int)

end interface

[imported] 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

[imported] 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

[imported] class HumanPlayer inherits Player
  constructor(startingPoints as Int)
    set property.score to startingPoints
  end constructor

  property score as Int

  property hand as Hand

  procedure newHand()
    set property.hand to new Hand()
    call property.hand.draw()
    call property.hand.draw()
    call property.hand.setStatus(Status.playing)
  end procedure

  function isPlaying() returns Boolean
    return property.hand.status is Status.playing
  end function

  procedure nextMove()
    let inp be inputStringFromOptions("Enter s to stand, d to draw card:", ["s", "d"])
    if inp is "d" then
      call property.hand.draw()
    else
      call property.hand.stand()
    end if
  end procedure

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

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

end class

[imported] 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

[imported] 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

[imported] 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

[imported] enum Action stand, draw

[imported] enum Outcome undecided, lose, draw, win, winDouble

[imported] enum Status pending, playing, standing, blackjack, bust

[imported] 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}

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

main
  let startPoints be 1000
  variable dealer set to createDealer(startPoints)
  variable playerA set to createPlayerA(startPoints)
  variable playerB set to createPlayerB(startPoints)
  call playAllHands(dealer, playerA, playerB)
  call displayHtml("{dealer.asHtml()}<br>{playerA.asHtml()}<br>{playerB.asHtml()}")
  print "\n\n\n\nDealer has {dealer.score} points\nPlayer A has {playerA.score} points\nPlayer B has {playerB.score} points"
end main

function getActionForPlayerA(hand as Hand, dealerFaceUp as Card) returns Action
  variable act set to Action.draw
  return act
end function

function getActionForPlayerB(hand as Hand, dealerFaceUp as Card) returns Action
  variable act set to Action.stand
  return act
end function
