In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: 格ゲーコマンド入力判定

格ゲーコマンド入力判定
Fighting game command detection
http://geoquake.jp/blog/2009/05/20/fightinggame/
Stick        = arrow keys or numpad
Punch button = Space, Ctrl, Z
Kick button  = Shift, N, M, X
/**
 * Copyright ruphy ( http://wonderfl.net/user/ruphy )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/i7L7
 */

// forked from k0rin's 格ゲーコマンド入力判定
// 格ゲーコマンド入力判定
// Fighting game command detection
// http://geoquake.jp/blog/2009/05/20/fightinggame/
//
// Stick        = arrow keys or numpad
// Punch button = Space, Ctrl, Z
// Kick button  = Shift, N, M, X
//
package {
	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;
	import flash.text.*;
	
	[SWF(width = "465", height = "465", frameRate = "50")]
	public class Main extends Sprite {
		private const SCREEN_W:int = 465;
		private const SCREEN_H:int = 465;
		
		private var input:FightingGameInput = new FightingGameInput(stage);
		
		private var technique:TextField = new TextField();
		private var techniqueTicks:int = 0;
		
		private var controlPanel:ControlPanel;
		
		public function Main() {
			// インストラクションを描画
			
			// 0-7	= 矢印(右から時計回り)
			// P	= 「P」
			// K	= 「K」
			// +	= 「+」
			// T	= 「溜め」
			// R	= 「連打」
			// H	= 「離す」
			// N	= 「ニュートラル」
			print("210+P");
			print("021+P");
			print("234+K");
			print("44");
			gotoRight();
			print("4T0+P");
			print("PKT H");
			print("PR");
                        print("00");
			
			addChild(new Bitmap(instruction));
			
			controlPanel = new ControlPanel();
			controlPanel.x = SCREEN_W / 2;
			controlPanel.y = 400;
			addChild(controlPanel);
			
			technique.defaultTextFormat = new TextFormat("_ゴシック", 50, 0x00000000, true);
			technique.autoSize = TextFieldAutoSize.CENTER;
			technique.selectable = false;
			technique.x = 30;
			technique.y = 210;
			technique.width = SCREEN_W - 30 * 2;
			addChild(technique);
			
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		private function enterFrameHandler(event:Event):void {
			input.update();
			
			controlPanel.setState(input.getStick(), 
				input.isDown(FightingGameInput.BUTTON_K), 
				input.isDown(FightingGameInput.BUTTON_P));
			
			function showTechnique(name:String):void {
				technique.text = name;
				techniqueTicks = 40;
			}
		
			// 必殺技チェック!
			// checkCommand(判定フレーム数, コマンドスクリプト)
			if (input.checkCommand(20, "2 1 0P")) {	// 0-7(右から時計回り)でレバーの状態。0P=右にレバーが入った状態でパンチボタンが押し下げられた
				showTechnique("著作権ッ!");
				input.clearBuffer();
			} else
			if (input.checkCommand(20, "0 2 1P")) {
				showTechnique("肖像権ッ!");
				input.clearBuffer();
			} else
			if (input.checkCommand(20, "2 3 4K")) {
				showTechnique("則巻千兵衛脚ッ!"); // なんかもうほんとごめんなさい
				input.clearBuffer();
			} else
			if (input.checkCommand(10, "N 4 N 4")) { // N=ニュートラル
				showTechnique("バックステップ");
				input.clearBuffer();
			} else
			if (input.checkCommand(10, "N 0 N 0")) { // N=ニュートラル
				showTechnique("前ダッシュ");
				input.clearBuffer();
			} else
			if (input.checkCommand(25, "4x20 0P")) { // 4x20=レバー左が20フレーム
				showTechnique("溜めコマンド");
				input.clearBuffer();
			} else
			if (input.checkCommand(25, "PdKdx20 PuKu")) { // Pd=パンチボタンが下がっている状態、Pu=同じく上がっている状態
				showTechnique("溜めコマンド2");
				input.clearBuffer();
			} else
			if (input.checkCommand(30, "P P P P P")) {
				showTechnique("打つべし!打つべし!");
				//input.clearBuffer();
			} else 
			if (input.checkCommand(255, "N 6 N 6 N 2 N 2 N 4 N 0 N 4 N 0 N K P")) {
				showTechnique("コナミコマンド");
				input.clearBuffer();
			} else 
			if (input.isPressed(FightingGameInput.BUTTON_P)) {
				showTechnique("パンチ");
			} else 
			if (input.isPressed(FightingGameInput.BUTTON_K)) {
				showTechnique("キック");
			}
			
			if (techniqueTicks > 0) {
				techniqueTicks--;
				technique.visible = true;
			} else {
				technique.visible = false;
			}
		}
		
		// インストラクションの描画
		private var instruction:BitmapData = new BitmapData(SCREEN_W, SCREEN_H, false, 0xFF9020);
		private var line:int = 33;
		private var instX:int = 33;
		
		private function print(s:String):void {
			var x:int = instX;
			
			var arrow:Shape = new Shape();
			var g:Graphics = arrow.graphics;
			g.clear();
			g.beginFill(0x000000);
			g.drawPath(Vector.<int>([1, 2, 2, 2, 2, 2, 2, 2]), 
				Vector.<Number>([
					-15, -5,  0, -5,  0, -15,  15, 0,  0, 15,  0, 5,  -15, 5,  -15, -5
				])
			);
			g.endFill();
			
			var matrix:Matrix = new Matrix();
			
			var textField:TextField = new TextField();
			textField.defaultTextFormat = new TextFormat("_sans", 34, 0x00000000, true);
			textField.autoSize = TextFieldAutoSize.LEFT;
			// ボーダーを表示するとレイアウトが確認しやすい。
			//textField.border = true;
			
			for (var i:int = 0; i < s.length; i++) {
				switch (s.charAt(i)) {
				case "P":
				case "K":
				case "+":
				case " ":
					textField.text = s.charAt(i);
					break;
					
				case "T":
					textField.text = "溜め";
					break;
					
				case "R":
					textField.text = "連打";
					break;
					
				case "H":
					textField.text = "離す";
					break;
					
				case "N":
					textField.text = "ニュートラル";
					break;
					
				default:
					// 矢印
					matrix.identity();
					matrix.rotate(Number(s.charAt(i)) * Math.PI / 4);
					matrix.translate(x + 15, line + 15);
					instruction.draw(arrow, matrix);
					
					x += 35;
					continue;
				}
				matrix.identity();
				matrix.translate(x, line - 6);
				instruction.draw(textField, matrix);
				x += textField.width;
			}
			line += 40;
		}
		
		private function gotoRight():void {
			instX = SCREEN_W / 2;
			line = 33;
		}
	}
}

import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.ui.*;

class ControlPanel extends Sprite {
	private var stickBall:Shape = new Shape();
	private var stickPole:Shape = new Shape();
	private var buttons:Vector.<Shape> = new Vector.<Shape>();
	
	private const STICK_X:int = -70;
	private const STICK_Y:int = -80;
	private const BUTTON_Y:Array = [ -43, -53 ];
	
	function ControlPanel() {
		var g:Graphics = graphics;
		g.beginFill(0x1E2C2F);
		g.drawRect(-200, -85, 400, 90);
		g.endFill();
		
		g.beginFill(0x000000);
		g.drawEllipse(-90, -60, 40, 30);
		g.endFill();
		
		g.beginFill(0x01792B);
		g.drawEllipse(0, -55, 40, 30);
		g.drawEllipse(45, -65, 40, 30);
		g.endFill();
		
		g = stickBall.graphics;
		var matrix:Matrix = new Matrix();
		matrix.createGradientBox(40, 40, 0, -23, -25);
		g.beginGradientFill(GradientType.RADIAL, [0xFFFFFF, 0x00BD51, 0x01692B], null, [10, 30, 230], matrix);
		g.drawCircle(0, 0, 20);
		g.endFill();
		
		for (var i:int = 0; i < 2; i++) {
			var button:Shape = new Shape();
			g = button.graphics;
			matrix.createGradientBox(34, 24, 0, -17, -13);
			g.beginGradientFill(GradientType.RADIAL, [0x01A03C, 0x01A032, 0x20B040], null, [0, 245, 250], matrix);
			g.drawEllipse( -17, -12, 34, 24);
			g.endFill();
			g.beginFill(0xFFFFFF);
			g.endFill();
			
			buttons.push(button);
		}
		
		addChild(stickPole);
		stickBall.x = STICK_X;
		stickBall.y = STICK_Y;
		addChild(stickBall);
		
		buttons[0].x = 20;
		buttons[0].y = BUTTON_Y[0];
		addChild(buttons[0]);
		
		buttons[1].x = 65;
		buttons[1].y = BUTTON_Y[1];
		addChild(buttons[1]);
	}
	
	public function setState(stick:int, button0:Boolean, button1:Boolean):void {
		var sx:Number = STICK_X;
		var sy:Number = STICK_Y;
		
		if (stick != GameInput.STICK_NEUTRAL) {
			var angle:Number = stick * Math.PI / 4;
			sx = STICK_X + Math.cos(angle) * 10;
			sy = STICK_Y + Math.sin(angle) * 10 + 1;
		}
		stickBall.x = sx;
		stickBall.y = sy;
		
		var g:Graphics = stickPole.graphics;
		g.clear();
		g.lineStyle(8, 0x556666);
		g.moveTo(-70, -45);
		g.lineTo(sx, sy);
		
		buttons[0].y = button0 ? BUTTON_Y[0] + 3 : BUTTON_Y[0];
		buttons[1].y = button1 ? BUTTON_Y[1] + 3 : BUTTON_Y[1];
	}
}

// ゲーム用入力クラス(つくりかけ)
//
// キーボードの状態を毎フレームのupdate()で仮想ボタンの状態に整理し、
// 仮想ボタンの上下左右をgetStick()でスティック方向に整理という流れです。
// スティック方向は0=右から時計回りで、8(STICK_NEUTRAL)でニュートラルになっています。
// これはベクトルに変換する際便利なためです(テンキー配列も直感的なので迷う……)。
//
// isDown()、isPressed()、isReleased()で、ボタンの押下状態、押した瞬間、離した瞬間を検出します。
// getButtonTicks()でボタンが押されているフレーム数を取得します。
//

// 仮想ボタン
class Button {
	public static const UP:int = 0;
	public static const DOWN:int = 1;
	public static const LEFT:int = 2;
	public static const RIGHT:int = 3;
	public static const A:int = 4;
	public static const B:int = 5;
	public static const MAX:int = 6;
}

class GameInput {
	public static const STICK_NEUTRAL:int = 8;
	
	private var stage:Stage;
	
	private const KEY_CODE_MAX:int = 256;
	private var keyState:Vector.<Boolean> = new Vector.<Boolean>(KEY_CODE_MAX, true);
	
	private var buttonState:Vector.<Boolean> = new Vector.<Boolean>(Button.MAX, true);
	private var buttonTicks:Vector.<int> = new Vector.<int>(Button.MAX, true);
	
	function GameInput(stage:Stage)  {
		this.stage = stage;
		
		clearKeyState();
		for (var i:int = 0; i < Button.MAX; i++) {
			buttonState[i] = false;
			buttonTicks[i] = 0;
		}
		
		stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
		stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
		stage.addEventListener(Event.DEACTIVATE, deactivateHandler);
	}
	
	private function clearKeyState():void {
		for (var i:int = 0; i < KEY_CODE_MAX; i++) {
			keyState[i] = false;
		}
	}
	
	private function keyDownHandler(event:KeyboardEvent):void {
		keyState[event.keyCode] = true;
	}
	private function keyUpHandler(event:KeyboardEvent):void {
		keyState[event.keyCode] = false;
	}
	private function deactivateHandler(event:Event):void {
		clearKeyState();
	}
	
	private const TO_STICK:Array = [ -1, 3, 2, 1, 4, STICK_NEUTRAL, 0, 5, 6, 7 ];
	
	public function getStick():int {
		// 一旦テンキー配列にする。
		var d:int = 5;
		if (isDown(Button.UP)) {
			d += 3;
		} else if (isDown(Button.DOWN)) {
			d -= 3;
		}
		if (isDown(Button.RIGHT)) {
			d += 1;
		} else if (isDown(Button.LEFT)) {
			d -= 1;
		}
		// テンキー配列を時計回り配列に変換
		return TO_STICK[d];
	}
	
	public function getButtonTicks(button:int):int {
		return buttonTicks[button];
	}
	
	public function isDown(button:int):Boolean {
		return (getButtonTicks(button) > 0);
	}
	public function isPressed(button:int):Boolean {
		return (getButtonTicks(button) == 1);
	}
	public function isReleased(button:int):Boolean {
		return (getButtonTicks(button) < 0);
	}
	
	public function update():void {
		buttonState[Button.UP] = keyState[Keyboard.UP] || keyState[Keyboard.NUMPAD_8];
		buttonState[Button.DOWN] = keyState[Keyboard.DOWN] || keyState[Keyboard.NUMPAD_2];
		buttonState[Button.LEFT] = keyState[Keyboard.LEFT] || keyState[Keyboard.NUMPAD_4];
		buttonState[Button.RIGHT] = keyState[Keyboard.RIGHT] || keyState[Keyboard.NUMPAD_6];
		
		buttonState[Button.A] = keyState[Keyboard.SPACE] || keyState[Keyboard.CONTROL] || keyState[90];
		buttonState[Button.B] = keyState[Keyboard.SHIFT] || keyState[78] || keyState[77] || keyState[88];
		
		for (var i:int = 0; i < Button.MAX; i++) {
			if (buttonState[i]) {
				buttonTicks[i]++;
			} else {
				if (buttonTicks[i] > 0) {
					buttonTicks[i] = -buttonTicks[i];
				} else if (buttonTicks[i] < 0) {
					buttonTicks[i] = 0;
				}
			}
		}
	}
}

// GameInputクラスに格ゲーコマンド判定を付加
class FightingGameInput extends GameInput {
	// ボタン別名
	public static const BUTTON_P:int = Button.A;
	public static const BUTTON_K:int = Button.B;
	
	// 入力状態を溜め込むリングバッファ
	private const BUFFER_SIZE:int = 256; // 2の乗数
	private var buffer:Vector.<uint> = new Vector.<uint>(BUFFER_SIZE, true);
	private var cursor:int = 0;
	
	// bufferの要素の構造
	// |76543210|76543210|76543210|76543210|
	// |        |        |KKKKPPPP|<stick >|
	//                       /   |
	//			+--------+-------+--+----+
	//          |released|pressed|up|down|
	//			+--------+-------+--+----+
	
	function FightingGameInput(stage:Stage) {
		super(stage);
		clearBuffer();
	}
	
	public function clearBuffer():void {
		for (var i:int = 0; i < BUFFER_SIZE; i++) {
			buffer[i] = STICK_NEUTRAL;
		}
	}
	
	public override function update():void {
		super.update();
		
		buffer[cursor] = getStick()
			| (int(isDown(BUTTON_P))     <<  8)
			| (int(!isDown(BUTTON_P))    <<  9)
			| (int(isPressed(BUTTON_P))  << 10)
			| (int(isReleased(BUTTON_P)) << 11)
			| (int(isDown(BUTTON_K))     << 12)
			| (int(!isDown(BUTTON_K))    << 13)
			| (int(isPressed(BUTTON_K))  << 14)
			| (int(isReleased(BUTTON_K)) << 15);
		if (++cursor >= BUFFER_SIZE) {
			cursor = 0;
		}
	}
	
	public function checkCommand(ticks:int, command:String):Boolean {
		if (ticks >= BUFFER_SIZE) {
			throw Error("too long ticks");
		}
		
		//	メモ
		//  p                cursor
		//  v                v
		//	88833333333882288
		
		var p:int = cursor - ticks;
		
		function search(state:uint, mask:uint, length:int):Boolean {
			var matchLength:int = 0;
			
			for (; (p & (BUFFER_SIZE - 1)) != cursor; p++) {
				if ((buffer[p & (BUFFER_SIZE - 1)] & mask) == state) {
					matchLength++;
					if (matchLength >= length) {
						p++;
						return true;
					}
				} else {
					matchLength = 0;
				}
			}
			// バッファ末端までマッチしなかった。
			return false;
		}
		
		var rex:RegExp = /([0-8NPKudpr]+)(x\d+)?/g;
		for (var result:Object = rex.exec(command); result; result = rex.exec(command)) {
			var length:int = result[2] ? result[2].substring(1) : 1;
			
			var state:uint = 0x0000;
			var mask:uint = 0x0000;
			
			var r:RegExp = /([0-8N]|[PK])([udpr])?/g;
			for (var button:Object = r.exec(result[1]); button; button = r.exec(result[1])) {
				var shift:int = 2;
				switch (button[2]) {
				case "d": // down
					shift = 0;
					break;
				case "u": // up
					shift = 1;
					break;
				case "p": // pressed
					shift = 2;
					break;
				case "r": // released
					shift = 3;
					break;
				}
				
				if (button[1] == "N") {
					button[1] = "8";
				}
				switch (button[1]) {
				case "P":
					shift += 8;
					mask |= 1 << shift;
					state |= 1 << shift;
					break;
				case "K":
					shift += 12;
					mask |= 1 << shift;
					state |= 1 << shift;
					break;
				default:
					mask |= 0xFF;
					state |= button[1];
					break;
				}
			}
			if (!search(state, mask, length)) {
				return false;
			}
		}
		return true;
	}
}