In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

ONO (English version)

OFFICIAL SITE
http://onohugou.sakura.ne.jp/en.html


RULES

Empty your cards earlier than the others.
Play stronger and the same count of cards.
You win if you survive 3 rounds.

Weaker A 2 3 4 5 6 7 8 9 10 J Q K Stronger

J: Revolution card. It reverses the card strength order.
3: Counterrevolution card. It restores the card strength order.
JOKER: You can play a joker in any case. Play jokers one by one.

DRAW: You can draw one card.
PLAY: You can play stronger and the same count of cards.
DISCARD: You can discard one unnecessary card.

*3 decks of playing cards are used.
Get Adobe Flash player
by YAU 27 Dec 2011
/**
 * Copyright YAU ( http://wonderfl.net/user/YAU )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/rrhi
 */

/*
ONO (English version)
(c)2011 YAU
The MIT License


OFFICIAL SITE
http://onohugou.sakura.ne.jp/en.html


RULES

Empty your cards earlier than the others.
Play stronger and the same count of cards.
You win if you survive 3 rounds.

Weaker A 2 3 4 5 6 7 8 9 10 J Q K Stronger

J: Revolution card. It reverses the card strength order.
3: Counterrevolution card. It restores the card strength order.
JOKER: You can play a joker in any case. Play jokers one by one.

DRAW: You can draw one card.
PLAY: You can play stronger and the same count of cards.
DISCARD: You can discard one unnecessary card.

*3 decks of playing cards are used.
*/

package {
    import flash.display.*
    import flash.events.UncaughtErrorEvent

    [SWF(backgroundColor=0xffffff, width=500, height=500, frameRate=12)]
    
    public class Ono extends Sprite {
        public static const version:String = '2.0.13.1_wonderflen'

        function Ono() {
            this.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, g.errorHandler)
            g.stage = this.stage
            new Game
        }
    }
}

import flash.display.*
import flash.events.*
import flash.text.*
import flash.geom.Rectangle
import flash.geom.Matrix
import flash.utils.Timer
import flash.net.*

class g {
    public static const testHandCount:int = 0
    public static const opened:Boolean = false
    public static const autoPlay:Boolean = false
    public static const skipConfirm:Boolean = false

    public static const normalSpeed:Number = 1
    public static const hiSpeed:Number = g.normalSpeed * 0.3
    public static var speed:Number = g.normalSpeed

    public static const font:String = '_sans'
    public static var lang:Object
    public static var stage:Stage
    public static var game:Game
    public static var players:Players
    public static var player:Player
    public static var stock:Stock
    public static var playPile:PlayPile
    public static var drawPile:DrawPile
    public static var discardPile:DiscardPile
    public static var revolution:Revolution
    public static var message:Message
    public static var commandButtons:CommandButtons

    public static function defaultable(value:*, defaultValue:*):* {
        if (value === null || value === undefined || value === '' || isNaN(value)) {
            return defaultValue
        }
        return value
    }

    public static function setTimeout(callback:Function, delay:int=0):void {
        var timer:Timer = new Timer(delay * g.speed, 1)
        timer.addEventListener(TimerEvent.TIMER, callback)
        timer.start()
    }

    public static var errored:Boolean = false
    public static function errorHandler(event:*):void {
        if (g.errored) {
            return
        }
        g.errored = true

        try {
            _DisplayObjectContainer.removeAllChildren(g.stage)
            var text:TextField = _TextField.newText('ERROR', {color: 0x777777, size: 20, letterSpacing: 5})
            g.stage.addChild(text)
            _DisplayObject.align(text, ['center', 'middle'])
        } catch (error:*) {
            trace('#failed to show error message')
        }
    }
}

class Game {
    private static var gameNo:int = 0
    private var roundNo:int
    private var turnNo:int

    function Game() {
        _DisplayObjectContainer.removeAllChildren(g.stage)
        Game.gameNo++
        this.roundNo = 0

        new Version
        g.lang = Lang.lang
        g.game = this
        g.players = new Players
        g.stock = new Stock
        g.commandButtons = new CommandButtons
        g.message = new Message
        g.revolution = new Revolution
        g.drawPile = new DrawPile
        g.playPile = new PlayPile
        g.discardPile = new DiscardPile

        this.initializeRound()
    }

    private function initializeRound():void {
        this.roundNo++
        this.turnNo = 0
        g.speed = g.normalSpeed

        g.commandButtons.drawMoveButtons()
        g.revolution.revolutionized = false
        g.drawPile.opened = false
        g.player = this.roundNo == 1 ? g.players.human : g.players.first
        g.players.resetRank()
        for each (var player:Player in g.players.array) {
            player.hand.opened = false
        }

        g.stock.deal()
        this.initializeTurn()
    }

    private function initializeTurn():void {
        this.turnNo++
        if (this.turnNo == 1 || g.drawPile.drawn) {
            this.turn()
        } else {
            g.player = g.players.next()
    
            if (g.player == g.playPile.player) {
                g.playPile.clear()
            }
    
            if (!g.player.hand.count()) {
                this.initializeTurn()

            } else {
                this.turn()
            }
        }
    }

    private function turn():void {
        g.setTimeout(function():void {
            g.player.inputter.input()
        }, g.player.human ? 0 : 1000)
    }

    public function terminateTurn():void {
        g.message.undraw()

        if (!g.player.hand.count()) {
            g.players.setRank(g.player)

            if (g.player.human || g.players.last) {
                g.drawPile.opened = true
                for each (var player:Player in g.players.array) {
                    player.hand.opened = true
                }
            }
            if (g.player.human) {
                g.speed = g.hiSpeed
            }
        }

        if (!g.players.last) {
            this.initializeTurn()

        } else {
            if (g.players.last.human) {
                g.message.draw(g.lang.lose)
                g.commandButtons.drawRetryButton()

            } else if (g.players.countWhoPlaying() == 2) {
                g.message.draw(g.lang.win)
                g.commandButtons.drawRetryButton()
    
            } else {
                g.players.last.playing = false
                g.commandButtons.drawNextRoundButton()
            }
        }
    }

    public function terminateRound():void {
        g.stock.collect()
        g.message.undraw()
        g.revolution.undraw()
        this.initializeRound()
    }

    public function terminateGame():void {
        new Game
    }
}

class Player {
    private static const HUMAN_PLAYER_ID:int = 0

    public var id:int
    public var playing:Boolean
    public var rank:Number
    public var human:Boolean
    public var inputter:*
    public var hand:Hand
    public var stage:Sprite

    private var STAGE_CONFS:Array = [
        {x: 0, y: 0, rotation: 0},
        {x: g.stage.stageWidth, y: 0, rotation: 90},
        {x: g.stage.stageWidth, y: g.stage.stageHeight, rotation: 180},
        {x: 0, y: g.stage.stageHeight, rotation: 270}
    ]

    function Player(id:int) {
        this.id = id
        this.playing = true
        this.human = id == HUMAN_PLAYER_ID
        this.stage = this.newStage()
        this.inputter    = this.human && !g.autoPlay ? new HumanInputter : new MachineInputter(this)
        this.hand = new Hand(this)
    }

    private function newStage():Sprite {
        var conf:Object = STAGE_CONFS[this.id]
        var stage:Sprite = new Sprite
        stage.rotation = conf.rotation
        stage.x = conf.x
        stage.y = conf.y
        g.stage.addChild(stage)
        return stage
    }
}

class Players {
    private static const COUNT:int = 4

    public var array:Array = []
    public var human:Player
    private var nextRank:Number

    function Players() {
        for (var id:int = 0; id < COUNT; id++) {
            var player:Player = new Player(id)
            if (player.human) {
                this.human = player
            }
            this.array.push(player)
        }
    }

    public function next():Player {
        return _Array.circularAt(this.array, g.player.id + 1)
    }

    public function setRank(player:Player):void {
        player.rank = this.nextRank++
    }
    
    public function resetRank():void {
        this.nextRank = 1
        for each (var player:Player in this.array) {
            player.rank = NaN
        }
    }

    public function get first():Player {
        var first:Player = null
        for each (var player:Player in this.array) {
            if (player.rank == 1) {
                first = player
                break
            }
        }
        return first
    }

    public function get last():Player {
        var last:Player = null
        if (this.countWhoHaveHand() == 1) {
            for each (var player:Player in this.array) {
                if (player.hand.count()) {
                    last = player
                }
            }
            if (!last) throw Error('last can not be null')
        }
        return last
    }

    public function minOtherHandCount():int {
        var counts:Array = []
        for each (var player:Player in this.array) {
            if (player != g.player && player.hand.count()) {
                counts.push(player.hand.count())
            }
        }
        return Math.min.apply(null, counts)
    }

    public function countWhoHaveHand():int {
        return this.array.filter(function(player:Player, i:int, array:Array):Boolean {
            return Boolean(player.hand.count())
        }).length
    }

    public function countWhoPlaying():int {
        return this.array.filter(function(player:Player, i:int, array:Array):Boolean {
            return player.playing
        }).length
    }
}

class Card extends Sprite {
    public static const WIDTH:Number = 30
    public static const HEIGHT:Number = 40

    public static const BORDER_WIDTH:int = 1
    private static const BORDER_COLOR:int = 0x777777

    public static const BACKGROUND_COLORS:Object = {3: 0xDDE6EE, 11: 0xeedddd}
    private static const DEFAULT_BACKGROUND_COLOR:int = 0xffffff

    private static const BACK_BACKGROUND_COLORS:Array = [0xE6E6E6, 0xE6E6E6, 0xE6E6E6]
    private static const RANK_TEXTS:Object = {1: 'A', 11: 'J', 12: 'Q', 13: 'K', 14: "JO\nKER"}

    private static const RED_SUIT_COLOR:int = 0xE68989
    public static const BLACK_SUIT_COLOR:int = BORDER_COLOR
    private static const SUIT_COLORS:Object = {heart: RED_SUIT_COLOR, diamond: RED_SUIT_COLOR, club: BLACK_SUIT_COLOR, spade: BLACK_SUIT_COLOR}

    public var rank:Number
    public var suit:String
    public var deckId:int
    public var opened:Boolean
    public var selected:Boolean
    public var select:Function

    function Card(conf:Object) {
            this.rank = conf.rank
            this.suit = conf.suit
            this.deckId = g.defaultable(conf.deckId, 0)
            this.opened = g.defaultable(conf.opened, true)
            this.selected = false
            if (g.defaultable(conf.draw, true)) this.draw()
    }

    public function draw():void {
            this.undraw()
            this.drawBackground()
            if (this.opened) {
                this.drawSuit()
                this.drawRank()
            }
            this.drawBorder()
    }

    public function undraw():void {
        if (this.parent) {
            this.parent.removeChild(this)
        }
        this.graphics.clear()
        _DisplayObjectContainer.removeAllChildren(this)
    }

    private function drawBackground():void {
        var color:int = this.opened ? BACKGROUND_COLORS[this.rank] || DEFAULT_BACKGROUND_COLOR : BACK_BACKGROUND_COLORS[this.deckId]
        this.graphics.beginFill(color)
        this.graphics.lineStyle(NaN)
        this.graphics.drawRect(0, 0, WIDTH, HEIGHT)
        this.graphics.endFill()
    }

    private function drawSuit():void {
        if (this.rank == Rank.JOKER) {
            return
        }
        var suitColor:int = SUIT_COLORS[this.suit]
        var suitSize:Number = WIDTH / 40 * 15

        this.graphics.beginFill(suitColor)
        this.graphics.lineStyle(BORDER_WIDTH, suitColor)

        this.graphics.moveTo(0, 0)
        this.graphics.lineTo(suitSize, 0)
        if (_Array.includes(['heart', 'club'], this.suit)) {
            var control:Number = suitSize * 1.05
            this.graphics.curveTo(control, control, 0, suitSize)
        } else {
            this.graphics.lineTo(0, suitSize)
        }
        this.graphics.endFill()
    }

    private function drawRank():void {
        var text:TextField = new TextField
        text.autoSize = TextFieldAutoSize.LEFT

        var format:TextFormat = new TextFormat
        format.font = g.font
        format.color = SUIT_COLORS[this.suit]

        if (this.rank == Rank.JOKER) {
            format.size = Math.ceil(WIDTH * 1.5 / 4)
            format.bold = true
        } else {
            format.size = Math.ceil(WIDTH * 3 / 4)
        }
        if (_Array.includes([6, 9], this.rank)) {
            format.underline = true
        }

        text.defaultTextFormat = format
        text.text = RANK_TEXTS[this.rank] || String(this.rank)

        var bitmapData:BitmapData = new BitmapData(text.width, text.height, true, 0)
        var rect:Rectangle = new Rectangle(0, 0, text.width, text.height)
        bitmapData.draw(text, null, null, null, rect, true)

        var bitmap:Bitmap = new Bitmap(bitmapData, 'auto', true)
        this.addChild(bitmap)
        _DisplayObject.align(bitmap, ['center', 'middle'])
    }

    private function drawBorder():void {
        this.graphics.lineStyle(BORDER_WIDTH, BORDER_COLOR)
        this.graphics.drawRect(0, 0, WIDTH, HEIGHT)
    }
}

class Cards {
    public var array:Array

    function Cards(array:Array=null) {
        this.array = array || []
    }

    public function shuffle():void {
        _Array.shuffle(this.array)
    }

    public function popAll():Cards {
        return new Cards(_Array.popAll(this.array))
    }

    public function push(_cards:*):void {
        var cards:Cards = Cards.cast(_cards)
        for each (var card:Card in cards.array) {
            this.array.push(card)
        }
    }

    public function pop():Card {
        return this.array.pop()
    }

    public function count():int {
        return this.array.length
    }

    public function sort():void {
        var array:Array = this.array.slice()
        this.array.sort(function(a:Card, b:Card):int {
            return a.rank - b.rank || array.indexOf(a) - array.indexOf(b)
        })
    }

    static public function cast(cards:*):Cards {
        if (cards is Cards) {
            return Cards(cards)
        } else if (cards is Card) {
            return new Cards([cards])
        }
        throw Error('invalid cards=' + cards)
    }

    public function selectees():Cards {
        var cards:Cards = new Cards
        for each (var card:Card in this.array) {
            if (card.selected) {
                cards.push(card)
            }
        }
        return cards
    }

    public function popSelectees():Cards {
        return new Cards(_Array.popValues(this.array, this.selectees().array))
    }

    public function get rank():Number {
        var _ranks:Array = _Array.unique(this.ranks)
        var rank:Number = _ranks.length == 1 ? _ranks[0] : NaN
        return rank
    }

    public function get ranks():Array {
        return this.array.map(function(card:Card, i:int, array:Array):Number {
            return card.rank
        })
    }
}

class Hand {
    public static const COUNT:int = g.testHandCount || 13
    public static var Y:int
    private static const SELECT_Y:int = 10

    private var cards:Cards
    private var player:Player
    private var group:MyGroup
    private var _opened:Boolean

    function Hand(player:Player) {
        Hand.Y = g.stage.stageHeight - Card.HEIGHT - Card.BORDER_WIDTH
        this.cards = new Cards
        this.player = player
        this.group = new MyGroup({parent: this.player.stage, direction: 'horizontal', gap: -Card.BORDER_WIDTH})
    }

    private function push(card:Card):void {
        card.selected = false
        card.opened = this._opened || this.player.human || g.opened
        card.x = 0
        card.y = 0

        if (this.player.human) {
            this.bind(card)
        }

        this.cards.push(card)
        card.draw()
    }

    public function pushToDeal(cards:Cards):void {
        for each (var card:Card in cards.array) {
            this.push(card)
        }
        this.arrange()
    }

    public function pushToDraw(card:Card):void {
        this.push(card)
        this.group.addChild(new Spacer(10))
        this.group.addChild(card)
        _DisplayObject.align(this.group, ['center'], g.stage)
    }

    private function bind(card:Card):void {
        card.buttonMode = true
        card.select = function():void {
            card.y += SELECT_Y * (card.selected ? 1 : -1)
            card.selected = !card.selected
        }
        card.addEventListener(MouseEvent.CLICK, card.select)
    }

    private function unbind(card:Card):void {
        card.buttonMode = false
        card.removeEventListener(MouseEvent.CLICK, card.select)
    }

    public function set opened(b:Boolean):void {
        this._opened = b
        if (this.cards.count()) {
            for each (var card:Card in this.cards.array) {
                card.opened = b
                card.draw()
            }
            this.arrange()
        }
    }

    private function arrange():void {
        this.cards.sort()
        _DisplayObjectContainer.removeAllChildren(this.group)
        for each (var card:Card in this.cards.array) {
            this.group.addChild(card)
        }

        _DisplayObject.align(this.group, ['center'], g.stage)
        this.group.y = Y
    }

    public function count():int {
        return this.cards.count()
    }

    public function get ranks():Array {
        return this.cards.ranks
    }

    public function selectees():Cards {
        return this.cards.selectees()
    }

    public function popSelectees():Cards {
        var cards:Cards = this.cards.popSelectees()
        this.remove(cards)
        this.arrange()
        return cards
    }

    private function remove(cards:Cards):void {
        for each (var card:Card in cards.array) {
            if (this.player.human) {
                this.unbind(card)
            }
            card.selected = false
            card.parent.removeChild(card)
        }
    }

    public function select(rank:Number, count:int):void {
        var _count:int = 0
        for (var i:int = this.count() - 1; i >= 0; i--) {
            var card:Card = this.cards.array[i]

            card.selected = false
            if (_count == count) {
                break
            }
            if (card.rank == rank) {
                card.selected = true
                _count++
            }
        }
        if (_count != count) throw Error('wrong count')
        if (_count != this.selectees().count()) throw Error('failed to select')
    }

    public function clear():void {
        var cards:Cards = this.cards.popAll()
        this.remove(cards)
        g.stock.push(cards)
    }
}

class Stock {
    private static const DECK_COUNT:int = 3

    private var frontCards:Cards
    private var backCards:Cards
    private var createdCardCount:int

    function Stock() {
        this.frontCards = new Cards
        this.backCards = this.createCards()
    }

    private function createCards():Cards {
        var cards:Cards = new Cards
        for (var deckId:int = 0; deckId < DECK_COUNT; deckId++) {
            for each (var rank:Number in Rank.ALL) {
                for each (var suit:String in Suit.ALL) {
                    if (rank == Rank.JOKER && !_Array.includes(['heart', 'spade'], suit)) {
                        continue
                    }
                    cards.push(new Card({rank: rank, suit: suit, deckId: deckId, draw: false}))
                }
            }
        }

        this.createdCardCount = cards.count()
        if (this.createdCardCount != 162) throw Error('wrong created card count')

        return cards
    }

    public function pop():Card {
        if (!this.frontCards.count()) {
            this.frontCards.push(this.backCards.popAll())
            this.frontCards.shuffle()
        }
        var card:Card = this.frontCards.pop()
        card.x = 0
        card.y = 0
        card.rotation = 0
        card.opened = false
        return card
    }

    public function popSome(count:int):Cards {
        var cards:Cards = new Cards
        for (var i:int = 0; i < count; i++) {
            cards.push(this.pop())
        }
        return cards
    }

    public function push(_cards:*):void {
        var cards:Cards = Cards.cast(_cards)
        for each (var card:Card in cards.array) {
            if (card.parent) {
                card.parent.removeChild(card)
            }
            card.undraw()
        }
        this.backCards.push(cards)
    }

    public function deal():void {
        for each (var player:Player in g.players.array) {
            if (!player.playing) {
                continue
            }
            player.hand.pushToDeal(g.stock.popSome(Hand.COUNT))
        }
        g.drawPile.push(g.stock.pop())
    }

    public function collect():void {
        g.playPile.clear()
        g.drawPile.clear()
        g.discardPile.clear()
        for each (var player:Player in g.players.array) {
            player.hand.clear()
        }

        this.backCards.push(this.frontCards.popAll())
        if (this.backCards.count() != this.createdCardCount) throw Error('some cards lost=' + (this.backCards.count() - this.createdCardCount))
        if (_Array.unique(this.backCards.array).length != this.createdCardCount) throw Error('some cards duplicated=' + (_Array.unique(this.backCards.array).length - this.createdCardCount))
    }
}

class PlayPile {
    public var player:Player
    private var cards:Cards = new Cards
    private var group:MyGroup

    public function clear():void {
        this.player = null

        if (this.group) {
            g.stage.removeChild(this.group)
            this.group = null
            g.stock.push(this.cards.popAll())
        }
    }

    public function count():int {
        return this.cards.count()
    }
    
    public function get rank():Number {
        return this.cards.rank
    }

    public function push(cards:Cards, player:Player):void {
        this.clear()
        this.player = player
        this.group = new MyGroup({parent: g.stage, direction: 'horizontal', gap: -Card.BORDER_WIDTH})
        this.cards = cards

        for each (var card:Card in this.cards.array) {
            card.opened = true
            card.x = 0
            card.y = 0
            card.draw()
            this.group.addChild(card)
        }
        _DisplayObject.transform(this.group, player.stage.rotation, g.stage.stageWidth / 2, g.stage.stageHeight / 2)
    }

    public function playerHaveHand():Boolean {
        return this.player && this.player.hand.count()
    }
}

class DrawPile {
    private var card:Card
    public var drawn:Boolean = false
    private var _opened:Boolean

    public function push(card:Card):void {
        this.card = card
        this.card.opened = this._opened || g.opened
        this.draw()
    }

    private function draw():void {
        this.card.draw()
        _DisplayObject.transform(this.card, 0, g.stage.stageWidth / 2 - 110, g.stage.stageHeight / 2 + Card.HEIGHT + 10)
        g.stage.addChild(this.card)
    }

    public function pop():Card {
        var _card:Card = this.card
        this.push(g.stock.pop())
        g.stage.removeChild(_card)
        return _card
    }

    public function clear():void {
        if (this.card) {
            g.stock.push(this.card)
            this.card = null
        }
    }

    public function set opened(b:Boolean):void {
        this._opened = b
        if (this.card) {
            this.card.opened = b
            this.draw()
        }
    }
}

class DiscardPile {
    private var card:Card

    public function push(cards:Cards, player:Player):void {
        if (cards.count() != 1) throw Error('invalid count=' + cards.count())

        this.clear()
        this.card = cards.array[0]
        this.card.opened = true
        this.card.draw()
        this.card.x = 0
        this.card.y = 0
        g.stage.addChild(this.card)
        _DisplayObject.transform(this.card, player.stage.rotation, g.stage.stageWidth / 2 + 110, g.stage.stageHeight / 2 + Card.HEIGHT + 10)
    }

    public function clear():void {
        if (this.card) {
            g.stock.push(this.card)
            this.card = null
        }
    }
}

class Suit {
    public static const ALL:Array = ['heart', 'diamond', 'club', 'spade']
}

class Rank {
    public static const COUNTERREVOLUTION:int = 3
    public static const CENTER:int = 7
    public static const REVOLUTION:int = 11
    public static const JOKER:int = 14

    public static const ALL:Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

    private static function filter(rank:Number):int {
        if (!_Array.includes(Rank.ALL, rank)) throw Error('invalid rank')
        return rank
    }

    public static function abs(rank:Number):int {
        Rank.filter(rank)
        var _rank:Number = Rank.CENTER + Math.abs(rank - Rank.CENTER)
        return Rank.filter(_rank)
    }

    public static function reverse(rank:Number):int {
        Rank.filter(rank)
        var _rank:Number
        if (rank == Rank.JOKER) {
            _rank = rank
        } else {
            _rank = Rank.JOKER - rank
        }
        return Rank.filter(_rank)
    }

    public static function current(rank:Number):int {
        Rank.filter(rank)
        if (g.revolution.revolutionized) {
            return reverse(rank)
        } else {
            return rank
        }
    }
}

class Revolution {
    public var revolutionized:Boolean
    private var indicator:MyLabel
    private static var INDICATOR_CONFS:Array

    function Revolution() {
        INDICATOR_CONFS = [
            {text: g.lang.counterrevolution, backgroundColor: Card.BACKGROUND_COLORS[3]},
            {text: g.lang.revolution, backgroundColor: Card.BACKGROUND_COLORS[11]}
        ]
    }

    public function revolutionize():void {
        this.revolutionized = !this.revolutionized
        this.draw()
    }

    private function draw():void {
        this.undraw()
        this.indicator = this.newIndicator()
        g.stage.addChild(this.indicator)
        _DisplayObject.setCenterX(this.indicator, 250)
        this.indicator.y = 350
    }

    public function undraw():void {
        if (this.indicator) {
            g.stage.removeChild(this.indicator)
            this.indicator = null
        }
    }

    private function newIndicator():MyLabel {
        var conf:Object = INDICATOR_CONFS[int(this.revolutionized)]
        return new MyLabel(conf.text, {color: Card.BLACK_SUIT_COLOR, bold: true, size: 16, backgroundColor: conf.backgroundColor, letterSpacing: 2, horizontalPadding: 4, verticalPadding: 4})
    }
}

class Message {
    private var label:MyLabel

    public function draw(text:String, conf:Object=null):void {
        conf = conf || {}
        text = g.lang[text] ? g.lang[text] : text

        this.undraw()
        this.label = new MyLabel(text, {size: conf.size || 14, color: 0x777777, backgroundColor: 0xffffff, letterSpacing: 0, horizontalPadding: 2})
        this.label.y = 390
        g.stage.addChild(this.label)
        _DisplayObject.align(this.label, ['center'])
    }

    public function undraw():void {
        if (this.label) {
            g.stage.removeChild(this.label)
            this.label = null
        }
    }
}

class CommandButtons {
    private var group:MyGroup

    function CommandButtons() {
        this.group = new MyGroup({parent: g.stage, direction: 'horizontal', gap: 10})
    }

    private function drawButtons(buttons:Array):void {
        _DisplayObjectContainer.removeAllChildren(this.group)
        for each (var button:MyButton in buttons) {
            this.group.addChild(button)
        }
        _DisplayObject.align(this.group, ['center'])
        this.group.y = Hand.Y - this.group.height - 20
    }

    public function drawMoveButtons():void {
        this.drawButtons([this.newDrawButton(), this.newPlayButton(), this.newDiscardButton()])
    }

    private function newDrawButton():MyButton {
        var button:MyButton = new MyButton(g.lang.Command_draw)
        button.addEventListener(MouseEvent.CLICK, function():void {
            g.players.human.inputter.accept(new Move({type: 'draw'}))
        })
        return button
    }

    private function newPlayButton():MyButton {
        var button:MyButton = new MyButton(g.lang.Command_play)
        button.addEventListener(MouseEvent.CLICK, function():void {
            accept('play')
        })
        return button
    }

    private function newDiscardButton():MyButton {
        var button:MyButton = new MyButton(g.lang.Command_discard)
        button.addEventListener(MouseEvent.CLICK, function():void {
            accept('discard')
        })
        return button
    }

    private function accept(type:String):void {
        var cards:Cards = g.players.human.hand.selectees()

        var move:Move = new Move
        move.count = cards.count()
        move.rank = cards.rank
        move.type = type

        g.players.human.inputter.accept(move)
    }

    public function drawRetryButton():void {
        var f:Function = function():void {
            g.game.terminateGame()
        }
        if (g.skipConfirm) {
            f()
        } else {
            var button:MyButton = new MyButton(g.lang.Command_retry, {width: 0, horizontalPadding: 3})
            button.addEventListener(MouseEvent.CLICK, f)
            this.drawButtons([button])
        }
    }

    public function drawNextRoundButton():void {
        var f:Function = function():void {
            g.game.terminateRound()
        }
        if (g.skipConfirm) {
            f()
        } else {
            var button:MyButton = new MyButton(g.lang.Command_nextRound, {width: 0, horizontalPadding: 3})
            button.addEventListener(MouseEvent.CLICK, f)
            this.drawButtons([button])
        }
    }
}

class Move {
    public var type:String
    public var rank:Number
    public var count:int
    public var priorities:Array

    public function Move(conf:Object=null) {
        conf = conf || {}
        this.type = conf.type || ''
        this.rank = conf.rank || NaN
        this.count = conf.count || 0
        this.priorities = conf.priorities || []
    }

    public function validate():String {
        if (!(_Array.includes(Rank.ALL, this.rank) || isNaN(this.rank))) throw Error('invalid rank=' + this.rank)
        if (!(this.count >= 0)) throw Error('invalid count=' + this.count)

        switch (this.type) {
            case 'play':
                if (this.count <= 0) {
                    return 'Error_playingNothing'
                }

                if (isNaN(this.rank)) {
                    return 'Error_playingRanks'
                }

                if (this.rank == Rank.JOKER) {
                    if (this.count != 1) {
                        return 'Error_playingJorkers'

                    } else if (g.player.hand.count() == 1) {
                        return 'Error_playingJokerAtLast'
                    }

                    return ''
                }

                if (g.playPile.count()) {
                    if (this.count != g.playPile.count()) {
                        return 'Error_playingDifferentCountOfCards'

                    } else if (Rank.current(this.rank) <= Rank.current(g.playPile.rank)) {
                        if (g.revolution.revolutionized) {
                            return 'Error_playingWeakerInRevolution'
                        } else {
                            return 'Error_playingWeakerNotInRevolution'
                        }
                    }

                    return ''
                }

                return ''

            case 'draw':
                if (!Move.validType(this.type)) {
                    return 'Error_drawingTwice'
                }

                return ''

            case 'discard':
                if (!Move.validType(this.type)) {
                    return 'Error_discardingBeforeDrawing'
                }

                if (this.count <= 0) {
                    return 'Error_discardingNothing'
                }

                if (this.count != 1) {
                    return 'Error_discardingMultiple'
                }

                return ''
        }

        return 'Error_invalidMoveType'
    }

    public function execute():void {
        if (this.validate()) throw Error('move validation failed')
        trace(
            '#player=' + g.player.id + 
            ' playPile=[' + (g.playPile.rank || g.playPile.count() ? g.playPile.rank + ' * ' + g.playPile.count() + ' player=' + g.playPile.player.id : '') + ']' +
            ' revolutionized=' + int(g.revolution.revolutionized) +
            ' drawn=' + int(g.drawPile.drawn) +
            ' hand=' + '[' + g.player.hand.ranks.join(' ') + ']' +
            ' move=[' + this.type + (this.rank || this.count ? ' ' + this.rank + ' * ' + this.count : '') + ']' +
            ' remain=' + (g.player.hand.count() - this.count + (this.type == 'draw'))
        )

        g.drawPile.drawn = false
        switch (this.type) {
            case 'play':
                g.playPile.push(g.player.hand.popSelectees(), g.player)
                if (Rank.current(g.playPile.rank) == Rank.REVOLUTION) {
                    g.revolution.revolutionize()
                }
                break
            case 'draw':
                var card:Card = g.drawPile.pop()
                g.player.hand.pushToDraw(card)
                g.drawPile.drawn = true
                break
            case 'discard':
                g.discardPile.push(g.player.hand.popSelectees(), g.player)
                break
            default:
                throw Error('invalid type=' + this.type)
        }

        g.game.terminateTurn()
    }

    public function onlyJokersWillRemain():Boolean {
        return (
            this.rank != Rank.JOKER &&
            _Array.countValues(g.player.hand.ranks, Rank.JOKER) &&
            (g.player.hand.count() - this.count) == _Array.countValues(g.player.hand.ranks, Rank.JOKER)
        )
    }

    public static function validType(type:String):Boolean {
        return {
            draw: !g.drawPile.drawn,
            discard: g.drawPile.drawn,
            play: true
        }[type]
    }

    public static function sortMovesByPriorities(moves:Array):void {
        moves.sort(function(a:Move, b:Move):int {
            for (var i:int = 0; i < Math.min(a.priorities.length, b.priorities.length); i++) {
                var compare:Number = Number(b.priorities[i]) - Number(a.priorities[i])
                if (compare) {
                    return compare < 0 ? -1 : 1
                }
            }
            return b.priorities.length - a.priorities.length
        })
    }

    public static function validMoves(type:String):Array {
        var moves:Array = []
        if (type == 'draw') {
            moves.push(new Move({type: type}))
        } else {
            for each (var rank:Number in Rank.ALL) {
                for (var count:int = 1; count <= _Array.countValues(g.player.hand.ranks, rank); count++) {
                    var move:Move = new Move({type: type, rank: rank, count: count})
                    if (!move.validate() && !move.onlyJokersWillRemain()) {
                        moves.push(move)
                    }
                }
            }
        }    
        return moves
    }
}

class HumanInputter {
    private var accepting:Boolean

    public function input():void {
        this.accepting = true
    }

    public function accept(move:Move):void {
        if (!this.accepting) {
            return
        }

        var error:String = move.validate()

        if (error) {
            g.message.draw(error)
            return
        }

        this.accepting = false
        move.execute()
    }
}

class MachineInputter {
    private var player:Player
    private var machine:Machine

    function MachineInputter(player:Player) {
        this.player = player
        this.machine = new Machine
    }

    public function input():void {
        var move:Move = this.get()
        if (_Array.includes(['play', 'discard'], move.type)) {
            this.player.hand.select(move.rank, move.count)
        }
        move.execute()
    }

    private function get():Move {
        var move:Move
        if (Move.validType('draw')) {
            move = this.getByType('draw')
            if (move.priorities.length) {
                return move
            }
        }

        move = this.getByType('play')
        if (move.priorities.length) {
            return move
        }

        if (Move.validType('draw')) {
            return new Move({type: 'draw'})
        }

        if (Move.validType('discard')) {
            return this.getByType('discard')
        }

        throw Error('wrong flow')
    }

    private function getByType(type:String):Move {
        var moves:Array = []
        for each (var move:Move in Move.validMoves(type)) {
            move.priorities = this.machine[type](move, g.player.hand)
            if (!(move.priorities is Array)) throw Error('priorities is not array')
            for each (var priority:* in move.priorities) {
                if (!((priority is Number && !isNaN(priority))|| priority is int || priority is Boolean)) throw Error('invalid priority')
            }

            moves.push(move)
        }
        if (type == 'play' && !moves.length) {
            moves.push(new Move({type: type}))
        }
        if (!moves.length) throw Error('no moves')

        Move.sortMovesByPriorities(moves)
        return moves[0]
    }
}

class Machine {
    public function draw(move:Move, hand:Hand):Array {
        return []
    }

    public function play(move:Move, hand:Hand):Array {
        if (move.count == hand.count()) {
            return [4]
        }
        if (move.rank == Rank.JOKER) {
            if (
                (g.players.countWhoHaveHand() == 2) &&
                g.playPile.playerHaveHand() && (
                    g.playPile.player.hand.count() == 1 ||
                    (g.playPile.player.hand.count() <= 5 && Math.random() < 0.5)
                )
            ) {
                return [3]
            } else if (_Array.unique(hand.ranks).length <= 2) {
                return [1]
            } else if (_Array.unique(hand.ranks).length <= 3 && Math.random() < 0.2) {
                return [1]
            } else if (g.playPile.playerHaveHand() && g.playPile.player.hand.count() <= 3 && _Array.countValues(hand.ranks, Rank.JOKER) >= 2 && Math.random() < 0.5) {
                return [1]
            }
            return []
        }

        var priorities:Array = [2]
        if (g.players.minOtherHandCount() <= 7) {
            priorities.push(Rank.current(move.rank))
            priorities.push(move.count != g.players.minOtherHandCount())
            priorities.push(move.count == _Array.countValues(hand.ranks, move.rank))
            priorities.push(_Array.countValues(hand.ranks, move.rank))
        } else {
            priorities.push(Rank.current(move.rank) < 11)
            priorities.push(move.count == _Array.countValues(hand.ranks, move.rank))
            priorities.push(-_Array.countValues(hand.ranks, move.rank))
            priorities.push(-Rank.abs(move.rank))
        }
        return priorities
    }
    
    public function discard(move:Move, hand:Hand):Array {
        var priorities:Array = []
        if (g.players.minOtherHandCount() <= 2) {
            priorities.push(-Rank.current(move.rank))
        } else {
            priorities.push(Rank.current(move.rank) < 12)
            priorities.push(Rank.abs(move.rank) < 12)
            priorities.push(move.rank != Rank.JOKER)
            priorities.push(-_Array.countValues(hand.ranks, move.rank))
        }
        return priorities
    }
}

class _DisplayObject {
    static public function align(obj:DisplayObject, types:Array, parent:*=null):void {
        for each (var type:String in types) {
            _DisplayObject.alignOne(obj, type, parent)
        }
    }

    static private function alignOne(obj:DisplayObject, type:String, parent:*=null):void {
        parent = parent || obj.parent
        if (type == 'center') {
            obj.x = ((parent is Stage ? parent.stageWidth : parent.width) - obj.width) / 2
        } else if (type == 'middle') {
            obj.y = ((parent is Stage ? parent.stageHeight : parent.height) - obj.height) / 2
        } else {
            throw Error('invalid type=' + type)
        }
    }

    public static function setCenterX(obj:DisplayObject, x:Number):void {
        obj.x = x - obj.width / 2
    }

    public static function transform(obj:DisplayObject, rotation:Number, centerX:Number, centerY:Number):void {
        obj.x = 0
        obj.y = 0
        var matrix:Matrix = new Matrix
        var centerX0:Number = obj.width / 2
        var centerY0:Number = obj.height / 2
        matrix.translate(-centerX0, -centerY0)
        matrix.rotate(rotation / 180 * Math.PI)
        matrix.translate(centerX, centerY)
        obj.transform.matrix = matrix
    }
}

class _DisplayObjectContainer {
    static public function removeAllChildren(obj:DisplayObjectContainer):void {
        while (obj.numChildren) {
            obj.removeChildAt(0)
        }
    }
    
    static public function arrangeChildren(obj:DisplayObjectContainer, direction:String, gap:int):void {
        var confs:Object = {
            horizontal: {axis: 'x', length: 'width'},
            vertical: {axis: 'y', length: 'height'}
        }
        var conf:Object = confs[direction]
        if (!conf) throw Error('invalid direction=' + direction)

        for (var i:int = 0; i < obj.numChildren; i++) {
            var curr:DisplayObject = obj.getChildAt(i)
            curr[conf.axis] = i == 0 ? 0 : prev[conf.axis] + prev[conf.length] + gap
            var prev:DisplayObject = curr
        }
    }
}

class MyGroup extends Sprite {
    private var gap:int
    private var direction:String

    function MyGroup(conf:Object) {
        this.direction = conf.direction
        if (conf.parent) {
            conf.parent.addChild(this)
        }
        this.gap = conf.gap
    }

    override public function addChild(obj:DisplayObject):DisplayObject {
        super.addChild(obj)
        _DisplayObjectContainer.arrangeChildren(this, this.direction, this.gap)
        return obj
    }
}

class Spacer extends Sprite {
    function Spacer(size:int) {
        this.graphics.drawRect(0, 0, size, size)
    }
}

class MyLabel extends Sprite {
    function MyLabel(_text:String, conf:Object) {
        var format:TextFormat = new TextFormat
        format.font = g.font
        format.color = g.defaultable(conf.color, 0xffffff)
        format.size = g.defaultable(conf.size, 11)
        format.letterSpacing = conf.letterSpacing
        format.bold = conf.bold

        var text:TextField = new TextField
        text.defaultTextFormat = format
        text.autoSize = TextFieldAutoSize.LEFT
        text.text = _text
        text.mouseEnabled = false

        this.graphics.beginFill(conf.backgroundColor)
        var width:int = (conf.width || text.width) + (conf.horizontalPadding || 0) * 2
        var height:int = (conf.height || text.height) + (conf.verticalPadding || 0) * 2
        this.graphics.drawRect(0, 0, width, height)
        this.graphics.endFill()

        this.addChild(text)
        _DisplayObject.align(text, ['center', 'middle'])
        text.x += conf.letterSpacing / 2
    }
}

class MyButton extends MyLabel {
    function MyButton(text:String, conf:Object=null) {
        conf = conf || {}
        var conf:Object = {horizontalPadding: conf.horizontalPadding, width: g.defaultable(conf.width, 100), backgroundColor: 0x888888, letterSpacing: 5}
        super(text, conf)
        this.buttonMode = true
    }
}

class _TextField {
    public static function newText(_text:String, conf:Object):TextField {
        var format:TextFormat = new TextFormat
        format.font = g.font
        format.color = g.defaultable(conf.color, 0xaaaaaa)
        format.size = conf.size
        format.letterSpacing = conf.letterSpacing
        format.bold = conf.bold

        var text:TextField = new TextField
        text.autoSize = TextFieldAutoSize.LEFT
        text.defaultTextFormat = format
        text.text = _text
        return text
    }
}

class _Array {
    public static function shuffle(array:Array):void {
        // Knuth-Fisher-Yates shuffle
        var length:int = array.length
        for (var i:int = length - 1; i > 0; i--) {
            var j:int = Math.floor(Math.random() * (i + 1))
            var temp:* = array[i]
            array[i] = array[j]
            array[j] = temp

            if (!(0 <= i && i < length)) throw Error('i is out of range. shuffle failed.')
            if (!(0 <= j && j < length)) throw Error('j is out of range. shuffle failed.')
        }
        if (!(array.length == length)) throw Error('array length changed. shuffle failed.')
    }

    public static function popAll(array:Array):Array {
        var _array:Array = []
        while (array.length) {
            _array.push(array.pop())
        }
        return _array
    }

    public static function popValues(heystack:Array, needles:Array):Array {
        for each (var needle:* in needles) {
            var i:int = heystack.indexOf(needle)
            if (!(i >= 0)) throw Error('invalid argument')
            heystack.splice(i, 1)
        }
        return needles
    }

    public static function circularAt(array:Array, i:int):* {
        return array[i % array.length]
    }

    public static function unique(array:Array):Array {
        var _array:Array = []
        for each (var v:Object in array) {
            if (!_Array.includes(_array, v)) {
                _array.push(v)
            }
        }
        return _array
    }

    public static function includes(heystack:Array, needle:*):Boolean {
        return heystack.indexOf(needle) >= 0
    }

    public static function countValues(heystack:Array, needles:*):int {
        needles = needles is Array ? needles : [needles]
        var count:int = 0
        for each (var hay:* in heystack) {
            count += _Array.includes(needles, hay)
        }
        return count
    }
}

class Version {
    function Version() {
        var text:TextField = _TextField.newText('ONO ' + Ono.version + ' ', {size: 10, letterSpacing: 3})
        text.x = Card.HEIGHT + 10
        text.y = Card.HEIGHT + 10
        g.stage.addChild(text)
    }
}

class Lang {
    public static const lang:Object = {
        revolution: 'REVOLUTION',
        counterrevolution: 'COUNTERREVOLUTION',
    
        lose: 'YOU LOSE...',
        win: 'YOU WIN!!!',

        Command_play: 'PLAY',
        Command_draw: 'DRAW',
        Command_discard: 'DISCARD',
        Command_retry: 'RETRY',
        Command_nextRound: 'NEXT ROUND',

        Error_playingNothing: 'Select card.',
        Error_playingWeakerNotInRevolution: 'Play bigger rank or a joker.',
        Error_playingWeakerInRevolution: 'In the revolution, play smaller rank or a joker.',
        Error_playingDifferentCountOfCards: 'Play the same count of cards.',
        Error_playingRanks: 'Play cards of a single rank.',
        Error_playingJorkers: 'Play jokers one by one.',
        Error_playingJokerAtLast: 'You can not play a joker at last.',

        Error_drawingTwice: 'You can not draw twice.',
        
        Error_discardingNothing: 'Select a card.',
        Error_discardingMultiple: 'Discard only one card.',
        Error_discardingBeforeDrawing: 'Draw a card before discarding.'
    }
}