Screeps:Arena Tutorialやった.
Screeps:Arena Tutorialやった
- [[2022-04-08]] 買った
「JavaScript」を実際に打ち込んで対戦する『Screeps: Arena』が配信 をみて, 面白そうだと思ったので購入. [[CODINGAME]] みたいにゲームAIを書いて対戦するらしい.
チュートリアルのメモ書きです.
Tutorial
spawnCreep
:creeps
(ユニットのこと) を spawn- それぞれ名前といろいろなスキルを持つ
Game.spawns['Spawn1']
でアクセス
Game.spawns['Spawn1'].spawnCreep([WORK, CARRY, MOVE], 'Harvester1');
黄色いマスはエネルギー源, WORK
で収穫でき, CARRY
で運べる
Script
ctrl+enter
で実行
creep
は1500tickの寿命を持つ- この例では,
spawnCreep
には 200energy必要
Room Controller
- 施設を色々作れる, レベル上げると色々出来る
creep.memory
に色々記憶できる- 内容はタブで確認できる
import { getObjectsByPrototype } from "/game/utils";
import { Creep, Flag } from "/game/prototypes";
import {} from "/game/constants";
import {} from "/arena";
import { getTicks } from "/game/utils";
export function loop() {
var myCreep = getObjectsByPrototype(Creep).find(creep => creep.my);
MOVE
: cost = 50, 動けるATTACK
: cost = 80, 攻撃できるRANGED_ATTACK
: cost = 250, 3マス以内なら攻撃できるHEAL
: cost = 250, 回復できるWORK
: cost = 100, エネルギーを収穫できるCARRY
: cost = 50, リソースを運べるTOUGH
: cost = 10, 何も出来ない
const hasPart = (creep, part) => {
return creep.body.some(bodyPart => bodyPart.type === part);
}
const invoke = (creep, method, ...args) => {
creep[method](...args);
};
import { getObjectsByPrototype } from "/game/utils";
import { Creep } from "/game/prototypes";
import { ATTACK, RANGED_ATTACK, HEAL, ERR_NOT_IN_RANGE } from "/game/constants";
import {} from "/arena";
export function loop() {
const party = getObjectsByPrototype(Creep).filter((c) => c.my);
const enemies = getObjectsByPrototype(Creep).filter((c) => !c.my);
for (let creep of party) {
if (hasPart(creep, ATTACK)) {
invoke(creep, "attack", enemies[0]);
}
if (hasPart(creep, RANGED_ATTACK)) {
invoke(creep, "rangedAttack", enemies[0]);
}
if (hasPart(creep, HEAL)) {
const damaged = party.find((c) => c.hits < c.hitsMax);
if (damaged) {
invoke(creep, "moveTo");
invoke(creep, "heal", damaged);
}
}
}
}
Tower
は10エネルギー使って, 50マス以内の敵を攻撃する(減衰有り)creep.transfer(tower, RESOURCE_ENERGY)
- エネルギーは
Container
から引き出せるcreep.withdraw(container, RESOURCE_ENERGY)
const party = getObjectsByPrototype(Creep).filter((c) => c.my);
const enemies = getObjectsByPrototype(Creep).filter((c) => !c.my);
const tower = getObjectsByPrototype(StructureTower)[0];
const container = getObjectsByPrototype(StructureContainer)[0];
if (tower.store[RESOURCE_ENERGY] < 10) {
if (party[0].store[RESOURCE_ENERGY] == 0) {
invoke(party[0], "withdraw", container, RESOURCE_ENERGY);
} else {
invoke(party[0], "transfer", tower, RESOURCE_ENERGY);
}
} else {
invoke(tower, "attack", enemies[0]);
}
Terrain
Plain
: 平地Natural
: 壊せない壁Constructed
: 壊せる壁Swamps
: 5tickに1回しか動けない, 5個のMOVE
があれば毎ターン動けるRoads
: ?? (後でちゃんと読む)
const flags = getObjectsByPrototype(Flag);
const flag = creep.findClosestByPath(flags);
creep.moveTo(flag);
spawnCreep
に必要なRESOURCE_ENERGY
が足りないと{ error: -6 }
が返ってくる- 事前にコストを計算する必要がある
Havest Source
const source = getObjectsByPrototype(Source)[0];
creep.harvest(source);
Build Structure
const site = createConstructionSite(50, 55, StructureTower);
creep.build(site);
// -1: ERR_NOT_OWNER
source
は.my
がtrue
にならないので注意
Tutorial: Final test
- しばらく経つと敵が壁を壊してこっちに来るのでそれまでにたくさんスポーンさせて迎え撃てばok
- 色々ユーティリティコード書いたら膨れた
- フェーズ毎にコールバックを呼んでステートマシンみたいにした
import {
getObjectsByPrototype,
createConstructionSite,
findClosestByPath,
} from "/game/utils";
import {
// Flag,
Creep,
Source,
StructureTower,
StructureContainer,
StructureSpawn,
ConstructionSite,
} from "/game/prototypes";
import {
MOVE,
WORK,
CARRY,
ATTACK,
RANGED_ATTACK,
HEAL,
ERR_NOT_IN_RANGE,
RESOURCE_ENERGY,
} from "/game/constants";
import { getTicks } from "/game";
const hasPart = (creep, part) => {
return creep.body.some((bodyPart) => bodyPart.type === part);
};
const invoke = (creep, method, ...args) => {
const res = creep[method](...args);
console.log(`${method}() => ${res}`);
if (res === ERR_NOT_IN_RANGE) {
creep.moveTo(args[0]);
}
return res;
};
const inspect = (creeps) => {
console.log(
creeps
.filter((c) => !!c)
.map((c) => c.body.map((part) => part.type).join(", "))
);
};
const i = (view) => {
console.log(`${view.name} @ (${view.x},${view.y})`);
};
const $ = (type, my = true) => {
const result = getObjectsByPrototype(type).filter((o) =>
my ? o.my === my : true
)[0];
if (!result) {
console.log(`No ${type.name} found`);
return null;
}
return result;
};
const $$ = (type, my = true) => {
return getObjectsByPrototype(type).filter((o) => o.my === my);
};
const needCost = (parts) => {
const map = {
[MOVE]: 50,
[WORK]: 100,
[CARRY]: 50,
[ATTACK]: 80,
[RANGED_ATTACK]: 150,
[HEAL]: 250,
};
return parts.map((part) => map[part]).reduce((a, b) => a + b, 0);
};
var party = [];
const tasks = [
// spawn creeps
() => {
const spawner = $(StructureSpawn);
const parts = [MOVE, ATTACK];
if (party.length < 5 && spawner.store[RESOURCE_ENERGY] >= needCost(parts)) {
const res = spawner.spawnCreep(parts);
console.log(res);
if (res.object) {
party.push(res.object);
}
}
console.log(
`party: ${party.length}, spawner: ${spawner.store[RESOURCE_ENERGY]}`
);
// inspect(party);
return party.length === 5;
},
// wait enemy
() => {
const enemies = getObjectsByPrototype(Creep).filter((c) => !c.my);
inspect(enemies);
const creep = $(Creep);
const path = findClosestByPath(creep, enemies);
console.log(`path: ${path}`);
creep.moveTo(path);
return !path;
},
// attack enemy
() => {
const creeps = $$(Creep);
const enemies = getObjectsByPrototype(Creep).filter((c) => !c.my);
creeps.forEach((creep) => {
invoke(creep, "attack", enemies[0]);
});
return false;
},
];
export function loop() {
console.log(`t = ${getTicks()} tasks: ${tasks.length}`);
const task = tasks[0];
if (task && task()) {
tasks.shift();
}
}
所感
- import pathが通らないので補完が全く効かなく, DXが低い
- Rustで書きたい (Web Assembly として実行できるらしい? #todo )
- デバッグが結構辛い
- エラーコードが定数なのでドキュメントを見に行かなければいけない
- シミュレータみたいの欲しい