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

Add TypeScript client #54

Open
wants to merge 1 commit into
base: main
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
Binary file added clients/TypeScript/.DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions clients/TypeScript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package-lock.json
node_modules
output
4 changes: 4 additions & 0 deletions clients/TypeScript/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM node:15.3

COPY . /project
WORKDIR /project
4 changes: 4 additions & 0 deletions clients/TypeScript/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set -ex

npm i
npm run build
20 changes: 20 additions & 0 deletions clients/TypeScript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "russian-ai-cup.code-craft",
"version": "1.0.0",
"description": "Соревнование по написанию ИИ для стратегии 2020 года",
"scripts": {
"build": "npm run clean && npm run compile",
"clean": "rm -rf output",
"compile": "tsc"
},
"keywords": [
"ai",
"ai cup",
"russian ai cup"
],
"author": "WalkInWay",
"devDependencies": {
"@types/node": "^14.14.10",
"typescript": "3.9.3"
}
}
4 changes: 4 additions & 0 deletions clients/TypeScript/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set -ex

cd /output
node ./index.js "$@"
21 changes: 21 additions & 0 deletions clients/TypeScript/src/DebugInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {ClientMessage} from './model/ClientMessage';
import {DebugCommand} from './model/DebugCommand';
import {DebugState} from './model/DebugState';
import {StreamWrapper} from './StreamWrapper';

export class DebugInterface {
constructor(
private streamWrapper: StreamWrapper,
) {}

async send(command: DebugCommand) {
await (new ClientMessage.DebugMessage(command)).writeTo(this.streamWrapper);
// TODO: only flush stream once here?
}

async getState() {
await (new ClientMessage.RequestDebugState()).writeTo(this.streamWrapper);
// TODO: only flush stream once here?
return await DebugState.readFrom(this.streamWrapper);
}
}
13 changes: 13 additions & 0 deletions clients/TypeScript/src/MyStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {DebugInterface} from './DebugInterface';
import * as model from './model'

export class MyStrategy {
async getAction(playerView: model.PlayerView, debugInterface: DebugInterface | null) {
return new model.Action(new Map());
}

async debugUpdate(_playerView: model.PlayerView, debugInterface: DebugInterface) {
await debugInterface.send(new model.DebugCommand.Clear());
await debugInterface.getState();
}
}
178 changes: 178 additions & 0 deletions clients/TypeScript/src/StreamWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import * as os from 'os';
import {Socket} from 'net';
import {Stream} from './model/Stream';

const BOOL_SIZE = 1;
const INT_SIZE = 4;
const LONG_SIZE = 8;
const FLOAT_SIZE = 4;
const DOUBLE_SIZE = 8;

export class StreamWrapper implements Stream {
private socket: Socket;
private data: Buffer;
private needAmount: number | null;
private resolve: any | null;
private isLittleEndianMachine: boolean;

constructor(socket: Socket) {
this.socket = socket;
this.data = Buffer.alloc(0);
this.needAmount = null;
this.resolve = null;
this.socket.on('data', data => this.dataHandler(data));
this.isLittleEndianMachine = (os.endianness() === 'LE');
}

dataHandler(data: Buffer) {
this.data = Buffer.concat([this.data, data]);
this.update();
}

update() {
if (this.needAmount === null || this.needAmount > this.data.length) {
return;
}
const data = this.data.slice(0, this.needAmount);
this.data = this.data.slice(this.needAmount);
this.needAmount = null;
this.resolve(data);
this.update();
}

close() {
this.socket.destroy();
}

// Reading primitives

private _read(size: number): Promise<Buffer> {
return new Promise<Buffer>((resolve) => {
this.needAmount = size;
this.resolve = resolve;
this.update();
}).catch(error => {
throw new Error('Error while reading data: ' + error.message)
});
}

async readBool() {
const buffer = await this._read(BOOL_SIZE);
return !!buffer.readInt8();
}

async readInt() {
const buffer = await this._read(INT_SIZE);
if (this.isLittleEndianMachine) {
// @ts-ignore
return buffer.readInt32LE(0, INT_SIZE);
}
// @ts-ignore
return buffer.readInt32BE(0, INT_SIZE);
}

async readLong() {
const buffer = await this._read(LONG_SIZE);
if (this.isLittleEndianMachine) {
// @ts-ignore
return parseInt(buffer.readBigInt64LE());
}
// @ts-ignore
return parseInt(buffer.readBigInt64BE());
}

async readFloat() {
const buffer = await this._read(FLOAT_SIZE);
if (this.isLittleEndianMachine) {
return buffer.readFloatLE();
}
return buffer.readFloatBE();
}

async readDouble() {
const buffer = await this._read(DOUBLE_SIZE);
if (this.isLittleEndianMachine) {
return buffer.readDoubleLE();
}
return buffer.readDoubleBE();
}

async readString() {
const length = await this.readInt();
const buffer = await this._read(length);
const result = buffer.toString();
if (result.length !== length) {
throw new Error('Unexpected EOF');
}
return result;
}

// Writing primitives

_write(data: Buffer) {
const socket = this.socket;
return new Promise<boolean | undefined>(function (resolve, reject) {
socket.write(data, 'utf8', function (error) {
if (error) {
return reject(error);
}
resolve(true);
});
}).catch(function (error) {
console.log('Error while writing data ' + error.message);
});
}

async writeBool(value: boolean) {
const buffer = Buffer.alloc(BOOL_SIZE);
// @ts-ignore
buffer.writeInt8(value);
return await this._write(buffer);
}

async writeInt(value: number) {
const buffer = Buffer.alloc(INT_SIZE);
if (this.isLittleEndianMachine) {
buffer.writeInt32LE(value);
} else {
buffer.writeInt32BE(value);
}
return await this._write(buffer);
}

async writeLong(value: bigint) {
const buffer = Buffer.alloc(LONG_SIZE);
if (this.isLittleEndianMachine) {
buffer.writeBigInt64LE(value);
} else {
buffer.writeBigInt64BE(value);
}
return await this._write(buffer);
}

async writeFloat(value: number) {
const buffer = Buffer.alloc(FLOAT_SIZE);
if (this.isLittleEndianMachine) {
buffer.writeFloatLE(value);
} else {
buffer.writeFloatBE(value);
}
return await this._write(buffer);
}

async writeDouble(value: number) {
const buffer = Buffer.alloc(DOUBLE_SIZE);
if (this.isLittleEndianMachine) {
buffer.writeDoubleLE(value);
} else {
buffer.writeDoubleBE(value);
}
return await this._write(buffer);
}

async writeString(value: string) {
this.writeInt(value.length);
// @ts-ignore
return await this._write(value, 'utf8');
}
}
72 changes: 72 additions & 0 deletions clients/TypeScript/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {Socket} from 'net';

import {DebugInterface} from './DebugInterface';
import {MyStrategy} from './MyStrategy';
import {StreamWrapper} from './StreamWrapper';
import {ServerMessage} from './model/ServerMessage';
import {ClientMessage} from './model/ClientMessage';

class Runner {
private socket = new Socket({ readable: true, writable: true });
private streamWrapper = new StreamWrapper(this.socket)

constructor(
private host: string,
private port: number,
private token: string,
) {
this.socket
.setNoDelay(true)
.on('error', (error) => {
console.error('Socket error: ' + error.message);
process.exit(1);
});
}

async connect() {
await new Promise(resolve => {
this.socket.connect({
host: this.host,
port: this.port
}, function () {
resolve();
});
});
await this.streamWrapper.writeString(this.token);
}

async run() {
try {
await this.connect();
const strategy = new MyStrategy();
const debugInterface = new DebugInterface(this.streamWrapper);
while (true) {
const message = await ServerMessage.readFrom(this.streamWrapper);
if (message instanceof ServerMessage.GetAction) {
await (new ClientMessage.ActionMessage(
await strategy.getAction(message.playerView, message.debugAvailable ? debugInterface : null)
).writeTo(this.streamWrapper));
// TODO: only flush stream once here?
} else if (message instanceof ServerMessage.Finish) {
break;
} else if (message instanceof ServerMessage.DebugUpdate) {
await strategy.debugUpdate(message.playerView, debugInterface);
await (new ClientMessage.DebugUpdateDone().writeTo(this.streamWrapper));
// TODO: only flush stream once here?
} else {
throw new Error('Unexpected server message');
}
}
} catch (e) {
console.error(e);
process.exit(1);
}
}
}

const argv = process.argv;
const host = argv.length < 3 ? '127.0.0.1' : argv[2];
const port = argv.length < 4 ? 31001 : parseInt(argv[3]);
const token = argv.length < 5 ? '0000000000000000' : argv[4];

(new Runner(host, port, token)).run();
28 changes: 28 additions & 0 deletions clients/TypeScript/src/model/Action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {EntityAction} from './EntityAction';
import {Stream} from './Stream';

export class Action {
constructor(
public entityActions: Map<number, EntityAction>,
) {}

static async readFrom(stream: Stream) {
const entityActions = new Map<number, EntityAction>();

for (let i = await stream.readInt(); i > 0; i--) {
const entityActionsKey: number = await stream.readInt();
const entityActionsValue = await EntityAction.readFrom(stream);

entityActions.set(entityActionsKey, entityActionsValue);
}

return new Action(entityActions);
}
async writeTo(stream: Stream) {
await stream.writeInt(this.entityActions.size);
for (let [key, entityAction] of this.entityActions) {
await stream.writeInt(key);
await entityAction.writeTo(stream);
}
}
}
29 changes: 29 additions & 0 deletions clients/TypeScript/src/model/AttackAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {AutoAttack} from './AutoAttack';
import {EntityType} from './EntityType';
import {Stream} from './Stream';

export class AttackAction {
constructor(private target: EntityType | null, private autoAttack: AutoAttack | null) {}

static async readFrom(stream: Stream) {
const target = await stream.readBool() ? await stream.readInt() : null;
const autoAttack = await stream.readBool() ? await AutoAttack.readFrom(stream) : null;

return new AttackAction(target, autoAttack);
}
async writeTo(stream: Stream) {
if (this.target === null) {
await stream.writeBool(false);
} else {
await stream.writeBool(true);
await stream.writeInt(this.target);
}

if (this.autoAttack === null) {
await stream.writeBool(false);
} else {
await stream.writeBool(true);
await this.autoAttack.writeTo(stream);
}
}
}
Loading