「あのゲーム」試作中 (その3)
----------------------------------------------------------------
Todo: (連休あけまでに対応)
サウンドエフェクトをつけること
----------------------------------------------------------------
2010年4月28日
* 点数計算式の再検討.
- 消去時の得点は (消去ぷよ数 + 消去色数 - 4) x 10 x 2^(連鎖 - 1)
- 長い連鎖と多色同時消しは高レート
- 下向き矢印で一段下げるごとに 1 点加算
- レベルアップ時にボーナス加算 ( 新レベル x 100点 )
----------------------------------------------------------------
2010年4月27日
* レベルアップに応じて落下を加速
* ステージの deactivate イベントに反応して一時停止
* 背景色の変更
----------------------------------------------------------------
2010年4月26日
* ゲームオーバー後はステージをクリックで再度プレイ可能
* 下降タイミングを再度調整
----------------------------------------------------------------
2010年4月25日
* 一時停止可能に (TABキーで一時停止/再開)
* グラフィックスの微修正
* 下降タイミングの改良
* 点数計算式の変更
----------------------------------------------------------------
2010年4月24日
数日前から「あのゲーム」を試作中です
自分のフィールドの操作がひととおり書けたので
まだタイミングの調整とかいろいろ未熟ですがひとまず公開します
対戦相手の AI をまだ書けないので, ひたすら消してください
----------------------------------------------------------------
/**
* Copyright tenasaku ( http://wonderfl.net/user/tenasaku )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/4nzr
*/
/*
----------------------------------------------------------------
Todo: (連休あけまでに対応)
サウンドエフェクトをつけること
----------------------------------------------------------------
2010年4月28日
* 点数計算式の再検討.
- 消去時の得点は (消去ぷよ数 + 消去色数 - 4) x 10 x 2^(連鎖 - 1)
- 長い連鎖と多色同時消しは高レート
- 下向き矢印で一段下げるごとに 1 点加算
- レベルアップ時にボーナス加算 ( 新レベル x 100点 )
----------------------------------------------------------------
2010年4月27日
* レベルアップに応じて落下を加速
* ステージの deactivate イベントに反応して一時停止
* 背景色の変更
----------------------------------------------------------------
2010年4月26日
* ゲームオーバー後はステージをクリックで再度プレイ可能
* 下降タイミングを再度調整
----------------------------------------------------------------
2010年4月25日
* 一時停止可能に (TABキーで一時停止/再開)
* グラフィックスの微修正
* 下降タイミングの改良
* 点数計算式の変更
----------------------------------------------------------------
2010年4月24日
数日前から「あのゲーム」を試作中です
自分のフィールドの操作がひととおり書けたので
まだタイミングの調整とかいろいろ未熟ですがひとまず公開します
対戦相手の AI をまだ書けないので, ひたすら消してください
---------------------------------------------------------------- */
package {
import flash.display.*;
import flash.events.*;
import flash.text.*;
import flash.ui.*;
import flash.utils.*;
public class Main extends Sprite {
/* ----------------------------------------------------------------
グローバル定数の宣言
---------------------------------------------------------------- */
// ステージ全体の背景色
private const STAGE_BGCOLOR:uint = 0x009999;
// ゲーム開始時のフレームレート
private const FRAMERATE_AT_START:int = 24;
// ゲーム開始時のフレームレート
private const FRAMERATE_AT_HIGHER_LEVEL:int = 36;
// ぷよ消去動作時のフレームレート
private const FRAMERATE_ON_ERASE:int = 12;
// 浮遊ぷよが最初にフィールドにあらわれる場所
// ここに堆積ぷよが置かれたらゲームオーバー
private const START_COL:int = Math.floor((Field.NUM_COLS-1)/2);
private const START_ROW:int = Field.NUM_ROWS - 1;
// keysPressed グローバル変数の各ビットの名前
private const UP_KEYMASK:int = 1;
private const LEFT_KEYMASK:int = 2;
private const DOWN_KEYMASK:int = 4;
private const RIGHT_KEYMASK:int = 8;
/* ----------------------------------------------------------------
いつのまにこんなに増えやがったのか, グローバル変数どもの宣言
---------------------------------------------------------------- */
private var field:Field; // フィールド. ここにぷよが堆積し, ゲームが進行する
private var fieldMask:Shape; // フィールド の表示窓を決めるためのマスク
private var gameState:int; // いま現在せにゃならん処理は何か, 俺は忘れっぽいのでここに書いてある
private var erasedObjectCount:int; // これまでに消したぷよの総数
private var maximumChainCount:int; // これまでに実現した連鎖の最大回数
private var score:int; // 読んで字のごとく, スコア
private var dropPeriod:int; // 浮遊ぷよが下降するタイミング(このフレーム数ごとに下降)
private var gameLevel:int; // 読んで字のごとく, レベル
private var nextLevelUp:int; // 次回レベルアップするための ぷよ消去数ノルマ
private var floatingPhase:int; // == 0 なら新しい浮遊ぷよペア発生, それ以外は落下・左右移動・回転
private var keysPressed:uint; // その時点で押されているコントロールボタンのフラグ列
private var floating1:FloatingPuyo; // 浮遊ぷよ1 最初下にあり, 回転のとき軸になるほう
private var floating2:FloatingPuyo; // 浮遊ぷよ2 最初上にあり, 回転のとき降り回されるほう
private var next1:int; // 次回の浮遊ぷよ1
private var next1Shape:Shape; // そのグラフィクス
private var next2:int; // 次回の浮遊ぷよ2
private var next2Shape:Shape; // そのグラフィクス
private var dropCounter:int; // 浮遊ぷよツインズの年齢というか, フレーム齢
private var pilingUpPhase:int; // 積みぷよ処理の進行を制御する変数
private var eraseCount:int; // いまこの時に何個のぷよが消去されるか, 個数を保持
private var eraseColors:uint; // いまこの時に消去されるぷよの色をビット列として保持
private var chainReactionCount:int; // いまの消去が何度めの連鎖かを保持
private var frameCount:int; // 現在何フレームめ? (結局あんまり使ってない)
private var lastTime:Number; // テスト運転用に時刻を記録する変数
private var meanFps:Number; // テスト運転用に過去10フレーム分の実 fps を保持
private var savedFrameRate:Number; // ぷよ消滅表示中に, その前後のフレームレートを保持
private var monitor:TextField; // ひとまずすべての文字表示をここに放りこむ
private var suspending:Boolean; // 一時停止状態のとき true になる
/* ----------------------------------------------------------------
ゲームの各段階での動作の定義
---------------------------------------------------------------- */
// スコア加算とレベルアップ
private function addScore( n:int ):void {
score += n;
if ( erasedObjectCount >= nextLevelUp ) {
soundEffect(GameSound.LEVEL_UP);
gameLevel ++;
score += 100*gameLevel;
if ( gameLevel <= 7 ) {
dropPeriod = 24 - 3*(gameLevel-1);
nextLevelUp += 40;
} else if ( gameLevel <= 14 ) {
// フレームレートを上げ, 代わりに一旦落下周期を伸ばす
if ( gameLevel == 8 ) {
savedFrameRate = FRAMERATE_AT_HIGHER_LEVEL;
}
dropPeriod = 24 - 3*(gameLevel-8);
nextLevelUp += 80;
} else {
// これ以上は加速しない
dropPeriod = 4;
nextLevelUp += 100;
}
}
}
// サウンドエフェクトをここで開始 (これまた未設計)
// サウンドの停止処理はどこでやりましょうか? ( Event.COMPLETE のイベントリスナを使う )
private function soundEffect(why:int):void {
return;
}
// 次回の浮遊ぷよペアを発生させ, 予告窓に表示
private function setNextPair():void {
next1 = Math.floor(PuyoType.RED+Math.random()*4.9999);
next2 = Math.floor(PuyoType.RED+Math.random()*4.9999);
PuyoGraphics.draw(next1Shape.graphics, next1);
PuyoGraphics.draw(next2Shape.graphics, next2);
}
// 浮遊ぷよの回転
private function turn(direction:int):void {
if ( gameState != GameState.FLOATING ) return;
stage.removeEventListener(Event.ENTER_FRAME, atEveryFrame);
var a11:int,a12:int,a21:int,a22:int;
// 高校の数学の時間に習う (はずの) 90度回転の行列
switch (direction) {
case -2:
a11 = a22 = -1;
a12 = a21 = -1;
break;
case -1:
a11 = a22 = 0;
a12 = 1;
a21 = -1;
break;
case 0:
a11 = a22 = 1;
a12 = a21 = 0;
break;
case 1:
a11 = a22 = 0;
a12 = -1;
a21 = 1;
break;
case 2:
a11 = a22 = -1;
a12 = a21 = -1;
break;
default:
a11 = a22 = 1;
a12 = a21 = 0;
}
var dx:int = floating2.col - floating1.col;
var dy:int = floating2.row - floating1.row;
var newCol:int = floating1.col + a11*dx + a12*dy;
var newRow:int = floating1.row + a21*dx + a22*dy;
if ( ( newRow == Field.NUM_ROWS )
||( field.occupant(newCol,newRow) == PuyoType.SPACE ) ) {
// 普通に回転
floating2.disappear(field);
floating1.disappear(field);
floating2.locate(newCol,newRow);
floating1.appear(field);
floating2.appear(field);
} else {
// 普通に回転する余地がない場合. 軸が逆方向へ移動.
// それすらできない場合, 二つの浮遊ぷよの位置を交換
var newCol2:int = floating1.col*2 - newCol;
var newRow2:int = floating1.row*2 - newRow;
if ( ( newRow2 == Field.NUM_ROWS )||( field.occupant(newCol2,newRow2) == PuyoType.SPACE ) ) {
newCol = floating1.col;
newRow = floating1.row;
floating2.disappear(field);
floating1.disappear(field);
floating1.locate(newCol2,newRow2);
floating2.locate(newCol,newRow);
floating1.appear(field);
floating2.appear(field);
} else {
newCol = floating1.col;
newRow = floating1.row;
floating1.disappear(field);
floating2.disappear(field);
floating1.locate(floating2.col,floating2.row);
floating2.locate(newCol,newRow);
floating2.appear(field);
floating1.appear(field);
}
}
stage.addEventListener(Event.ENTER_FRAME, atEveryFrame);
}
// ぷよ浮遊状態の処理: ユーザのコントロールを受ける
private function floatingProcess():void {
if ( gameState != GameState.FLOATING ) return;
var swingOK:Boolean;
monitor.text = "Level "+gameLevel.toString()
+ "\n\nScore: " + score.toString()
+ "\nMax Chain: " + maximumChainCount.toString()
+ "\nErased: " + erasedObjectCount.toString()
+ "\n\n(" + meanFps.toFixed(2) + " fps)";
if ( floatingPhase == 0 ) {
floating1 = new FloatingPuyo(next1);
floating2 = new FloatingPuyo(next2);
floatingPhase = 1;
dropCounter = 0;
setNextPair();
floating2.locate(START_COL,START_ROW+1);
floating1.locate(START_COL,START_ROW);
floating2.appear(field);
floating1.appear(field);
keysPressed = 0;
} else {
dropCounter++;
// ユーザの操作への反応.
// 下向きキーにはこの次のぷよ下降処理のブロックで対応する.
if ( keysPressed&UP_KEYMASK ) {
keysPressed &= ~UP_KEYMASK;
soundEffect(GameSound.TURN);
turn(1);
return;
}
// 左移動の条件
if ( ( (keysPressed&LEFT_KEYMASK) != 0 )
&&( field.occupant( floating1.col-1, floating1.row ) == PuyoType.SPACE )
&&( ( floating2.row == Field.NUM_ROWS )
||( field.occupant( floating2.col-1, floating2.row ) == PuyoType.SPACE ) ) ) {
// ビット演算子 ( & や | ) の優先度が 関係演算子 == や != より 低いというのは
// どういう発想の仕様なんだろうか... ぶぅっ...
keysPressed &= ~LEFT_KEYMASK;
floating1.disappear(field);
floating2.disappear(field);
floating1.goLeft(field);
floating2.goLeft(field);
floating1.appear(field);
floating2.appear(field);
return;
}
// 右移動の条件
if ( ( (keysPressed&RIGHT_KEYMASK) != 0 )
&& ( field.occupant( floating1.col+1, floating1.row ) == PuyoType.SPACE )
&&( ( floating2.row == Field.NUM_ROWS )
||( field.occupant( floating2.col+1, floating2.row ) == PuyoType.SPACE ) ) ) {
keysPressed &= ~RIGHT_KEYMASK;
floating1.disappear(field);
floating2.disappear(field);
floating1.goRight(field);
floating2.goRight(field);
floating1.appear(field);
floating2.appear(field);
return;
}
if ( ( (dropCounter%dropPeriod) == 0 )||( keysPressed&DOWN_KEYMASK ) ) {
// 可能なら浮遊ぷよ下降
if ( ( field.occupant(floating1.col,floating1.row-1) == PuyoType.SPACE )
&&( field.occupant(floating2.col,floating2.row-1) == PuyoType.SPACE ) ) {
if ( keysPressed&DOWN_KEYMASK ) addScore(1);
floating1.disappear(field);
floating2.disappear(field);
floating1.goDown(field);
floating2.goDown(field);
floating1.appear(field);
floating2.appear(field);
} else { // 下降できなければ堆積状態へ遷移
floating1.pileOn(field);
floating2.pileOn(field);
gameState = GameState.PILING_UP;
pilingUpPhase = 0;
eraseCount = 0;
eraseColors = 0;
chainReactionCount = 0;
}
}
}
}
// 堆積状態: 積みぷよに対する処理
private function pilingUpProcess():void {
if ( gameState != GameState.PILING_UP ) return;
var scanResult:int = 0;
switch(pilingUpPhase) {
case 0: // 空白を詰める
field.pack();
field.drawMembers();
break;
case 1: // 消えるぷよがあるか判断
scanResult = field.scanPile();
if ( scanResult == 0 ) { // 処理完了. ゲームオーバー判定へ遷移
gameState = GameState.JUDGING;
} else { // 連鎖開始
// フレームレートを下げてアニメーションを効果的にする
savedFrameRate = stage.frameRate;
stage.frameRate = FRAMERATE_ON_ERASE;
chainReactionCount++;
if ( chainReactionCount > maximumChainCount ) maximumChainCount = chainReactionCount;
eraseCount = scanResult&0x7fff;
erasedObjectCount += eraseCount;
eraseColors = (scanResult>>16)&255;
// スコアを計算
var raise:int = 1;
for ( var i:int = 1 ; i < chainReactionCount ; ++i ) raise *= 2;
var setBits:uint = eraseColors;
var colorCount:int = 0;
while ( setBits != 0 ) { colorCount += setBits&1; setBits >>= 1; }
addScore( (eraseCount + colorCount - 4)*10*raise );
// 表示
monitor.text = "Reaction: " + chainReactionCount.toString();
}
break;
case 2:
soundEffect(GameSound.ERASE);
field.effectBeforeErase(0);
break;
case 3:
field.effectBeforeErase(0);
break;
case 4: field.effectBeforeErase(1); break;
case 5: field.effectBeforeErase(2); break;
case 6: field.effectBeforeErase(3); break;
case 7: field.effectBeforeErase(4); break;
case 8: field.effectBeforeErase(5); break;
case 9: field.effectBeforeErase(6); break;
case 10:
field.drawMembers();
stage.frameRate = savedFrameRate;
break;
}
pilingUpPhase = (pilingUpPhase+1)%11;
}
private function judgementProcess():void {//ゲームオーバー判定
if ( gameState != GameState.JUDGING ) return;
if ( field.occupant(START_COL,START_ROW) != PuyoType.SPACE ) {
gameState = GameState.GAME_OVER; // ゲームオーバー状態へ遷移
} else {
gameState = GameState.FLOATING; // ぷよ浮遊状態へ遷移
floatingPhase = 0;
}
}
// ゲームオーバー状態の動作, というより停止処理の定義
private function gameOverProcess():void {
if ( gameState != GameState.GAME_OVER ) return;
soundEffect(GameSound.GAME_OVER);
monitor.text = "\nLevel " + gameLevel.toString()
+ "\n\nScore: " + score.toString()
+ "\nMax Chain: " + maximumChainCount.toString()
+ "\nErased: " + erasedObjectCount.toString()
+ "\n** GAME OVER **"
+ "\n\nClick the stage\nto play again";
stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyPress);
stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyRelease);
stage.removeEventListener(Event.ENTER_FRAME, atEveryFrame);
stage.addEventListener(MouseEvent.CLICK, onClick);
}
// 一時停止
private function pauseOrResume(why:int):void {
monitor.text = "** SUSPENDING **";
if ( why != 0 ) { // フォーカスが外れた
monitor.appendText("\n\nResume: Click");
} else { // TABキーが押された
monitor.appendText("\n\nResume: TAB key");
}
keysPressed = 0; // キー入力をキャンセル
suspending = !suspending;
if ( suspending ) { // 一時停止
stage.removeEventListener(Event.ENTER_FRAME, atEveryFrame);
this.removeChild(field);
this.graphics.lineStyle(NaN);
this.graphics.beginFill(PuyoGraphics.FIELD_BGCOLOR);
this.graphics.drawRect(field.x,field.y,field.width,field.height);
PuyoGraphics.draw(next1Shape.graphics, PuyoType.SPACE);
PuyoGraphics.draw(next2Shape.graphics, PuyoType.SPACE);
} else { // 一時停止解除
monitor.text = "Resumed...";
this.graphics.lineStyle(NaN);
this.graphics.beginFill(STAGE_BGCOLOR);
this.graphics.drawRect(field.x,field.y,field.width,field.height);
PuyoGraphics.draw(next1Shape.graphics, next1);
PuyoGraphics.draw(next2Shape.graphics, next2);
this.addChild(field);
stage.addEventListener(Event.ENTER_FRAME, atEveryFrame);
}
}
/* ----------------------------------------------------------------
イベント処理ルーチン群
---------------------------------------------------------------- */
// キーボードイベントへの反応: キーが押された
private function onKeyPress(e:KeyboardEvent):void {
switch(e.keyCode) {
case Keyboard.LEFT: keysPressed |= LEFT_KEYMASK; break;
case Keyboard.UP: keysPressed |= UP_KEYMASK; break;
case Keyboard.RIGHT: keysPressed |= RIGHT_KEYMASK; break;
case Keyboard.DOWN: keysPressed |= DOWN_KEYMASK; break;
}
}
// キーボードイベントへの反応: キーが離された
private function onKeyRelease(e:KeyboardEvent):void {
switch(e.keyCode) {
case Keyboard.TAB: pauseOrResume(0); break;
case Keyboard.LEFT: keysPressed &= ~LEFT_KEYMASK; break;
case Keyboard.UP: keysPressed &= ~UP_KEYMASK; break;
case Keyboard.RIGHT: keysPressed &= ~RIGHT_KEYMASK; break;
case Keyboard.DOWN: keysPressed &= ~DOWN_KEYMASK; break;
}
}
// deactivate イベントリスナ. 一時停止する
private function onDeactivate(e:Event):void {
suspending = false;
pauseOrResume(1);
}
// activate イベントリスナ. 一時停止を解除する
private function onActivate(e:Event):void {
suspending = true;
pauseOrResume(0);
}
// click マウスイベントリスナ. ゲーム開始処理
private function onClick(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.CLICK, onClick);
monitor.text = "";
keysPressed = 0;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPress);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyRelease);
stage.addEventListener(Event.DEACTIVATE, onDeactivate)
stage.addEventListener(Event.ACTIVATE, onActivate)
suspending = false;
stage.frameRate = FRAMERATE_AT_START;
frameCount = 0;
floatingPhase = 0;
erasedObjectCount = 0;
maximumChainCount = 0;
gameLevel = 1;
nextLevelUp = 10;
dropPeriod = 24;
score = 0;
gameState = GameState.FLOATING;
field.clearMembers();
field.drawBackground();
field.drawMembers();
soundEffect(GameSound.GAME_START);
lastTime = 0;
stage.addEventListener(Event.ENTER_FRAME, atEveryFrame);
}
// enterFrame イベントリスナ. ここが主ループに相当
private function atEveryFrame(e:Event):void {
switch(gameState) {
case GameState.NO_PLAY: /* することがないなぁ... */ break;
case GameState.FLOATING: floatingProcess(); break;
case GameState.PILING_UP: pilingUpProcess(); break;
case GameState.JUDGING: judgementProcess(); break;
case GameState.GAME_OVER: gameOverProcess(); break;
default://未定義の状態? なんぢゃそりゃ...
}
frameCount ++;
if ( (frameCount%24) == 0 ) { // 24フレームごとに時刻を計って fps を計算
var deltaTime:Number = getTimer()-lastTime;
meanFps = 2.4e+4/deltaTime;
lastTime += deltaTime;
}
}
/* ----------------------------------------------------------------
初期化ルーチン群
---------------------------------------------------------------- */
private function initializeNextBox():void {
this.graphics.lineStyle(1,PuyoGraphics.FIELD_LINECOLOR);
this.graphics.beginFill(PuyoGraphics.FIELD_BGCOLOR);
this.graphics.drawRect(298,298,76,76);
this.graphics.endFill();
this.graphics.drawRect(300,300,72,72);
next1Shape = new Shape();
next2Shape = new Shape();
next1Shape.x = 336;
next1Shape.y = 336 + Field.GRID_SIZE/2;
next2Shape.x = next1Shape.x;
next2Shape.y = next1Shape.y - Field.GRID_SIZE;
next1Shape.filters = PuyoGraphics.PUYOFILTERLIST;
next2Shape.filters = PuyoGraphics.PUYOFILTERLIST;
this.addChild(next2Shape);
this.addChild(next1Shape);
}
private function initializeMonitorTextField():void {
var fmt:TextFormat = new TextFormat();
fmt.font = "Courier";
fmt.size = 18;
monitor.defaultTextFormat = fmt;
monitor.x = 240;
monitor.y = 32;
monitor.width = (stage.stageWidth - monitor.x - 20);
monitor.height = (288 - monitor.y);
monitor.textColor = 0xffffff;
}
private function initializeStageBackground():void {
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
this.graphics.clear();
this.graphics.beginFill(STAGE_BGCOLOR);
this.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
this.graphics.endFill();
}
private function setupField():void {
field.x = 32;
field.y = (stage.stageHeight-Field.GRID_SIZE*Field.NUM_ROWS)/2;
field.drawBackground();
field.drawMembers();
}
private function setupFieldMask():void {
if ( fieldMask == null ) {
fieldMask = new Shape();
}
fieldMask.graphics.clear();
fieldMask.graphics.beginFill(0xffffff);
fieldMask.graphics.drawRect(
-1, -1,
Field.GRID_SIZE*Field.NUM_COLS+2,
Field.GRID_SIZE*Field.NUM_ROWS+2
);
fieldMask.x = field.x;
fieldMask.y = field.y;
field.mask = fieldMask;
}
// プログラム全体の初期化
private function initialize(e:Event):void {
this.removeEventListener(Event.ADDED_TO_STAGE, initialize);
initializeStageBackground();
field = new Field();
setupField();
setupFieldMask();
this.addChild(field);
initializeNextBox();
setNextPair();
monitor = new TextField();
initializeMonitorTextField();
this.addChild(monitor);
monitor.text = "To start game,\nclick on the stage.\n\nUse arrow keys\nfor control.";
stage.addEventListener(MouseEvent.CLICK, onClick);
}
// The Main constructor simply calles initialize() function.
public function Main():void {
if ( stage != null ) {
initialize(null);
} else {
this.addEventListener(Event.ADDED_TO_STAGE, initialize);
}
}
} // end of class Main
} // end of package
/* ---------------------------------------------------------------- */
import flash.display.*;
import flash.filters.*;
import flash.utils.*;
/* ----------------------------------------------------------------
フィールドの保持すべきデータと受け持つべき処理を定義
---------------------------------------------------------------- */
class Field extends Sprite {
public static const GRID_SIZE:int = 32;
public static const NUM_ROWS:int = 12;
public static const NUM_COLS:int = 6;
private var memberList:Array; // 生き残っている積みぷよのリスト
private var eraseList:Array; // これから消去されるべき積みぷよのリスト
internal var shapeList:Array; // フィールドの各点ごとに, 表示用グラフィック・オブジェクト
// 積みぷよの消滅にともなってできた空白に
// 上にあるぷよを詰めなおす配列操作
public function pack():void {
var col:int,row:int;
for ( col = 0 ; col < NUM_COLS ; ++col ) {
var _list:Array = new Array(NUM_ROWS);
for ( row = 0 ; row < NUM_ROWS ; ++row ) {
_list[row] = memberList[arrayIndex(col,row)];
}
row = 0;
while ( row < _list.length ) {
if ( _list[row] == PuyoType.SPACE ) {
_list.splice(row,1);
} else {
row++;
}
}
while ( _list.length < NUM_ROWS ) {
_list.push(PuyoType.SPACE);
}
for ( row = 0 ; row < NUM_ROWS ; ++row ) {
memberList[arrayIndex(col,row)] = _list[row];
eraseList[arrayIndex(col,row)] = PuyoType.SPACE;
}
}
}
// 消去予定リストに載った位置のグラフィックに対して
// 消え去る挙動を描画
public function effectBeforeErase(phase:int):void {
for ( var i:int = 0 ; i < NUM_COLS*NUM_ROWS ; ++i ) {
if ( eraseList[i] != PuyoType.SPACE ) {
PuyoGraphics.drawBeforeErase(shapeList[i].graphics,eraseList[i],phase);
}
}
}
// 積みぷよを走査して
// サイズ4以上のクラスターのメンバーを積みぷよリストから
// 消去予定リストに移す
// 積みぷよリストの該当位置は空白に置き換える
// 返り値(uint型)の下位16ビットは 消えるぷよの個数
// 上位16ビットは, 消えるぷよの種類をあらわすビット列
// BIT 16: 赤ぷよ
// BIT 17: 青ぷよ
// BIT 18: 緑ぷよ
// BIT 19: 黄ぷよ
// BIT 20: 紫ぷよ
// BIT 21: おじゃまぷよ
// BIT 22..31 は 未定義でゼロ
// ただし, いまのところ おじゃまぷよ の消滅には未対応...
public function scanPile():uint {
const CLUSTER_BIG_ENOUGH:int = 4;
var pointStack:Array = [];
var workList:Array = new Array(NUM_COLS*NUM_ROWS);
var previousList:Array = new Array(NUM_COLS*NUM_ROWS);
// eraseList を消去し, memberListのコピーをふたつ作る
var i:int;
for ( i = 0 ; i < NUM_COLS*NUM_ROWS ; ++i ) {
eraseList[i] = PuyoType.SPACE;
workList[i] = memberList[i];
previousList[i] = memberList[i];
}
var col:int,row:int;
for ( col = 0 ; col < NUM_COLS ; ++col )
for ( row = 0 ; row < NUM_ROWS ; ++row ) {
// 位置 (col,row) のメンバーの属するクラスターを検出する
var count:int = 0;
var clusterValue:int = workList[arrayIndex(col,row)];
if ( clusterValue <= PuyoType.SPACE ) continue;
if ( clusterValue >= PuyoType.NEUTRAL ) continue;
var p0:Object = new Object();
p0.h = col;
p0.v = row;
pointStack.push(p0);
while ( pointStack.length > 0 ) {
var p:Object = pointStack.pop();
if ( workList[arrayIndex(p.h,p.v)] != clusterValue ) continue;
workList[arrayIndex(p.h,p.v)] = PuyoType.SPACE;
count++;
var up:int = 1;
while ( ( p.v+up < NUM_ROWS )
&&( workList[arrayIndex(p.h,p.v+up)] == clusterValue ) ) {
workList[arrayIndex(p.h,p.v+up)] = PuyoType.SPACE;
count++;
up++;
}
up--;
var down:int = 1;
while ( ( p.v-down >= 0 )
&&( workList[arrayIndex(p.h,p.v-down)] == clusterValue ) ) {
workList[arrayIndex(p.h,p.v-down)] = PuyoType.SPACE;
count++;
down++;
}
down--;
for each ( var n:int in [-1,1]) {
if ( ( p.h+n < 0 )||( p.h+n >= NUM_COLS ) ) continue;
var inCluster:Boolean = false;
for ( var k:int = p.v-down ; k <= p.v+up ; ++k ) {
if ( ( !inCluster )
&&( workList[arrayIndex(p.h+n,k)] == clusterValue ) ) {
var q:Object = new Object();
q.h = p.h+n;
q.v = k;
pointStack.push(q);
}
inCluster = ( workList[arrayIndex(p.h+n,k)] == clusterValue );
}
}
}
// クラスターのサイズが大きければ次のターンに備えて作業領域のコピーを作る
// クラスターのサイズが小さければ変更前の状態に戻す
if ( count >= CLUSTER_BIG_ENOUGH ) {
for ( i = 0 ; i < NUM_COLS*NUM_ROWS ; ++i ) {
previousList[i] = workList[i];
}
} else {
for ( i = 0 ; i < NUM_COLS*NUM_ROWS ; ++i ) {
workList[i] = previousList[i];
}
}
}
// 処理結果を memberList と eraseList に書き込む
// ついでにいくつ消えるか数えて値を返す
count = 0;
var colorBits:uint = 0;
for ( i = 0 ; i < NUM_COLS*NUM_ROWS ; ++i ) {
if ( workList[i] == memberList[i] ) {
eraseList[i] = PuyoType.SPACE;
memberList[i] = workList[i];
} else {
eraseList[i] = memberList[i];
memberList[i] = workList[i];
count++;
switch (eraseList[i]) {
case PuyoType.RED: colorBits |= 1; break;
case PuyoType.BLUE: colorBits |= 2; break;
case PuyoType.GREEN: colorBits |= 4; break;
case PuyoType.YELLOW: colorBits |= 8; break;
case PuyoType.PURPLE: colorBits |= 16; break;
case PuyoType.NEUTRAL: colorBits |= 32; break;
}
}
}
return (colorBits<<16)|count;
}
// フィールド位置にあるオブジェクトの種類を返す関数
public function occupant(col:int,row:int):int {
if ( ( col < 0 ) || ( col >= NUM_COLS ) ) return PuyoType.WALL;
if ( row >= NUM_ROWS ) return PuyoType.FLOOR;
if ( row < 0 ) return PuyoType.CEILING;
return memberList[arrayIndex(col,row)];
}
// フィールド位置を指定して配列へメンバーを代入
public function occupy(col:int,row:int,puyoType:int):void {
if ( ( col < 0 ) || ( col >= NUM_COLS ) ) return;
if ( row >= NUM_ROWS ) return;
if ( row < 0 ) return;
var index:int = arrayIndex(col,row);
memberList[index] = puyoType;
}
// 背景の再描画
public function drawBackground():void {
this.graphics.clear();
this.graphics.beginFill(PuyoGraphics.FIELD_BGCOLOR);
this.graphics.drawRect(0,0,GRID_SIZE*NUM_COLS+1,GRID_SIZE*NUM_ROWS+1);
this.graphics.endFill();
this.graphics.lineStyle(1,PuyoGraphics.FIELD_LINECOLOR);
for ( var n:int = 0 ; n <= NUM_COLS ; ++n ) {
this.graphics.moveTo(n*GRID_SIZE,0);
this.graphics.lineTo(n*GRID_SIZE, NUM_ROWS*GRID_SIZE);
}
for ( n = 0 ; n <= NUM_ROWS ; ++n ) {
this.graphics.moveTo(0,n*GRID_SIZE);
this.graphics.lineTo(NUM_COLS*GRID_SIZE,n*GRID_SIZE);
}
}
// 積みぷよアイコンの再描画
public function drawMembers():void {
for ( var index:int = 0 ; index < memberList.length ; ++index ) {
PuyoGraphics.draw(shapeList[index].graphics, memberList[index]);
}
}
// 列と行の二次元インデックスで一次元配列にアクセスするための
// インデックスの換算
private function arrayIndex(_x:int,_y:int):int {
return _x*NUM_ROWS+_y;
}
public function clearMembers():void {
for ( var index:int = NUM_ROWS*NUM_COLS-1 ; index >= 0 ; --index ) {
memberList[index] = 0;
eraseList[index] = 0;
}
}
// Fieldインスタンスのコンストラクタ
public function Field() {
memberList = new Array(NUM_ROWS*NUM_COLS);
eraseList = new Array(NUM_ROWS*NUM_COLS);
shapeList = new Array(NUM_ROWS*NUM_COLS);
this.clearMembers();
for ( var index:int = NUM_ROWS*NUM_COLS-1 ; index >= 0 ; --index ) {
shapeList[index] = new Shape();
shapeList[index].x = GRID_SIZE*(0.5+Math.floor(index/NUM_ROWS));
shapeList[index].y = GRID_SIZE*(-0.5+NUM_ROWS-(index%NUM_ROWS));
shapeList[index].filters = PuyoGraphics.PUYOFILTERLIST;
this.addChild(shapeList[index]);
}
}
} // Field クラスの定義おわり
/* ----------------------------------------------------------------
浮遊ぷよのクラス
---------------------------------------------------------------- */
class FloatingPuyo {
private var _col:int;
private var _row:int;
private var _puyoType:int;
public function get col():int {
return _col;
}
public function get row():int {
return _row;
}
public function get puyoType():int {
return _puyoType;
}
public function locate(h:int,v:int):void {
_col = h;
_row = v;
if ( _col < 0 ) _col = 0;
if ( _col >= Field.NUM_COLS ) _col = Field.NUM_COLS-1;
if ( _row < 0 ) _row = 0;
if ( _row >= Field.NUM_ROWS ) _row = Field.NUM_ROWS; // 天井のひとつ上まで許す
}
public function goDown(f:Field):void { // 下降する
_row--;
}
public function goLeft(f:Field):void {
_col--;
}
public function goRight(f:Field):void {
_col++;
}
public function pileOn(f:Field):void { // 自分自身を積みぷよとして field に固定する
f.occupy(_col,_row,_puyoType);
}
public function appear(f:Field):void { // フィールドの指定された位置にあらわれる
if ( ( _col < 0 )||( _col >= Field.NUM_COLS ) ) return;
if ( ( _row < 0 )||( _row >= Field.NUM_ROWS ) ) return;
var index:int = _col*Field.NUM_ROWS + _row;
PuyoGraphics.draw(f.shapeList[index].graphics, _puyoType);
}
public function disappear(f:Field):void { // 消える
if ( ( _col < 0 )||( _col >= Field.NUM_COLS ) ) return;
if ( ( _row < 0 )||( _row >= Field.NUM_ROWS ) ) return;
var index:int = _col*Field.NUM_ROWS + _row;
PuyoGraphics.draw(f.shapeList[index].graphics, f.occupant(_col,_row));
}
public function FloatingPuyo(puyoType:int) {
_puyoType = puyoType;
_col = Math.floor(Field.NUM_COLS/2);
_row = Field.NUM_ROWS-1;
}
} // FloatingPuyo クラスの定義おわり
/* ----------------------------------------------------------------
ぷよやその他オブジェクトの種類を示す定数値に名前でアクセスするためのクラス
---------------------------------------------------------------- */
class PuyoType {
public static const WALL:int = -3; // 左右の壁
public static const CEILING:int = -2; // 天井 (実際には天井はない)
public static const FLOOR:int = -1; // 床
public static const SPACE:int = 0; // 空所
public static const RED:int = 1; // 赤
public static const BLUE:int = 2; // 青
public static const GREEN:int = 3; // 緑
public static const YELLOW:int = 4; // 黄色
public static const PURPLE:int = 5; // 紫
public static const NEUTRAL:int = 6; // おじゃまぷよ
public static const ICE:int = 7; // 固ぷよ
public static const GLOW:int = 16; // 光っているフラグ
public static const BLINK:int = 32; // 目をつぶっているフラグ
public static const MAD:int = 64; // 目を大きく開いているフラグ
}
/* ----------------------------------------------------------------
ぷよの絵柄を描画するための手続を(どこからでも呼べるよう)別クラスとして用意
---------------------------------------------------------------- */
class PuyoGraphics {
public static const FIELD_BGCOLOR:uint = 0xcccccc;
public static const FIELD_LINECOLOR:uint = 0x9999cc;
public static const FACECOLOR_RED:uint = 0xff0000;
public static const FACECOLOR_BLUE:uint = 0x0000ff;
public static const FACECOLOR_GREEN:uint = 0x00cc00;
public static const FACECOLOR_YELLOW:uint = 0xffff00;
public static const FACECOLOR_PURPLE:uint = 0x800099;
public static const FACECOLOR_NEUTRAL:uint = 0xcccccc;
public static const FACECOLOR_ICE:uint = 0x666666;
public static const PUYOFILTERLIST:Array = [ new DropShadowFilter() ];
public static const DIAMETER:Number = 30;
public static function draw(g:Graphics, puyoType:int):void {
//....実際の描画手続き
g.clear();
if ( puyoType <= PuyoType.SPACE ) return; // 壁や床
if ( (puyoType&15) > PuyoType.ICE ) return; // 存在しない種類のぷよ
var cf:uint; // 塗り色
// 現時点では変形のフラグ (16,32,64)には対応しない
switch (puyoType&15) {
case PuyoType.RED: cf = FACECOLOR_RED; break;
case PuyoType.BLUE: cf = FACECOLOR_BLUE; break;
case PuyoType.GREEN: cf = FACECOLOR_GREEN; break;
case PuyoType.YELLOW: cf = FACECOLOR_YELLOW; break;
case PuyoType.PURPLE: cf = FACECOLOR_PURPLE; break;
case PuyoType.NEUTRAL: cf = FACECOLOR_NEUTRAL; break;
case PuyoType.ICE: cf = FACECOLOR_ICE; break;
}
g.beginFill(cf);
g.drawCircle(0,0,DIAMETER/2);
g.endFill();
g.lineStyle(0,0x000000);
g.beginFill(0xffffff);
g.drawCircle(-DIAMETER/4,0,DIAMETER/6);
g.endFill();
g.beginFill(0xffffff);
g.drawCircle( DIAMETER/4,0,DIAMETER/6);
g.endFill();
g.beginFill(0x000000);
g.drawCircle(-DIAMETER/5.2,0,DIAMETER/20);
g.endFill();
g.beginFill(0x000000);
g.drawCircle( DIAMETER/5.2,0,DIAMETER/20);
g.endFill();
}
public static function drawBeforeErase(g:Graphics, puyoType:int,phase:int):void {
//消え去るぷよのグラフィクスを描画
g.clear();
if ( puyoType <= PuyoType.SPACE ) return; // 壁や床
if ( (puyoType&15) > PuyoType.ICE ) return; // 存在しない種類のぷよ
var cf:uint; // 塗り色
// 現時点では変形のフラグ (16,32,64)には対応しない
switch (puyoType&15) {
case PuyoType.RED: cf = FACECOLOR_RED; break;
case PuyoType.BLUE: cf = FACECOLOR_BLUE; break;
case PuyoType.GREEN: cf = FACECOLOR_GREEN; break;
case PuyoType.YELLOW: cf = FACECOLOR_YELLOW; break;
case PuyoType.PURPLE: cf = FACECOLOR_PURPLE; break;
case PuyoType.NEUTRAL: cf = FACECOLOR_NEUTRAL; break;
case PuyoType.ICE: cf = FACECOLOR_ICE; break;
}
g.lineStyle(3,0xffcccc);
g.beginFill(cf);
switch(phase) {
case 0: g.drawCircle(0,0,DIAMETER/2); break;
case 1: g.drawCircle(0,0,DIAMETER/2*0.8); break;
case 2: g.drawCircle(0,0,DIAMETER/2*0.8*0.8); break;
case 3: g.drawCircle(0,0,DIAMETER/2*0.8*0.8*0.8); break;
case 4: g.drawCircle(0,0,DIAMETER/2*0.8*0.8*0.8*0.8); break;
case 5: g.drawCircle(0,0,DIAMETER/2*0.8*0.8*0.8*0.8*0.8); break;
case 6: g.drawCircle(0,0,DIAMETER/2*0.8*0.8*0.8*0.8*0.8*0.8); break;
default: g.drawCircle(0,0,1);
}
g.endFill();
if ( phase <= 1 ) {
g.lineStyle(0,0x000000);
g.beginFill(0xffffff);
g.drawCircle(-DIAMETER/4,0,DIAMETER/4.5);
g.endFill();
g.beginFill(0xffffff);
g.drawCircle( DIAMETER/4,0,DIAMETER/4.5);
g.endFill();
g.beginFill(0x000000);
g.drawCircle(-DIAMETER/4,0,DIAMETER/9);
g.endFill();
g.beginFill(0x000000);
g.drawCircle( DIAMETER/4,0,DIAMETER/9);
g.endFill();
}
}
}
/* ----------------------------------------------------------------
サウンドまわりのことをここで定義...
Todo: サウンドの読み込み, 再生開始処理, 再生終了時の処理
---------------------------------------------------------------- */
class GameSound {
internal static const GAME_OVER:int = -4;
internal static const GAME_START:int = -3;
internal static const TURN:int = -2;
internal static const LEVEL_UP:int = -1;
internal static const PILE_ON:int = 0;
internal static const ERASE:int = 1;
}
/* ----------------------------------------------------------------
ゲームの進行状況をあらわす整数値に名前でアクセスできるようにするための定義
---------------------------------------------------------------- */
class GameState {
internal static const NO_PLAY:int = 0;
internal static const FLOATING:int = 1;
internal static const PILING_UP:int = 2;
internal static const JUDGING:int = 3;
internal static const GAME_OVER:int = 4;
}