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
* [ ] ○か×かを選べるようにする
* [/] level 2: 考えて取る
* [/] 引き分け時の処理
* [/] level 1: ランダムに取る
* [/] レベル選択機能
* [/] リプレイ時に左上に○と×が重なって表示されるバグを解決
* [/] 勝敗を大きく表示する
* [/] リプレイ機能
/**
* Copyright hermit ( http://wonderfl.net/user/hermit )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/dxps
*/
// forked from hermit's Tic-tac-toe ○×ゲーム
/**
* 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
* [ ] ○か×かを選べるようにする
* [/] 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);
//addChild(msgArea);
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 T:uint = 10;
/**
* 桝目の大きさ.
*
* (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()
{
drawGrid();
for (var i:uint = 0; i < 3; ++i)
for (var j:uint = 0; j < 3; ++j) {
boxes[i][j] = new Box;
boxes[i][j].x = T + UNIT * i;
boxes[i][j].y = T + UNIT * j;
addChild(boxes[i][j]);
}
addChild(resultMsg);
}
public function clear():void
{
for each (var line:Array in boxes)
for each (var box:Box in line)
box.clear();
}
/**
* 枠を描く
*/
private function drawGrid():void
{
x = y = 5; // padding... 座標をここに書くのは変
//filters = [new BlurFilter(2, 2)];
//filters = [new GlowFilter(0xffffff)];
with (graphics) {
lineStyle(T, 0xffffff);
moveTo(T/2 + UNIT, T/2);
lineTo(T/2 + UNIT, T/2 + UNIT * 3);
moveTo(T/2 + UNIT * 2, T/2);
lineTo(T/2 + UNIT * 2, T/2 + UNIT * 3);
moveTo(T/2, T/2 + UNIT);
lineTo(T/2 + UNIT * 3, T/2 + UNIT);
moveTo(T/2, T/2 + UNIT * 2);
lineTo(T/2 + UNIT * 3, T/2 + UNIT * 2);
}
}
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;
}
}
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 (getKimarite()) // 決まり手があれば取る
return;
if (getGuard())
return; // 防ぐべき場所があれば取る
// TODO: ダブルリーチがあれば取る
if (boxes[1][1].status == Box.NONE) { // 中央を取る
boxes[1][1].mark();
return;
}
level1();
}
/**
* 決まり手があれば取ってtrueを返す。
* なければfalseを返す。
*/
private function getKimarite():Boolean
{
if (tryCorner(Box.COMPUTER) ||
tryCenter(Box.COMPUTER) ||
trySide(Box.COMPUTER))
return true;
return false;
}
/**
* 四隅に取るべき手があれば、取ってtrueを返す。
* 見つからなければfalseを返す。
*
* @param s Box.COMPUTERなら、決まり手を探す。
* Box.PLAYERなら、防ぎ手を探す。
*/
private function tryCorner(s:uint):Boolean
{
if ((boxes[0][0].status == 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) &&
((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) &&
((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) &&
((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なら、防ぎ手を探す。
*/
private function tryCenter(s:uint):Boolean
{
if ((boxes[1][1].status == 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 getGuard():Boolean
{
if (tryCorner(Box.PLAYER) ||
tryCenter(Box.PLAYER) ||
trySide(Box.PLAYER))
return true;
return false;
}
}
import flash.events.MouseEvent;
/**
* 桝目, grid.
*
* 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;
public var status:uint = NONE;
function Box()
{
clear();
//addEventListener(Event.ADDED, function():void {
// width = height = UNIT - T;
// msgArea.text = width + ', ' + height;
//});
}
public function clear():void
{
status = NONE;
with (graphics) {
lineStyle(0);
beginFill(0);
drawRect(0, 0, UNIT - T - 1, UNIT - T - 1);
}
}
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 (graphics) {
lineStyle(T, 0xffffff);
beginFill(0);
drawCircle(width / 2, height / 2, width / 2 - T - T);
}
with (BoardLevel2(parent))
if (checkAndNext())
myTurn();
}
/**
* コンピュータがこの枡を取る
*/
public function mark():void
{
disableButton();
status = COMPUTER;
// draw cross "X"
with (graphics) {
lineStyle(T, 0xffffff);
moveTo(T * 2, T * 2);
lineTo(UNIT - T * 3, UNIT - T * 3);
moveTo(UNIT - T * 3, T * 2);
lineTo(T * 2, UNIT - T * 3);
}
Board(parent).checkAndNext();
}
}
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.filters.GlowFilter;
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, next);
}
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 next(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;
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; // これだけだと見にくい
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);
}
}