Flex 3 SDKとで三次元を遊ぶ[17]
Flex 3 SDKとPapervision3Dで三次元を遊ぶ [17]
http://www.ObjectClub.jp/ml-arch/magazine/361.html
/**
* Copyright Akiyah ( http://wonderfl.net/user/Akiyah )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/ivYf
*/
/*
Flex 3 SDKとPapervision3Dで三次元を遊ぶ [17]
http://www.ObjectClub.jp/ml-arch/magazine/361.html
動かす条件:
・ボードからはみ出すことは出来ない
・駒毎の進める方向にしか進めない
・自分の駒と同じところに置くことは出来ない
・相手の駒と同じところに置いたら相手のコマをもらうことができて、パーキングにおく
・パーキングの駒はすでにコマが置いていない場所にうつことができる。
・自分と相手は交互にコマを動かす
・自分の番のときにライオンがとられていたか、相手のライオンが自陣に入っていたらまけ
・ひよこは相手陣についたらニワトリになる。
・ニワトリは、とられたらひよこになる
・ひよこを打ったときに相手陣においてもニワトリにはならない
座標
(1,1) (2,1) (3,1)
(1,2) (2,2) (3,2)
(1,3) (2,3) (3,3)
(1,4) (2,4) (3,4)
side == true 上向き 手前 自分
side == false 下向き 奥 コンピューター
持ち駒がある場合の座標
(-1,1) () (1,1) (2,1) (3,1) () (5,1)
(-1,2) () (1,2) (2,2) (3,2) () (5,2)
(-1,3) () (1,3) (2,3) (3,3) () (5,3)
(-1,4) () (1,4) (2,4) (3,4) () (5,4)
駒のid
0:tora, 味方
1:tora, 敵
2:hiyoko, 味方
3:hiyoko, 敵
4:kirin, 味方
5:kirin, 敵
6:zou, 味方
7:zou, 敵
*/
package {
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.filters.BlurFilter;
import caurina.transitions.Tweener;
import org.papervision3d.core.effects.BitmapFireEffect;
import org.papervision3d.core.effects.view.ReflectionView;
import org.papervision3d.core.math.Number3D;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.BitmapFileMaterial;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.layer.BitmapEffectLayer;
public class DoubutsuShogi extends ReflectionView {
public var plane_for_drug:Plane;
public var kanban:Cube;
public var mouse_points:Array = [];
public var camera_length:Number = Rule.CAMERA_LENGTH_DEFAULT;
public var bfx:BitmapEffectLayer;
public var bfe:BitmapFireEffect;
public function DoubutsuShogi() {
viewport.interactive = true;
viewportReflection.filters = [new BlurFilter()];
createBitmapEffectLayer();
scene.addChild(createBoard());
scene.addChild(createKanban());
plane_for_drug = createPlane();
scene.addChild(plane_for_drug);
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener(MouseEvent.MOUSE_UP, released);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, mouse_wheel);
Rule.init();
Rule.ds = this;
for each(var pieceUI:PieceUI in Rule.pieceUIs) {
scene.addChild(pieceUI.cube);
}
view();
}
public function view():void {
Rule.board.reset_position();
var count_piece_true:int = 0;
var count_piece_false:int = 0;
for each(var piece:Piece in Rule.board.pieces) {
var pieceUI:PieceUI = Rule.pieceUIs[piece.id];
pieceUI.view();
}
Tweener.addTween(this, {
time: 2,
onComplete: show_pieceUIs_fire
});
}
public function createBitmapEffectLayer():void {
bfx = new BitmapEffectLayer(viewport, stage.width, stage.height);
bfe = new BitmapFireEffect();
bfe.fadeRate = 0.1;
bfe.flameSpread = 1;
bfe.flameHeight = 0.5;
bfe.distortion = 1;
bfe.distortionScale = 1;
bfe.smoke = 1;
//f.blueFlame = true;
bfx.addEffect(bfe);
viewport.containerSprite.addLayer(bfx);
}
public function show_pieceUIs_fire():void {
for each(var piece:Piece in Rule.board.pieces) {
//log("piece.id:" + piece.id + ", piece.side:" + piece.side + ", Rule.board.side:" + Rule.board.side)
if (piece.side == Rule.board.side) {
show_fire(piece.id);
}
}
Tweener.addTween(this, {
time: 2,
onComplete: function():void {
for each(var piece:Piece in Rule.board.pieces) {
hide_fire(piece.id);
}
}
});
}
public function show_fire(id:int):void {
bfe.blueFlame = !Rule.board.pieces[id].side;
bfx.addDisplayObject3D(Rule.pieceUIs[id].cube);
}
public function hide_fire(id:int):void {
bfx.removeDisplayObject3D(Rule.pieceUIs[id].cube);
}
public function createBoard():Cube {
var boardTopMaterial:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.BOARD_TOP_URL);
var boardMaterial:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.BOARD_URL);
boardTopMaterial.interactive = true;
var mlist:MaterialsList = new MaterialsList( { all:boardMaterial, top:boardTopMaterial } );
var board:Cube = new Cube(mlist, Rule.UNIT_SIZE / 2 * 3 + 10, Rule.UNIT_SIZE / 2 * 4 + 10, 10, 3, 2, 4);
board.y = 5;
return board;
}
public function createKanban():Cube {
var topMaterial:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.KANBAN_KATI_URL);
var bottomMaterial:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.KANBAN_MAKE_URL);
var material:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.KANBAN_URL);
var mlist:MaterialsList = new MaterialsList( { all:material, top:topMaterial , bottom:bottomMaterial } );
kanban = new Cube(mlist, 100, 100, 10, 4, 2, 4);
kanban.rotationX = -90;
kanban.visible = false;
return kanban;
}
public function show_kanban():void {
kanban.y = 500;
kanban.visible = true;
Tweener.addTween(this, {
time: 1,
onComplete: function():void {
Tweener.addTween(kanban, {
y: 100,
time: 3,
transition: 'easeOutBounce'
})
}
});
}
public function show_win():void {
kanban.rotationY = 180;
show_kanban();
}
public function show_loose():void {
kanban.rotationY = 0;
show_kanban();
}
public function show_win_loose():Boolean {
if (Rule.board.is_win()) {
if (Rule.board.side) {
show_loose();
} else {
show_win();
}
return true;
}
if (Rule.board.is_lose()) {
if (Rule.board.side) {
show_win();
} else {
show_loose();
}
return true;
}
return false;
}
public function createPlane():Plane {
var material:MaterialObject3D = new MaterialObject3D();
//var material:MaterialObject3D = new WireframeMaterial();
material.doubleSided = true;
material.interactive = true;
var plane:Plane = new Plane(material, 400, 400, 10, 10);
plane.pitch(90);
plane.y = 80;
plane.visible = false;
plane.addEventListener(InteractiveScene3DEvent.OBJECT_MOVE, moved);
return plane;
}
public function released(event:MouseEvent):void {
plane_for_drug.visible = false;
mouse_points = [];
if (!Rule.select_pieceUI) { return; }
var i:int = x_to_i(Rule.select_pieceUI.cube.x);
var j:int = z_to_j(Rule.select_pieceUI.cube.z);
var b:Board = Rule.board.move(Rule.select_pieceUI.id, i, j);
if (!b) {
view();
//Rule.select_pieceUI.revert();
Rule.unselect();
return;
}
Rule.board = b;
//log("1 Rule.board.side:" + Rule.board.side);
view(); // 2sec
Rule.unselect();
var win_loose:Boolean = show_win_loose();
if (win_loose) {
for each(var pieceUI:PieceUI in Rule.pieceUIs) {
pieceUI.stop();
}
return;
}
Tweener.addTween(this, {
time: 2,
onComplete: function():void {
var bp:Array = Rule.board.next_board_best_with_point(5);
Rule.board = bp[0];
Tweener.addTween(this, {
time: 5,
onComplete: function():void {
//log("2 Rule.board.side:" + Rule.board.side);
view();
//show_win_loose();
var win_loose:Boolean = show_win_loose();
if (win_loose) {
for each(var pieceUI:PieceUI in Rule.pieceUIs) {
pieceUI.stop();
}
}
}
})
}
})
return;
}
public function moved(event:InteractiveScene3DEvent):void {
if (!Rule.select_pieceUI) { return; }
var x:Number = event.renderHitData.x;
var y:Number = event.renderHitData.y;
var z:Number = event.renderHitData.z;
mouse_points.unshift(new Number3D(x, y, z));
if (mouse_points.length > 1) {
Rule.select_pieceUI.cube.x += mouse_points[0].x - mouse_points[1].x;
Rule.select_pieceUI.cube.z += mouse_points[0].z - mouse_points[1].z;
}
}
public function update(e:Event):void {
var c:Number = (1 - ((mouseX / stage.width) - 0.5) * 1.5) * (Math.PI / 4) + (Math.PI);
var d:Number = (1 - ((mouseY / stage.height) - 0.5) * 1.5) * (Math.PI / 4);
camera.x = Math.sin(c) * Math.cos(d) * camera_length;
camera.z = Math.cos(c) * Math.cos(d) * camera_length;
camera.y = Math.sin(d) * camera_length;
singleRender();
}
public function mouse_wheel(event:MouseEvent):void {
camera_length -= event.delta * 5;
camera_length = Math.max(camera_length, 50);
camera_length = Math.min(camera_length, 1000);
}
public function select():void {
plane_for_drug.visible = true;
}
public static function i_to_x(i:int):Number {
return (i - 2) * Rule.UNIT_SIZE / 2; // (i - 1) * UNIT_SIZE / 2 - UNIT_SIZE / 2;
}
public static function x_to_i(x:Number):int {
return int(x / (Rule.UNIT_SIZE / 2) + 2 + 0.5);
}
public static function j_to_z(j:int):Number {
return (- j + 2.5) * Rule.UNIT_SIZE / 2; // - (j - 1.5) * UNIT_SIZE / 2 + UNIT_SIZE / 2;
}
public static function z_to_j(z:Number):int {
return int(- z / (Rule.UNIT_SIZE / 2) + 2.5 + 0.5);
}
}
}
import flash.display.Bitmap;
import caurina.transitions.Tweener;
import flash.events.MouseEvent;
import flash.display.BitmapData;
import flash.system.Security;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
import org.ascollada.utils.StringUtil;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.BitmapFileMaterial;
import org.papervision3d.materials.BitmapMaterial;
import org.papervision3d.materials.special.CompositeMaterial;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Cube;
class Rule {
public static var pieceUIs:Array;
public static var select_pieceUI:PieceUI;
public static var ds:DoubutsuShogi;
public static var board:Board;
public static const CAMERA_LENGTH_DEFAULT:Number = 300;
public static const UNIT_SIZE:Number = 100;
public static const PIECE_SIZE:Number = UNIT_SIZE * 0.8;
Security.loadPolicyFile("http://assets.wonderfl.net/crossdomain.xml");
public static const IMAGE_URL:String = "http://assets.wonderfl.net/images/related_images/";
public static const PIECE_IMAGE_URL:Object = {
hiyoko: "b/ba/ba3b/ba3b579e1504b12fa567dc41055d01c53101e077",
kirin: "c/c7/c771/c771f6190264a7629a5ee1870aac8f37baf72685",
zou: "4/4f/4f9b/4f9bc33c1f980a2966316ccd00579ed34644720e",
tora: "0/05/05e6/05e6dd6f27b161e2145454a58b66100011c3e8b8",
niwatori: "7/7e/7ea7/7ea774a207e87eac909343ca90528467f3be4449"
};
public static const BOARD_URL:String = "a/a4/a408/a408a5d33c12f0a559f761afe70dedae02ef61a9";
public static const BOARD_TOP_URL:String = "7/7c/7cb8/7cb89b99e9b0633f58000ff968deecfc17d1e0d0";
public static const KI_URL:String = "4/4b/4b43/4b43a7eae5a186a1af9267c0697d66b91d9d38a4";
public static const KANBAN_URL:String = "b/b0/b08e/b08ef67a78d08af9cb9e3a03b069706dcb8985b0";
public static const KANBAN_KATI_URL:String = "d/d0/d03e/d03e8b0f6897df951bc18382e2291153fc2b4e5f";
public static const KANBAN_MAKE_URL:String = "f/f1/f10c/f10cfce67511df8ae09b779f9d3bcac217a4f70c";
public static const PIECE_POSITION:Array = [
["tora", 2, 4],
["hiyoko", 2, 3],
["kirin", 3, 4],
["zou", 1, 4]
];
public static const DIRECTIONS:Object = {
hiyoko: [[0,1,0], [0,0,0], [0,0,0]],
niwatori: [[1,1,1], [1,0,1], [0,1,0]],
kirin: [[0,1,0], [1,0,1], [0,1,0]],
tora: [[1,1,1], [1,0,1], [1,1,1]],
zou: [[1,0,1], [0,0,0], [1,0,1]]
};
public static function random_choose(arr:Array):Object {
var i:int = Math.floor(Math.random() * arr.length);
return arr[i];
}
public static function init():void {
createPieceUIs();
}
public static function createPieceUIs():void {
board = new Board(true);
pieceUIs = [];
var k:int = 0;
for each(var pieceData:Array in PIECE_POSITION) {
var pieceUI:PieceUI;
var id:int;
var type:String;
var i:int;
var j:int;
type = pieceData[0];
id = k * 2;
i = pieceData[1];
j = pieceData[2];
board.pieces.push(new Piece(id, type, i, j, true, true));
pieceUIs.push(new PieceUI(id));
id = k * 2 + 1;
i = 4 - pieceData[1];
j = 5 - pieceData[2];
board.pieces.push(new Piece(id, type, i, j, false, true));
pieceUIs.push(new PieceUI(id));
k++;
}
}
public static function in_board(i:int, j:int):Boolean {
if (i <= 0 || 3 < i) { return false; }
if (j <= 0 || 4 < j) { return false; }
return true;
}
public static function select(pieceUI:PieceUI):void {
ds.select();
ds.show_fire(pieceUI.id);
select_pieceUI = pieceUI;
}
public static function unselect():void {
ds.hide_fire(select_pieceUI.id);
select_pieceUI = null;
}
}
class PieceUI {
public var id:int;
public var cube:Cube;
public function PieceUI(id:int) {
this.id = id;
cube = createCube();
cube.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, pressed);
}
public function pressed(event:InteractiveScene3DEvent):void {
if (Rule.board.side != Rule.board.pieces[id].side) { return; }
Rule.select(this);
Tweener.addTween(cube, {
y: Rule.UNIT_SIZE / 4 + 30,
time:1,
transition: 'easeInOutCubic'
});
}
public function stop():void {
cube.removeEventListener(InteractiveScene3DEvent.OBJECT_PRESS, pressed);
}
public function createCube():Cube {
var materialsList:MaterialsList = new MaterialsList( { all:materialKi} );
var materialKi:BitmapFileMaterial = new BitmapFileMaterial(Rule.IMAGE_URL + Rule.KI_URL);
materialKi.interactive = true
materialsList.addMaterial(materialKi, "all")
materialsList.addMaterial(createCompositeMaterial(Rule.board.pieces[id].type), "top")
if (Rule.board.pieces[id].type == "hiyoko") {
materialsList.addMaterial(createCompositeMaterial("niwatori"), "bottom")
}
return new Cube(materialsList, Rule.PIECE_SIZE / 2, Rule.PIECE_SIZE / 2, Rule.PIECE_SIZE / 4, 2, 2, 2);
}
private function createCompositeMaterial(type:String):CompositeMaterial {
var material:CompositeMaterial = new CompositeMaterial();
material.addMaterial(new BitmapFileMaterial(Rule.IMAGE_URL + Rule.PIECE_IMAGE_URL[type]));
material.addMaterial(createDirectionMarkMaterial(type));
material.interactive = true;
return material;
}
private function createDirectionMarkMaterial(type:String):BitmapMaterial {
var bd:BitmapData = new BitmapData(100, 100, true, 0x00000000);
for (var j:Number = 0; j < 3; j++) {
for (var i:Number = 0; i < 3; i++) {
if (Rule.DIRECTIONS[type][j][i] == 1) {
bd.fillRect(new Rectangle(5 + i*40, 5 + j*40, 10, 10), 0xFFFF0000);
}
}
}
return new BitmapMaterial(bd);
}
public function view():void {
var piece:Piece = Rule.board.pieces[id];
var y:Number = Rule.UNIT_SIZE / 4;
var rotationY:Number = 0;
var rotationZ:Number = 0;
if (!piece.side) {
rotationY = 180;
}
if (piece.type == "niwatori") {
rotationZ = 180;
}
if (!piece.alive) {
y -= 10;
}
Tweener.addTween(cube, {
x: DoubutsuShogi.i_to_x(piece.i),
y: y,
z: DoubutsuShogi.j_to_z(piece.j),
rotationY: rotationY, // turn
rotationZ: rotationZ, // side
time:1,
transition: 'easeInOutCubic'
});
}
}
class Piece {
public var id:int;
public var type:String;
public var i:int;
public var j:int;
public var side:Boolean;
public var alive:Boolean;
public function Piece(id:int, type:String, i:int, j:int, side:Boolean, alive:Boolean) {
this.id = id;
this.type = type;
this.i = i;
this.j = j;
this.side = side;
this.alive = alive;
}
public function move(i:int, j:int):Piece {
var p:Piece = new Piece(this.id, this.type, i, j, this.side, true);
if (p.type == "hiyoko" && this.alive && p.entered()) {
p.type = "niwatori";
}
return p;
}
public function cought():Piece {
var type:String = this.type;
if (type == "niwatori") {
type = "hiyoko";
}
return new Piece(this.id, type, 0, 0, !this.side, false);
}
public function is_movable_direction(i:int, j:int):Boolean {
var di:int = i - this.i;
var dj:int = j - this.j;
if (!this.side) {
di = -di;
dj = -dj;
}
if (Math.abs(di) > 1 || Math.abs(dj) > 1 ) {
return false;
}
return Rule.DIRECTIONS[this.type][dj + 1][di + 1] == 1;
}
public function entered():Boolean {
return (side && j == 1) || (!side && j == 4);
}
}
class Board {
public var pieces:Array= [];
public var side:Boolean;
public function toString():String {
var s:String = "";
for each (var piece:Piece in pieces) {
s += "piece: " + piece.id + ", " + piece.type + ", " + piece.side + ", " + piece.i + ", " + piece.j + "\n";
}
return s;
}
public function Board(side:Boolean) {
this.side = side;
}
public function reset_position():void {
var count_true_side:int = 0;
var count_false_side:int = 0;
for each (var piece:Piece in pieces) {
if (!piece.alive) {
if (piece.side) {
piece.i = -1;
piece.j = 4 - count_true_side;
count_true_side++;
} else {
piece.i = 5;
piece.j = 1 + count_false_side;
count_false_side++;
}
}
}
}
public function is_movable(piece:Piece, i:int, j:int):Boolean {
var target_piece:Piece;
if (!Rule.in_board(i, j)) {
return false;
}
if (piece.side != this.side) {
// そのコマを動かす番じゃないとき
return false;
}
if (piece.alive) {
// コマを動かすとき
if (!piece.is_movable_direction(i, j)) {
// 動かせる方向じゃない場合
return false;
}
target_piece = this.get_piece_by_ij(i, j);
if (target_piece && target_piece.side == piece.side) {
// 自分のコマをとることは出来ない
return false;
}
} else {
// 持ち駒を打つとき
target_piece = this.get_piece_by_ij(i, j);
if (target_piece) {
// 元々コマがあるところには打てない
return false;
}
}
return true;
}
public function move(id:int, i:int, j:int):Board {
var piece:Piece = this.pieces[id];
if (!this.is_movable(piece, i, j)) {
return null;
}
var b:Board = new Board(!side);
b.pieces = this.pieces.slice(0);
if (piece.alive) {
var target_piece:Piece = this.get_piece_by_ij(i, j);
if (target_piece && target_piece.side != piece.side) {
// 元々あった敵のコマをとる
b.change(target_piece.cought());
}
}
b.change(piece.move(i, j));
return b;
}
public function change(piece:Piece):void {
this.pieces[piece.id] = piece;
}
public function is_lose():Boolean {
var tora:Piece = this.own_tora();
// 自分のトラがいなくて負け
return !tora;
}
public function is_win():Boolean {
var tora:Piece = this.own_tora();
// 自分のトラが相手のエリアにいて勝ち
return tora != null && tora.entered();
}
public function own_tora():Piece {
var piece:Piece = this.pieces[this.side ? 0 : 1];
if (piece.side != this.side) {
return null;
}
return piece;
}
public var _pieces_by_ij:Array;
public function get_piece_by_ij(i:int, j:int):Piece {
if (this._pieces_by_ij == null) {
var ps:Array = [[null,null,null],[null,null,null],[null,null,null],[null,null,null]];
for (var id:int = 0; id < 8; id++) {
var piece:Piece = this.pieces[id];
if (piece.alive) {
ps[piece.j - 1][piece.i - 1] = piece;
}
}
this._pieces_by_ij = ps;
}
return this._pieces_by_ij[j - 1][i - 1];
}
public function next_board_best_with_point(n:int):Array {
//var tab:String = "";
//for (var k:int = 0; k < (4 - n); k++) {
// tab += " ";
//}
//log(tab + "next_board_best_with_point(" + n + ")");
//var boards_win:Array = [];
var boards_lose:Array = [];
var boards_other:Array = [];
for (var piece_id:int = 0; piece_id < 8; piece_id++) {
for (var x:int = 1; x <= 3; x++) {
for (var y:int = 1; y <= 4; y++) {
if (this.is_movable(this.pieces[piece_id], x, y)) {
//log(tab + "is_movable(this.pieces[" + piece_id + "], " + x + ", " + y + ")");
var b:Board = this.move(piece_id, x, y);
if (b.is_win()) {
// 相手が勝った場合
boards_lose.push(b);
} else if (b.is_lose()) {
// 相手が負けた場合
//boards_win.push(b);
return [b, 1];
} else {
boards_other.push(b);
}
}
}
}
}
//if (boards_win.length != 0) {
// return [Rule.random_choose(boards_win), 1];
//}
if (boards_other.length != 0) {
if (n == 0) {
return [Rule.random_choose(boards_other), 1/2];
}
// その次の相手の最善手で、相手にとって一番悪い手を選ぶ
var worst_i:int = 0;
var worst_point:int = 1;
var boards_other_best:Array = [];
for (var i:int = 0; i < boards_other.length; i++) {
var b2:Board = boards_other[i];
var b_point:Array = b2.next_board_best_with_point(n - 1);
var point:int = b_point[1];
if (point == worst_point) {
boards_other_best.push(b2);
}
if (point < worst_point) {
boards_other_best = [b2];
worst_point = point;
}
}
// 相手の最善手の点数を逆にして、1/2に点数を上に縮めて返す
return [Rule.random_choose(boards_other_best), 1/2 + (1-worst_point)/2];
}
return [Rule.random_choose(boards_lose), 0];
}
}