State mode design program: Heroes in the game can rest, defend, attack normally and attack with skills according to different physical strength values.

Posted by alecks on Sun, 13 Feb 2022 06:12:53 +0100

1, Application scenario and case description

We often play games in our spare time to relax and relieve pressure. In many games, heroes can make different actions to face the enemy according to different physical strength.

Heroes can perform rest, defense, ordinary attack and skill attack. The physical strength consumed or recovered is different, for example:

  • Rest: recover the physical strength to the maximum, and there is no need to rest when the physical strength is the maximum;
  • Defense: consume 10 points of physical strength. You can't defend without physical strength;
  • Ordinary attack: consume 15 points of physical strength. You can't attack without physical strength;
  • Skill attack: consume 20 points of physical strength. Skill attack cannot be carried out without physical strength;

2, Case analysis and problem solving

If we write these operation logic in the same class - Hero class as the method of hero, the following problems will occur:

  1. In the later stage, if the operation logic (such as the change of consumed physical strength value) changes, the whole hero class needs to be modified;
  2. In the later stage, if you want to add operations (such as adding magic attacks), you need to modify the whole hero class;
  3. Only a lot of if else can be stacked. The code is hard to maintain and has poor readability;

So I decided to use state mode to solve this problem. Advantages of status mode in this scenario:

  1. Using a class to encapsulate a state (operation) of an object, it is easy to add a new state (operation);
  2. In the state mode, there is no need for a large number of conditional judgment statements in the Context - Hero class. The state presented by the Context instance becomes clearer and easier to understand;
  3. Using the state mode allows the user program to easily switch the state (operation) of the Context - Hero instance;
  4. Using the state mode will not make the internal state inconsistent in the instance of Context Hero;
  5. When the state object has no instance variable, each instance of Context Hero class can share a state object;

I abstract the hero's operations into states. The four operations correspond to four different specific states:

  • RestState
  • Defense state
  • NormalAttackState
  • SkillAttackState

3, Each role description and UML diagram

Role description:

  1. Context: Hero class
  2. Abstract State: State abstract class
  3. Concrete State: RestState, DefenseState, NormalAttackState and SkillAttackState classes

UML diagram:

IV Operation effect

  1. Generated apk

  1. Icon after installation

  1. Operation results

    1. Take a break:

    2. Defense:

    3. Common attacks:

    4. Skill attack:

5, Program complete source code

1. Environment (CONTEXT)

In this design, the environmental role is Hero class, and the code is as follows:

Hero.java

package com.yuebanquan.statepattern;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import pl.droidsonroids.gif.GifImageView;

public class Hero extends AppCompatActivity implements View.OnClickListener {
    State state;        //current state
    State lastState;    //Previous status
    private State restState;    //Resting state
    private State defenseState; //Defensive state
    private State normalAttackState;    //Normal attack state
    private State skillAttackState;     //Skill attack status

    private int MP;     //Hero's physical strength
    int MAXMP = 100;    //Maximum physical strength
    int MINMP = 0;      //Minimum physical strength

    //Declare the components of the interface
    TextView t_MP;
    TextView t_state;
    Button b_rest;
    Button b_defense;
    Button b_normalAttack;
    Button b_skillAttack;
    GifImageView doingGif;

    //Declare a Toast
    ToastShow toast = new ToastShow(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init(MAXMP);

    }

    /*************
     * Initialize Hero
     *************/
    public void init(int MP) {
        this.MP = MP;

        //Initialize various states
        restState = new RestState(this);
        defenseState = new DefenseState(this);
        normalAttackState = new NormalAttackState(this);
        skillAttackState = new SkillAttackState(this);

        //Heroes default to rest
        state = restState;
        lastState = restState;

        //Binding interface components
        t_MP = (TextView) findViewById(R.id.t_MP);
        t_state = (TextView) findViewById(R.id.t_state);
        b_rest = (Button) findViewById(R.id.b_rest);
        b_defense = (Button) findViewById(R.id.b_defense);
        b_normalAttack = (Button) findViewById(R.id.b_normalAttack);
        b_skillAttack = (Button) findViewById(R.id.b_skillAttack);
        doingGif = (GifImageView) findViewById(R.id.doingGif);
http://www.biyezuopin.vip
        //Set up listeners for several buttons
        b_rest.setOnClickListener(this);
        b_defense.setOnClickListener(this);
        b_normalAttack.setOnClickListener(this);
        b_skillAttack.setOnClickListener(this);
    }


    //monitor
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.b_rest:
                setLastState(getState());
                setState(getRestState());
                state.toDo();
                break;
            case R.id.b_defense:
                setLastState(getState());
                setState(getDefenseState());
                state.toDo();
                break;
            case R.id.b_normalAttack:
                setLastState(getState());
                setState(getNormalAttackState());
                state.toDo();
                break;
            case R.id.b_skillAttack:
                setLastState(getState());
                setState(getSkillAttackState());
                state.toDo();
                break;
        }
    }

    void midToast(String str, int showTime) {
        Toast toast = Toast.makeText(Hero.this, str, showTime);
        TextView v = (TextView) toast.getView().findViewById(android.R.id.message);
        toast.show();
    }


    //Various getter s and setter s
    public int getMP() {
        return MP;
    }

    public void setMP(int MP) {
        this.MP = MP;http://www.biyezuopin.vip
        t_MP.setText(String.valueOf(getMP()));
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public State getLastState() {
        return lastState;
    }

    public void setLastState(State lastState) {
        this.lastState = lastState;
    }

    public State getRestState() {
        return restState;
    }

    public State getDefenseState() {
        return defenseState;
    }

    public State getNormalAttackState() {
        return normalAttackState;
    }

    public State getSkillAttackState() {
        return skillAttackState;
    }
}

2. Abstract STATE

For this problem, abstract State is an abstract class of State, and the code is as follows:

State.java

package com.yuebanquan.statepattern;

public abstract class State {
    public abstract void toDo();
}

3. Concrete State

For this problem, there are four specific state roles, namely RestState, DefenseState, NormalAttackState and SkillAttackState. The codes are as follows:

RestState.java

package com.yuebanquan.statepattern;

import android.widget.Toast;

public class RestState extends State {
    Hero hero;

    RestState(Hero hero) {
        this.hero = hero;
    }

    public void toDo() {
        if (hero.getMP() == hero.MAXMP) { //Physical strength is full
            //hero.midToast("full physical strength, no need to rest", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Physical strength is full, no need to rest");
            hero.setState(hero.getLastState());
        } 
        else { //Enter a resting state
            hero.setMP(hero.MAXMP); //Physical strength is set to a maximum of 100
            hero.setState(hero.getRestState());
            hero.t_state.setText("Resting state");
            hero.doingGif.setImageResource(R.drawable.rest);
            //hero.midToast("rest, physical strength reaches the upper limit", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Rest and your physical strength reaches the upper limit");
        }
    }
}

DefenseState.java

package com.yuebanquan.statepattern;

import android.widget.Toast;

public class DefenseState extends State {
    Hero hero;

    DefenseState(Hero hero) {
        this.hero = hero;
    }

    public void toDo() {
        if (hero.getMP() < (hero.MINMP + 10)) { //Insufficient physical strength
            //hero.midToast("insufficient physical strength, unable to defend", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Insufficient physical strength, unable to defend");
            hero.setState(hero.getLastState());
        } http://www.biyezuopin.vip
        else {  //Enter a defensive state
            hero.setMP(hero.getMP() - 10); //Defence stamina - 10
            hero.setState(hero.getDefenseState());
            hero.t_state.setText("Defensive state");
            hero.doingGif.setImageResource(R.drawable.defense);
            //hero.midToast("defend, physical strength - 10", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Defense, physical strength-10");
        }
    }
}

NormalAttackState.java

package com.yuebanquan.statepattern;

import android.widget.Toast;

public class NormalAttackState extends State {
    Hero hero;

    NormalAttackState(Hero hero) {
        this.hero = hero;
    }

    public void toDo() {
        if (hero.getMP() < (hero.MINMP + 15)) { //Insufficient physical strength
            //hero.midToast("insufficient physical strength, unable to attack normally", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Insufficient physical strength, unable to attack normally");
            hero.setState(hero.getLastState());
        } else {  //Enter normal attack state
            hero.setMP(hero.getMP() - 15); //Normal attack stamina - 15
            hero.setState(hero.getNormalAttackState());
            hero.t_state.setText("Normal attack state");
            hero.doingGif.setImageResource(R.drawable.normal_attack);
            //hero.midToast("issue normal attack, physical strength - 15", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Normal attack, stamina-15");
        }
    }
}

SkillAttackState.java

package com.yuebanquan.statepattern;

import android.widget.Toast;

public class SkillAttackState extends State {
    Hero hero;

    SkillAttackState(Hero hero) {
        this.hero = hero;
    }

    public void toDo() {
        if (hero.getMP() < (hero.MINMP + 20)) { //Insufficient physical strength
            //hero.midToast("insufficient physical strength, unable to attack skills", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Insufficient physical strength, unable to attack skill");
            hero.setState(hero.getLastState());
        }
        else {  //Enter skill attack state
            hero.setMP(hero.getMP() - 20); //Skill attack stamina - 20
            hero.setState(hero.getSkillAttackState());
            hero.t_state.setText("Skill attack status");
            hero.doingGif.setImageResource(R.drawable.skill_attack);
            //hero.midToast("issue skill attack, physical strength - 20", Toast.LENGTH_SHORT);
            hero.toast.toastShow("Skill attack, physical strength-20");
        }
    }
}

4. Other categories

Expand a Toast show class to solve the Toast display delay problem of Android. The code is as follows:

Toast.java

package com.yuebanquan.statepattern;

import android.content.Context;
import android.widget.Toast;

public class ToastShow {
    private Context context;  //Prompt information in this window

    private Toast toast = null;  //Used to judge whether Toast has been executed
    public ToastShow(Context context) {
        this.context = context;
    }
    public void toastShow(String text) {
        if(toast == null)
        {
            toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);  //Normal execution
        }
        else {
            toast.setText(text);  //Used to overwrite the prompt information that has not disappeared before
        }
        toast.show();
    }
}

5. Android layout file

The layout file is located in app/src/main/res/layout, and the code is as follows:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".Hero">

    <!--seize a seat-->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp" />

    <!--Hero name-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hero name:"
            android:textSize="30sp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Moon half spring"
            android:textSize="30sp"/>
    </LinearLayout>

    <!--Physical strength value-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Physical strength:"
            android:textSize="30sp"/>

        <TextView
            android:id="@+id/t_MP"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="100"
            android:textSize="30sp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="/100"
            android:textSize="30sp"/>
    </LinearLayout>

    <!--state-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Status:"
            android:textSize="30sp"/>

        <TextView
            android:id="@+id/t_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Resting state"
            android:textSize="30sp"/>
    </LinearLayout>

    <!--&lt;!&ndash;seize a seat&ndash;&gt;-->
    <!--<TextView-->
        <!--android:layout_width="match_parent"-->
        <!--android:layout_height="100dp" />-->

    <pl.droidsonroids.gif.GifImageView
        android:id="@+id/doingGif"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:src="@drawable/rest"/>

    <Button
        android:id="@+id/b_rest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Take a break"
        android:textSize="30sp" />

    <Button
        android:id="@+id/b_defense"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Defend"
        android:textSize="30sp"/>

    <Button
        android:id="@+id/b_normalAttack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Common attack"
        android:textSize="30sp"/>

    <Button
        android:id="@+id/b_skillAttack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Skill attack"
        android:textSize="30sp"/>

</LinearLayout>

6. Android program declaration file

The program declaration file is located in app/src /, and the code is as follows:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yuebanquan.statepattern">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Hero">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

7. Android build gradle

The location is app /, specify the generated apk name and the introduced module, and the code is as follows:

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.yuebanquan.statepattern"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

android.applicationVariants.all {
    variant ->
        variant.outputs.all {
            // Specify the generated apk file name here
            outputFileName = "Big homework(State mode).apk"
        }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:support-vector-drawable:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    //Display GifView module
    implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
}

Topics: Game Development