如何在减少代码重复的情况下为游戏添加Levels?

时间:2016-12-22 01:45:04

标签: java oop design-patterns code-duplication

我正在设计一个多层次的游戏。我有一个安装类,它根据收到的参数设置电路板,它指示应该设置的电平。这是班级:

public class BoardState {
    public BoardState(InitialState state) {
        switch (state) {
            case EMPTY:
                setupEmptyState();
                break;
            case INTEGRATIONTEST:
                setupIntegrationTestState();
                break;
            case LEVEL_1:
                setupLevelOne();
                break;
            case LEVEL_2:
                setupLevelTwo();
                break;
            default:
                throw new Error("Invalid level selection");
        }
    }

    private void setupEmptyState() { }

    private void setupIntegrationTestState() { }

    private void setupLevelOne() { }

    private void setupLevelTwo() { }
}

这很好用,但每次添加新关卡时我都要在三个地方添加代码:InitialState enum定义接受状态列表,switch语句在构造函数和类的主体中,我必须添加一个方法来设置相关级别。

我想要保留的一件好事是,我的GUI会根据定义级别列表的enum为我添加的每个级别自动填充一个新按钮。

如何重构此代码,以便减少与添加新关卡相关的开销?

3 个答案:

答案 0 :(得分:3)

通常,当您需要减少代码重复时,会出现一个界面。这一次(根据您在OP中的评论),您似乎需要根据您所在的级别向电路板添加不同的对象:

import java.util.List;

public interface LevelSettings {
    List<GameObject> startingObjects();
}

现在,BoardState看起来像那样(不再是setupX()方法)

import java.util.List;

public class BoardState {
    private final List<GameObject> gameObjects;

    public BoardState(LevelSettings settings) {
        this.gameObjects = settings.startingObjects();
    }
}

由于您还指定了一个enum在GUI上动态创建按钮很不错,因此可以通过在枚举中实现接口来结合两者的优点(接口和枚举)。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY {
        @Override
        public List<GameObject> startingObjects() {
            return Collections.emptyList();
        }
    },
    INTEGRATIONTEST {
        @Override
        public List<GameObject> startingObjects() {
            GameObject g1 = new GameObject("dummy 1");
            GameObject g2 = new GameObject("dummy 2");
            return Arrays.asList(g1, g2);
        }
    },
    LEVEL_1 {
        @Override
        public List<GameObject> startingObjects() {
            //read a config file to get the starting objects informations
            //or also hardcoded (not preferred)
        }
    },
    LEVEL_2 {
        @Override
        public List<GameObject> startingObjects() {
            //read a config file to get the starting objects
            //or also hardcoded (not preferred)
        }
    };
}

基本上就是这样。如果您需要在LEVEL_3中添加InitialState,请执行所有操作。

更进一步

从这里开始,它超出了你的要求,如果你不相信,可以随意忽略这一部分。

作为一种好的做法,我只将这些配置存储在配置文件中,以减少更多的代码重复并获得灵活性:

import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("empty.level");
        }
    },
    INTEGRATIONTEST {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("integration_test.level");
        }
    },
    LEVEL_1 {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("1.level");
        }
    },
    LEVEL_2 {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("2.level");
        }
    };

    private static List<GameObject> readFromFile(String filename) {
        //Open file
        //Serialize its content in GameObjects
        //return them as a list
    }
}

因此,当您决定添加新级别时,您实际上只需要知道存储级别配置的文件名。

再往前走一步

您将看到的内容非常棘手,我建议您不要在生产代码中使用(但它会减少代码重复)!

import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY, INTEGRATIONTEST, LEVEL_1, LEVEL_2;

    @Override
    public List<GameObject> startingObjects() {
        return readFromFile(this.name() + ".level");
    }

    private static List<GameObject> readFromFile(String filename) {
        //Open file
        //Serialize its content in GameObjects
        //return them as a list
    }
}

这里我们依靠枚举名称来找到相应的正确文件。此代码有效,因为它基于约定,文件使用&#34; .level&#34;相应地命名为枚举名称。延期。当您需要添加新级别时,只需将其添加到枚举即可...

答案 1 :(得分:2)

您可以使用继承,多态是此处的关键字。

InitialState类设置为抽象基类(如果没有公共字段,则设置接口)并定义方法public abstract void setup();

abstract class InitialState {
    public abstract void setup();
}

然后,对于每个原始切换案例,从基类派生一个类,例如LevelOne,并通过覆盖setup()来实现其特定。

class LevelOne extends InitialState {
    @Override
    public void setup() {
        // The code from "setupLevelOne()" goes here
    }
}

您的BoardState课程缩小为:

public class BoardState {
    public BoardState(InitialState state) {
        // At runtime, the right method of the actual
        // state type will be called dynamically
        state.setup();
    }
}

但是,如果您需要设置BoardState类的内部状态,请考虑将设置方法定义为public abstract void setup(BoardState boardState),以便您可以访问其getter和setter方法。

这个appraoch也可以促进代码的重用,因为你可以为不同类型的级别添加几个抽象层。

答案 2 :(得分:1)

您可以将所有工作重构为一种方法。 假设它将一个int作为关卡的ID,同时它加载一个包含每个关卡的结构化信息的JSON文件,并创建给定的关卡。 例如:

"levels" : [
  "level" : {
      "id" : "001",
      "size" : "200",
      "difficulty" : "2"
  },
  "level" : {
      "id" : "002",
      "size" : "300",
      "difficulty" : "3"
  }
]

然后,在你的代码中:

public void setupLevel(int id) throws levelNotFoundException{
    //somehow like this
    Document doc = parse("levels.json");
    for(element elm: doc.get("levels")){
        if(Integer.parseInt(elm.get("id")).equals(id)){
            //setup your level
        }
    }
}

然后在某个地方调用你的方法:

int levelId = getNextLevel();
try{
setupLevel(levelId);
} catch (LevelNotFoundException e){e.printStackTrace();}

或者您可以使用XML,或者只是对其进行硬编码,并将所有级别存储在数组中