ActionScript 3.0: State Pattern

1. Pendahuluan
State Pattern merupakan salah satu bentuk dari design pattern yang memungkinkan sebuah objek merubah behaviournya ketika terjadi perubahan state.
Contohnya: pada objek lampu. Lampu punya dua buah state yaitu: on dan off. Kedua state tersebut punya struktur yang sama, tapi fungsinya jelas jauh berbeda. Satu lagi perbedaannya adalah bagaimana tiap state menrespon transisi. Untuk kasus lampu ini, transisinya adalah tombol on-off.
image
Keuntungan menggunakan pattern ini:
  1. Tiap behaviour object diletakkan pada state object yang berbeda-beda
  2. Transisi diletakkan pada tiap state object, sehingga tidak terbentuk monolithic conditional statement (if atau switch). Ketika jumlah state hanya sedikit, mungkin masih bisa pakai if atau switch, tetapi ketika jumlah state sudah cukup besar, pemakaian if atau switch untuk menghandle transisi jelas akan menimbulkan masalah
  3. Menghindari state yang inkonsisten, karena dengan memecah menjadi beberapa state object, dapat dipastikan transisi hanya akan terjadi di satu state object saja
Sedangkan untuk kekurangannya yaitu: jumlah objek menjadi lebih banyak. Sangat jelas sekali….

2. Struktur
Untuk struktur class dari state pattern kurang lebih adalah sebagai berikut ini:
image
Ada 4 komponen utama:
1. State Interface
Nantinya semua state objek akan mengimplement interface ini. Yang artinya juga semua state object bakal memiliki method yang didefinisikan di interface ini.
Selain itu, interface juga punya sifat dapat berperan sebagai datatype, jadi ketika ingin menginstatiating state object di context, kita cukup pake data tye dari interface ini

2. State object
Tempat di mana behaviour objek bakal diletakkan

3. Context
Berperan sebagai proxy untuk semua state object. Context ini juga untuk mengatur pemanggilan dan pergantian state object

4. Transisi
Sesuatu yang akan men-trigger sebuah state untuk berpidah ke state lainnya
Ada dua bentuk transisi:
    1. transisi didefinisikan di context
    2. transisi didefinisikan di state object

3. Lets Rawk!
Contoh implementasi state pattern ini gw ambil dari Buku Advanced ActionScript3.0 by Joey Lott & Danny Pattersen yang ceritanya membuat sebuah agent pemaen basket. Agent tsb punya 3 state: layup, threepoint, dan freethrow. Dan di sini transisinya nanti akan didefinisikan di MainClass, jangan bingung dulu, walau didefinisikan di MainClass namun pada intinya adalah berarti transisinya didefinisikan di context bukan di state object.

1. Pertama kita bangun dulu state interface nya. Kita beri nama IShooterState:
   1: package  
   2: {    
   3:     public interface IShooterState 
   4:     {
   5:         function getAccuracy():Number;
   6:         function getPointByVal():Number;
   7:     }    
   8: }

Ada dua method yaitu getAccuracy() untuk meng-generate kemungkinan masuk atau tidak masuk, dan method getPointByVal() untuk mendapatkan nilai dari shoot tersebut kalo berhasil masuk.

2. Membuat State Object nya
Di sini kita akan membangun 3 buah state object yang di beri nama: FreeThrowState, LayUpState, ThreePointState. Semua tentunya harus mengimplement interface IShooterState

   1: package State 
   2: {
   3:     import IShooterState;
   4:     
   5:     public class FreeThrowState implements IShooterState
   6:     {
   7:         
   8:         public function getAccuracy():Number
   9:         {
  10:             return 0.7;
  11:         }
  12:         
  13:         public function getPointByVal():Number
  14:         {
  15:             return 1;
  16:         }
  17:     }
  18: }

Yang di atas adalah contoh untuk FreeThrowState, karena mengimplement interface IShooterState jadi dia juga punya method getAccuracy() dan getPointByVal(). Method ini akan me-return nilai dari akurasi tembakan dan point kalo bola berhasil masuk.

Untuk dua state laennya strukturnya sama persis, hanya nilai yang di return oleh method2xnya yg beda. Monggo silakan berimajinasi dikit

3. Membuat context atau StateMachine
Buat kelas dan kasih nama ShooterStateMachine

   1: package
   2: {    
   3:     import State.*;
   4:     
   5:     public class ShooterStateMachine
   6:     {
   7:         private var state:IShooterState;
   8:         
   9:         private var index:int;
  10:         private var stateArray:Array = new Array();
  11:         
  12:         public static const LAY_UP_STATE:String = "lay_up_state";
  13:         public static const FREE_THROW_STATE:String = "free_throw_state";
  14:         public static const THREE_POINT_STATE:String = "three_point_state";
  15:         
  16:         public function ShooterStateMachine() 
  17:         {
  18:             index = 0;
  19:             
  20:             stateArray.push(LAY_UP_STATE);
  21:             stateArray.push(FREE_THROW_STATE);
  22:             stateArray.push(THREE_POINT_STATE);
  23:             
  24:             setState(stateArray[index]);
  25:         }    
  26:         
  27:         public function getAccuracy():Number
  28:         {
  29:             return state.getAccuracy();
  30:         }
  31:         
  32:         public function getPointByVal():Number
  33:         {
  34:             return state.getPointByVal();
  35:         }
  36:         
  37:         public function getIndex():int
  38:         {
  39:             return index;
  40:         }
  41:         
  42:         public function setIndex(val:int):void
  43:         {
  44:             index = val;
  45:         }
  46:         
  47:         public function getStateArray():Array
  48:         {
  49:             return stateArray;
  50:         }
  51:         
  52:         public function incrementIndex():void
  53:         {
  54:             index++;
  55:         }
  56:         
  57:         public function setState(stateName:String):void
  58:         {
  59:             switch(stateName)
  60:             {
  61:                 case LAY_UP_STATE:
  62:                 {
  63:                     state = new LayUpState();
  64:                     
  65:                     break;
  66:                 }
  67:                 case FREE_THROW_STATE:
  68:                 {
  69:                     state = new FreeThrowState();
  70:                     
  71:                     break;
  72:                 }
  73:                 case THREE_POINT_STATE:
  74:                 {
  75:                     state = new ThreePointState;
  76:                     
  77:                     break;
  78:                 }
  79:                 default:
  80:                 {
  81:                     state = null;
  82:                     
  83:                     break;
  84:                 }
  85:             }
  86:         }
  87:         
  88:         public function getState():String
  89:         {
  90:             var temp:String;
  91:             
  92:             switch(index)
  93:             {
  94:                 case 0:
  95:                 {
  96:                     temp = "lay up";
  97:                     
  98:                     break;
  99:                 }
 100:                 case 1:
 101:                 {
 102:                     temp = "free throw";
 103:                     
 104:                     break;
 105:                 }
 106:                 case 2:
 107:                 {
 108:                     temp = "three point";
 109:                     
 110:                     break;
 111:                 }
 112:             }
 113:             
 114:             return temp;
 115:         }
 116:     }
 117: }

lets break this f***ing code:

7: kita cukup buat satu variabel object state, dengan datatype IShooterState (inilah salah satu the power of interface)

9: variabel index untuk menunjukkan index dalam stateArray

10: stateArray, array untuk menyimpan state2x tsb

16-25: set nilai index menjadi = 0, masukkan semua state ke dalam array, set state awal

27: method getAccuracy(), untuk mengambil data akurasi dari state yang sedang berjalan

32: method getPointByVal(), untuk mengambil data point dari state yang sedang berjalan

37: method getIndex(), untuk mengambil current index di array

42: setIndex(), untuk mengganti index array

52: incrementIndex(), untuk menambahkan nilai index array seiring dengan berjalannya waktu

57: setState(), untuk mengganti state yang sedang berjalan sekarang dengan state baru

88: getState(), untuk mendapatkan data state yang sedang berjalan


4. Buat MainClassnya….
Almost there….

   1: package 
   2: {
   3:     import flash.display.Sprite;
   4:     import flash.events.Event;
   5:     import flash.events.TimerEvent;
   6:     import flash.utils.*;
   7:     
   8:     import com.carlcalderon.arthropod.Debug;
   9:     
  10:     public class Main extends Sprite 
  11:     {
  12:         private var point:Number;
  13:         private var shooter:ShooterStateMachine;
  14:         
  15:         public function Main():void 
  16:         {
  17:             if (stage) init();
  18:             else addEventListener(Event.ADDED_TO_STAGE, init);
  19:         }
  20:         
  21:         private function init(e:Event = null):void 
  22:         {
  23:             removeEventListener(Event.ADDED_TO_STAGE, init);
  24:             
  25:             point = 0;
  26:             
  27:             shooter = new ShooterStateMachine();
  28:             
  29:             var timer:Timer = new Timer(1000, 0);
  30:             timer.addEventListener(TimerEvent.TIMER, update, false, 0, true);
  31:             timer.start();
  32:         }
  33:         
  34:         private function calculateShot(acc:Number):Boolean
  35:         {
  36:             return Math.random() < acc;
  37:         }
  38:         
  39:         private function update(event:TimerEvent):void
  40:         {
  41:             if (calculateShot(shooter.getAccuracy()) == true)
  42:             {
  43:                 point += shooter.getPointByVal();
  44:                 
  45:                 Debug.log(shooter.getState() + " " + point);
  46:             }
  47:             else
  48:             {
  49:                 Debug.log(shooter.getState() + " miss");
  50:             }
  51:             
  52:             shooter.incrementIndex();                
  53:                         
  54:             if (shooter.getIndex() >= shooter.getStateArray().length)
  55:             {
  56:                 shooter.setIndex(0);
  57:             }
  58:             
  59:             shooter.setState(shooter.getStateArray()[shooter.getIndex()]);
  60:         }
  61:     }
  62:     
  63: }

12-13: buat variabel point untuk menampung poin dari hasil tembakan, dan object shooter sbg agen yang akan diujicoba

25-31: beri nilai awal point = 0. Instatiate object shooter. Buat sebuah timer dengan delay 1 detik. Nantinya state akan terus berubah tiap periode satu detik

34: method yang akan melakukan randomisasi masuk tidaknya tembakan berdasarkan nilai akurasi dari tiap2x state

39-50: Method update() atau ‘main game loop’. Pertama, cek apakah tembakan masuk atau tidak. Jika masuk, tambahkan point berdasarkan nilai poin yang dimiliki state, trace outputnya. Jika tidak, maka hanya akan mengeluarkan output “miss”.

52: tambahkan index dengan memanggil method incrementIndex()

54-57: Jika index lebih besar dari jumlah item dalam array, maka set index menjadi 0, atau berarti balik mulai dari index pertama lagi

59: ganti state dengan state yang batu sesuai nilai indexnya.

5. Test dan liad hasilnya….

image


Yap, that’s all buat state pattern…..

silakan mencoba…

untuk source codenya dapat di donlot dengan mencet tombol bawah ini:

downlx

Freelance 2D game artist, occassional game developers, lazy blogger, and professional procrasctinator

1 comment: