forked from: Tic-tac-toe ○×ゲーム Level 2, 光と影

by nayu forked from Tic-tac-toe ○×ゲーム Level 2, 光と影 (diff: 4)
Tic-tac-toe, ○×ゲーム, 三目並べ.
*  since 2010-06-24
*
* TODO
* [ ] ダブルリーチがあれば取る
* [ ] 防御しながらリーチになる手を優先して取る
* [ ] ボタンにフォーカスしたら目立たせる(白っぽくする?)
* [ ] 勝敗がついた行・列を強く光らせる
* [ ] 勝敗の表示を、プレイヤーが勝ったら輝かせる・敗けたら溶けて落ちるようにする
* [ ] 枡を取る時の表現を「ぼわっと光る」ものにする
* [ ] 先攻を×にする
* [ ] 得点記録
* [ ] Flash Builderから実行した時、意図せず拡大されて下にはみ出すのを解決する
* [ ] 広告を入れる
* [ ] BetweenAS3を使ってみる    http://www.be-interactive.org/index.php?itemid=472
*                                http://wonderfl.kayac.com/code/7e3f080227a081bafbcdb09dd898b27c057cbf76
* [ ] ○か×かを選べるようにする
* [ ] 日本語版 (getText?)
* [ ] 中国語版
* [ ] インドネシア語版
* [ ] level 1とlevel 2の強さの差が大き過ぎる
* [ ] ダイアログを、もうちょっと字の大きいものにしたい
* [/] 枠○×を「ぼわっと光った線」で描画する
* [/] ダイアログに影をつける
* [/] level 2: 考えて取る
* [/] 引き分け時の処理
* [/] level 1: ランダムに取る
* [/] レベル選択機能
* [/] リプレイ時に左上に○と×が重なって表示されるバグを解決
* [/] 勝敗を大きく表示する
* [/] リプレイ機能
♥0 | Line 394 | Modified 2010-10-18 09:55:46 | MIT License
play

ActionScript3 source code

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

// forked from hermit's Tic-tac-toe ○×ゲーム Level 2, 光と影
/**
 * Tic-tac-toe, ○×ゲーム, 三目並べ.
 *  since 2010-06-24
 *
 * TODO
 * [ ] ダブルリーチがあれば取る
 * [ ] 防御しながらリーチになる手を優先して取る
 * [ ] ボタンにフォーカスしたら目立たせる(白っぽくする?)
 * [ ] 勝敗がついた行・列を強く光らせる
 * [ ] 勝敗の表示を、プレイヤーが勝ったら輝かせる・敗けたら溶けて落ちるようにする
 * [ ] 枡を取る時の表現を「ぼわっと光る」ものにする
 * [ ] 先攻を×にする
 * [ ] 得点記録
 * [ ] Flash Builderから実行した時、意図せず拡大されて下にはみ出すのを解決する
 * [ ] 広告を入れる
 * [ ] BetweenAS3を使ってみる    http://www.be-interactive.org/index.php?itemid=472
 *                                http://wonderfl.kayac.com/code/7e3f080227a081bafbcdb09dd898b27c057cbf76
 * [ ] ○か×かを選べるようにする
 * [ ] 日本語版 (getText?)
 * [ ] 中国語版
 * [ ] インドネシア語版
 * [ ] level 1とlevel 2の強さの差が大き過ぎる
 * [ ] ダイアログを、もうちょっと字の大きいものにしたい
 * [/] 枠○×を「ぼわっと光った線」で描画する
 * [/] ダイアログに影をつける
 * [/] level 2: 考えて取る
 * [/] 引き分け時の処理
 * [/] level 1: ランダムに取る
 * [/] レベル選択機能
 * [/] リプレイ時に左上に○と×が重なって表示されるバグを解決
 * [/] 勝敗を大きく表示する
 * [/] リプレイ機能
 */

package {
    import flash.display.Sprite;
    import flash.events.MouseEvent;

    [SWF(backgroundColor = 0)]

    public class Main extends Sprite
    {
        private var dialog:Dialog;
        private const board:BoardLevel2 = new BoardLevel2;

        public function Main()
        {
            game = this;
            addChild(board);
            dialog = new Dialog(this, start);
        }

        private function start(e:MouseEvent):void
        {
            board.clear();
            if (dialog.first)
                board.myTurn();
            board.enableBoxButtons();
        }

        public function replay():void
        {
            dialog.visible = true;
        }

        public function get level():uint { return dialog.level }
    }
}

var game:Main;

// ゲーム本体 ///////////////////////////////////////////////

/** thickness 枠線、○、×の太さ */
const THICKNESS:uint = 10;
/**
 * gridの中のboxのpadding.
 *
 * 偶数でなければいけない。
 */
const P:uint = 2;

/**
 * 桝目の大きさ.
 *
 * (stage.stageWidth - 5*2 - 10) / 3
 */
const UNIT:uint = 148;

import flash.display.Sprite;

// TODO: Mainに入れる?
class Board extends Sprite
{
    protected const boxes:Array = [[], [], []];
    private const resultMsg:ResultMsg = new ResultMsg;

    public function Board()
    {
        x = y = 5;    // padding... 座標をここに書くのは変

        addChild(new Grid);

        for (var i:uint = 0; i < 3; ++i)
            for (var j:uint = 0; j < 3; ++j)
                with (addChild(boxes[i][j] = new Box)) {
                    x = THICKNESS + UNIT * i + P;
                    y = THICKNESS + UNIT * j + P;
                }

        addChild(resultMsg);
    }

    public function clear():void
    {
        for each (var line:Array in boxes)
            for each (var box:Box in line)
                box.clear();
    }

    public function enableBoxButtons():void
    {
        for each (var line:Array in boxes)
            for each (var box:Box in line)
                if (box.status == Box.NONE)
                    box.enableButton();
    }

    public function disableBoxButtons():void
    {
        for each (var line:Array in boxes)
            for each (var box:Box in line)
                if (box.status == Box.NONE)
                    box.disableButton();
    }

//    /**
//     * コンピュータが一手進める
//     */
//    public function myTurn():void
//    {
//        switch (game.level) {
//            case 0:     level0(); break;
//            case 1:     level1(); break;
//            default: Level2.level2(boxes);
//        }
//    }

    // 枡を取るアルゴリズム ////////////

    /** Level 0: 端から取っていく */
    protected function level0():void
    {
        for each (var line:Array in boxes)
            for each (var box:Box in line)
                if (box.status == Box.NONE) {
                    box.mark();
                    return;
                }
    }

    /** Level 1: ランダムに取る */
    protected function level1():void
    {
        var box:Box;
        do {
            var rx:uint = Math.floor(Math.random() * 3);    // 0, 1, 2のどれかの乱数
            var ry:uint = Math.floor(Math.random() * 3);
            box = boxes[rx][ry];
        } while (box.status != Box.NONE);

        box.mark();
    }

    // 勝敗判定関連 ////////////

    /**
     * 勝敗判定.
     *
     * 勝敗をbox.status形式で返す。
     */
    private function check():uint
    {
        for (var i:uint = 0; i < 3; ++i) {
            if (boxes[i][0].status != Box.NONE &&
                boxes[i][0].status == boxes[i][1].status &&
                boxes[i][0].status == boxes[i][2].status)
                return boxes[i][0].status;
            if (boxes[0][i].status != Box.NONE &&
                boxes[0][i].status == boxes[1][i].status &&
                boxes[0][i].status == boxes[2][i].status)
                return boxes[0][i].status;
        }
        if (boxes[0][0].status != Box.NONE &&
            boxes[0][0].status == boxes[1][1].status &&
            boxes[0][0].status == boxes[2][2].status)
            return boxes[0][0].status;
        if (boxes[0][2].status != Box.NONE &&
            boxes[0][2].status == boxes[1][1].status &&
            boxes[0][2].status == boxes[2][0].status)
            return boxes[0][2].status;

        for each (var line:Array in boxes)
            for each (var box:Box in line)
                if (box.status == Box.NONE)        // まだ空きがある
                    return Box.NONE;            //    勝敗がついてない

        return Box.DRAWN;
    }

    /**
     * 判定して、勝敗がついていたら結果を表示する.
     *
     * 続きがあればtrueを返す。
     * 勝敗がついたらfalseを返す。
     *
     * メソッド名がよくない。
     */
    public function checkAndNext():Boolean
    {
        var result:uint = check();
        switch (result) {
            case Box.NONE:
                return true;

            case Box.COMPUTER:
                resultMsg.display('You lost!');
                break;
            case Box.PLAYER:
                resultMsg.display('You won!');
                break;
            case Box.DRAWN:
                resultMsg.display('Drawn game...', 70);
        }
        disableBoxButtons();
        return false;
    }
}

/** BoardにLevel 2の思考ルーチンを追加するクラス */
class BoardLevel2 extends Board
{
    /** コンピュータが一手進める */
    public function myTurn():void
    {
        switch (game.level) {
            case 0:     level0(); break;
            case 1:     level1(); break;
            default: level2();
        }
    }

    /** Level 2: 考えて取る */
    public function level2():void
    {
        if (tryKimarite())                        // 決まり手があれば取る
            return;
        if (tryGuard())
            return;                                // 防ぐべき場所があれば取る

        // TODO: ダブルリーチがあれば取る

        if (tryCenter())    // 中央は4通り
            return;
        if (tryCorner())    // 角は3通り
            return;
        // 辺は2通り

        level1();
    }

    /**
     * 決まり手があれば取ってtrueを返す。
     * なければfalseを返す。
     */
    private function tryKimarite():Boolean
    {
        return tryCorner(Box.COMPUTER) ||
                tryCenter(Box.COMPUTER) ||
                trySide(Box.COMPUTER);
    }

    /**
     * 四隅に取るべき手があれば、取ってtrueを返す。
     * 見つからなければfalseを返す。
     *
     * @param s Box.COMPUTERなら、決まり手を探す。
     *            Box.PLAYERなら、防ぎ手を探す。
     *            Box.NONE(デフォルト)なら、空いていれば取る。
     */
    private function tryCorner(s:uint = Box.NONE):Boolean
    {
        if ((boxes[0][0].status == Box.NONE) &&
            (s == Box.NONE ||
             (boxes[0][1].status == s && boxes[0][2].status == s) ||
             (boxes[1][0].status == s && boxes[2][0].status == s) ||
             (boxes[1][1].status == s && boxes[2][2].status == s))) {
            boxes[0][0].mark();
            return true;
        }
        if ((boxes[0][2].status == Box.NONE) &&
            (s == Box.NONE ||
             (boxes[0][0].status == s && boxes[0][1].status == s) ||
             (boxes[1][2].status == s && boxes[2][2].status == s) ||
             (boxes[1][1].status == s && boxes[2][0].status == s))) {
            boxes[0][2].mark();
            return true;
        }
        if ((boxes[2][0].status == Box.NONE) &&
            (s == Box.NONE ||
             (boxes[0][0].status == s && boxes[1][0].status == s) ||
             (boxes[2][1].status == s && boxes[2][2].status == s) ||
             (boxes[1][1].status == s && boxes[0][2].status == s))) {
            boxes[2][0].mark();
            return true;
        }
        if ((boxes[2][2].status == Box.NONE) &&
            (s == Box.NONE ||
             (boxes[2][0].status == s && boxes[2][1].status == s) ||
             (boxes[0][2].status == s && boxes[1][2].status == s) ||
             (boxes[0][0].status == s && boxes[1][1].status == s))) {
            boxes[2][2].mark();
            return true;
        }
        return false;
    }

    /**
     * 中央が取るべき手であれば、取ってtrueを返す。
     * そうでなければfalseを返す。
     *
     * @param s Box.COMPUTERなら、決まり手を探す。
     *            Box.PLAYERなら、防ぎ手を探す。
     *            Box.NONE(デフォルト)なら、空いていれば取る。
     */
    private function tryCenter(s:uint = Box.NONE):Boolean
    {
        if ((boxes[1][1].status == Box.NONE) &&
            (s == Box.NONE ||
             (boxes[0][0].status == s && boxes[2][2].status == s) ||
             (boxes[0][1].status == s && boxes[2][1].status == s) ||
             (boxes[2][0].status == s && boxes[0][2].status == s) ||
             (boxes[1][0].status == s && boxes[1][2].status == s))) {
            boxes[1][1].mark();
            return true;
        }
        return false;
    }

    /**
     * 辺に取るべき手があれば、取ってtrueを返す。
     * 見つからなければfalseを返す。
     *
     * @param s Box.COMPUTERなら、決まり手を探す。
     *            Box.PLAYERなら、防ぎ手を探す。
     */
    private function trySide(s:uint):Boolean
    {
        if ((boxes[0][1].status == Box.NONE) &&
            ((boxes[0][0].status == s && boxes[0][2].status == s) ||
             (boxes[1][1].status == s && boxes[2][1].status == s))) {
            boxes[0][1].mark();
            return true;
        }
        if ((boxes[2][1].status == Box.NONE) &&
            ((boxes[2][0].status == s && boxes[2][2].status == s) ||
             (boxes[0][1].status == s && boxes[1][1].status == s))) {
            boxes[2][1].mark();
            return true;
        }

        if ((boxes[1][0].status == Box.NONE) &&
            ((boxes[0][0].status == s && boxes[2][0].status == s) ||
             (boxes[1][1].status == s && boxes[1][2].status == s))) {
            boxes[1][0].mark();
            return true;
        }
        if ((boxes[1][2].status == Box.NONE) &&
            ((boxes[0][2].status == s && boxes[2][2].status == s) ||
             (boxes[1][0].status == s && boxes[1][1].status == s))) {
            boxes[1][2].mark();
            return true;
        }

        return false;
    }

    private function tryGuard():Boolean
    {
        return tryCorner(Box.PLAYER) ||
                tryCenter(Box.PLAYER) ||
                trySide(Box.PLAYER);
    }
}

import flash.display.Shape;
import flash.filters.GlowFilter;

const lightGlowFilters:Array = [new GlowFilter(0xffffff)];

/** 枠 */
class Grid extends Shape
{
    function Grid():void
    {
        //filters = [new BlurFilter(2, 2)];
        filters = lightGlowFilters;
        with (graphics) {
            lineStyle(THICKNESS, 0xffffff);
            moveTo(THICKNESS/2 + UNIT,     THICKNESS/2);
            lineTo(THICKNESS/2 + UNIT,     THICKNESS/2 + UNIT * 3);
            moveTo(THICKNESS/2 + UNIT * 2, THICKNESS/2);
            lineTo(THICKNESS/2 + UNIT * 2, THICKNESS/2 + UNIT * 3);
            moveTo(THICKNESS/2,            THICKNESS/2 + UNIT);
            lineTo(THICKNESS/2 + UNIT * 3, THICKNESS/2 + UNIT);
            moveTo(THICKNESS/2,            THICKNESS/2 + UNIT * 2);
            lineTo(THICKNESS/2 + UNIT * 3, THICKNESS/2 + UNIT * 2);
        }
    }
}

import flash.events.MouseEvent;

/**
 * 桝目.
 *
 * TODO
 * [ ] buttonModeをクラス外から直接変更できない様にする
 */
class Box extends Sprite
{
    /** 枡の状態、または勝敗を表す定数 */
    public static const NONE:uint      = 0;
    public static const COMPUTER:uint = 1;
    public static const PLAYER:uint      = 2;
    public static const DRAWN:uint      = 3;

    /** ○か×(光つき)を描く領域 */
    private var shape:Shape = new Shape;

    public var status:uint = NONE;
    function Box()
    {
        shape.filters = lightGlowFilters;
        addChild(shape);

        clear();

        //addEventListener(Event.ADDED, function():void {
        //    width = height = UNIT - T;
        //    msgArea.text = width + ', ' + height;
        //});
    }

    public function clear():void
    {
        status = NONE;

        shape.graphics.clear();

        with (graphics) {
            lineStyle(0);
            beginFill(0);
            drawRect(0, 0, UNIT - THICKNESS - 1 - P * 2, UNIT - THICKNESS - 1 - P * 2);
        }
    }

    public function enableButton():void
    {
        buttonMode = true;
        addEventListener(MouseEvent.CLICK, clicked);
    }

    public function disableButton():void
    {
        buttonMode = false;
        removeEventListener(MouseEvent.CLICK, clicked);
    }

    /** プレイヤーがこの枡を取る */
    private function clicked(event:MouseEvent):void
    {
        disableButton();
        status = PLAYER;
        with (shape.graphics) {
            lineStyle(THICKNESS, 0xffffff);
            drawCircle(width / 2, height / 2, width / 2 - THICKNESS - THICKNESS + 1);
        }

        with (BoardLevel2(parent))
            if (checkAndNext())
                myTurn();
    }

    /** コンピュータがこの枡を取る */
    public function mark():void
    {
        disableButton();
        status = COMPUTER;
        // draw cross "X"
        with (shape.graphics) {
            lineStyle(THICKNESS, 0xffffff);
            moveTo(THICKNESS * 2,             THICKNESS * 2);
            lineTo(UNIT - THICKNESS * 3 - P, UNIT - THICKNESS * 3 - P);
            moveTo(UNIT - THICKNESS * 3 - P, THICKNESS * 2);
            lineTo(THICKNESS * 2,             UNIT - THICKNESS * 3 - P);
        }

        Board(parent).checkAndNext();
    }
}

import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
import flash.utils.Timer;
import flash.events.TimerEvent;

class ResultMsg extends TextField
{
    private const timer:Timer = new Timer(2000, 1);

    function ResultMsg()
    {
        selectable = false;
        defaultTextFormat =
            new TextFormat("Arial", null, 0xffffff);
        autoSize = TextFieldAutoSize.LEFT;
        filters = [new GlowFilter(0)]; // 周りをぼわっと黒くする

        timer.addEventListener(TimerEvent.TIMER_COMPLETE, closeAndReplay);
    }

    public function display(str:String, size:uint = 90):void
    {
        text = str;
        setTextFormat(new TextFormat(null, size));

        x = (parent.width  - width)  / 2;
        y = (parent.height - height) / 2;

        visible = true;

        timer.start();
    }

    private function closeAndReplay(e:TimerEvent):void
    {
        visible = false;

        game.replay();
    }
}

///////////////////////////////////////////////////////////////////////////////

import com.bit101.components.Window;
import com.bit101.components.Label;
import com.bit101.components.RadioButton;
import com.bit101.components.PushButton;
import flash.events.Event;
import flash.filters.DropShadowFilter;

class Dialog extends Window
{
    private var _level:uint = 2;
    /** ゲームの難しさ */
    public function get level():uint { return _level }

    private var _first:Boolean = false;
    /** コンピュータが先攻かどうか */
    public function get first():Boolean { return _first }

    public function Dialog(parent:Sprite, f:Function)
    {
        super(parent, 0, 0, 'Tic-tac-toe');
        width = 120;
        height = 130;
        x = (stage.stageWidth  - width)  / 2;
        y = (stage.stageHeight - height) / 2;
        //color = 0;    // これだけだと見にくい
        filters = [new DropShadowFilter];

        new Label(this, 10, 30, 'Level');
        var l0:RadioButton = new RadioButton(this, 40, 34, '0', false, function (e:Event):void { _level = 0 });
        var l1:RadioButton = new RadioButton(this, 65, 34, '1', false, function (e:Event):void { _level = 1 });
        var l2:RadioButton = new RadioButton(this, 90, 34, '2', true,  function (e:Event):void { _level = 2 });
        l0.groupName = l1.groupName = l2.groupName = 'level';

        new Label(this, 10, 60, 'Who marks first?');
        var f0:RadioButton = new RadioButton(this, 12, 79, 'You', true,  function (e:Event):void { _first = false });
        var f1:RadioButton = new RadioButton(this, 52, 79, 'Me',  false, function (e:Event):void { _first = true });
        f0.groupName = f1.groupName = '1st';

        var b:PushButton = new PushButton(this, 10, 100, 'Go !', function (e:Event):void { visible = false });
        b.width = 40;
        b.x = (width - b.width) / 2;
        b.addEventListener(MouseEvent.CLICK, f);
    }
}