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

Classify and cleanups #12

Open
wants to merge 7 commits into
base: development
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
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.5",
"@total-typescript/ts-reset": "^0.5.1",
"@types/node": "^20.8.0",
"@types/screeps": "^3.3.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
Expand Down
28 changes: 17 additions & 11 deletions src/exampleBot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { simpleAllies } from './simpleAllies';
import { SimpleAllies } from './simpleAllies';

const simpleAllies = new SimpleAllies();

/**
* Example bot loop
Expand All @@ -23,25 +25,29 @@ export function loop() {
* Example of responding to ally defense requests
*/
function respondToAllyDefenseRequests() {
if (!simpleAllies.allySegmentData) return;
for (const playerName in simpleAllies.allySegments) {
const segment = simpleAllies.allySegments[playerName];

// Send creeps to defend rooms
for (const request of simpleAllies.allySegmentData.requests.defense) {
console.log('[simpleAllies] Respond to defense request', JSON.stringify(request));
// ...
// Send creeps to defend rooms
for (const request of segment.requests.defense) {
console.log('[simpleAllies] Respond to defense request', JSON.stringify(request));
// ...
}
}
}

/**
* Example of responding to ally resource requests
*/
function respondToAllyResourceRequests() {
if (!simpleAllies.allySegmentData) return;
for (const playerName in simpleAllies.allySegments) {
const segment = simpleAllies.allySegments[playerName];

// Send resources to rooms
for (const request of simpleAllies.allySegmentData.requests.resource) {
console.log('[simpleAllies] Respond to resource request', JSON.stringify(request));
// ...
// Send resources to rooms
for (const request of segment.requests.resource) {
console.log('[simpleAllies] Respond to resource request', JSON.stringify(request));
// ...
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/reset.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Do not add any other lines of code to this file!
import '@total-typescript/ts-reset';
192 changes: 159 additions & 33 deletions src/simpleAllies.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as Types from './types';

/**
* Allies list
* The segment ID used for communication
*/
export const allies = ['Player1', 'Player2', 'Player3'];
export const SIMPLE_ALLIES_SEGMENT_ID = 90;

/**
* This segment ID used for team communication
* Max number of segments openable at once
* This isn't in the docs for some reason, so we need to add it
*/
export const allySegmentID = 90;
const MAX_OPEN_SEGMENTS = 10;

/**
* The rate at which to refresh allied segments
*/
const SIMPLE_ALLIES_MIN_REFRESH_RATE = 5;

/**
* Represents the goal type enum for javascript
Expand All @@ -30,7 +36,7 @@ export const EWorkType = {
/**
* Simple allies class manages ally requests
*/
class SimpleAllies {
export class SimpleAllies {
/**
* State
*/
Expand All @@ -44,8 +50,46 @@ class SimpleAllies {
room: [],
};
private myEconInfo?: Types.EconInfo;
public allySegmentData?: Types.SimpleAlliesSegment;
public currentAlly?: string;
public allySegments: { [playerName: string]: Types.SimpleAlliesSegment };
private allyIdx: number;
private _allies: Set<string>;
private refreshRate: number;
private _debug: boolean;

constructor(options?: { debug?: boolean; refreshRate?: number }) {
this._debug = options?.debug ?? false;
this.refreshRate = options?.refreshRate ?? SIMPLE_ALLIES_MIN_REFRESH_RATE;
this._allies = new Set();
this.allyIdx = 0;
this.allySegments = {};
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private log(...args: any[]) {
console.log('[SimpleAllies]', ...args);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private debug(...args: any[]) {
if (!this._debug) return;

this.log(...args);
}

addAlly(...allies: string[]) {
for (const ally of allies) {
this._allies.add(ally);
}
}
removeAlly(...allies: string[]) {
for (const ally of allies) {
this._allies.delete(ally);
}
}

get allies() {
return [...this._allies.keys()];
}

/**
* To call before any requests are made or responded to.
Expand All @@ -66,53 +110,137 @@ class SimpleAllies {
this.myEconInfo = undefined;

// Read ally segment data
this.allySegmentData = this.readAllySegment();
this.readAllySegment();
}

// Private Segment helpers

/**
* Try to read the ally segment data
* Private helper to check for segment availability
*
* Subclasses can override that to perform their own segment processing
*/
private readAllySegment() {
if (!allies.length) {
console.log('[simpleAllies] You have no allies');
return;
}
private canOpenSegment() {
return Object.keys(RawMemory.segments).length >= MAX_OPEN_SEGMENTS;
}

this.currentAlly = allies[Game.time % allies.length];
/**
* Private helper to write a segment
*
* Subclasses can override that to perform their own segment processing
*/
private writeSegment(id: number, segment: Types.SimpleAlliesSegment) {
RawMemory.segments[id] = JSON.stringify(segment);
}

/**
* Private helper to mark a segment as public
*
* Subclasses can override that to perform their own segment processing
*/
private markPublic(id: number) {
RawMemory.setPublicSegments([id]);
}

// Make a request to read the data of the next ally in the list, for next tick
const nextAllyName = allies[(Game.time + 1) % allies.length];
RawMemory.setActiveForeignSegment(nextAllyName, allySegmentID);
/**
* Private helper to activate a foreign segment
*
* Subclasses can override that to perform their own segment processing
*/
private setForeignSegment(playerName: string, id: number) {
RawMemory.setActiveForeignSegment(playerName, id);
}

// Maybe the code didn't run last tick, so we didn't set a new read segment
/**
* Private helper to read and parse a foreign segment
*
* Subclasses can override that to perform their own segment processing
*/
private readForeignSegment(playerName: string, id: number) {
if (!RawMemory.foreignSegment) return;
if (RawMemory.foreignSegment.username !== this.currentAlly) return;
if (
RawMemory.foreignSegment.username !== playerName ||
RawMemory.foreignSegment.id !== id
) {
this.debug(`not the segment we were expecting, ignoring`);
return undefined;
}

// Try to parse the segment data
// Safely grab the segment and parse it in
let segment;
try {
return JSON.parse(RawMemory.foreignSegment.data) as Types.SimpleAlliesSegment;
} catch (e) {
console.log(
`[simpleAllies] Error reading ${this.currentAlly} segment ${allySegmentID}`
);
const parsed = JSON.parse(RawMemory.foreignSegment.data);
if (
parsed &&
typeof parsed === 'object' &&
'requests' in parsed &&
Array.isArray(parsed.requests) &&
'updated' in parsed &&
typeof parsed.updated === 'number'
) {
segment = parsed as Types.SimpleAlliesSegment;
}
throw new Error();
} catch (err) {
this.log(`Error reading ${playerName} segment ${SIMPLE_ALLIES_SEGMENT_ID}`);
}
return segment;
}

/**
* Refresh our allies' shared segments in a round-robin
*/
private readAllySegment() {
if (!this._allies.size) {
this.log(`no allies, skipping`);
return;
}

const clock = ((Game.time - 1) % this.refreshRate) - this.refreshRate + 1;
switch (clock) {
case -1: {
// Make a request to read the data of the next ally in the list, for next tick
this.allyIdx = (this.allyIdx + 1) % this._allies.size;
const ally = this.allies[this.allyIdx];
this.debug(`loading segment for ${ally}`);
this.setForeignSegment(ally, SIMPLE_ALLIES_SEGMENT_ID);
break;
}
case 0: {
this.debug(`checking loaded segment…`);
const ally = this.allies[this.allyIdx];
const segment = this.readForeignSegment(ally, SIMPLE_ALLIES_SEGMENT_ID);
if (segment) {
this.debug(`successfully loaded segment for ${ally}`);
this.allySegments[ally] = segment;
} else {
this.debug(`unable to load segment for ${ally}, resetting`);
delete this.allySegments[ally];
}

break;
}
default:
break;
}
}

/**
* To call after requests have been made, to assign requests to the next ally
*/
public endRun() {
if (Object.keys(RawMemory.segments).length >= 10) {
console.log('[simpleAllies] Too many segments open');
if (this.canOpenSegment()) {
this.log('Too many segments open');
return;
}

RawMemory.segments[allySegmentID] = JSON.stringify({
const segment: Types.SimpleAlliesSegment = {
requests: this.myRequests,
econ: this.myEconInfo,
updated: Game.time,
});
RawMemory.setPublicSegments([allySegmentID]);
};
this.writeSegment(SIMPLE_ALLIES_SEGMENT_ID, segment);
this.markPublic(SIMPLE_ALLIES_SEGMENT_ID);
}

/**
Expand Down Expand Up @@ -216,5 +344,3 @@ class SimpleAllies {
this.myEconInfo = args;
}
}

export const simpleAllies = new SimpleAllies();