Init files
This commit is contained in:
commit
3cd6a07fbf
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.rb]
|
||||
indent_style = space
|
||||
indent_size = 4
|
92
.gitignore
vendored
Normal file
92
.gitignore
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
# yarn lock file
|
||||
/yarn.lock
|
||||
|
||||
# npm lock file (v5.0.0+)
|
||||
/package-lock.json
|
||||
|
||||
# Ignore basic folders
|
||||
/dist
|
||||
/.rpt2_cache
|
||||
/tsc-out
|
||||
/node_modules
|
||||
/_book
|
||||
/build/*
|
||||
|
||||
# TypeScript definitions installed by Typings
|
||||
/typings
|
||||
|
||||
# Screeps Config
|
||||
screeps.json
|
||||
|
||||
# ScreepsServer data from integration tests
|
||||
/server
|
||||
|
||||
# Numerous always-ignore extensions
|
||||
*.diff
|
||||
*.err
|
||||
*.orig
|
||||
*.log
|
||||
*.rej
|
||||
*.swo
|
||||
*.swp
|
||||
*.zip
|
||||
*.vi
|
||||
*~
|
||||
|
||||
# Editor folders
|
||||
.cache
|
||||
.project
|
||||
.settings
|
||||
.tmproj
|
||||
*.esproj
|
||||
nbproject
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.idea
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# OSX
|
||||
# =========================
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM node:10-alpine
|
||||
|
||||
RUN apk add --no-cache --virtual .gyp \
|
||||
python \
|
||||
make \
|
||||
g++
|
16
Makefile
Normal file
16
Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
all: format build
|
||||
|
||||
build-docker:
|
||||
docker build -t screeps-node .
|
||||
|
||||
build:
|
||||
tools/npm run build:dev
|
||||
|
||||
format:
|
||||
tools/npm run format
|
||||
|
||||
test:
|
||||
tools/npm run test
|
||||
|
||||
coverage:
|
||||
tools/npm run coverage
|
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Screeps Typescript Starter
|
||||
|
||||
Screeps Typescript Starter is a starting point for a Screeps AI written in Typescript. It provides everything you need to start writing your AI whilst leaving `main.ts` as empty as possible.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
You will need:
|
||||
|
||||
- [Node.JS](https://nodejs.org/en/download) (10.x)
|
||||
- A Package Manager ([Yarn](https://yarnpkg.com/en/docs/getting-started) or [npm](https://docs.npmjs.com/getting-started/installing-node))
|
||||
- Rollup CLI (Optional, install via `npm install -g rollup`)
|
||||
|
||||
Download the latest source [here](https://github.com/screepers/screeps-typescript-starter/archive/master.zip) and extract it to a folder.
|
||||
|
||||
Open the folder in your terminal and run your package manager to install install the required packages and TypeScript declaration files:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# yarn
|
||||
yarn
|
||||
```
|
||||
|
||||
Fire up your preferred editor with typescript installed and you are good to go!
|
||||
|
||||
### Rollup and code upload
|
||||
|
||||
Screeps Typescript Starter uses rollup to compile your typescript and upload it to a screeps server.
|
||||
|
||||
Move or copy `screeps.sample.json` to `screeps.json` and edit it, changing the credentials and optionally adding or removing some of the destinations.
|
||||
|
||||
Running `rollup -c` will compile your code and do a "dry run", preparing the code for upload but not actually pushing it. Running `rollup -c --environment DEST:main` will compile your code, and then upload it to a screeps server using the `main` config from `screeps.json`.
|
||||
|
||||
You can use `-cw` instead of `-c` to automatically re-run when your source code changes - for example, `rollup -cw --environment DEST:main` will automatically upload your code to the `main` configuration every time your code is changed.
|
||||
|
||||
Finally, there are also NPM scripts that serve as aliases for these commands in `package.json` for IDE integration. Running `npm run push-main` is equivalent to `rollup -c --environment DEST:main`, and `npm run watch-sim` is equivalent to `rollup -cw --dest sim`.
|
||||
|
||||
#### Important! To upload code to a private server, you must have [screepsmod-auth](https://github.com/ScreepsMods/screepsmod-auth) installed and configured!
|
||||
|
||||
## Typings
|
||||
|
||||
The type definitions for Screeps come from [typed-screeps](https://github.com/screepers/typed-screeps). If you find a problem or have a suggestion, please open an issue there.
|
||||
|
||||
## Documentation
|
||||
|
||||
We've also spent some time reworking the documentation from the ground-up, which is now generated through [Gitbooks](https://www.gitbook.com/). Includes all the essentials to get you up and running with Screeps AI development in TypeScript, as well as various other tips and tricks to further improve your development workflow.
|
||||
|
||||
Maintaining the docs will also become a more community-focused effort, which means you too, can take part in improving the docs for this starter kit.
|
||||
|
||||
To visit the docs, [click here](https://screepers.gitbook.io/screeps-typescript-starter/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Issues, Pull Requests, and contribution to the docs are welcome! See our [Contributing Guidelines](CONTRIBUTING.md) for more details.
|
66
package.json
Normal file
66
package.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "screeps-typescript-starter",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"//": "If you add or change the names of destinations in screeps.json, make sure you update these scripts to reflect the changes",
|
||||
"scripts": {
|
||||
"lint": "tslint -p tsconfig.json \"src/**/*.ts\"",
|
||||
"build": "rollup -c",
|
||||
"push-main": "rollup -c --environment DEST:main",
|
||||
"push-pserver": "rollup -c --environment DEST:pserver",
|
||||
"push-sim": "rollup -c --environment DEST:sim",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"test": "npm run test-unit",
|
||||
"test-unit": "rollup -c rollup.test-unit-config.js && mocha dist/test-unit.bundle.js",
|
||||
"test-integration": "echo 'See docs/in-depth/testing.md for instructions on enabling integration tests'",
|
||||
"watch-main": "rollup -cw --environment DEST:main",
|
||||
"watch-pserver": "rollup -cw --environment DEST:pserver",
|
||||
"watch-sim": "rollup -cw --environment DEST:sim"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/screepers/screeps-typescript-starter.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Unlicense",
|
||||
"bugs": {
|
||||
"url": "https://github.com/screepers/screeps-typescript-starter/issues"
|
||||
},
|
||||
"homepage": "https://github.com/screepers/screeps-typescript-starter#readme",
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^11.1.0",
|
||||
"@rollup/plugin-multi-entry": "^3.0.0",
|
||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||
"@types/chai": "^4.1.6",
|
||||
"@types/lodash": "3.10.2",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^13.13.1",
|
||||
"@types/screeps": "^3.1.0",
|
||||
"@types/sinon": "^5.0.5",
|
||||
"@types/sinon-chai": "^3.2.0",
|
||||
"chai": "^4.2.0",
|
||||
"lodash": "^3.10.1",
|
||||
"mocha": "^5.2.0",
|
||||
"prettier": "^2.0.4",
|
||||
"rollup": "^2.6.1",
|
||||
"rollup-plugin-buble": "^0.19.8",
|
||||
"rollup-plugin-clear": "^2.0.7",
|
||||
"rollup-plugin-nodent": "^0.2.2",
|
||||
"rollup-plugin-screeps": "^1.0.0",
|
||||
"rollup-plugin-typescript2": "^0.27.0",
|
||||
"sinon": "^6.3.5",
|
||||
"sinon-chai": "^3.2.0",
|
||||
"ts-node": "^8.8.2",
|
||||
"tslint": "^6.1.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-plugin-prettier": "^2.3.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
}
|
32
rollup.config.js
Normal file
32
rollup.config.js
Normal file
@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
import clear from 'rollup-plugin-clear';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
import screeps from 'rollup-plugin-screeps';
|
||||
|
||||
let cfg;
|
||||
const dest = process.env.DEST;
|
||||
if (!dest) {
|
||||
console.log("No destination specified - code will be compiled but not uploaded");
|
||||
} else if ((cfg = require("./screeps.json")[dest]) == null) {
|
||||
throw new Error("Invalid upload destination");
|
||||
}
|
||||
|
||||
export default {
|
||||
input: "src/main.ts",
|
||||
output: {
|
||||
file: "dist/main.js",
|
||||
format: "cjs",
|
||||
sourcemap: true
|
||||
},
|
||||
|
||||
plugins: [
|
||||
clear({ targets: ["dist"] }),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
typescript({tsconfig: "./tsconfig.json"}),
|
||||
screeps({config: cfg, dryRun: cfg == null})
|
||||
]
|
||||
}
|
34
rollup.test-integration-config.js
Normal file
34
rollup.test-integration-config.js
Normal file
@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
|
||||
import clear from 'rollup-plugin-clear';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
import buble from 'rollup-plugin-buble';
|
||||
import multiEntry from '@rollup/plugin-multi-entry';
|
||||
import nodent from 'rollup-plugin-nodent';
|
||||
|
||||
export default {
|
||||
input: 'test/integration/**/*.test.ts',
|
||||
output: {
|
||||
file: 'dist/test-integration.bundle.js',
|
||||
name: 'lib',
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
globals: {
|
||||
chai: 'chai',
|
||||
it: 'it',
|
||||
describe: 'describe'
|
||||
}
|
||||
},
|
||||
external: ['chai', 'it', 'describe'],
|
||||
plugins: [
|
||||
clear({ targets: ["dist/test.bundle.js"] }),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
typescript({tsconfig: "./tsconfig.test-integration.json"}),
|
||||
nodent(),
|
||||
multiEntry(),
|
||||
buble()
|
||||
]
|
||||
}
|
32
rollup.test-unit-config.js
Normal file
32
rollup.test-unit-config.js
Normal file
@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
import clear from 'rollup-plugin-clear';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
import buble from 'rollup-plugin-buble';
|
||||
import multiEntry from '@rollup/plugin-multi-entry';
|
||||
|
||||
export default {
|
||||
input: 'test/unit/**/*.test.ts',
|
||||
output: {
|
||||
file: 'dist/test-unit.bundle.js',
|
||||
name: 'lib',
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
globals: {
|
||||
chai: 'chai',
|
||||
it: 'it',
|
||||
describe: 'describe'
|
||||
}
|
||||
},
|
||||
external: ['chai', 'it', 'describe'],
|
||||
plugins: [
|
||||
clear({ targets: ["dist/test.bundle.js"] }),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
typescript({tsconfig: "./tsconfig.json"}),
|
||||
multiEntry(),
|
||||
buble()
|
||||
]
|
||||
}
|
27
screeps.sample.json
Normal file
27
screeps.sample.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"main": {
|
||||
"token": "YOUR_TOKEN",
|
||||
"protocol": "https",
|
||||
"hostname": "screeps.com",
|
||||
"port": 443,
|
||||
"path": "/",
|
||||
"branch": "main"
|
||||
},
|
||||
"sim": {
|
||||
"token": "YOUR_TOKEN",
|
||||
"protocol": "https",
|
||||
"hostname": "screeps.com",
|
||||
"port": 443,
|
||||
"path": "/",
|
||||
"branch": "sim"
|
||||
},
|
||||
"pserver": {
|
||||
"email": "username",
|
||||
"password": "Password",
|
||||
"protocol": "http",
|
||||
"hostname": "1.2.3.4",
|
||||
"port": 21025,
|
||||
"path": "/",
|
||||
"branch": "main"
|
||||
}
|
||||
}
|
93
src/harvester.ts
Normal file
93
src/harvester.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/** @param {Creep} creep **/
|
||||
export function runAsHarvester(creep: Creep) {
|
||||
if (creep.store.getFreeCapacity() > 0) {
|
||||
let sources = creep.room.find(FIND_SOURCES);
|
||||
if (creep.harvest(sources[0]) === ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(sources[0], { visualizePathStyle: { stroke: '#ffaa00' } });
|
||||
}
|
||||
} else {
|
||||
let targets = creep.room.find(FIND_STRUCTURES, {
|
||||
filter: (structure) => {
|
||||
return (
|
||||
(structure.structureType === STRUCTURE_EXTENSION ||
|
||||
structure.structureType === STRUCTURE_SPAWN ||
|
||||
structure.structureType === STRUCTURE_TOWER) &&
|
||||
structure.store.getFreeCapacity(RESOURCE_ENERGY) > 0
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (targets.length > 0) {
|
||||
if (creep.transfer(targets[0], RESOURCE_ENERGY) === ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(targets[0], { visualizePathStyle: { stroke: '#ffffff' } });
|
||||
}
|
||||
} else {
|
||||
const spawns = creep.room.find(FIND_MY_SPAWNS);
|
||||
if (spawns.length > 0) {
|
||||
creep.say('to spawn');
|
||||
creep.moveTo(spawns[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Creep} creep **/
|
||||
export function runAsBuilder(creep: Creep) {
|
||||
const memory = creep.memory as CreepMemory & { building: boolean | undefined };
|
||||
|
||||
if (memory.building && creep.store[RESOURCE_ENERGY] === 0) {
|
||||
memory.building = false;
|
||||
creep.say('🔄 harvest');
|
||||
}
|
||||
|
||||
if (!memory.building && creep.store.getFreeCapacity() === 0) {
|
||||
memory.building = true;
|
||||
creep.say('🚧 build');
|
||||
}
|
||||
|
||||
if (memory.building) {
|
||||
const targets = creep.room.find(FIND_CONSTRUCTION_SITES);
|
||||
if (targets.length > 0) {
|
||||
if (creep.build(targets[0]) == ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(targets[0], { visualizePathStyle: { stroke: '#ffffff' } });
|
||||
}
|
||||
} else {
|
||||
const spawns = creep.room.find(FIND_MY_SPAWNS);
|
||||
if (spawns.length > 0) {
|
||||
creep.say('to spawn');
|
||||
creep.moveTo(spawns[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const sources = creep.room.find(FIND_SOURCES);
|
||||
if (creep.harvest(sources[0]) == ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(sources[0], { visualizePathStyle: { stroke: '#ffaa00' } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Creep} creep **/
|
||||
export function runAsUpgrader(creep: Creep) {
|
||||
const memory = creep.memory as CreepMemory & { upgrading: boolean | undefined };
|
||||
|
||||
if (memory.upgrading && creep.store[RESOURCE_ENERGY] === 0) {
|
||||
memory.upgrading = false;
|
||||
creep.say('🔄 harvest');
|
||||
}
|
||||
|
||||
if (!memory.upgrading && creep.store.getFreeCapacity() === 0) {
|
||||
memory.upgrading = true;
|
||||
creep.say('⚡ upgrade');
|
||||
}
|
||||
|
||||
if (memory.upgrading) {
|
||||
if (creep.room.controller && creep.upgradeController(creep.room.controller) == ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(creep.room.controller, { visualizePathStyle: { stroke: '#ffffff' } });
|
||||
}
|
||||
} else {
|
||||
const sources = creep.room.find(FIND_SOURCES);
|
||||
if (creep.harvest(sources[0]) == ERR_NOT_IN_RANGE) {
|
||||
creep.moveTo(sources[0], { visualizePathStyle: { stroke: '#ffaa00' } });
|
||||
}
|
||||
}
|
||||
}
|
77
src/main.ts
Normal file
77
src/main.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { ErrorMapper } from 'utils/ErrorMapper';
|
||||
import { runAsBuilder, runAsHarvester, runAsUpgrader } from './harvester';
|
||||
|
||||
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
const ALPHABET_LENGTH = ALPHABET.length - 1;
|
||||
|
||||
function generateId(count: number): string {
|
||||
let str = '';
|
||||
for (let i = 0; i < count; ++i) {
|
||||
let symbolIndex = Math.floor(Math.random() * ALPHABET_LENGTH);
|
||||
str += ALPHABET[symbolIndex];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function uniqId(prefix: string = 'id'): string {
|
||||
return prefix + generateId(16);
|
||||
}
|
||||
|
||||
const ROLE_HARVESTER = 'harvester';
|
||||
const ROLE_UPGRADER = 'upgrader';
|
||||
const ROLE_BUILDER = 'builder';
|
||||
|
||||
// When compiling TS to JS and bundling with rollup, the line numbers and file names in error messages change
|
||||
// This utility uses source maps to get the line numbers and file names of the original, TS source code
|
||||
export const loop = ErrorMapper.wrapLoop(() => {
|
||||
console.log(`Current game tick is ${Game.time}`);
|
||||
|
||||
// Automatically delete memory of missing creeps
|
||||
for (const name in Memory.creeps) {
|
||||
if (!(name in Game.creeps)) {
|
||||
delete Memory.creeps[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Create new creeps
|
||||
const HARVESTER_CREEP_COUNT = 1;
|
||||
const harvesterCreeps = Object.values(Game.creeps).filter((c) => c.memory.role === ROLE_HARVESTER);
|
||||
if (harvesterCreeps.length < HARVESTER_CREEP_COUNT) {
|
||||
const firstSpawn = _.first(Object.values(Game.spawns));
|
||||
const name = uniqId(ROLE_HARVESTER);
|
||||
const err = firstSpawn.spawnCreep([WORK, CARRY, MOVE], name, { memory: { role: ROLE_HARVESTER } as CreepMemory });
|
||||
console.log('Err', err);
|
||||
}
|
||||
|
||||
const UPGRADER_CREEP_COUNT = 2;
|
||||
const upgraderCreeps = Object.values(Game.creeps).filter((c) => c.memory.role === ROLE_UPGRADER);
|
||||
if (upgraderCreeps.length < UPGRADER_CREEP_COUNT) {
|
||||
const firstSpawn = _.first(Object.values(Game.spawns));
|
||||
const name = uniqId(ROLE_UPGRADER);
|
||||
const err = firstSpawn.spawnCreep([WORK, CARRY, MOVE], name, { memory: { role: ROLE_UPGRADER } as CreepMemory });
|
||||
console.log('Err', err);
|
||||
}
|
||||
|
||||
const BUILDER_CREEP_COUNT = 2;
|
||||
const builderCreeps = Object.values(Game.creeps).filter((c) => c.memory.role === ROLE_BUILDER);
|
||||
if (builderCreeps.length < BUILDER_CREEP_COUNT) {
|
||||
const firstSpawn = _.first(Object.values(Game.spawns));
|
||||
const name = uniqId(ROLE_BUILDER);
|
||||
const err = firstSpawn.spawnCreep([WORK, CARRY, MOVE], name, { memory: { role: ROLE_BUILDER } as CreepMemory });
|
||||
console.log('Err', err);
|
||||
}
|
||||
|
||||
// Process current creeps
|
||||
for (let name in Game.creeps) {
|
||||
const creep = Game.creeps[name];
|
||||
if (creep.memory.role === ROLE_HARVESTER) {
|
||||
runAsHarvester(creep);
|
||||
}
|
||||
if (creep.memory.role === ROLE_UPGRADER) {
|
||||
runAsUpgrader(creep);
|
||||
}
|
||||
if (creep.memory.role === ROLE_BUILDER) {
|
||||
runAsBuilder(creep);
|
||||
}
|
||||
}
|
||||
});
|
20
src/types.d.ts
vendored
Normal file
20
src/types.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// example declaration file - remove these and add your own custom typings
|
||||
|
||||
// memory extension samples
|
||||
interface CreepMemory {
|
||||
role: string;
|
||||
room: string;
|
||||
working: boolean;
|
||||
}
|
||||
|
||||
interface Memory {
|
||||
uuid: number;
|
||||
log: any;
|
||||
}
|
||||
|
||||
// `global` extension samples
|
||||
declare namespace NodeJS {
|
||||
interface Global {
|
||||
log: any;
|
||||
}
|
||||
}
|
90
src/utils/ErrorMapper.ts
Normal file
90
src/utils/ErrorMapper.ts
Normal file
@ -0,0 +1,90 @@
|
||||
// tslint:disable:no-conditional-assignment
|
||||
import { SourceMapConsumer } from 'source-map';
|
||||
|
||||
export class ErrorMapper {
|
||||
// Cache consumer
|
||||
private static _consumer?: SourceMapConsumer;
|
||||
|
||||
public static get consumer(): SourceMapConsumer {
|
||||
if (this._consumer == null) {
|
||||
this._consumer = new SourceMapConsumer(require('main.js.map'));
|
||||
}
|
||||
|
||||
return this._consumer;
|
||||
}
|
||||
|
||||
// Cache previously mapped traces to improve performance
|
||||
public static cache: { [key: string]: string } = {};
|
||||
|
||||
/**
|
||||
* Generates a stack trace using a source map generate original symbol names.
|
||||
*
|
||||
* WARNING - EXTREMELY high CPU cost for first call after reset - >30 CPU! Use sparingly!
|
||||
* (Consecutive calls after a reset are more reasonable, ~0.1 CPU/ea)
|
||||
*
|
||||
* @param {Error | string} error The error or original stack trace
|
||||
* @returns {string} The source-mapped stack trace
|
||||
*/
|
||||
public static sourceMappedStackTrace(error: Error | string): string {
|
||||
const stack: string = error instanceof Error ? (error.stack as string) : error;
|
||||
if (this.cache.hasOwnProperty(stack)) {
|
||||
return this.cache[stack];
|
||||
}
|
||||
|
||||
const re = /^\s+at\s+(.+?\s+)?\(?([0-z._\-\\\/]+):(\d+):(\d+)\)?$/gm;
|
||||
let match: RegExpExecArray | null;
|
||||
let outStack = error.toString();
|
||||
|
||||
while ((match = re.exec(stack))) {
|
||||
if (match[2] === 'main') {
|
||||
const pos = this.consumer.originalPositionFor({
|
||||
column: parseInt(match[4], 10),
|
||||
line: parseInt(match[3], 10)
|
||||
});
|
||||
|
||||
if (pos.line != null) {
|
||||
if (pos.name) {
|
||||
outStack += `\n at ${pos.name} (${pos.source}:${pos.line}:${pos.column})`;
|
||||
} else {
|
||||
if (match[1]) {
|
||||
// no original source file name known - use file name from given trace
|
||||
outStack += `\n at ${match[1]} (${pos.source}:${pos.line}:${pos.column})`;
|
||||
} else {
|
||||
// no original source file name known or in given trace - omit name
|
||||
outStack += `\n at ${pos.source}:${pos.line}:${pos.column}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no known position
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// no more parseable lines
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.cache[stack] = outStack;
|
||||
return outStack;
|
||||
}
|
||||
|
||||
public static wrapLoop(loop: () => void): () => void {
|
||||
return () => {
|
||||
try {
|
||||
loop();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if ('sim' in Game.rooms) {
|
||||
const message = `Source maps don't work in the simulator - displaying original error`;
|
||||
console.log(`<span style='color:red'>${message}<br>${_.escape(e.stack)}</span>`);
|
||||
} else {
|
||||
console.log(`<span style='color:red'>${_.escape(this.sourceMappedStackTrace(e))}</span>`);
|
||||
}
|
||||
} else {
|
||||
// can't handle it
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
59
test/integration/helper.ts
Normal file
59
test/integration/helper.ts
Normal file
@ -0,0 +1,59 @@
|
||||
const { readFileSync } = require('fs');
|
||||
const _ = require('lodash');
|
||||
const { ScreepsServer, stdHooks } = require('screeps-server-mockup');
|
||||
const DIST_MAIN_JS = 'dist/main.js';
|
||||
|
||||
/*
|
||||
* Helper class for creating a ScreepsServer and resetting it between tests.
|
||||
* See https://github.com/Hiryus/screeps-server-mockup for instructions on
|
||||
* manipulating the terrain and game state.
|
||||
*/
|
||||
class IntegrationTestHelper {
|
||||
private _server: any;
|
||||
private _player: any;
|
||||
|
||||
get server() {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
get player() {
|
||||
return this._player;
|
||||
}
|
||||
|
||||
async beforeEach() {
|
||||
this._server = new ScreepsServer();
|
||||
|
||||
// reset world but add invaders and source keepers bots
|
||||
await this._server.world.reset();
|
||||
|
||||
// create a stub world composed of 9 rooms with sources and controller
|
||||
await this._server.world.stubWorld();
|
||||
|
||||
// add a player with the built dist/main.js file
|
||||
const modules = {
|
||||
main: readFileSync(DIST_MAIN_JS).toString(),
|
||||
};
|
||||
this._player = await this._server.world.addBot({ username: 'player', room: 'W0N1', x: 15, y: 15, modules });
|
||||
|
||||
// Start server
|
||||
await this._server.start();
|
||||
}
|
||||
|
||||
async afterEach() {
|
||||
await this._server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.beforeEach();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.afterEach();
|
||||
});
|
||||
|
||||
before(() => {
|
||||
stdHooks.hookWrite();
|
||||
});
|
||||
|
||||
export const helper = new IntegrationTestHelper();
|
18
test/integration/integration.test.ts
Normal file
18
test/integration/integration.test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {assert} from "chai";
|
||||
import {helper} from "./helper";
|
||||
|
||||
describe("main", () => {
|
||||
it("runs a server and matches the game tick", async function () {
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
assert.equal(await helper.server.world.gameTime, i);
|
||||
await helper.server.tick();
|
||||
}
|
||||
});
|
||||
|
||||
it("writes and reads to memory", async function () {
|
||||
await helper.player.console(`Memory.foo = 'bar'`);
|
||||
await helper.server.tick();
|
||||
const memory = JSON.parse(await helper.player.memory);
|
||||
assert.equal(memory.foo, 'bar');
|
||||
});
|
||||
});
|
13
test/mocha.opts
Normal file
13
test/mocha.opts
Normal file
@ -0,0 +1,13 @@
|
||||
--require test/setup-node.js
|
||||
--require ts-node/register
|
||||
--ui bdd
|
||||
|
||||
--reporter spec
|
||||
--bail
|
||||
--full-trace
|
||||
--watch-extensions tsx,ts
|
||||
--colors
|
||||
|
||||
--recursive
|
||||
--timeout 5000
|
||||
--exit
|
6
test/setup-node.js
Normal file
6
test/setup-node.js
Normal file
@ -0,0 +1,6 @@
|
||||
//inject mocha globally to allow custom interface refer without direct import - bypass bundle issue
|
||||
global._ = require('lodash');
|
||||
global.mocha = require('mocha');
|
||||
global.chai = require('chai');
|
||||
global.sinon = require('sinon');
|
||||
global.chai.use(require('sinon-chai'));
|
25
test/unit/main.test.ts
Normal file
25
test/unit/main.test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {assert} from "chai";
|
||||
import {loop} from "../../src/main";
|
||||
import {Game, Memory} from "./mock"
|
||||
|
||||
describe("main", () => {
|
||||
before(() => {
|
||||
// runs before all test in this block
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// runs before each test in this block
|
||||
// @ts-ignore : allow adding Game to global
|
||||
global.Game = _.clone(Game);
|
||||
// @ts-ignore : allow adding Memory to global
|
||||
global.Memory = _.clone(Memory);
|
||||
});
|
||||
|
||||
it("should export a loop function", () => {
|
||||
assert.isTrue(typeof loop === "function");
|
||||
});
|
||||
|
||||
it("should return void when called with no context", () => {
|
||||
assert.isUndefined(loop());
|
||||
});
|
||||
});
|
10
test/unit/mock.ts
Normal file
10
test/unit/mock.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const Game = {
|
||||
creeps: [],
|
||||
rooms: [],
|
||||
spawns: {},
|
||||
time: 12345
|
||||
};
|
||||
|
||||
export const Memory = {
|
||||
creeps: []
|
||||
};
|
21
tools/node
Executable file
21
tools/node
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
node "$@"
|
27
tools/npm
Executable file
27
tools/npm
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
HOST_CACHE_DIR=$PWD/var/docker-cache/.npm
|
||||
CONTAINER_CACHE_DIR=/tmp/.npm
|
||||
|
||||
mkdir -p ${HOST_CACHE_DIR}
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$UID:$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--env npm_config_cache="${CONTAINER_CACHE_DIR}" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
npm "$@"
|
16
tools/tsc
Executable file
16
tools/tsc
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
--tty \
|
||||
--init \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
./node_modules/.bin/tsc "$@"
|
26
tools/yarn
Executable file
26
tools/yarn
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
HOST_CACHE_DIR=$PWD/var/docker-cache/.npm
|
||||
CONTAINER_CACHE_DIR=/tmp/.npm
|
||||
|
||||
mkdir -p ${HOST_CACHE_DIR}
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$UID:$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
yarn "$@"
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"target": "es2017",
|
||||
"moduleResolution": "Node",
|
||||
"outDir": "dist",
|
||||
"baseUrl": "src/",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitReturns": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
20
tsconfig.test-integration.json
Normal file
20
tsconfig.test-integration.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"target": "es5",
|
||||
"moduleResolution": "Node",
|
||||
"outDir": "dist",
|
||||
"baseUrl": "src/",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitAny": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
21
tslint.json
Normal file
21
tslint.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"rulesDirectory": "tslint-plugin-prettier",
|
||||
"extends" : [
|
||||
"tslint:recommended",
|
||||
"tslint-config-prettier"
|
||||
],
|
||||
"rules": {
|
||||
"forin": false,
|
||||
"interface-name": [true, "never-prefix"],
|
||||
"member-ordering": [false],
|
||||
"no-console": [false],
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case",
|
||||
"allow-leading-underscore"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user