티스토리 뷰
🎥 ... 지난번
지난번에는 간단한 미네랄 채취를 하는 방법에 대해 설명하고 채취하였습니다. 이미 저번에 말했듯이 미네랄을 보다 효율적으로 미네랄을 채취할 수 있도록 하겠습니다.
우선 간단하게 자주 사용될 가능성이 높아 코드의 중복을 줄이고자 유틸 클래스를 만들고, 기존 game 변수를 static으로 하여 접근 용이하도록 하였습니다. 기타 여러 코드를 수정하였습니다. 이는 코드와 함께 설명하도록 하겠습니다.
🎯 코드와 설명
import bwapi.Unit;
import com.tistory.iqpizza6349.scb.util.BotUtil;
import java.util.Collections;
public class Worker {
private final Unit unit;
private Unit mineral;
public Worker(Unit unit, Unit mineral) {
this.unit = unit;
this.mineral = mineral;
}
public Worker(Unit unit) {
this(unit, BotUtil.findNearestUnit(unit, BotUtil.getGame().getMinerals(),
Collections.emptySet()));
}
public void reassignMineral(Unit mineral) {
this.mineral = mineral;
}
public Unit getUnit() {
return unit;
}
public Unit getMineral() {
return mineral;
}
public void gatherMineral() {
if (unit.isGatheringMinerals() || unit.isCarryingMinerals()) {
return;
}
unit.gather(mineral);
}
@Override
public String toString() {
return String.format("[Worker::%d] - [gather::%d]",
unit.getID(), mineral.getID());
}
}
먼저 일꾼(Worker) 클래스를 추가하였습니다. 일꾼과 해당 데이터를 쉽게 저장하기 위해 클래스를 따로 지정해두었습니다.
현재는 미네랄만 채취하면 되기에 미네랄과 관련해서만 개발하였습니다.
import bwapi.Unit;
import com.tistory.iqpizza6349.scb.unit.Worker;
import com.tistory.iqpizza6349.scb.util.BotUtil;
import java.util.HashSet;
import java.util.Set;
public class WorkerManager {
private static final Set<Worker> WORKERS = new HashSet<>();
private static final Set<Unit> SELECTED_MINERALS = new HashSet<>();
private WorkerManager() {}
public static void addMinerals(Worker worker) {
WORKERS.add(worker);
Unit mineral = worker.getMineral();
if (SELECTED_MINERALS.contains(mineral)) {
mineral = BotUtil.findNearestUnit(worker.getUnit(),
BotUtil.getGame().getMinerals(), SELECTED_MINERALS);
worker.reassignMineral(mineral);
}
SELECTED_MINERALS.add(mineral);
}
public static void gatherMineral() {
if (BotUtil.getGame().getFrameCount() % 24 != 0) { // 24 프레임, 약 1 ~ 1.2초
return;
}
for (Worker worker : WORKERS) {
worker.gatherMineral();
}
}
}
위 일꾼 클래스를 도와 더욱 정밀하게 행동을 수행할 수 있도록 하기 위해 WorkerManager라는 클래스 역시 개발하였습니다. 일종의 유틸 클래스로 지정된 미네랄이 비효율적인 미네랄(이미 누가 차지하고 있는 미네랄)인 경우, 다른 미네랄을 찾아 이를 다시 재지정해주는 역할과 함께 실질적으로 채취하도록 명령합니다.
import bwapi.Game;
import bwapi.Unit;
import com.tistory.iqpizza6349.scb.MineralGatherBot;
import java.util.List;
import java.util.Set;
public final class BotUtil {
private BotUtil() {}
public static Unit findNearestUnit(Unit standard, List<Unit> targets, Set<Unit> exclusions) {
Unit closestUnit = null;
int closestDistance = Integer.MAX_VALUE;
if (!exclusions.isEmpty()) {
targets.removeIf(exclusions::contains);
}
for (Unit target : targets) {
int distance = standard.getDistance(target);
if (distance < closestDistance) {
closestUnit = target;
closestDistance = distance;
}
}
return closestUnit;
}
public static Game getGame() {
return MineralGatherBot.getGame();
}
}
유틸 클래스는 다음과 같이 되어서 중복되거나 보다 편하게 사용하기 위한 메소드들을 위주로 두는 곳입니다.
import bwapi.*;
import com.tistory.iqpizza6349.scb.manager.WorkerManager;
import com.tistory.iqpizza6349.scb.unit.Worker;
public class MineralGatherBot extends DefaultBWListener {
private BWClient bwClient;
private static Game game;
public void gameStart() {
bwClient = new BWClient(this);
bwClient.startGame();
}
public static Game getGame() {
return game;
}
@Override
public void onStart() {
game = bwClient.getGame();
// game.setCommandOptimizationLevel(1); // API 최적화 옵션
// game.enableFlag(Flag.UserInput); // 사용자 입력
}
@Override
public void onFrame() {
Player player = game.self();
game.drawTextScreen(20, 20, String.format("%s has %d minerals",
player.getName(), player.minerals()));
WorkerManager.gatherMineral();
}
@Override
public void onUnitComplete(Unit unit) {
if (unit.getType().isWorker()) {
Worker worker = new Worker(unit);
WorkerManager.addMinerals(worker);
System.out.println(worker);
}
}
}
위 클래스들이 늘어남에 따라 봇의 코드 역시 수정되었습니다. (현재 아직 정보 은닉과 캡슐화를 할 정도로 아키텍처를 확실히 정한 것은 아니기에 대부분의 메소드들이 public인 점 양해 부탁드립니다.)
또 봇의 최적화를 setCommandOptimizationLevel 메소드를 통해 이를 최적화할 수 있습니다.
공식문서는 다음과 같이 작성되어 있습니다.
Sets the command optimization level. Command optimization is a feature in BWAPI that tries to reduce the APM of the bot by grouping or eliminating unnecessary game actions. For example, suppose the bot told 24 @Zerglings to @Burrow. At command optimization level 0, BWAPI is designed to select each Zergling to burrow individually, which costs 48 actions. With command optimization level 1, it can perform the same behaviour using only 4 actions. The command optimizer also reduces the amount of bytes used for each action if it can express the same action using a different command. For example, Right_Click uses less bytes than Move.
대충 해석하자면 APM을 최소화하여 최적화할 수 있다는 정도로 해석할 수 있습니다.
이 수치는 공식문서상, 0에서 4까지 존재합니다. 해당 정보는 다음과 같습니다.
level | 설명 |
0 |
최적화 하지 않음 |
1 | 일부 최적화 유닛 그룹화(12개), 명령 구조가 단순해짐 |
2 | 해킹이라고 오해를 받을 수 있을 정도의 최적화 1 단계에 취소 명령에 대해 유닛을 그룹화한다. |
3 | 유닛의 행동이 예상과 다르게 행동할 수 있을 정도의 최적화 1, 2단계의 최적화와 함께 UNLOAD 에 대해서 최적화됨 |
4 | 1, 2, 3단계 모두 최적화를 하며, 모든 행동을 수행하는 위치는 32배수의 반올림으로 처리됨 |
요약하자면, 행동을 예측하기 위해서는 레벨 2까지를 권장하며, 보다 단순하게 최적화하기를 원한다면 레벨 4를 권장합니다. 정밀하게 해야 하는 경우, 0 혹은 1을 권장합니다. 여담으로 사이다 봇은 레벨 1을 사용하였습니다.
enableFlag는 이미 정해진 Flag 중 필요한 Flag를 지정하여 사용할 수 있습니다.
그중 대표적으로 UserInput이 있습니다. 이는 사용자 입력을 허용하는 Flag입니다.
📷 시연 사진 및 영상
📋 다음엔...
유닛을 생산하는 방법에 대해서 다룰 것인데, 총 3파트로 나누어 진행할 예정입니다.
1. 단순 유닛 생산 (특정 건물에서 특정 유닛을 생산)
2. 마법 유닛 생산 (특정 유닛 혹은 건물에서 사용하는 능력에 따라 마법 형태의 유닛)
3. 특수 유닛 생산 (특정 유닛이 특정 유닛을 생산하거나 변경되는 유닛)
건물 건설은 위 3가지를 모두 다룬 후, 건물 건설 파트에서 따로 다룰 것입니다.
(가스 채취는 건물 건설에 대해 설명한 이후 다루도록 하겠습니다.)
'스타크래프트 봇' 카테고리의 다른 글
[스타크래프트 봇] 스타크래프트 봇을 자바로 개발해보자 (3) (0) | 2022.07.30 |
---|---|
[스타크래프트 봇] 스타크래프트 봇을 자바로 개발해보자 (1) (0) | 2022.07.16 |
[스타크래프트 봇] 스타크래프트 봇을 자바로 개발해보자 (0) (0) | 2022.07.09 |