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

碁盤

ActionScript Igo, 20101010, naraba
 人同士の対局のみ可能、思考ルーチンなし
 劣化版 日本ルール (セキが認識できない、地の計算が不正確)

参考:
 http://homepage1.nifty.com/Ike/katsunari/
Get Adobe Flash player
by naraba 10 Oct 2010

    Tags

    go
    Embed
/**
 * Copyright naraba ( http://wonderfl.net/user/naraba )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/deMH
 */

/**
 * ActionScript Igo, 20101010, naraba
 *  人同士の対局のみ可能、思考ルーチンなし
 *  劣化版 日本ルール (セキが認識できない、地の計算が不正確)
 * 
 * 参考:
 *  http://homepage1.nifty.com/Ike/katsunari/
 */
package 
{
    import adobe.utils.CustomActions;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import com.bit101.components.Style;
    import com.bit101.components.Label;
    import com.bit101.components.PushButton;
    import com.bit101.components.Panel;
    import com.bit101.components.NumericStepper;
    import com.bit101.components.Text;
    
    [SWF(width="465", height="465", frameRate="10")]
    public class AsIgo extends Sprite 
    {
        private const STONE_EMPTY:int = 0;
        private const STONE_BLACK:int = 1;
        private const STONE_WHITE:int = 2;
        private const STONE_OUTER:int = 4;
        private const STONE_MIXED:int = STONE_BLACK | STONE_WHITE;
        private const STONE_SCORED:int = 32;
        private const MARK_FREE:int = -1;
        private const MARK_EMPTY:int = STONE_EMPTY;
        private const MARK_BLACK:int = STONE_BLACK;
        private const MARK_WHITE:int = STONE_WHITE;
        private const TERRITORY_BLACK:int = STONE_BLACK;
        private const TERRITORY_WHITE:int = STONE_WHITE;
        private const TERRITORY_NEUTRAL:int = STONE_MIXED;
        private const TERRITORY_SCORED:int = STONE_SCORED;
        
        private const STATUS_OVER:int = 0;
        private const STATUS_PLAY:int = 1;
        private const STATUS_EDIT:int = 2;  // 未実装
        private const BI_PASS:int = -1;
        
        private var size:int;
        private var side:int;
        private var boardSize:int;
        private var biN:int;
        private var biE:int;
        private var biS:int;
        private var biW:int;
        
        private var status:int;
        private var board:Array;
        private var markBoard:Array;
        private var agehama:Array;
        private var emptyList:Array;
        private var numMove:int;
        private var koBi:int;
        private var koMove:int;
        private var passes:int;
        private var komi:Number;
        private var prevMove:int;
        
        // 画面描画用
        private const VER_STRING:String = "ActionScript Igo, 20101010, naraba";
        private const DR_BOARD_WIDTH:int = 300;
        private const DR_BOARD_LEFT:int = 10;
        private const DR_BOARD_TOP:int = 10;
        private const DR_COLORS:Array = [0xEEEEEE, 0x000000, 0xFFFFFF, 0x333333];
        private const DR_STAR_RAD:Number = 2;
        private const DR_STAR_POS:Array = [[36, 40, 60, 80, 84], [64, 70, 112, 154, 160], 
                                            [88, 94, 100, 214, 220, 226, 340, 346, 352]];
        private const DR_UI_LEFT:int = 325;
        private const DR_UI_TOP:int = 70;
        
        private var drCell:Number;
        private var bcanvas:Sprite;
        private var text:Text;
        
        public function AsIgo():void {
            bcanvas = new Sprite();
            addChild(bcanvas);
            bcanvas.x = DR_BOARD_LEFT;
            bcanvas.y = DR_BOARD_TOP;
            bcanvas.addEventListener(MouseEvent.CLICK, onBoardClick);
            createUI();
            initGame();
        }
        
        private function nm2c(n:int):int { return n % 2 == 1 ? STONE_BLACK : STONE_WHITE; }
        private function bi2x(bi:int):int { return bi % side; }
        private function bi2y(bi:int):int { return int(bi / side); } 
        private function xy2bi(x:int, y:int):int { return y * side + x; }
        
        private function initGame():void {
            initBoard();
            initMarkBoard();
            initRen();
            agehama = [0, 0, 0];
            numMove = 0;
            koBi = BI_PASS;
            koMove = -1;
            passes = 0;
            prevMove = BI_PASS;
            
            status = STATUS_PLAY;            
            drawBoard();
        }
        
        private function initBoard():void {
            side = size + 2;
            boardSize = side * side;
            board = new Array(boardSize);
            biN = -side;
            biE = 1;
            biS = side;
            biW = -1;
            drCell = DR_BOARD_WIDTH / side;
            
            emptyList = new Array();
            var x:int, y:int;
            for (var i:int = 0; i < boardSize; i++) {
                x = bi2x(i);
                y = bi2y(i);
                if (x == 0 || x == side - 1 || y == 0 || y == side - 1) {
                    board[i] = STONE_OUTER;
                } else {
                    board[i] = STONE_EMPTY;
                    emptyList.push(i);
                }
            }
        }
        
        private function initMarkBoard():void {
            markBoard = new Array(boardSize);
            clearMarkBoard();
        }
        
        private function clearMarkBoard():void {
            for (var i:int = 0; i < boardSize; i++) {
                markBoard[i] = MARK_FREE;
            }            
        }
        
        private function markTerritory(bi:int, ter:Array):int {
            if (markBoard[bi] != MARK_FREE) {
                if (board[bi] == STONE_BLACK || board[bi] == STONE_WHITE) return board[bi];
                return STONE_SCORED;
            }
            if (board[bi] != STONE_EMPTY) {
                markBoard[bi] = MARK_EMPTY;
                return board[bi];
            }
            
            markBoard[bi] = MARK_EMPTY;
            ter.push(bi);
            var cn:int = markTerritory(bi + biN, ter);
            var ce:int = markTerritory(bi + biE, ter);
            var cs:int = markTerritory(bi + biS, ter);
            var cw:int = markTerritory(bi + biW, ter);
            var col:int = cn | ce | cs | cw;
            
            if ((col & STONE_MIXED) == STONE_MIXED) return TERRITORY_NEUTRAL;
            if ((col & STONE_BLACK) == STONE_BLACK) return TERRITORY_BLACK;
            if ((col & STONE_WHITE) == STONE_WHITE) return TERRITORY_WHITE;
            return TERRITORY_SCORED;
        }
        
        private function initRen():void {
            Ren.init(side);
        }
        
        private function updateRen(c:int, bi:int):int {
            var opp:int = c ^ STONE_MIXED;
            var adjs:Array = new Array();
            var opps:Array = new Array();
            
            switch (board[bi + biN]) {
                case c: adjs.push(bi + biN); break;
                case opp: opps.push(bi + biN); break;
            }
            switch (board[bi + biE]) {
                case c: adjs.push(bi + biE); break;
                case opp: opps.push(bi + biE); break;
            }
            switch (board[bi + biS]) {
                case c: adjs.push(bi + biS); break;
                case opp: opps.push(bi + biS); break;
            }
            switch (board[bi + biW]) {
                case c: adjs.push(bi + biW); break;
                case opp: opps.push(bi + biW); break;
            }
            
            if (adjs.length == 0) {
                Ren.create(c, bi);
            } else {
                var parId:int = Ren.rboard[adjs[0]];
                Ren.join(parId, bi);
                var sibId:int;
                for (var i:int = 1; i < adjs.length; i++) {
                    sibId = Ren.rboard[adjs[i]];
                    if (sibId == parId) continue;
                    Ren.merge(parId, sibId);
                    Ren.headArray[sibId] = Ren.L_END;
                }
            }
            
            return opps.length > 0 ? updateRenOpps(c, bi, opps) : 0;
        }
        
        private function updateRenOpps(c:int, bi:int, opps:Array):int {
            var caps:int = 0;
            var oppId:int;
            var oppHead:int;
            var next:int;
            
            for (var i:int = 0; i < opps.length; i++) {
                oppId = Ren.rboard[opps[i]];
                if (oppId == Ren.EMPTY || Ren.colorArray[oppId] == Ren.EMPTY) continue;
                oppHead = Ren.headArray[oppId];
                if ( !isAlive(oppHead) ) {
                    for (var dbi:int = oppHead; dbi != Ren.L_END; dbi = next) {
                        emptyList.push(dbi);
                        board[dbi] = STONE_EMPTY;
                        Ren.rboard[dbi] = Ren.EMPTY;
                        next = Ren.rlists[dbi];
                        Ren.rlists[dbi] = Ren.L_END;
                    }
                    Ren.colorArray[oppId] = Ren.EMPTY;
                    caps += Ren.sizeArray[oppId];
                    koBi = opps[i];
                }
            }
            return caps;
        }
        
        private function isAlive(head:int):Boolean {
            for (var bi:int = head; bi != Ren.L_END; bi = Ren.rlists[bi]) {
                if (board[bi + biN] == STONE_EMPTY || board[bi + biE] == STONE_EMPTY || 
                    board[bi + biS] == STONE_EMPTY || board[bi + biW] == STONE_EMPTY)
                    return true;
            }
            return false;
        }
        
        private function canCapture(c:int, bi:int):Boolean {
            var id:int = Ren.rboard[bi];
            if (id < 0) return false;
            return (Ren.colorArray[id] == c && !isAlive(Ren.headArray[id]));
        }
        
        private function isSuicide(c:int, bi:int):Boolean {
            var opp:int = c ^ STONE_MIXED;
            board[bi] = c;
            if ((board[bi + biN] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biN]])) ||
                (board[bi + biE] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biE]])) ||
                (board[bi + biS] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biS]])) ||
                (board[bi + biW] == opp && !isAlive(Ren.headArray[Ren.rboard[bi + biW]])) ) {
                board[bi] = STONE_EMPTY;
                return false;
            }
            if ((board[bi + biN] == STONE_EMPTY || 
                    board[bi + biN] == c && isAlive(Ren.headArray[Ren.rboard[bi + biN]])) ||
                (board[bi + biE] == STONE_EMPTY || 
                    board[bi + biE] == c && isAlive(Ren.headArray[Ren.rboard[bi + biE]])) ||
                (board[bi + biS] == STONE_EMPTY || 
                    board[bi + biS] == c && isAlive(Ren.headArray[Ren.rboard[bi + biS]])) ||
                (board[bi + biW] == STONE_EMPTY || 
                    board[bi + biW] == c && isAlive(Ren.headArray[Ren.rboard[bi + biW]])) ) {
                board[bi] = STONE_EMPTY;
                return false;
            }
            board[bi] = STONE_EMPTY;
            return true;
        }
        
        private function isLegal(c:int, bi:int):Boolean {
            if (board[bi] != STONE_EMPTY) return false;
            if (koBi == bi && koMove == numMove) return false;
            if (isSuicide(c, bi)) return false;
            return true;
        }
        
        private function computeScore():Number {
            var bs:Number = 0;
            var ws:Number = 0;
            var col:int;
            var ter:Array;
            var flag:Boolean = false;
            
            clearMarkBoard();
            for (var i:int = 0; i < emptyList.length; i++) {
                if (markBoard[emptyList[i]] != MARK_FREE) continue;
                ter = new Array();
                col = markTerritory(emptyList[i], ter);                
                if (col == TERRITORY_BLACK) bs += ter.length;
                else if (col == TERRITORY_WHITE) ws += ter.length;
                else flag = true;
            }
            var score:Number = (bs + agehama[STONE_BLACK]) - (ws + agehama[STONE_WHITE]) - komi;
            
            inform("対局終了 (" + (numMove + 1) + "手)\n", true);
            if (score == 0) inform("結果  持碁");
            else inform("結果  " + (score > 0 ? "黒" : "白") + Math.abs(score) + "目勝ち");
            inform("------------------------------------------------------------------");
            inform("    地  黒: " + bs + "目、 白: " + ws + "目");
            if (flag) inform("      ※所属が確定しない、または算出できない地点があります");
            informHama();
            
            return score;
        }
        
        private function playByMan(c:int, bi:int):void {
            if (status != STATUS_PLAY) return;
            if ( !play(c, bi) ) {
                inform("着手できない点です");
                return;
            } else if (status == STATUS_PLAY) {
                inform("次は" + (numMove + 1) + "手目: " + (nm2c(numMove + 1) == STONE_BLACK ? "黒" : "白") + "番\n", true);
            }
            if (status != STATUS_OVER) informHama();
        }
        
        private function play(c:int, bi:int):Boolean {
            prevMove = BI_PASS;
            if (bi == BI_PASS) {
                passes++;
                if (passes >= 2) {
                    status = STATUS_OVER;
                    computeScore();            
                }
                numMove++;
                drawBoard();
                return true;
            }
            
            if (isLegal(c, bi)) {
                numMove++;
                board[bi] = c;
                emptyList.splice(emptyList.indexOf(bi), 1);
                var cap:int = updateRen(c, bi);
                if (cap == 1 && 
                    board[bi + biN] != c && board[bi + biE] != c && board[bi + biS] != c && board[bi + biW] != c) {
                    koMove = numMove;
                }
                agehama[c] += cap;
                passes = 0;
                prevMove = bi;
                drawBoard();
                return true;
            }
            return false;
        }
        
        private function drawBoard():void {
            var offset:Number = drCell / 2;
            bcanvas.graphics.beginFill(0xEEEEEE);
            bcanvas.graphics.drawRect(0, 0, side * drCell, side * drCell);
            bcanvas.graphics.endFill();
            
            bcanvas.graphics.lineStyle(1, 0);
            for (var i:int = 0; i < size; i++) {
                bcanvas.graphics.moveTo((i + 1) * drCell + offset, drCell + offset);
                bcanvas.graphics.lineTo((i + 1) * drCell + offset, size * drCell + offset);
                bcanvas.graphics.moveTo(drCell + offset, (i + 1) * drCell + offset);
                bcanvas.graphics.lineTo(size * drCell + offset, (i + 1) * drCell + offset);
            }
            
            if (size == 9 || size == 13 || size == 19) {
                var si:int = size / 4 - 2;
                var bi:int;
                bcanvas.graphics.beginFill(0x000000);
                for (var st:int = 0; st < DR_STAR_POS[si].length; st++) {
                    bi = DR_STAR_POS[si][st];
                    bcanvas.graphics.drawCircle(bi2x(bi) * drCell + offset, bi2y(bi) * drCell + offset, DR_STAR_RAD);
                }
                bcanvas.graphics.endFill();
            }
            
            bcanvas.graphics.lineStyle(0, 0);
            for (var j:int = 0; j < boardSize; j++) {
                switch (board[j]) {
                    case STONE_BLACK:
                    case STONE_WHITE:
                        bcanvas.graphics.beginFill(DR_COLORS[board[j]]);
                        bcanvas.graphics.drawCircle(bi2x(j) * drCell + offset, 
                                                    bi2y(j) * drCell + offset, 
                                                    drCell / 2.1);
                        bcanvas.graphics.endFill();
                        if (j == prevMove) {
                            bcanvas.graphics.lineStyle(1.3, DR_COLORS[board[j] ^ STONE_MIXED]);
                            bcanvas.graphics.drawCircle(bi2x(j) * drCell + offset, 
                                                        bi2y(j) * drCell + offset, 
                                                        drCell / 3.5);
                            bcanvas.graphics.lineStyle(1, 0);
                        }
                        break;
                    case STONE_OUTER:                    
                    default:
                        break;
                }    
            }
        }
        
        private function createUI():void {
            var logo:TextField = new TextField();
            logo.x = DR_UI_LEFT;
            logo.y = DR_BOARD_TOP;
            logo.selectable = false;
            logo.text = " As I go ";
            logo.setTextFormat(new TextFormat("Arial", 30, 0xDEADBEEF, true, true, true));
            logo.width = 120;
            logo.height = 40;
            addChild(logo);
            Style.embedFonts = false;
            Style.fontName = "PF Ronda Seven";
            Style.fontSize = 15;
            // ボタン20
            var initBtn:PushButton = new PushButton(this, DR_UI_LEFT, DR_UI_TOP, "新規対局", onInitBtnClick);
            var passBtn:PushButton = new PushButton(this, DR_UI_LEFT, DR_UI_TOP + 200, "パス", onPassBtnClick);
            // 対局の設定
            var configPanel:Panel = new Panel(this, DR_UI_LEFT + 5, DR_UI_TOP + 35);
            configPanel.width = 110;
            configPanel.height = 120;
            // 碁盤
            Style.fontSize = 15;
            var sizeLabel:Label = new Label(configPanel, 5, 10, "碁盤(路)");
            Style.fontSize = 13;
            var sizeStep:NumericStepper = new NumericStepper(configPanel, 20, 30, onSizeStepChange);
            size = sizeStep.value = 9;
            sizeStep.width = 70;
            sizeStep.step = 1;
            sizeStep.minimum = 2;
            sizeStep.maximum = 19;
            // コミ
            Style.fontSize = 15;
            var komiLabel:Label = new Label(configPanel, 5, 70, "コミ(目)");
            Style.fontSize = 13;
            var komiStep:NumericStepper = new NumericStepper(configPanel, 20, 90, onKomiStepChange);
            komi = komiStep.value = 6.5;
            komiStep.width = 70;
            komiStep.step = 0.5;
            komiStep.minimum = -10.5;
            komiStep.maximum = 10.5;
            // 情報通知
            Style.fontSize = 14;
            text = new Text(this, DR_BOARD_LEFT, DR_BOARD_TOP + DR_BOARD_WIDTH + 10, null);
            text.text = VER_STRING + "\n\n";
            text.width = 430;
            text.height = 130;
            text.editable = false;
            text.selectable = false;
        }
        
        private function onBoardClick(e:MouseEvent):void {
            playByMan(nm2c(numMove + 1), xy2bi(int(e.localX / drCell), int(e.localY / drCell)));
        }
        
        private function onInitBtnClick(event:Event):void {
            status = STATUS_OVER;
            initGame();
            inform("対局開始: " + size + "路盤、 コミ" + komi + "目\n", true);
            inform("1手目: 黒番\n");
            informHama();
        }
        
        private function onPassBtnClick(event:Event):void {
            playByMan(nm2c(numMove + 1), BI_PASS);
        }
        
        private function onSizeStepChange(event:Event):void {
            var stepper:NumericStepper = event.currentTarget as NumericStepper;
            size = stepper.value;
        }
        
        private function onKomiStepChange(event:Event):void {
            var stepper:NumericStepper = event.currentTarget as NumericStepper;
            komi = stepper.value;
        }
        
        private function inform(msg:String, isReset:Boolean=false):void {
            if (isReset) text.textField.text = msg + "\n";
            else text.textField.appendText(msg + "\n");
        }
        
        private function informHama():void {
            inform("    ハマ 黒: " + agehama[STONE_BLACK] + "個、 白: " + agehama[STONE_WHITE] + "個");
        }
    }
}

class Ren
{    
    public static const OUTER:int = -2;
    public static const EMPTY:int = -1;
    public static const L_END:int = -1;
    public static const SOME_LIBS:int = 2;
    
    public static var nextId:int;
    public static var rboard:Array;
    public static var rlists:Array;
    
    public static var colorArray:Array;
    public static var sizeArray:Array;
    public static var headArray:Array;
    public static var tailArray:Array;
    
    public static var maxId:int;
    private static var boardSide:int;
    private static var boardSize:int;
    
    public static function init(bside:int):void
    {
        boardSide = bside;
        boardSize = boardSide * boardSide;
        maxId = (bside - 1) * (bside - 1) / 2;  // 多め
        
        nextId = 0;
        rboard = new Array(boardSize);
        rlists = new Array(boardSize);
        var x:int, y:int;
        for (var i:int = 0; i < boardSize; i++) {
            x = i % boardSide;
            y = int(i / boardSide);
            if (x == 0 || x == boardSide - 1 || y == 0 || y == boardSide - 1) {
                rboard[i] = OUTER;
            } else {
                rboard[i] = EMPTY;
            }
            rlists[i] = L_END;
        }
        
        colorArray = new Array(maxId);
        sizeArray = new Array(maxId);
        headArray = new Array(maxId);
        tailArray = new Array(maxId);
        for (i = 0; i < maxId; i++) colorArray[i] = EMPTY;
    }
    
    public static function create(c:int, bi:int):void
    {
        var id:int = nextId;
        rboard[bi] = id;
        rlists[bi] = L_END;
        colorArray[id] = c;
        sizeArray[id] = 1;
        tailArray[id] = headArray[id] = bi;
        
        nextId++;
        if (nextId >= maxId) {
            for (nextId = 0; colorArray[nextId] != EMPTY; nextId++) ;
        }
    }
    
    public static function join(rid:int, bi:int):void
    {
        rboard[bi] = rid;
        rlists[tailArray[rid]] = bi;
        rlists[bi] = L_END;        
        sizeArray[rid]++;
        tailArray[rid] = bi;
    }
    
    public static function merge(rid1:int, rid2:int):void
    {
        for (var bi:int = headArray[rid2]; bi != L_END; bi = rlists[bi]) {
            rboard[bi] = rid1;
        }
        sizeArray[rid1] += sizeArray[rid2];
        rlists[tailArray[rid1]] = headArray[rid2];
        tailArray[rid1] = tailArray[rid2];
        headArray[rid2] = L_END;
        colorArray[rid2] = EMPTY;
    }
}