Uno (Card Game)/Wren

From Rosetta Code
Revision as of 14:14, 29 March 2022 by Rdm (talk | contribs) (tasks need a description, and a task page)
Uno (Card Game)/Wren is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Library: DOME
Library: Wren-trait
Library: Wren-ellipse
Library: Wren-event
Library: Wren-dynamic

<lang ecmascript>import "dome" for Window, Platform, Process import "graphics" for Canvas, Color, Font import "audio" for AudioEngine import "input" for Mouse import "random" for Random import "./trait" for Indexed import "./ellipse" for Button import "./event" for Event import "./dynamic" for Tuple

var Rand = Random.new() var Card = Tuple.create("Card", ["colorNo", "face"]) var Players = ["PLAYER", "BOT A", "BOT B", "BOT C"] var Click = Event.new("click") var Colors = [Color.red, Color.yellow, Color.green, Color.blue, Color.indigo] var ColorNames = ["Red", "Yellow", "Green", "Blue", "Indigo"] var Pack = [] var Hands = List.filled(4, null) for (p in 0..3) Hands[p] = [] var Scores = List.filled(4, 0) var DrawPack = [] var DiscPack = [] var Dealer = 0 var PlayerNo = 0 var Penalty = "None" var Reversed = false var NextColor = 0 var MustDeclare = false var NeedCard = false var PlayDrawn = false var UnoPressed = false var MayChallenge = false var HelpShowing = false var HandOver = false var GameOver = false

var DeclareButtons = List.filled(4, null) for (i in 0..3) {

   DeclareButtons[i] = Button.square(25 + i * 40, 70, 30)

}

var PlayerButtons = List.filled(28, null) for (i in 0...28) {

   var r = (i / 7).floor + 1
   var c = i % 7
   PlayerButtons[i] = Button.square(270 + c * 60, 80 + 60 * r, 50)

}

var DrawButton = Button.square(270, 490, 50)

var BotButtons = List.filled(3, null) BotButtons[0] = Button.square(745, 490, 50) BotButtons[1] = Button.square(390, 830, 50) BotButtons[2] = Button.square( 85, 490, 50)

var UnoButton = Button.square(815, 70, 30)

var PlayDrawnButtons = List.filled(2, 0) for (i in 0..1) {

   PlayDrawnButtons[i] = Button.square(25 + i * 40, 760, 30)

}

var HelpButton = Button.square(815, 760, 30)

var Symbols = "0123456789STR"

var HelpText = """ This simulation is based on the official rules of the UNO card game, uses the standard pack of 108 cards and is played entirely with the mouse.

The following symbols, which appear on the face, are used to describe the cards:

  0 to 9  Colored card of that number
  S       Colored 'skip' card
  R       Colored 'reverse' card
  T       Colored 'draw two' card
  W       Wild card
  F       Wild 'draw four' card

You play against 3 bots: A, B and C. Your hand is visible but the bots' Hands are not.

When you click their icon, the bots play automatically, in a deterministic fashion and don't make mistakes except that, if you play a wild draw four card, the next bot (which cannot 'see' your cards) will randomly challenge you 50% of the time.

The cards in your or the bot's hand will be automatically adjusted depending on whether the challenge is won or lost.

Just click a card in your hand to play it OR click the draw pack to add the top card to your hand. If the drawn card is playable, click 'Y' to play it or 'N' to leave it in your hand.

If you play a wild card then you will need to declare the next color by clicking the appropriate button. When a wild card is displayed on opening, the next color will be deduced from the card you play.

If you only have one card left after making a play, you will need to click the Uno button before clicking the next bot to avoid being penalized.

There are some unusual scenarios which will result in a void hand. For example, if you were to accumulate more than 28 cards in your hand, the hand would be declared void as the display can only handle a maximum of 28 cards.

Click the mouse's left button to return to the current hand. """

class Main {

   construct new() {
       Window.resize(900, 900)
       Canvas.resize(900, 900)
       Window.title = "Uno simulation"
       Font.load("Go-Regular20", "Go-Regular.ttf", 20)
       Canvas.font = "Go-Regular20"
       // download from https://soundbible.com/509-Mouse-Double-Click.html
       AudioEngine.load("clicked", "mouse_click.wav")
       Click.register { |o, argMap|
           onUnoButtonClick(argMap)
       }
       Click.register { |o, argMap|
           onHelpButtonClick(argMap)
       }
       Click.register { |o, argMap|
           onPlayerButtonClick(argMap)
       }
       Click.register { |o, argMap|
           onDrawButtonClick(argMap)
       }
       Click.register { |o, argMap|
           onBotButtonClick(argMap)
       }
   }
   init() {
       startUp()
   }
   startUp() {
       createPack()
       shuffle(Pack)
       // deal 7 cards to each player
       for (i in 0..6) {
           for (p in 0..3) Hands[p].add(Pack[i * 4 + p])
       }
       DrawPack = Pack[28..-1]
       while (DrawPack[0].face == "F") {
           shuffle(DrawPack)
       }
       var discTop = DrawPack.removeAt(0)
       DiscPack = [discTop]
       Dealer = Rand.int(4)
       PlayerNo = Dealer
       var face = discTop.face
       NextColor = discTop.colorNo
       if (face == "S") {
           nextPlayer(2)
       } else if (face == "R") {
           Reversed = true
           redraw()
       } else if (face == "T") {
           nextPlayer(1, false)
           for (i in 1..2) {
               var newCard = DrawPack.removeAt(0)
               Hands[PlayerNo].add(newCard)
           }
           nextPlayer(1)
       } else if (face == "W") {
           nextPlayer(1, false)
           if (Hands[0].all { |c| c.colorNo == 4 }) {
               handVoid("Player has no colored card.")
               return
           }
           if (PlayerNo == 0) {
               NeedCard = true
               redraw()
           } else {
               for (card in Hands[PlayerNo]) {
                   if (card.colorNo < 4) {
                       if (card.face == "S") {
                           skip(card)
                           return
                       } else if (card.face == "R") {
                           reverse(card)
                           return
                       } else if (card.face == "T") {
                          draw2(card)
                          return
                       } else {
                          playSame(card)
                          return
                       }
                   }
               }
           }
       } else {
           nextPlayer(1)
       }
   }
   createPack() {
       for (i in 0..3) Pack.add(Card.new(4, "W"))
       for (i in 0..3) Pack.add(Card.new(4, "F"))
       for (i in 0..3) {
           Pack.add(Card.new(i, "0"))
           for (j in 1..2) {
               for (f in "123456789SRT") Pack.add(Card.new(i, f))
           }
       }
   }
   shuffle(a) { Rand.shuffle(a) }
   reshuffle() {
       var discTop = DiscPack.removeAt(0)
       shuffle(DiscPack)
       DrawPack = DiscPack
       DiscPack = [discTop]
   }
   nextPlayer(places, draw) {
       if (!Reversed) {
           PlayerNo = (PlayerNo + places) % 4
       } else {
           PlayerNo = PlayerNo - places
           if (PlayerNo < 0) PlayerNo = PlayerNo + 4
       }
       Penalty = "None"
       if (draw) redraw()
   }
   nextPlayer(places) {
       nextPlayer(places, true)
   }
   redraw() {
       Canvas.cls()
       Canvas.print("Declare color:", 10, 20, Color.white)
       for (i in 0..3) {
           var cb = DeclareButtons[i]
           cb.drawfill(Colors[i])
       }
       Canvas.print("Uno:", 800, 20, Color.white)
       UnoButton.drawfill(Color.orange)
       Canvas.print("U", UnoButton.cx-5, UnoButton.cy-10, Color.black)
       var pc = Hands[0].count
       if (pc > 28) {
           handVoid("Player has more than 28 cards.")
           return
       }
       if (UnoPressed && pc != 1) UnoPressed = false
       Canvas.print("Name: PLAYER", 365, 20, Color.white)
       var uno = (UnoPressed) ? " (UNO)" : ""
       Canvas.print("Cards: %(pc) %(uno)", 365, 50, Color.white)
       Canvas.print("Score: %(Scores[0])", 365, 80, Color.white)
       for (i in 0...Hands[0].count) {
           var b = PlayerButtons[i]
           var c = Hands[0][i]
           b.drawfill(Colors[c.colorNo])
           Canvas.print(c.face, b.cx-5, b.cy-10, Color.black)
       }
       for (iv in Indexed.new(BotButtons)) {
           var ix = iv.index
           var bb = iv.value
           var letter = "ABC"[ix]
           Canvas.print("Name: BOT %(letter)", bb.cx-25, bb.cy-120, Color.white)
           uno = (Hands[ix+1].count == 1) ? " (UNO)" : ""
           Canvas.print("Cards: %(Hands[ix+1].count)%(uno)", bb.cx-25, bb.cy-90, Color.white)
           Canvas.print("Score: %(Scores[ix+1])", bb.cx-25, bb.cy-60, Color.white)
           bb.drawfill(Color.peach)
           Canvas.print(letter, bb.cx-5, bb.cy-10, Color.black)
       }
       Canvas.print("Name: DRAW", 245, 370, Color.white)
       Canvas.print("Cards: %(DrawPack.count)", 245, 400, Color.white)
       Canvas.print("Direction: %(Reversed ? "Anti-clock" : "Clock")", 245, 430, Color.white)
       DrawButton.drawfill(Color.pink)
       Canvas.print("D", DrawButton.cx-5, DrawButton.cy-10, Color.black)
       var discTop = DiscPack[0]
       Canvas.print("Name: DISCARD", 485, 370, Color.white)
       Canvas.print("Cards: %(DiscPack.count)", 485, 400, Color.white)
       var cc
       var ct
       if (NeedCard) {
           cc = Color.peach
           ct = "Need card"
       } else if (MustDeclare) {
           cc = Color.pink
           ct = "Declare"
       } else if (PlayDrawn) {
           cc = Color.orange
           ct = "Play drawn?"
       } else {
           cc = Color.white
           ct = ColorNames[NextColor]
       }
       Canvas.print("Color: %(ct)", 485, 430, cc)
       var dib = Button.square(510, 490, 50)
       dib.drawfill(Colors[discTop.colorNo])
       Canvas.print(discTop.face, dib.cx-5, dib.cy-10, Color.black)
       Canvas.print("Play drawn card:", 10, 710, Color.white)
       for (iv in Indexed.new(["Y", "N"])) {
           var ix = iv.index
           var ncb = PlayDrawnButtons[ix]
           ncb.drawfill(Color.orange)
           Canvas.print(iv.value, ncb.cx-5, ncb.cy-10, Color.black)
       }
       Canvas.print("Help:", 800, 710, Color.white)
       HelpButton.drawfill(Color.orange)
       Canvas.print("H", HelpButton.cx-5, HelpButton.cy-10, Color.black)
       Canvas.print("Dealer: %(Players[Dealer])", 60, 600, Color.white)
       Canvas.print("Player: %(Players[PlayerNo])", 365, 600, Color.yellow)
       var cp = (Penalty == "None") ? Color.white :
                (Penalty == "+2")   ? Color.blue  :
                (Penalty == "+4"  ) ? Color.red  : Color.green
       Canvas.print("Penalty: %(Penalty)", 720, 600, cp)
   }
   checkDeclareButton(x, y) {
       if (!MustDeclare) return
       for (i in 0..3) {
           if (DeclareButtons[i].contains(x, y)) {
               AudioEngine.play("clicked")
               MustDeclare = false
               NextColor = i
               var face = DiscPack[0].face
               if (face == "F") {
                   MayChallenge = true
                   nextPlayer(1, false)
                   for (i in 1..4) {
                       var newCard = DrawPack.removeAt(0)
                       Hands[PlayerNo].add(newCard)
                       if (DrawPack.isEmpty) reshuffle()
                   }
               }
               nextPlayer(1)
           }
       }
   }
   checkPlayDrawnButton(x, y) {
       if (!PlayDrawn) return
       for (i in 0..1) {
           if (PlayDrawnButtons[i].contains(x, y)) {
               AudioEngine.play("clicked")
               PlayDrawn = false
               if (i == 0) {
                   var card = Hands[0][-1]
                   playerPlay(card)
               } else {
                   nextPlayer(1)
               }
               return
           }
       }
   }
   onUnoButtonClick(argMap) {
       if (PlayerNo == 0 || UnoPressed || Hands[0].count != 1) return
       var x = argMap["x"]
       var y = argMap["y"]
       if (UnoButton.contains(x,y)) {
           AudioEngine.play("clicked")
           UnoPressed = true
           redraw()
       }
   }
   onHelpButtonClick(argMap) {
       if (HelpShowing) return
       var x = argMap["x"]
       var y = argMap["y"]
       if (HelpButton.contains(x,y)) {
           AudioEngine.play("clicked")
           HelpShowing = true
           help()
       }
   }
   onPlayerButtonClick(argMap) {
       if (PlayerNo != 0) return
       var x = argMap["x"]
       var y = argMap["y"]
       var discTop = DiscPack[0]
       for (iv in Indexed.new(PlayerButtons)) {
           var i = iv.index
           var btn = iv.value
           if (btn.contains(x, y)) {
               var card = Hands[0][i]
               if (NeedCard) {
                   if (card.colorNo == 4) return
                   NeedCard = false
               } else if (card.colorNo != 4 && card.colorNo != NextColor && card.face != discTop.face) {
                   return
               }
               AudioEngine.play("clicked")
               playerPlay(card)
               return
           }
       }
   }
   onDrawButtonClick(argMap) {
       if (PlayerNo != 0) return
       var x = argMap["x"]
       var y = argMap["y"]
       if (DrawButton.contains(x,y)) {
           if (NeedCard) return
           AudioEngine.play("clicked")
           var card = DrawPack.removeAt(0)
           Hands[0].add(card)
           if (DrawPack.isEmpty) reshuffle()
           if (card.colorNo != 4 && card.colorNo != NextColor && card.face != DiscPack[0].face) {
               nextPlayer(1)
               return
           }
           PlayDrawn = true
           redraw()
       }
   }
   onBotButtonClick(argMap) {
       if (PlayerNo == 0) return
       var x = argMap["x"]
       var y = argMap["y"]
       var btn = BotButtons[PlayerNo-1]
       if (btn.contains(x, y)) {
           AudioEngine.play("clicked")
           if (MayChallenge) {
               if (Rand.int(2) == 0) {
                   if (Hands[0].any { |card| card.colorNo == NextColor }) {
                       for (i in 1..4) {
                           Hands[0].add(Hands[PlayerNo-1].removeAt(-1))
                       }
                       Penalty = "+4"
                   } else {
                       if (Hands[PlayerNo].count == 0) {
                           handFinished(PlayerNo)
                           return
                       }
                       for (i in 1..2) {
                           var newCard = DrawPack.removeAt(0)
                           Hands[PlayerNo-1].add(newCard)
                           if (DrawPack.isEmpty) reshuffle()
                       }
                       Penalty = "-2"
                   }
               }
               MayChallenge = false
               redraw()
           } else if (Hands[0].count == 1 && !UnoPressed) {
               for (i in 1..2) {
                   var newCard = DrawPack.removeAt(0)
                   Hands[0].add(newCard)
                   if (DrawPack.isEmpty) reshuffle()
               }
               Penalty = "+2"
               redraw()
           } else {
               var saveNo = PlayerNo
               Penalty = "None"
               botPlay()
               if (Hands[saveNo].count == 0) handFinished(saveNo)
           }
       }
   }
   playerPlay(card) {
      if (card.face == "S") {
           skip(card)
       } else if (card.face == "R") {
           reverse(card)
       } else if (card.face == "T") {
           draw2(card)
       } else if (card.face == "W") {
           wild(card)
       } else if (card.face == "F") {
           draw4(card)
           MayChallenge = true
       } else {
           playSame(card)
       }
   }
   botPlay() {
       var face = DiscPack[0].face
       var hand = Hands[PlayerNo]
       var cardToPlay = null
       for (card in hand) {
           if (card.colorNo == NextColor || card.face == face) {
               cardToPlay = card
               break
           }
       }
       if (!cardToPlay) {
           for (card in hand) { 
               if (card.face == "W") {
                   cardToPlay = card
                   break
               }
           }
       }
       if (!cardToPlay) {
           for (card in hand) {
               if (card.face == "F") {
                   cardToPlay = card
                   break
               }
           }
       }
       if (!cardToPlay) {
           cardToPlay = DrawPack.removeAt(0)
           Hands[PlayerNo].add(cardToPlay)
           if (DrawPack.isEmpty) reshuffle()
           if (cardToPlay.colorNo != 4 && cardToPlay.colorNo != NextColor && cardToPlay.face != face) {
               nextPlayer(1)
               return
           }
       }
       if (cardToPlay.face == "S") {
           skip(cardToPlay)
       } else if (cardToPlay.face == "R") {
           reverse(cardToPlay)
       } else if (cardToPlay.face == "T") {
           draw2(cardToPlay)
       } else if (cardToPlay.face == "W") {
           wild(cardToPlay)
       } else if (cardToPlay.face == "F") {
           draw4(cardToPlay)
       } else {
           playSame(cardToPlay)
       }
   }
   skip(card) {
       if (Hands[PlayerNo].count == 1) {
           handFinished(PlayerNo)
           return
       }
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       NextColor = card.colorNo
       nextPlayer(2)
   }
   reverse(card) {
       if (Hands[PlayerNo].count == 1) {
           handFinished(PlayerNo)
           return
       }
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       Reversed = !Reversed
       NextColor = card.colorNo
       nextPlayer(1)
   }
   draw2(card) {
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       nextPlayer(1, false)
       for (i in 1..2) {
           var newCard = DrawPack.removeAt(0)
           Hands[PlayerNo].add(newCard)
           if (DrawPack.isEmpty) reshuffle()
       }
       if (Hands[PlayerNo].count == 0) {
           handFinished(PlayerNo)
           return
       }
       NextColor = card.colorNo
       nextPlayer(1)
   }
   wild(card) {
       if (Hands[PlayerNo].count == 1) {
           handFinished(PlayerNo)
           return
       }
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       if (PlayerNo > 0) {
           NextColor = Rand.int(4)
           nextPlayer(1)
       } else {
           MustDeclare = true
           redraw()
       }
   }
   draw4(card) {
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       if (PlayerNo > 0) {
           NextColor = Rand.int(4)
           nextPlayer(1, false)
           for (i in 1..4) {
               var newCard = DrawPack.removeAt(0)
               Hands[PlayerNo].add(newCard)
               if (DrawPack.isEmpty) reshuffle()
           }
           if (Hands[PlayerNo].count == 0) {
               handFinished(PlayerNo)
               return
           }
           nextPlayer(1)
       } else {
           MustDeclare = true
           redraw()
       }
   }
   playSame(card) {
       if (Hands[PlayerNo].count == 1) {
           handFinished(PlayerNo)
           return
       }
       Hands[PlayerNo].remove(card)
       DiscPack.insert(0, card)
       NextColor = card.colorNo
       nextPlayer(1)
   }
   update() {
       if (Mouse["left"].justPressed) {
           if (HelpShowing) {
               AudioEngine.play("clicked")
               HelpShowing = false
               redraw()
           } else if (GameOver) {
               AudioEngine.play("clicked")
               Process.exit()
           } else if (HandOver) {
               AudioEngine.play("clicked")
               Pack.clear()
               for (p in 0..3) Hands[p] = []
               Penalty = "None"
               Reversed = false
               MustDeclare = false
               NeedCard = false
               PlayDrawn = false
               UnoPressed = false
               MayChallenge = false
               HandOver = false
               startUp()
           } else if (MustDeclare) {
               checkDeclareButton(Mouse.x, Mouse.y)
           } else if (PlayDrawn) {
               checkPlayDrawnButton(Mouse.x, Mouse.y)
           } else {
               Click.notify({"x": Mouse.x, "y": Mouse.y})
           }
       }
   }
   draw(alpha) {
   }
   help() {
       Canvas.cls()
       Canvas.print(HelpText, 10, 10, Color.white)
   }
   handVoid(msg) {
       Canvas.cls()
       Canvas.print(msg, 50, 50, Color.white)
       Canvas.print("Click the mouse's left button to start a new hand.", 50, 100, Color.white)
       HandOver = true
   }
   handFinished(winner) {
       var total = 0
       for (player in 0..3) {
           if (player != winner) {
               for (card in Hands[player]) {
                   if (card.face == "S" || card.face == "R" || card.face == "T") {
                       total = total + 20
                   } else if (card.face == "W" || card.face == "F") {
                       total = total + 50
                   } else {
                       total = total + Num.fromString(card.face)
                   }
               }
           }
       }
       Scores[winner] = Scores[winner] + total
       Canvas.cls()
       if (Scores[winner] >= 500) {
           Canvas.print("%(Players[winner]) has won the game with %(Scores[winner]) points!", 50, 50, Color.white)
           Canvas.print("Click the mouse's left button to exit.", 50, 100, Color.white)
           GameOver = true
       } else {
           Canvas.print("%(Players[winner]) has won the hand with %(total) points!", 50, 50 , Color.white)
           Canvas.print("The total points scored by this player are now %(Scores[winner])", 50, 100, Color.white)
           Canvas.print("Click the mouse's left button to start the next hand.", 50, 150, Color.white)
           HandOver = true
       }
   }

}

var Game = Main.new()</lang>