Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Memory support for role based creeps and the like #246

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Breaking changes

- Improve `Memory` support for role based creeps ([#246](https://github.com/screepers/typed-screeps/pull/246))

### Added

- Add type inference for params in filter callbacks ([#221](https://github.com/screepers/typed-screeps/pull/221))
Expand Down
78 changes: 46 additions & 32 deletions README.md
Jomik marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,44 @@ This repo has more activity and is considerably more up-to-date.

### Breaking Changes:

- `Memory` is typed by default. The added typings are:
- `Memory` is typed by default, each record defaults to `unknown`. The added typings are:

- `CreepMemory`
- `FlagMemory`
- `SpawnMemory`
- `RoomMemory`

If you like the idea of typed memory, but aren't ready to just jump fully in, you only need to make sure you define an interface for the above four types. Then you can extend them at a later time.
If you like the idea of typed memory, but aren't ready to just jump fully in, you only need to make sure you define the `Memory` interface loosely. Then you can extend them at a later time.

Example:
Example for [tutorial section 5](https://github.com/screeps/tutorial-scripts/tree/0fc8e9d2d6fe55415d39cf1a0118297480e275f3/section5) types

```TypeScript
interface CreepMemory { [name: string]: any };
interface FlagMemory { [name: string]: any };
interface SpawnMemory { [name: string]: any };
interface RoomMemory { [name: string]: any };
```
```ts
interface BuilderMemory {
role: "builder";
building: boolean;
}
interface HarvesterMemory {
role: "harvester";
}
interface UpgraderMemory {
role: "upgrader";
upgrading: boolean;
}

If you don't want to add types to the global `Memory` object, you will need to add the following interface along with the four above.
type MyCreepMemory = BuilderMemory | HarvesterMemory | UpgraderMemory;

Example:
interface Memory {
// TODO: Remove this once types are in place.
// Everything is allowed.
[key: string]: any;

```Typescript
interface Memory { [key: string]: any };
// TODO: Replace with specific memory types.
// Narrow down for the record types.
creeps: Record<string, MyCreepMemory>;
flags: Record<string, any>;
spawns: Record<string, any>;
rooms: Record<string, any>;
}
```

- Any place in code that uses a constant (ex `STRUCTURE_EXTENSION` or `FIND_MY_SPAWNS` is now constrained to use literal types. Here is the list of the new types:
Expand All @@ -66,18 +80,18 @@ This repo has more activity and is considerably more up-to-date.

To update your code, you just need to change any `string` types to match one of the above. For example, if your code had:

```TypeScript
```ts
function getBody(): string[] {
return [ WORK, MOVE, CARRY ];
return [WORK, MOVE, CARRY];
}

```

Change it to:

```TypeScript
function getBody(): BodyPartConstant[] { // this line changed
return [ WORK, MOVE, CARRY ];
```ts
function getBody(): BodyPartConstant[] {
// this line changed
return [WORK, MOVE, CARRY];
}
```

Expand All @@ -88,52 +102,52 @@ This repo has more activity and is considerably more up-to-date.

If you have code like this (un-type-asserted use of `Game.getObjectById`)

```TypeScript
interface Memory{
```ts
interface Memory {
towerIds: string[];
}

Memory.towerIds.forEach((towerId) => {
const tower = Game.getObjectById(towerId); // type of returned tower is 'unknown' instead of 'any'
tower.attack(targetCreep); // Error Object is of type unknown ts(2571)
})
});
```

Change it store typed Ids:

```TypeScript
interface Memory{
```ts
interface Memory {
towerIds: Array<Id<StructureTower>>;
}

Memory.towerIds.forEach((towerId) => {
const tower = Game.getObjectById(towerId); // type of returned tower is StructureTower
tower.attack(targetCreep);
})
});
```

If you're already manually asserting the type of the game object, you're not required to change anything immediately however this is deprecated and may be removed in the future.

```TypeScript
const towerId: Id<StructureTower> = "" as Id<StructureTower>;
```ts
const towerId: Id<StructureTower> = "" as Id<StructureTower>;
const tower1 = Game.getObjectById(towerId); // recommended use, returns StructureTower type
const tower2 = Game.getObjectById<StructureTower>(""); // @deprecated returns StructureTower type
const tower3 = Game.getObjectById("") as StructureTower; // @deprecated returns StructureTower type
```

`Id<T>` types are assignable to `string` but the reverse is not allowed implicitly. To assign a `string` type to an `Id<T>` type, you must explicitly assert the type.

```TypeScript
```ts
const typedId: Id<Creep> = "123" as Id<Creep>; // assertion required
const untypedId1: string = typedId; // no assertion required
```

- Game objects have typed id properties `id: Id<this>`. These typed ids can by passed to `Game.getObjectById()` to receive typed game objects matching the type of the Id. See above bullet for more details.

```TypeScript
creep.id // has type Id<Creep>
copy = Game.getObjectById(creep.id) // returns type Creep
tower.id // has type Id<StructureTower>
```ts
creep.id; // has type Id<Creep>
copy = Game.getObjectById(creep.id); // returns type Creep
tower.id; // has type Id<StructureTower>
```

### Additional (non-breaking) Features:
Expand Down
19 changes: 10 additions & 9 deletions build/concat.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const fs = require("fs");
const path = require("path");

fs.readdir(path.join(__dirname, "..", "src"), function(err, files) {
files = files.map(function(value) {
fs.readdir(path.join(__dirname, "..", "src"), function (err, files) {
files = files.map(function (value) {
return path.join("src", value);
});

Promise.all(files.map((name)=>fs.promises.readFile(name))).then(
(fileContents)=>{
fs.writeFileSync(path.join(__dirname, "..", "dist", "index.d.ts"),
Buffer.concat(fileContents));
}, (reason)=>{
console.log(reason);
});
Promise.all(files.map((name) => fs.promises.readFile(name))).then(
(fileContents) => {
fs.writeFileSync(path.join(__dirname, "..", "dist", "index.d.ts"), Buffer.concat(fileContents));
},
(reason) => {
console.log(reason);
},
);
});
2 changes: 1 addition & 1 deletion build/prependDescription.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const version = `${major}.${minor}.${patch}`;
var description = `// Type definitions for Screeps ${version}`;

if (fs.existsSync(BUILT_FILE_PATH)) {
prepend(BUILT_FILE_PATH, description, function(err) {
prepend(BUILT_FILE_PATH, description, function (err) {
if (err) console.error(err);
});
}
2 changes: 1 addition & 1 deletion build/prependHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const BUILT_FILE_PATH = path.resolve(__dirname, "../dist/index.d.ts");
const HEADER_TEXT = path.resolve(__dirname, "header.txt");

if (fs.existsSync(BUILT_FILE_PATH) && fs.existsSync(HEADER_TEXT)) {
prepend(BUILT_FILE_PATH, fs.readFileSync(HEADER_TEXT), function(err) {
prepend(BUILT_FILE_PATH, fs.readFileSync(HEADER_TEXT), function (err) {
if (err) console.error(err);
});
}
22 changes: 9 additions & 13 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Type definitions for Screeps 3.3.3
// Type definitions for Screeps 4.0.0
// Project: https://github.com/screeps/screeps
// Definitions by: Nhan Ho <https://github.com/NhanHo>
// Bryan <https://github.com/bryanbecker>
Expand Down Expand Up @@ -3207,19 +3207,15 @@ interface PriceHistory {
avgPrice: number;
stddevPrice: number;
}
interface Memory {
creeps: { [name: string]: CreepMemory };
powerCreeps: { [name: string]: PowerCreepMemory };
flags: { [name: string]: FlagMemory };
rooms: { [name: string]: RoomMemory };
spawns: { [name: string]: SpawnMemory };
}
interface Memory {}

type MemoryType<K extends string> = Memory extends { [k in K]: Record<string, infer M> } ? M : unknown;

interface CreepMemory {}
interface FlagMemory {}
interface PowerCreepMemory {}
interface RoomMemory {}
interface SpawnMemory {}
type CreepMemory = MemoryType<"creeps">;
type FlagMemory = MemoryType<"flags">;
type PowerCreepMemory = MemoryType<"powerCreeps">;
type RoomMemory = MemoryType<"rooms">;
type SpawnMemory = MemoryType<"spawns">;

declare const Memory: Memory;
/**
Expand Down
46 changes: 41 additions & 5 deletions dist/screeps-tests.ts
Jomik marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,30 @@ const body: BodyPartConstant[] = [WORK, WORK, CARRY, MOVE];
// Sample inputs for Game.map.findRoute testing
const anotherRoomName: Room = Game.rooms.W10S11;

// Sample memory extensions
interface CreepMemory {
type AttachRole<T extends string, O extends object & Partial<Record<"role", never>>> = { role: T } & O;

interface MinerMemory {
sourceId: Id<Source>;
}

interface UpgraderMemory {
upgrading: boolean;
}

interface CommonCreepMemory {
lastHits: number;
}

type MyCreepMemory = CommonCreepMemory & (AttachRole<"miner", MinerMemory> | AttachRole<"upgrader", UpgraderMemory>);

// Sample memory extensions
interface Memory {
creeps: Record<string, MyCreepMemory>;
flags: Record<string, unknown>;
spawns: Record<string, unknown>;
rooms: Record<string, unknown>;
}

// Typescript always uses 'string' as the type of a key inside 'for in' loops.
// In case of objects with a restricted set of properties (e.g. ResourceConstant as key in StoreDefinition)
// the type of the key should be narrowed down in order to prevent casting (key as ResourceConstant).
Expand Down Expand Up @@ -135,7 +153,20 @@ function resources(o: GenericStore): ResourceConstant[] {

{
for (const i of Object.keys(Game.spawns)) {
Game.spawns[i].createCreep(body);
Game.spawns[i].spawnCreep(body, "miner", {
memory: {
lastHits: 0,
sourceId: "" as Id<Source>,
role: "miner",
},
});
Game.spawns[i].spawnCreep(body, "error", {
// @ts-expect-error
memory: {
lastHits: 0,
role: "upgrader",
},
});

// Test StructureSpawn.Spawning
const creep: Spawning | null = Game.spawns[i].spawning;
Expand Down Expand Up @@ -210,8 +241,13 @@ function resources(o: GenericStore): ResourceConstant[] {
// Game.getObjectById(id)

{
creep.memory.sourceId = creep.pos.findClosestByRange(FIND_SOURCES)!.id;
const source = Game.getObjectById<Source>(creep.memory.sourceId);
if (creep.memory.role === "miner") {
creep.memory.sourceId = creep.pos.findClosestByRange(FIND_SOURCES)!.id;
const source = Game.getObjectById<Source>(creep.memory.sourceId);
} else {
// @ts-expect-error
creep.memory.sourceId = creep.pos.findClosestByRange(FIND_SOURCES)!.id;
}
}

// Game.notify(message, [groupInterval])
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typed-screeps",
"version": "3.3.3",
"version": "4.0.0",
"description": "Strong TypeScript declarations for the game Screeps.",
"repository": "screepers/typed-screeps",
"types": "./dist/index.d.ts",
Expand Down
20 changes: 8 additions & 12 deletions src/memory.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
interface Memory {
creeps: { [name: string]: CreepMemory };
powerCreeps: { [name: string]: PowerCreepMemory };
flags: { [name: string]: FlagMemory };
rooms: { [name: string]: RoomMemory };
spawns: { [name: string]: SpawnMemory };
}
interface Memory {}

interface CreepMemory {}
interface FlagMemory {}
interface PowerCreepMemory {}
interface RoomMemory {}
interface SpawnMemory {}
type MemoryType<K extends string> = Memory extends { [k in K]: Record<string, infer M> } ? M : unknown;

type CreepMemory = MemoryType<"creeps">;
type FlagMemory = MemoryType<"flags">;
type PowerCreepMemory = MemoryType<"powerCreeps">;
type RoomMemory = MemoryType<"rooms">;
type SpawnMemory = MemoryType<"spawns">;

declare const Memory: Memory;