flash on 2010-3-16

by tenasaku
ちょっとしたパズルの実装
---------------------------------------------
右側と下側の丸いボタンを押すとその行またはカラムが
ひっくり返ります (順序反転および明暗逆転)
これは 4x4 ですが, 3x3 のバージョンは
最近市販されているある立体パズルと数学的に
同型です.  この 4x4 版は 機構的にみて立体パズル
として実現するのは無理なのではないかと思います.
---------------------------------------------
by tenasaku: 2010年3月16日火曜日
ロジックとしてはこれでOKだが娯楽としては全然だめ
もう少し遊ぶタノシミを演出せねば...
---------------------------------------------
♥0 | Line 238 | Modified 2010-03-16 23:40:19 | MIT License
play

ActionScript3 source code

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

// ちょっとしたパズルの実装
// ---------------------------------------------
// 右側と下側の丸いボタンを押すとその行またはカラムが
// ひっくり返ります (順序反転および明暗逆転)
// これは 4x4 ですが, 3x3 のバージョンは
// 最近市販されているある立体パズルと数学的に
// 同型です.  この 4x4 版は 機構的にみて立体パズル
// として実現するのは無理なのではないかと思います.
// ---------------------------------------------
// by tenasaku: 2010年3月16日火曜日
// ロジックとしてはこれでOKだが娯楽としては全然だめ
// もう少し遊ぶタノシミを演出せねば...
// ---------------------------------------------

package {
	import flash.display.*;
	import flash.events.*;
	public class Main extends Sprite {

		private const ROWS:int = 4; // パネルの行数
		private const COLS:int = 4; // パネルのカラム数
		private const BUTTONS:int = ROWS+COLS; // ボタンの個数
		private const PANELS:int = ROWS*COLS;
		private var perm:Array;
		private var panel:Array;
		private var btn:Array;

		// エントリ・ポイント
		public function Main():void {
			var i:int;
			// 長さ ROWS*COLS の順列.  最初はtrivialな順列とする.
			perm = new Array;
			for ( i = 0 ; i < PANELS ; ++ i ) {
				perm[i] = new Number(i);
			}
			// ROWS行 x COLS列 のパネルを生成
			panel = new Array;
			for ( i = 0 ; i < PANELS ; ++ i ) {
				panel[i] = new MyPanel();
				this.addChild(panel[i]);
			}
			// BUTTONS 個のボタンを生成
			btn = new Array;
			for ( i = 0 ; i < BUTTONS ; ++ i ) {
				btn[i] = new MyButton();
				this.addChild(btn[i]);
			}
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			drawButtons();
			scramble();
			// マウスイベントはボタンだけが受けつける
			for ( i = 0 ; i < BUTTONS ; ++ i ) {
				btn[i].addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
				btn[i].addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
				btn[i].addEventListener(MouseEvent.MOUSE_OUT, allButtonUp);
			}
			this.graphics.beginFill(0xffaa22);
			this.graphics.drawRect(0,0,400,400);
			this.graphics.endFill();
			this.x = (465 - this.width)/2;
			this.y = (465 - this.height)/2;
		} // function Main の終わり

		// マウスポインタが外れたときはすべてのボタンを上げる
		private function allButtonUp(e:MouseEvent):void {
			var i:int;
			for ( i = 0 ; i < BUTTONS ; ++ i ) {
				btn[i].up();
			}
		}
		// マウスボタンが押されたとき
		private function onMouseDown(e:MouseEvent):void {
			var i:int = whichButton(e.stageX-this.x,e.stageY-this.y);
			if ( i >= 0 ) { btn[i].down(); }
		}
		// マウスボタンが離されたとき
		private function onMouseUp(e:MouseEvent):void {
			var i:int = whichButton(e.stageX-this.x,e.stageY-this.y);
			if ( i >= 0 ) {
				if ( btn[i].isDown() ) {
					if ( i < COLS ) {
						changeColumn( i );
					} else if ( i < COLS + ROWS ) {
						changeRow( i - COLS );
					}
					btn[i].up();
				}
			}
		}

		// その座標はどのボタンの位置か番号で返す (ボタン上でなければ -1 を返す)
		private function whichButton(X:Number,Y:Number):int {
			var r:int = -1;
			var i:int;
			for ( i = 0 ; i < BUTTONS ; ++ i ) {
				if ( (X >= btn[i].x)&&(X<btn[i].x+btn[i].width) 
					&& (Y >= btn[i].y)&&(Y<btn[i].y+btn[i].height) ) {
					r = i;
					break;
				}
			}
			return r;
		}

		// ある行を変換する (左右反転&パネル裏返し)
		private function changeRow(row:int):void {
			var col:int;
			if ( ( row >= 0 ) && ( row < ROWS ) ) {
				for ( col = 0 ; col < Math.floor(COLS/2) ; ++ col ) {
					swap( row*COLS+col, (row+1)*COLS-1-col );
					var flag:Boolean = panel[row*COLS+col].reversed;
					panel[row*COLS+col].reversed = !panel[(row+1)*COLS-1-col].reversed;
					panel[(row+1)*COLS-1-col].reversed = !flag;
				}
				drawPanels();
			}
		}

		// ある列を変換する (上下反転&パネル裏返し)
		private function changeColumn(col:int):void {
			var row:int;
			if ( ( col >= 0 ) && ( col < COLS ) ) {
				for ( row = 0 ; row < Math.floor(ROWS/2) ; ++ row ) {
					swap( row*COLS+col, (ROWS-row-1)*COLS+col );
					var flag:Boolean = panel[row*COLS+col].reversed;
					panel[row*COLS+col].reversed = !panel[(ROWS-row-1)*COLS+col].reversed;
					panel[(ROWS-row-1)*COLS+col].reversed = !flag;
				}
				drawPanels();
			}
		}

		// 順列の二つの要素を入れ替える
		private function swap(i:int,j:int):void {
			if ( ( i >= 0 ) && ( i < ROWS*COLS ) ) {
				if ( ( j >= 0 ) && ( j < ROWS*COLS ) ) {
					if ( i != j ) {
						var k:Number = perm[i];
						perm[i] = perm[j];
						perm[j] = k;
					}
				}
			}
		}

		// 現在の状態でパネルを描画
		private function drawPanels():void {
			var i:int;
			for ( i = 0 ; i < ROWS*COLS ; ++ i ) {
				panel[i].letter = String(int(perm[i])+1);
				panel[i].x = (i%COLS)*MyPanel.WIDTH;
				panel[i].y = Math.floor(i/COLS)*MyPanel.HEIGHT;
				panel[i].draw();
			}
		}

		// ボタンを描画
		private function drawButtons():void {
			var i:int;
			for ( i = 0 ; i < COLS ; ++ i ) {
				btn[i].y = (MyPanel.HEIGHT-btn[i].height)/2 + ROWS*MyPanel.HEIGHT;
				btn[i].x = (MyPanel.WIDTH-btn[i+COLS].width)/2 + i*MyPanel.WIDTH;
			}
			for ( i = 0; i < ROWS ; ++ i ) {
				btn[i+COLS].x = (MyPanel.WIDTH-btn[i+COLS].width)/2 + COLS*MyPanel.WIDTH;
				btn[i+COLS].y = (MyPanel.HEIGHT-btn[i+COLS].height)/2 + i*MyPanel.HEIGHT;
			}
		}

		// パズルが完成した状態の時だけ true を返す
		// 作ったけど使ってないわ (2010-03-16)
		private function puzzleComplete():Boolean {
			var i:int = 0;
			var soFar:Boolean = true;
			while ( i < ROWS*COLS ) {
				if ( ( perm[i] != i ) || ( panel[i].revered ) ) {
					soFar = false;
					break;
				}
				++i;
			}
			return (soFar);
		}

		// パネルをかき回す = 問題を作る
		private function scramble():void {
			// n はランダムな変換の回数.  少な過ぎず多過ぎず...
			var n:int = 4 + Math.floor(Math.random()*(ROWS+COLS)*1.99999999);
			var i:int = 0;
			while (i < n) {
				var b:int = Math.floor(Math.random()*(ROWS+COLS)*0.99999999);
				if ( b < COLS ) { changeColumn(b); }
				else { changeRow(b-COLS); }
				++ i ;
			}
		}

	} // class Main の終わり

} // package の終わり


// 個々のパネルはこのクラスのインスタンスである
// このクラスの定義をイジればミテクレの変更が可能
import flash.text.*;
class MyPanel extends flash.display.Sprite {
	public static const WIDTH:int = 80;
	public static const HEIGHT:int = 80;
	public static const PADDING:int = 5;
	private static const HEADcolor:uint = 0x000080;
	private static const TAILcolor:uint = 0x00ff80;
	public var letter:String;
	public var reversed:Boolean;
	private var _tf:TextField;
	private static const _fmt:TextFormat = new TextFormat(
		"Palatino",	// font name
		36,			// font size
		0xff0000,	// color
		false,		// bold ?
		false,		// italic ?
		false,		// underline ?
		null,			// link URL
		null,			// link target
		TextFormatAlign.CENTER, // align
		null,			// left margin
		null,			// right margin
		null,			// indent
		null			// leading
	);
	// コンストラクタ
 	public function MyPanel():void {
		this.letter = "?";
		this.reversed = false;
		_tf = new TextField();
		_tf.selectable = false;
		_tf.background = false;
		_tf.width = WIDTH-PADDING;
		_tf.height = HEIGHT-PADDING;
		addChild(_tf);
		this.draw();
	}
	// 再描画
	public function draw():void {
		var c:uint = (this.reversed)?TAILcolor:HEADcolor;
		this.graphics.clear();
		this.graphics.beginFill(c);
		this.graphics.drawRect(PADDING/2,PADDING/2,WIDTH-PADDING,HEIGHT-PADDING);
		this.graphics.endFill();
		_tf.text = this.letter;
		_tf.setTextFormat(_fmt);
		_tf.x = PADDING/2;
		_tf.y = PADDING/2;
	}
}

// プッシュボタン(には見えない)
class MyButton extends flash.display.Sprite {
	private static const W:Number = (MyPanel.WIDTH-MyPanel.PADDING)/2;
	private static const H:Number = (MyPanel.HEIGHT-MyPanel.PADDING)/2;
	private static const BACKcolor:uint = 0x000000;
	private static const DOWNcolor:uint = 0xff6666;
	private static const UPcolor:uint = 0xffffff;
	private var pressed:Boolean;
	public function MyButton():void {
		this.up();
	}
	private function drawBG():void {
		this.graphics.clear();
		this.graphics.beginFill(BACKcolor);
		this.graphics.drawEllipse(0,0,W,H);
		this.graphics.endFill();
	}
	// 押されたときの様子
	public function down():void {
		this.pressed = true;
		this.drawBG();
		this.graphics.beginFill(DOWNcolor);
		this.graphics.drawEllipse(W*0.1,H*0.1,W*0.8,H*0.8);
		this.graphics.endFill();
	}
	// 離されたときの様子
	public function up():void {
		this.pressed = false;
		this.drawBG();
		this.graphics.beginFill(UPcolor);
		this.graphics.drawEllipse(W*0.2,H*0.2,W*0.6,H*0.6);
		this.graphics.endFill();
	}
	// 押されているかどうか
	public function isDown():Boolean {
		return this.pressed;
	}
}