forked from: Tic-tac-toe ○×ゲーム Level 2, 光と影
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: ランダムに取る * [/] レベル選択機能 * [/] リプレイ時に左上に○と×が重なって表示されるバグを解決 * [/] 勝敗を大きく表示する * [/] リプレイ機能
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);
}
}