「あのゲーム」試作中 (その2)
----------------------------------------------------------------
Todo: (連休あけまでに対応)
レベルを定める. レベルアップに合わせてスピードアップすること
サウンドエフェクトをつけること
----------------------------------------------------------------
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/dxVD
*/
/*
----------------------------------------------------------------
Todo: (連休あけまでに対応)
レベルを定める. レベルアップに合わせてスピードアップすること
サウンドエフェクトをつけること
----------------------------------------------------------------
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 FRAMERATE_AT_START:int = 24;
private const DROP_PERIOD:int = 24;
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;
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 const STAGE_BGCOLOR:uint = 0x000033;
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 floatingPhase:int;
private var keysPressed:uint; // その時点で押されているコントロールボタンのフラグ列
private var floating1:FloatingPuyo;
private var floating2:FloatingPuyo;
private var next1:int;
private var next2:int;
private var next1Shape:Shape;
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 savedFrameRate:Number;
private var monitor:TextField;
private var suspending:Boolean;
private var startTime:Number;
private var lapTime:Number;
private var deltaTime:Number;
private var fpsNow:Number;
private var fpsMax:Number;
private var fpsMin:Number;
private var fpsAvr:Number;
private function scoreKeeping():void {
// ここで得点を加算する
var raise:int = 1;
for ( var i:int = 0 ; i < chainReactionCount ; ++i ) raise *= 2;
score += (eraseCount-3)*10*raise;
monitor.text = "Chain reaction: " + chainReactionCount.toString()
+ "\nScore + " + (eraseCount*10*raise).toString();
}
private function soundEffectOnErase():void {
// ぷよ消去時のサウンドエフェクトをここで開始 (これまた未設計)
// サウンドの停止処理はどこでやりましょうか?
return;
}
private function showNextPair():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 {
stage.removeEventListener(Event.ENTER_FRAME, atEveryFrame);
var a11:int,a12:int,a21:int,a22:int;
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;
monitor.text = "Score: " + score.toString()
+ "\nMax Chain: " + maximumChainCount.toString()
+ "\nErased: " + erasedObjectCount.toString()
+ "\n\nframe rate "+stage.frameRate.toFixed(1)
+ "\nfps: "+fpsNow.toFixed(1)
+ "\nmin: "+fpsMin.toFixed(1)
+ "\nmax: "+fpsMax.toFixed(1)
+ "\navr: "+fpsAvr.toFixed(1);
if ( floatingPhase == 0 ) {
floating1 = new FloatingPuyo(next1);
floating2 = new FloatingPuyo(next2);
floatingPhase = 1;
dropCounter = 0;
showNextPair();
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;
turn(1);
return;
}
// それにしても, 関係演算子 == や != より
// ビット演算子 & や | の優先度が低いというのは
// どういう発想の仕様なんだろうか
var swingOK:Boolean //
= ( field.occupant( floating1.col-1, floating1.row ) == PuyoType.SPACE )
&&( ( floating2.row == Field.NUM_ROWS )
||( field.occupant( floating2.col-1, floating2.row ) == PuyoType.SPACE ) );
if ( ( (keysPressed&LEFT_KEYMASK) != 0 )&&( swingOK ) ) {
keysPressed &= ~LEFT_KEYMASK;
floating1.disappear(field);
floating2.disappear(field);
floating1.goLeft(field);
floating2.goLeft(field);
floating1.appear(field);
floating2.appear(field);
return;
}
swingOK = ( field.occupant( floating1.col+1, floating1.row ) == PuyoType.SPACE )
&&( ( floating2.row == Field.NUM_ROWS )
||( field.occupant( floating2.col+1, floating2.row ) == PuyoType.SPACE ) );
if ( ( (keysPressed&RIGHT_KEYMASK) != 0 )&&( swingOK ) ) {
keysPressed &= ~RIGHT_KEYMASK;
floating1.disappear(field);
floating2.disappear(field);
floating1.goRight(field);
floating2.goRight(field);
floating1.appear(field);
floating2.appear(field);
return;
}
if ( ( (dropCounter%DROP_PERIOD) == 0 )||( keysPressed&DOWN_KEYMASK ) ) {
// 可能なら浮遊ぷよ下降, できなければ堆積状態へ遷移
if ( ( field.occupant(floating1.col,floating1.row-1) == PuyoType.SPACE )
&&( field.occupant(floating2.col,floating2.row-1) == PuyoType.SPACE ) ) {
score += (keysPressed&DOWN_KEYMASK)?1:0;
floating1.disappear(field);
floating2.disappear(field);
floating1.goDown(field);
floating2.goDown(field);
floating1.appear(field);
floating2.appear(field);
} else /* if ( (dropCounter%DROP_PERIOD) == 0 ) */ {
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;
scoreKeeping();
}
break;
case 2:
soundEffectOnErase();
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;
monitor.text = "** GAME OVER! **"
+ "\n\nScore: " + score.toString()
+ "\nMax Chain: " + maximumChainCount.toString()
+ "\nErased: " + erasedObjectCount.toString()
+ "\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():void {
keysPressed = 0; // キー入力をキャンセル
suspending = !suspending;
if ( suspending ) {
stage.removeEventListener(Event.ENTER_FRAME, atEveryFrame);
this.removeChild(field);
this.graphics.lineStyle(NaN);
this.graphics.beginFill(0x000099);
this.graphics.drawRect(field.x,field.y,field.width,field.height);
PuyoGraphics.draw(next1Shape.graphics, PuyoType.SPACE);
PuyoGraphics.draw(next2Shape.graphics, PuyoType.SPACE);
monitor.text = "** SUSPENDING **\n\nResume: TAB key";
} 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);
/* === */ startTime = getTimer();
/* === */ lapTime = startTime;
/* === */ fpsMax = 0;
/* === */ fpsMin = 65536;
}
}
// キーボードイベントへの反応
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(); 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;
}
}
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);
suspending = false;
stage.frameRate = FRAMERATE_AT_START;
frameCount = 0;
floatingPhase = 0;
erasedObjectCount = 0;
maximumChainCount = 0;
score = 0;
gameState = GameState.FLOATING;
/* === */ startTime = getTimer();
/* === */ lapTime = startTime;
/* === */ fpsMax = 0;
/* === */ fpsMin = 65536;
field.clearMembers();
field.drawBackground();
field.drawMembers();
stage.addEventListener(Event.ENTER_FRAME, atEveryFrame);
}
// ここが主ループに相当
private function atEveryFrame(e:Event):void {
/* === */ deltaTime = getTimer() - lapTime;
/* === */ lapTime += deltaTime;
/* === */ fpsNow = 1000.0/deltaTime;
/* === */ if (fpsMax < fpsNow) fpsMax = fpsNow;
/* === */ if (fpsMin > fpsNow) fpsMin = fpsNow;
/* === */ fpsAvr = frameCount/lapTime*1000;
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 ++;
}
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 setFieldMask():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);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
// Your initialization code comes here...
this.graphics.clear();
this.graphics.beginFill(STAGE_BGCOLOR);
this.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
this.graphics.endFill();
field = new Field();
field.x = 32;
field.y = (stage.stageHeight-Field.GRID_SIZE*Field.NUM_ROWS)/2;
this.addChild(field);
setFieldMask();
field.drawBackground();
field.drawMembers();
initializeNextBox();
showNextPair();
monitor = new TextField();
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;
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.*;
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;
// case PuyoType.ICE:
}
}
}
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 = 0x666666;
public static const FIELD_LINECOLOR:uint = 0x333333;
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();
}
}
}
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;
}