Go Fish/Wren

From Rosetta Code
Translation of: Kotlin
Library: Wren-dynamic
Library: Wren-trait
Library: Wren-iterate
Library: Wren-seq
Library: Wren-sort
Library: Wren-ioutil
Library: Wren-str
import "random" for Random
import "./dynamic" for Enum
import "./trait" for Comparable
import "./iterate" for Stepped
import "./seq" for Lst
import "./sort" for Sort
import "./ioutil" for Input
import "./str" for Str

var FACES = "23456789tjqka"
var SUITS = "cdhs"

var Turn = Enum.create("Turn", ["user", "comp"])

class Card is Comparable {
    construct new(face, suit) {
        _face = face
        _suit = suit
        _value = FACES.indexOf(face) * 4 + SUITS.indexOf(suit)
    }

    face  { _face }
    suit  { _suit }
    value { _value }

    compare(other) { (_value - other.value).sign }

    toString { "%(face)%(suit)" }
}

var r = Random.new()

var ranks = [
    "twos", "threes", "fours", "fives", "sixes", "sevens", "eights",
    "nines", "tens", "jacks", "queens", "kings", "aces"
]

var deck      = []
var userHand  = []
var userBooks = []
var compHand  = []
var compBooks = []
var compPrev  = []

var turn = Turn.user  // user always starts

var createDeck = Fn.new {
    for (suit in SUITS) {
        for (face in FACES) deck.add(Card.new(face, suit))
    }
}

var shuffleDeck = Fn.new { r.shuffle(deck) }

/* Chooses a rank at random provided it hasn't already been chosen
   during the current turn. If all ranks have already been chosen,
   it chooses the first again. */
var compSelectRankIndex = Fn.new {
    var choices = Lst.distinct(compHand.map { |c| c.face }.toList).
                  where { |f| !compPrev.contains(f) }.toList
    var size = choices.count
    var choice = (size == 0) ? compHand[0].face : choices[r.int(size)]
    return FACES.indexOf(choice)
}

var userChoices = Fn.new { Lst.distinct(userHand.map { |c| c.face }.toList) }

var printHand = Fn.new { System.print("Your cards : %(userHand.join("  "))") }

var printBooks = Fn.new {
    System.print("Your books : %(userBooks.join("   "))")
    System.print("My books   : %(compBooks.join("   "))")
}

var printTurn = Fn.new {
    System.print( (turn == Turn.user) ? "--- YOUR TURN ---" : "--- MY TURN ---")
}

var checkForBooks = Fn.new { |hand, books|
    var newBooks = Lst.groupBy(hand) { |c| c.face }.toList.
                       where { |me| me.value.count == 4 }.
                       map { |me| me.key }.toList
    if (newBooks.count > 0) {
        books.addAll(newBooks)
        var cmp = Fn.new { |c1, c2| (FACES.indexOf(c1) - FACES.indexOf(c2)).sign }
        Sort.insertion(books, cmp)
        for (b in newBooks) {
            for (i in hand.count-1..0) {
                if (hand[i].face == b) hand.removeAt(i)
            }
        }
        if (hand.count == 0 && deck.count > 0) {
            var e = deck.removeAt(0)
            if (hand == userHand) System.print("You drew : %(e)")
            hand.add(e)
        }
        System.print("Added %(newBooks.join("   ")) to books")
    }
}

var showStateOfPlay = Fn.new { |showTurn|
    System.print()
    Sort.quick(userHand)
    printHand.call()
    printBooks.call()
    if (showTurn) printTurn.call()
    System.print()
}

// create and shuffle deck and deal cards
createDeck.call()
shuffleDeck.call()
for (i in Stepped.new(0..16, 2)) userHand.add(deck[i])
for (i in Stepped.new(1..17, 2)) compHand.add(deck[i])
for (i in 0..17) deck.removeAt(0)

// check if there are any books in initial hands
checkForBooks.call(userHand, userBooks)
checkForBooks.call(compHand, compBooks)
showStateOfPlay.call(true)

while (true) {
    while (true) {
        var rank
        if (turn == Turn.user) {
            while (true) {
                rank = Str.lower(Input.txtOpt("Enter the rank you want : ", ranks))
                var choice = FACES[ranks.indexOf(rank)]
                if (userChoices.call().contains(choice)) break
                System.print("Your hand does not contain that rank, try again.")
            }
        } else {
            var r = compSelectRankIndex.call()
            rank = ranks[r]
            System.print("The rank I want is : %(rank)")
            compPrev.add(FACES[r])
        }
        var face = FACES[ranks.indexOf(rank)]
        var matches = 0
        if (turn == Turn.user) {
            for (i in compHand.count-1..0) {
                if (compHand[i].face == face) {
                    matches = matches + 1
                    userHand.add(compHand.removeAt(i))
                }
            }
            System.print("Matches : %(matches)")
            if ((matches == 0 || userHand.count == 0) && deck.count > 0) {
                var e = deck.removeAt(0)
                System.print("You drew : %(e)")
                userHand.add(e)
            }
            checkForBooks.call(userHand, userBooks)
            if (userBooks.count >= 7) {
                showStateOfPlay.call(false)
                System.print("Congratulations, you've won!")
                return
            }
        } else {
            for (i in userHand.count-1..0) {
                if (userHand[i].face == face) {
                    matches = matches + 1
                    compHand.add(userHand.removeAt(i))
                }
            }
            System.print("Matches: %(matches)")
            if ((matches == 0 || compHand.count == 0) && deck.count > 0) {
                var e = deck.removeAt(0)
                compHand.add(e)
            }
            checkForBooks.call(compHand, compBooks)
            if (compBooks.count >= 7) {
                showStateOfPlay.call(false)
                System.print("Commiserations, but I've won!")
                return
            }
        }
        if (matches > 0) showStateOfPlay.call(true) else break
    }
    turn = (turn == Turn.user || userHand.count == 0) ? Turn.comp : Turn.user
    if (turn == Turn.comp) compPrev.clear()
    showStateOfPlay.call(true)
}