Uno (Card Game)/Wren
Uno (Card Game)/Wren is part of Uno_(Card_Game). You may find other members of Uno_(Card_Game) at Category:Uno_(Card_Game).
Code
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 "./iterate" 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()