// forked from shohei909's TETRIS AI //モンテカルロ法を使ったTETRIS AI //Bastet Tetris package { import flash.events.Event; import flash.display.Sprite; import com.bit101.components.*; import net.wonderfl.utils.WonderflAPI; [SWF(backgroundColor="0x2F2822", frameRate="30")] public class Main extends Sprite { private var tetris1:Tetris = new Tetris(); static public const W:uint = 465; static public const H:uint = 465; private var line:Label; private var point:Label; private var state:Label; private var radio:Array = [] private var ps:Array = [ "human", "randomCPU", "normalCPU", "MonteCarloCPU" ] function Main() { if( stage ){ init(null) } else addEventListener(Event.ADDED_TO_STAGE,init); } private function init(e:Event):void{ var flashVars:Object = root.loaderInfo.parameters; ScoreWindowLoader.init( this, new WonderflAPI(flashVars), init2 ); } private function init2():void{ line = new Label( this, 250, 400, "LINE:" ); line.scaleX = line.scaleY = 2; point = new Label( this, 250, 370, "POINT:" ); point.scaleX = point.scaleY = 2; //state = new Label( this, 250, 340, "" ); //state.scaleX = state.scaleY = 2; var fps:FPSMeter = new FPSMeter( this, 250, 310 ) fps.scaleX = fps.scaleY = 2; var lbl:Label = new Label( this, 0, 0, "BASTET AI" ); lbl.scaleX = lbl.scaleY = 3; for( var i:int=ps.length-1; i>=0; i-- ){ new RadioButton( this, 270, 80 + 40*i, ps[i], true, start ); } new PushButton( this, 270, 250, "Restart", start ); tetris1.player = new Human( tetris1, stage ); tetris1.onGameOver = onGameOver; var map:TetrisMap = new TetrisMap(tetris1) map.x = ( 250 - map.width ) / 2; map.y = 35 + ( 430 - map.height ) / 2; addChild( map ); tetris1.init() addEventListener( "exitFrame", progress ); } public function progress( e:Event ):void{ tetris1.progress(); ///state.text = "LIFE:" + tetris1.getState2(); point.text = "POINT:" + tetris1.point; line.text = "LINE:" + tetris1.line; } public function start( e:Event ):void{ switch( e.currentTarget.label ){ case "normalCPU": tetris1.player = new NormalCPU( tetris1 ); break; case "randomCPU": tetris1.player = new RandomCPU( tetris1 ); break; case "human": tetris1.player = new Human( tetris1, stage ); break; case "MonteCarloCPU": tetris1.player = new MonteCarloCPU( tetris1 ); break; } tetris1.init(); } public function onGameOver():void{ if( tetris1.player is Human ){ ScoreWindowLoader.show( tetris1.point, function f():void{} ); } } } } import flash.events.EventDispatcher; import flash.events.KeyboardEvent; import flash.display.Stage; import flash.geom.Matrix; import flash.geom.ColorTransform; import flash.events.Event; import flash.geom.Rectangle; import flash.display.BitmapData; import flash.display.Bitmap; import frocessing.color.FColor; class TetrisMap extends Bitmap{ public var tetris:Tetris; static public const CT:ColorTransform = new ColorTransform( 0.5, 0.7, 0.8, 1 ); static public const OUT_CT:ColorTransform = new ColorTransform( 0.2, 0.2, 0.2, 1, 20, 20, 20 ); static public const CELL:uint = 17; static private function f( u:int ):uint{ return FColor.HSVtoValue(u,0.5,0.5); } static public const BL_C:Array = [ f(0), f(37), f(73), f(110), f(147), f(183), f(220) ]; function TetrisMap( tetris:Tetris ){ this.tetris = tetris; super( new BitmapData( Tetris.W*CELL+1, Tetris.H*CELL+1, false, 0 ) ); addEventListener( "exitFrame", draw ); } public function draw( e:Event ):void{ var b:BitmapData = bitmapData; b.colorTransform( b.rect, CT ); for( var i:uint = 0; i < Tetris.W; i++ ){ for( var j:uint = 0; j < Tetris.H; j++ ){ if( tetris.map[i][j] > -1 ){ b.fillRect( new Rectangle(i*CELL+2, j*CELL+2, CELL-3, CELL-3), 0xFF223344 ) } } } drawGrid(); drawBlock(); b.colorTransform( new Rectangle( 0,0,Tetris.W*CELL+1,Tetris.OUT*CELL ), OUT_CT ); } private function drawGrid():void{ var b:BitmapData = bitmapData; for( var i:uint = 0; i < Tetris.W+1; i++ ){ b.fillRect( new Rectangle( i*CELL, 0, 1, Tetris.H*CELL+1 ), 0xFF112233 ) } for( var j:uint = 0; j < Tetris.H+1; j++ ){ b.fillRect( new Rectangle( 0, j*CELL, Tetris.W*CELL+1, 1 ), 0xFF112233 ) } } private function drawBlock():void{ var b:BitmapData = bitmapData; var m:Matrix = Tetris.DIR[tetris.dir]; var c:uint = BL_C[tetris.block]; var bl:Array = Tetris.BL[tetris.block]; for( var i:uint = 0; i < 2; i++ ){ for( var j:uint = 0; j < 4; j++ ){ if( bl[i][j] == 1 ){ var x:int = tetris.bx + j*m.a + i*m.b + m.tx; var y:int = tetris.by + j*m.c + i*m.d + m.ty; b.fillRect( new Rectangle( x*CELL+2, y*CELL+2, CELL-3, CELL-3 ), c ) } } } } } class Tetris implements Game{ static public const W:uint = 10; static public const H:uint = 22; static public const OUT:uint = 2; static public const BL:Array = [ /*O*/[[0,1,1,0],[0,1,1,0]], /*Z*/[[1,1,0,0],[0,1,1,0]], /*S*/[[0,1,1,0],[1,1,0,0]], /*I*/[[0,0,0,0],[1,1,1,1]], /*L*/[[0,0,1,0],[1,1,1,0]], /*Γ*/[[1,0,0,0],[1,1,1,0]], /*T*/[[0,1,0,0],[1,1,1,0]] ] static public const DIR:Array = [ new Matrix( 1,0,0,1,-1,-1 ), new Matrix( 0,-1,1,0,1,-1 ), new Matrix( -1,0,0,-1,1,1 ), new Matrix( 0,1,-1,0,-1,1 ) ]; static public var span:int = 100; static public var PT_AR:Array = [1,3,10,20]; public var player:Player; public var bastedPlayer:Player; public var onGameOver:Function; public var map:Vector.<Vector.<int>> = new Vector.<Vector.<int>>(); public var bx:int; public var by:int; public var dir:int; public var block:uint; public var count:int; public var point:int; public var line:int; public var gameOver:Boolean = false; public var activePlayer:Boolean = false; function Tetris(){ bastedPlayer = new BastedCPU( this ); } public function init():void{ activePlayer = false; gameOver = false; count = 0; point = 0; line = 0; for( var i:uint = 0; i < W; i++ ){ map[i] = new Vector.<int>() for( var j:uint = 0; j < H; j++ ){ map[i][j] = -1; } } if( player ){ player.init() } } private function setBlock( b:uint ):void { bx = 4; by = 1; dir = 0; block = b; activePlayer = true; } private function checkLine():void { var map:Vector.<Vector.<int>> = this.map; var count:int = -1; for( var j:uint = 0; j<H; j++ ){ s:{ var f:Boolean = true; for( var i:uint = 0; i<W; i++ ){ if( map[i][j] == -1 ){ f = false; break s; } } count++; line++; removeLine(j) } } if( count >= 0 ){ point += PT_AR[count] } } private function removeLine( h:uint ):void { var map:Vector.<Vector.<int>> = this.map; for( var j:uint = h; j>0; j-- ){ for( var i:uint = 0; i<W; i++ ){ map[i][j] = map[i][j-1]; } } for( var i2:uint = 0; i2<W; i2++ ){ map[i2][0] = -1; } } private function stopBlock():void{ var m:Matrix = DIR[dir]; var bl:Array = BL[block]; var map:Vector.<Vector.<int>> = this.map; for( var i:uint = 0; i < 2; i++ ){ for( var j:uint = 0; j < 4; j++ ){ if( bl[i][j] == 1 ){ var x:int = bx + j*m.a + i*m.b + m.tx; var y:int = by + j*m.c + i*m.d + m.ty; map[x][y] = block; if( y < OUT ){ if( (!gameOver) && onGameOver != null ){ onGameOver(); } gameOver = true; } } } } checkLine(); activePlayer = false; } public function progress():void{ if(! gameOver ){ if(! activePlayer ){ bastedPlayer.action(); } else if( player ){ player.action(); } if( count++ > span && (!_action(0,1,0)) ){ stopBlock(); } } } public function action( ...arg ):void{ if( activePlayer ){ var dx:int = arg[0], dy:int = arg[1], spin:int = arg[2], fall:Boolean = arg[3]; if( spin > 0 ){ _action(0,0,spin%4); spin = 0 } while( dx > 0 ){ _action(1,0,0); dx-- } while( dx < 0 ){ _action(-1,0,0); dx++ } while( dy > 0 ){ _action(0,1,0); dy-- } if( fall ){ while( _action(0,1,0) ){}; } }else{ setBlock( arg[0] ) } } private function _action( dx:int, dy:int, ddir:int ):Boolean{ if( block == 0 ){ ddir = 0 } var m:Matrix = DIR[ (dir + ddir)%4 ]; var bl:Array = BL[ block ]; for( var i:uint = 0; i < 2; i++ ){ for( var j:uint = 0; j < 4; j++ ){ if( bl[i][j] == 1 ){ var x:int = bx + j*m.a + i*m.b + m.tx + dx; var y:int = by + j*m.c + i*m.d + m.ty + dy; if( x < 0 || x >= W || y < 0 || y >= H || map[x][y] > -1 ){ if(dy>0){stopBlock()}; return false; } } } } bx+=dx; by+=dy; dir=(dir+ddir)%4; if( dy > 0 ){ count = 0 } return true; } public function clone():Game{ var c:Tetris = new Tetris(); var map:Vector.<Vector.<int>> = this.map; c.bx = bx; c.by = by; c.dir = dir; c.block = block; c.activePlayer = activePlayer; for( var i:uint = 0; i < W; i++ ){ c.map[i] = new Vector.<int>() for( var j:uint = 0; j < H; j++ ){ c.map[i][j] = map[i][j] } } return c; } public function getValue( ...arg ):Number{ if ( gameOver ) { return 0; } var num:Number = 0; var arr:Vector.<Number> = _getStateArray(); return arg[0]*arr[0] + arg[1]*arr[1] + arg[2]*arr[2]; } public function getDefaultValueArg():Array { return [1,3.0727,5.6932]; } public function getPoint():Number{ return line; } public function _getStateArray():Vector.<Number> { var array:Vector.<Number> = new Vector.<Number>(); var map:Vector.<Vector.<int>> = this.map; var c:int = 0; var p:int = 0; var line1:Vector.<int>, line2:Vector.<int>; var i:uint; var j:uint; var f:Boolean; for ( i = 0; i < W; i++ ) { line1 = map[i] f = false; for( j = 0; j<H; j++ ){ if ( f ) { if ( line1[j] == -1 ) { c--; } } else { if ( line1[j] > -1 ) { p += j; f = true } } } if(!f){ p += j }; } array.push(p); p = 0; for ( i = 0; i < W - 1; i++ ) { line1 = map[i]; line2 = map[i+1]; f = false; for( j = 0; j<H; j++ ){ if ( !f ) { if ( line1[j] > -1 || line2[j] > -1 ) { p += j; f = true } } } if(!f){ p += j }; } array.push(p); array.push(c); return array; } public function getAction():Array{ var arr:Array = []; if( activePlayer ){ var l:uint = block == 0 ? 1 : ( block < 4 ? 2 : 4); for( var s:uint = 0; s < l; s++ ){ for( var j:uint = 0; j < W; j++ ){ arr.push( [j-bx,0,s,true] ) } } }else{ for( var k:uint = 0; k < 7; k++ ){ arr.push( [k] ) } } return arr; } } interface Game{ function action( ...arg ):void function clone():Game; function getAction():Array; function getValue( ...arg ):Number; function getDefaultValueArg():Array; } class Player{ public var game:Game; function Player( game:Game ){ this.game = game; } public function action():void{} public function init():void{} public function getPoint(target:*):Object{ return null } } class Human extends Player{ public var dx:int = 0; public var dy:int = 0; public var fall:Boolean = false; public var spin:int = 0; function Human( game:Game, stage:Stage ){ super( game ); stage.addEventListener( KeyboardEvent.KEY_DOWN, onKey ) } override public function action():void{ game.action( dx, dy, spin, fall ); dx = 0; dy = 0; spin = 0; fall = false; } private function onKey( e:KeyboardEvent ):void{ switch( e.keyCode ){ case 90: spin++; break; case 88: spin+=3; break; case 38: fall=true; break; case 39: dx++; break; case 37: dx--; break; case 40: dy++; break; } } } class RandomCPU extends Player{ function RandomCPU( game:Game ){ super(game); } override public function action():void{ actionAt( game ); } static public function actionAt( game:Game ):void{ var act:Array = game.getAction(); game.action.apply( null, act[ (act.length*Math.random()) >>> 0 ] ); } } class NormalCPU extends Player { static public var valueRate:Array; public function NormalCPU( game:Game) { if(!valueRate){ valueRate=game.getDefaultValueArg() } super(game) } override public function action():void{ actionAt( game ) } static public function actionAt( game:Game ):void { if(!valueRate){ valueRate=game.getDefaultValueArg() } var act:Array = game.getAction(); var act2:Array = []; var act3:Array = []; var arg:Array = valueRate; var l2:int = 0; var l3:int = 0; var points:Vector.<Number> = new Vector.<Number>(); var l:uint = act.length; var max:int = 0; for( var i:uint = 0; i<l; i++ ){ var clone:Game = game.clone(); clone.action.apply( null, act[ i ] ); var p:int = clone.getValue.apply( null, arg ); if( p >= max ){ max = p; points.push( p ); act2.push( act[i] ); l2++; } } for( var j:uint = 0; j<l2; j++ ){ if( points[j] == max ){ act3.push( act2[j] ); l3++; } } game.action.apply( null, act3[ (l3*Math.random()) >>> 0 ] ); } } class BastedCPU extends Player{ function BastedCPU( game:Game ){ super(game); } override public function action():void{ actionAt( game ); } static public function actionAt( game:Game ):void{ var act:Vector.<Array> = Vector.<Array>( game.getAction() ); var points:Vector.<Number> = new Vector.<Number>; var clones:Vector.<Game> = new Vector.<Game>; var l:uint = act.length; var max:uint = 0; //2手読む function _1():void{ for( var i:uint = 0; i<l; i++ ){ var clone:Game = game.clone(); clone.action.apply( null, act[ i ] ); NormalCPU.actionAt( clone ); var p:int = clone.getValue.apply( null, clone.getDefaultValueArg() ); if( p > max ){ max = p } points.push( p ); clones.push( clone ); } } //点の低いものを選び出す。 function _2():void{ var act2:Vector.<Array> = new Vector.<Array>; var points2:Vector.<Number> = new Vector.<Number>; var clones2:Vector.<Game> = new Vector.<Game>; var l2:int = 0; var max2:int = 0; for( var i:uint = 0; i<l; i++ ){ var pt:int = points[i]; l1:{ for( var j:int = i; j>0; j-- ){ if( pt >= points2[j-1] ){ break; l1; } } points2.splice( j, 0, points[i] ); clones2.splice( j, 0, clones[i] ); act2.splice( j, 0, act[i] ); } } act=act2; points=points2; clones=clones2; } //1番良い手を70%、2番目に良い手を15%、3番目を9%、4番目を6%で実行する。 function _3():void { var r:Number = Math.random(); var i:int if(r<0.7){ i=0} else if(r<0.85){ i=1 } else if(r<0.96){ i=2 } else{ i=3 } game.action.apply( null, act[ i ] ); } _1(); _2(); _3(); } } class MonteCarloCPU extends Player{ public var depth:int = 2; //読みの深さ public var tolerance:int = 0; //寛容さ public var fork:int = 12; //分岐の数 public var valueRate:Array; function MonteCarloCPU( game:Game ){ super(game) } override public function action():void{ var act:Vector.<Array> = Vector.<Array>( game.getAction() ); var points:Vector.<Number> = new Vector.<Number>; var clones:Vector.<Game> = new Vector.<Game>; var l:uint = act.length; var max:uint = 0; if(!valueRate){ valueRate=game.getDefaultValueArg() } var arg:Array = valueRate //1手読む function _1():void{ var clone:Game; for ( var i:uint = 0; i < l; i++ ) { clone = game.clone(); clone.action.apply( null, act[ i ] ); var p:int = clone.getValue.apply( null, arg ); if( p > max ){ max = p } points.push( p ); clones.push( clone ); } } //点の高いものを選び出す。 function _2():void{ var act2:Vector.<Array> = new Vector.<Array>; var points2:Vector.<Number> = new Vector.<Number>; var clones2:Vector.<Game> = new Vector.<Game>; var l2:int = 0; for( var i:uint = 0; i<l; i++ ){ var pt:int = points[i] if( pt + tolerance >= max ){ points2.push( points[i] ); clones2.push( clones[i] ); act2.push( act[i] ); l2++; } } act=act2; points=points2; clones=clones2; l = l2; } //各手を深読み function _3():void { var g:Game; for( var i:uint = 0; i<l; i++ ){ for( var j:uint = 0; j<fork; j++ ){ g = clones[i].clone(); for( var k:uint = 0; k<depth; k++ ){ RandomCPU.actionAt( g ); NormalCPU.actionAt( g ); points[i] += g.getValue.apply( null, valueRate ); } } } } //もっとも点の高い手を実行する。 function _4():void { max = 0; for( var i:uint = 0; i<l; i++ ){ var p:int = points[i]; if( p > max ){ max = p } points.push( p ) } for( var j:uint = 0; j<l; j++ ){ if( points[j] < max ){ points.splice( j, 1 ); act.splice( j, 1 ); j--; l--; } } game.action.apply( null, act[ (l*Math.random()) >>> 0 ] ); } _1(); _2(); _3(); _4(); } } import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Graphics; import flash.display.Loader; import flash.events.Event; import flash.net.URLRequest; import flash.system.LoaderContext; import net.wonderfl.utils.WonderflAPI; class ScoreWindowLoader { private static var _top: DisplayObjectContainer; private static var _api: WonderflAPI; private static var _content: Object; //private static const URL: String = "wonderflScore.swf"; private static const URL: String = "http://swf.wonderfl.net/swf/usercode/5/57/579a/579a46e1306b5770d429a3738349291f05fec4f3.swf"; private static const TWEET: String = "Playing What the Hex [score: %SCORE%] #wonderfl"; public static function init(top: DisplayObjectContainer, api: WonderflAPI, handler: Function = null): void { _top = top, _api = api; var loader: Loader = new Loader(); var comp: Function = function(e: Event): void { loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, comp); _content = loader.content; handler(); } loader.contentLoaderInfo.addEventListener(Event.COMPLETE, comp); loader.load(new URLRequest(URL), new LoaderContext(true)); } /** * Wonderfl の Score API 用 * ランキング表示から Tweet までをひとまとめにしたSWF素材を使う * @param score : 取得スコア * @param closeHandler : Window が閉じるイベントハンドら */ public static function show( score: int, closeHandler: Function): void { var window: DisplayObject = _content.makeScoreWindow(_api, score, "BASTET", 1, TWEET); var close: Function = function(e: Event): void { window.removeEventListener(Event.CLOSE, close); closeHandler(); } window.addEventListener(Event.CLOSE, close); _top.addChild(window); } } Bastet AI (いじわるテトリス)