Uno (Card Game)/Wren
<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>