From 2d5dcff46bbfc5221ea3061169b30383e7739b9c Mon Sep 17 00:00:00 2001 From: Zhu Liang Date: Thu, 21 Apr 2022 14:31:30 +0800 Subject: [PATCH 1/2] feat(kore): ts miner starter bot --- .../kore_fleets/starter_bots/ts/MinerBot.ts | 61 +++++++++++++++++ .../kore_fleets/starter_bots/ts/README.md | 9 +++ .../starter_bots/ts/kore/Direction.ts | 4 ++ .../envs/kore_fleets/starter_bots/ts/miner.py | 66 +++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts create mode 100644 kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts new file mode 100644 index 00000000..21f0079e --- /dev/null +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts @@ -0,0 +1,61 @@ +import {Board} from "./kore/Board"; +import { Direction } from "./kore/Direction"; +import { ShipyardAction } from "./kore/ShipyardAction"; +import { KoreIO } from "./kore/KoreIO"; + +const io = new KoreIO(); +// agent.run takes care of running your code per tick +io.run((board: Board): Board => { + const me = board.currentPlayer; + const spawnCost = board.configuration.spawnCost; + const convertCost = board.configuration.convertCost; + let remainingKore = me.kore; + + // the following code is mostly auto-generated using GitHub co-pilot + // using miner Python code and instruction "convert python into javascript" as comment prompts + for (let shipyard of me.shipyards) { + if(remainingKore > 1000 && shipyard.maxSpawn > 5) { + if(shipyard.shipCount >= convertCost + 10) { + const gap1 = getRandomInt(3, 9); + const gap2 = getRandomInt(3, 9); + const startDir = Math.floor(Math.random() * 4); + let flightPlan = Direction.listDirections()[startDir].toChar() + gap1; + const nextDir = (startDir + 1) % 4; + flightPlan += Direction.listDirections()[nextDir].toChar() + gap2; + const nextDir2 = (nextDir + 1) % 4; + flightPlan += Direction.listDirections()[nextDir2].toChar(); + shipyard.setNextAction(ShipyardAction.launchFleetWithFlightPlan(Math.min(convertCost + 10, Math.floor(shipyard.shipCount / 2)), flightPlan)); + } else if(remainingKore >= spawnCost) { + remainingKore -= spawnCost; + shipyard.setNextAction(ShipyardAction.spawnShips(Math.min(shipyard.maxSpawn, Math.floor(remainingKore / spawnCost)))); + } + } else if(shipyard.shipCount >= 21) { + const gap1 = getRandomInt(3, 9); + const gap2 = getRandomInt(3, 9); + const startDir = Math.floor(Math.random() * 4); + let flightPlan = Direction.listDirections()[startDir].toChar() + gap1; + const nextDir = (startDir + 1) % 4; + flightPlan += Direction.listDirections()[nextDir].toChar() + gap2; + const nextDir2 = (nextDir + 1) % 4; + flightPlan += Direction.listDirections()[nextDir2].toChar() + gap1; + const nextDir3 = (nextDir2 + 1) % 4; + flightPlan += Direction.listDirections()[nextDir3].toChar(); + shipyard.setNextAction(ShipyardAction.launchFleetWithFlightPlan(21, flightPlan)); + } else if(remainingKore > board.configuration.spawnCost * shipyard.maxSpawn) { + remainingKore -= board.configuration.spawnCost; + if(remainingKore >= spawnCost) { + shipyard.setNextAction(ShipyardAction.spawnShips(Math.min(shipyard.maxSpawn, Math.floor(remainingKore / spawnCost)))); + } + } else if(shipyard.shipCount >= 2) { + const dirStr = Direction.randomDirection().toChar(); + shipyard.setNextAction(ShipyardAction.launchFleetWithFlightPlan(2, dirStr)); + } + } + + // nextActions will be pulled off of your shipyards + return board; +}); + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} \ No newline at end of file diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md b/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md index bcd2bc7b..a4041e0e 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md @@ -34,3 +34,12 @@ A basic TS interpreter has been created in `interpreter.ts`. You can use or modi Currently it supports 2 agents and customizable number of episodes. After each episode, you can access the complete history of the game. For each turn, you can access the full observation (state) as a Board object, actions performed and the reward obtained after performing the action. Sample command to run the interpreter can be found in npm scripts as `npm run interpreter`. + +## Miner bot + +A sample miner bot `MinerBot.ts` is provided, with Python entrypoint as `miner.py`. It has the same logic as the Python `miner` bot in `kore_fleets.py`. + +To run it aginst Python miner bot with TS interpreter for 20 episodes: + +1. `npm run compile` +2. `node --require ts-node/register interpreter.ts 20 ./miner.py miner` diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts index 0be284cf..60fe37cd 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts @@ -153,6 +153,10 @@ export class Direction extends Point { throw new Error("invalid direction"); } + public static randomDirection(): Direction { + return Direction.fromIndex(Math.floor(Math.random() * 4)); + } + public static listDirections(): Direction[] { return [ Direction.NORTH, diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py b/kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py new file mode 100644 index 00000000..b5725567 --- /dev/null +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py @@ -0,0 +1,66 @@ +from subprocess import Popen, PIPE +from threading import Thread +from queue import Queue, Empty + +import atexit +import os +import sys +agent_processes = [None, None, None, None] +t = None +q = None +def cleanup_process(): + global agent_processes + for proc in agent_processes: + if proc is not None: + proc.kill() +def enqueue_output(out, queue): + for line in iter(out.readline, b''): + queue.put(line) + out.close() + +def agent(observation, configuration): + global agent_processes, t, q + + agent_process = agent_processes[observation.player] + ### Do not edit ### + if agent_process is None: + if "__raw_path__" in configuration: + cwd = os.path.dirname(configuration["__raw_path__"]) + else: + cwd = os.path.dirname(__file__) + agent_process = Popen(["node", "dist/MinerBot.js"], stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd) + agent_processes[observation.player] = agent_process + atexit.register(cleanup_process) + + # following 4 lines from https://stackoverflow.com/questions/375427/a-non-blocking-read-on-a-subprocess-pipe-in-python + q = Queue() + t = Thread(target=enqueue_output, args=(agent_process.stderr, q)) + t.daemon = True # thread dies with the program + t.start() + + # print observations to agent + import json + agent_process.stdin.write((json.dumps(observation) + "\n").encode()) + agent_process.stdin.write((json.dumps(configuration) + "\n").encode()) + agent_process.stdin.flush() + + # wait for data written to stdout + agent1res = (agent_process.stdout.readline()).decode() + + while True: + try: line = q.get_nowait() + except Empty: + # no standard error received, break + break + else: + # standard error output received, print it out + print(line.decode(), file=sys.stderr, end='') + + agent1res = agent1res.strip() + outputs = agent1res.split(",") + actions = {} + for cmd in outputs: + if cmd != "": + shipyard_id, action_str = cmd.split(":") + actions[shipyard_id] = action_str + return actions From f85673c90898abdb52a08e39fc6a8317607af96f Mon Sep 17 00:00:00 2001 From: Zhu Liang Date: Thu, 21 Apr 2022 23:46:16 +0800 Subject: [PATCH 2/2] feat(kore): ts step interpreter --- .../envs/kore_fleets/starter_bots/ts/Bot.ts | 10 +- .../starter_bots/ts/DoNothingBot.ts | 12 + .../kore_fleets/starter_bots/ts/MinerBot.ts | 9 +- .../kore_fleets/starter_bots/ts/README.md | 14 +- .../starter_bots/ts/interpreter.ts | 246 ++++++++++++++++-- .../kore_fleets/starter_bots/ts/kore/Board.ts | 4 +- .../starter_bots/ts/kore/Configuration.ts | 2 + .../starter_bots/ts/kore/KoreIO.ts | 25 +- .../kore_fleets/starter_bots/ts/kore/Point.ts | 3 +- .../kore_fleets/starter_bots/ts/package.json | 3 +- 10 files changed, 280 insertions(+), 48 deletions(-) create mode 100644 kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts index f6bfb28d..237c20ee 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts @@ -3,9 +3,7 @@ import { Direction } from "./kore/Direction"; import { ShipyardAction } from "./kore/ShipyardAction"; import { KoreIO } from "./kore/KoreIO"; -const io = new KoreIO(); -// agent.run takes care of running your code per tick -io.run((board: Board): Board => { +export const tick = async (board: Board): Promise => { const me = board.currentPlayer; const turn = board.step; const spawnCost = board.configuration.spawnCost; @@ -29,6 +27,8 @@ io.run((board: Board): Board => { // nextActions will be pulled off of your shipyards return board; -}); - +} +// agent.run takes care of running your code per tick +const io = new KoreIO(); +io.run(tick); diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts new file mode 100644 index 00000000..f32c0ab5 --- /dev/null +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts @@ -0,0 +1,12 @@ +import {Board} from "./kore/Board"; +import { Direction } from "./kore/Direction"; +import { ShipyardAction } from "./kore/ShipyardAction"; +import { KoreIO } from "./kore/KoreIO"; + + +export const tick = async (board: Board): Promise => { + return board; +} + +const io = new KoreIO(); +io.run(tick); diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts index 21f0079e..52e12803 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts @@ -3,9 +3,7 @@ import { Direction } from "./kore/Direction"; import { ShipyardAction } from "./kore/ShipyardAction"; import { KoreIO } from "./kore/KoreIO"; -const io = new KoreIO(); -// agent.run takes care of running your code per tick -io.run((board: Board): Board => { +export const tick = async (board: Board): Promise => { const me = board.currentPlayer; const spawnCost = board.configuration.spawnCost; const convertCost = board.configuration.convertCost; @@ -54,7 +52,10 @@ io.run((board: Board): Board => { // nextActions will be pulled off of your shipyards return board; -}); +} + +const io = new KoreIO(); +io.run(tick); function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md b/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md index a4041e0e..5ffc91aa 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md @@ -31,11 +31,17 @@ A basic TS interpreter has been created in `interpreter.ts`. You can use or modify this file to train machine learning models in JS/TS. -Currently it supports 2 agents and customizable number of episodes. After each episode, you can access the complete history of the game. For each turn, you can access the full observation (state) as a Board object, actions performed and the reward obtained after performing the action. +Currently it supports 2 agents and customizable number of episodes. -Sample command to run the interpreter can be found in npm scripts as `npm run interpreter`. +It has two modes: `run` and `step`. -## Miner bot +`run` mode: After each episode, you can access the complete history of the game. For each turn, you can access the full observation (state) as a Board object, actions performed and the reward obtained after performing the action. This mode is useful for evaluating an agent. + +`step` mode: The interpreter initializes new games and allows stepping through the game interactively. You have complete control over the board and the agent during each step. This mode is useful for training machine learning models. + +Sample command to run the interpreter can be found in npm scripts as `npm run interpreter:run` and `npm run interpreter:step`. + +## Miner bot and Do nothing bot A sample miner bot `MinerBot.ts` is provided, with Python entrypoint as `miner.py`. It has the same logic as the Python `miner` bot in `kore_fleets.py`. @@ -43,3 +49,5 @@ To run it aginst Python miner bot with TS interpreter for 20 episodes: 1. `npm run compile` 2. `node --require ts-node/register interpreter.ts 20 ./miner.py miner` + +A sample do nothing bot `DoNothingBot.ts` is also provided. \ No newline at end of file diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts index af524509..43fe67d0 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts @@ -1,5 +1,7 @@ import { Board } from './kore/Board'; -import { Observation } from './kore/Observation'; +import { tick as DoNothingTick } from './DoNothingBot'; +import { tick as MinerTick } from './MinerBot'; +import { tick as BotTick } from './Bot'; const { spawn } = require('child_process'); const fs = require('fs'); @@ -11,7 +13,24 @@ interface StepPlayerInfoRaw { status: string; } -const example = 'node --require ts-node/register interpreter.ts 2 ./main.py miner [out.log] [replay.json]'; +interface Agent { + name: string; + status: 'ACTIVE' | 'DONE'; + reward: number; + tickFunc: (board:Board) => void; +} + +const tickFuncMapping = { + 'miner': MinerTick, + 'do_nothing': DoNothingTick, + 'bot': BotTick, +} + +const MODE_STEP = 'step' as const; +const MODE_RUN = 'run' as const; +type MODE = typeof MODE_STEP | typeof MODE_RUN; + +const example = 'node --require ts-node/register interpreter.ts step 2 ./main.py miner [out.log] [replay.json]'; const DEFAULT_LOG_FILE_NAME = 'out.log'; const DEFAULT_RESULT_FILE_NAME = 'replay.json'; @@ -20,6 +39,8 @@ const MAX_CONCURRENT = 5; // agent under test const MAIN_AGENT_INDEX = 0; +// opponent agent +const OPPONENT_AGENT_INDEX = 1; const myArgs = process.argv.slice(2); // TODO: better handling of arguments, named args. @@ -29,14 +50,21 @@ if (myArgs.length < 3) { process.exit(1); } -const episodes = parseInt(myArgs[0], 10); +const mode = myArgs[0]; +if(mode !== MODE_STEP && mode !== MODE_RUN) { + console.log('Mode must be either step or run. Example:'); + console.log(example); + process.exit(1); +} + +const episodes = parseInt(myArgs[1], 10); if (!episodes) { console.log('Please provide number of episodes to run. Example:'); console.log(example); process.exit(1); } -const agentNames = myArgs.slice(1, 3); +const agentNames = myArgs.slice(2, 4); // TODO: support other number of agents if (agentNames.length !== 2) { console.log('Please provide exactly 2 agents. Example:'); @@ -44,34 +72,214 @@ if (agentNames.length !== 2) { process.exit(1); } -const userLogfilename = myArgs[3] || DEFAULT_LOG_FILE_NAME; +// validate agent names for step mode +if(mode === MODE_STEP) { + for (let i = 0; i < agentNames.length; i++) { + const agentName = agentNames[i]; + if(!tickFuncMapping[agentName]) { + console.log(`Agent ${agentName} tick function mapping does not exit. Define the mapping in interpreter.js -> tickFuncMapping.`); + console.log(example); + process.exit(1); + } + } +} + +const userLogfilename = myArgs[4] || DEFAULT_LOG_FILE_NAME; -const userResultfilename = myArgs[4] || DEFAULT_RESULT_FILE_NAME; +const userResultfilename = myArgs[5] || DEFAULT_RESULT_FILE_NAME; console.log(`Running ${episodes} episodes with agents: ${agentNames.join(' ')}`); -runAgent(episodes, agentNames, () => { - // post processing - console.log('done'); -}); +if(mode === MODE_RUN) { + runAgent(episodes, agentNames, () => { + // post processing + console.log('run done'); + }); +} else { + stepAgent(episodes, agentNames, () => { + // post processing + console.log('step done'); + }); +} + +function initEnv(agents: Agent[]): Promise { + // get init observation by playing a game between attacker agent and do_nothing agent + const resultFilename = `${userResultfilename}_init`; + + const pyArguments = [ + 'run', + '--environment', + 'kore_fleets', + '--agents', + 'attacker', + 'do_nothing', + '--out', + resultFilename, + ]; + + return new Promise((resolve, reject) => { + const kaggle = spawn('kaggle-environments', pyArguments, { cwd: __dirname }); + + kaggle.stderr.on('data', (data) => { + console.error(`stderr: ${data}`); + reject(data); + }); + + kaggle.on('close', (code) => { + console.log(`init complete`); + const result = processResult(resultFilename); + const { configuration, steps } = result; + const observation = steps[0][0].observation; + const board = Board.fromRaw(JSON.stringify(observation), JSON.stringify(configuration)); + resolve(board); + }); + }); +} + +// mimic the interpreter function in kore_fleets.py +function boardTick(board:Board, agents: Agent[]) { + const players = board.players; + + // Remove players with invalid status or insufficient potential. + for (let i = 0; i < players.length; i++) { + const agent = agents[i]; + const player = players[i]; + const playerKore = player.kore; + const shipyards = player.shipyards; + const fleets = player.fleets; + const canSpawn = shipyards.length > 0 && playerKore >= board.configuration.spawnCost; + + if(agent.status === 'ACTIVE' && shipyards.length === 0 && fleets.length === 0) { + agent.status = 'DONE'; + agent.reward = board.step - board.configuration.episodeSteps - 1; + } + if(agent.status === 'ACTIVE' && playerKore === 0 && fleets.length === 0 && !canSpawn) { + agent.status = 'DONE'; + agent.reward = board.step - board.configuration.episodeSteps - 1; + } + if(agent.status !== 'ACTIVE' && agent.status !== 'DONE') { + // TODO: handle this + } + } + + // Check if done (< 2 players and num_agents > 1) + const activeAgents = agents.filter(agent => agent.status === 'ACTIVE').length; + if(agents.length > 1 && activeAgents < 2) { + for (let i = 0; i < agents.length; i++) { + const agent = agents[i]; + if(agent.status === 'ACTIVE') { + agent.status = 'DONE'; + } + } + } + + // Update Rewards + for (let i = 0; i < agents.length; i++) { + const agent = agents[i]; + if(agent.status === 'ACTIVE') { + agent.reward = players[i].kore; + } else if(agent.status !== 'DONE') { + agent.reward = 0; + } + } + + let end = true; + if(board.step >= board.configuration.episodeSteps - 1) { + return true; + } + for (let i = 0; i < agents.length; i++) { + const agent = agents[i]; + if(agent.status === 'ACTIVE') { + end = false; + } + } + return end; +} + +async function stepAgent(episodes: number, agentsNames: string[], callback: Function = () => {}) { + let completed = 0; + let wins = 0; + + for (let index = 0; index < episodes; index++) { + let agentNamesMutable = agentsNames.slice(); + let episodeMainAgentIndex = MAIN_AGENT_INDEX; + let episodeOpponentAgentIndex = OPPONENT_AGENT_INDEX; + // randomize starting position + if (Math.random() < 0.5) { + const temp = agentNamesMutable[0]; + agentNamesMutable[0] = agentNamesMutable[1]; + agentNamesMutable[1] = temp; + episodeMainAgentIndex = OPPONENT_AGENT_INDEX; + episodeOpponentAgentIndex = MAIN_AGENT_INDEX; + } + + const agents: Agent[] = agentNamesMutable.map((name) => { + return { + name: name, + status: 'ACTIVE', + reward: 0, + tickFunc: tickFuncMapping[name], + }; + }); + + let gameBoard = await initEnv(agents); + while(!boardTick(gameBoard, agents)) { + // console.log(gameBoard.step); + for (let i = 0; i < agents.length; i++) { + const agent = agents[i]; + // rotate the board to the agent's perspective + // and assign agent action to game board + gameBoard.currentPlayerId = i; + agent.tickFunc(gameBoard); + + gameBoard.currentPlayer.shipyards.forEach(shipyard => { + // console.log(gameBoard.currentPlayerId, shipyard.position.toString(), shipyard.nextAction); + }) + } + gameBoard.currentPlayerId = episodeMainAgentIndex; + if (gameBoard.step % 100 === 0) { + console.log( + `[epi ${index}][step ${pad2(gameBoard.step)}] current player kore:${gameBoard.currentPlayer.kore} action 0: ${ + gameBoard.currentPlayer.shipyards.length ? gameBoard.currentPlayer.shipyards[0].nextAction : 'none' + } reward: ${agents[episodeMainAgentIndex].reward.toFixed(0)} status: ${agents[episodeMainAgentIndex].status}` + ); + } + gameBoard = gameBoard.next(); + } + console.log( + `[epi ${index}][step ${pad2(gameBoard.step)}] current player kore:${gameBoard.currentPlayer.kore} action 0: ${ + gameBoard.currentPlayer.shipyards.length ? gameBoard.currentPlayer.shipyards[0].nextAction : 'none' + } reward: ${agents[episodeMainAgentIndex].reward.toFixed(0)} status: ${agents[episodeMainAgentIndex].status}` + ); + console.log(agents[episodeMainAgentIndex].name, agents[episodeMainAgentIndex].reward); + console.log(agents[episodeOpponentAgentIndex].name, agents[episodeOpponentAgentIndex].reward); + if(agents[episodeMainAgentIndex].reward > agents[episodeOpponentAgentIndex].reward) { + wins++; + } + + completed++; + console.log(`${completed}/${episodes}`); + if (completed === episodes) { + const mainAgentName = agentsNames[MAIN_AGENT_INDEX]; + console.log(`agent ${mainAgentName} wins: ${pad(wins)}/${pad(episodes)}`); + callback(); + } + } +} -async function runAgent(iterations: number, agents: string[], callback: Function = () => {}) { +async function runAgent(episodes: number, agents: string[], callback: Function = () => {}) { let running = 0; let completed = 0; - const results = new Array(iterations); + const results = new Array(episodes); const agent = agents[MAIN_AGENT_INDEX]; const cleanAgentName = agent.replace(/\.py$/g, '').replace(/\W/g, ''); - for (let index = 0; index < iterations; index++) { + for (let index = 0; index < episodes; index++) { const resultFilename = `${userResultfilename}_${index}`; const logFilename = `${userLogfilename}_${index}`; const pyArguments = [ 'run', - // TODO: support evaluate mode - // 'evaluate', - // '--episodes', - // '1', '--environment', 'kore_fleets', '--agents', @@ -99,13 +307,13 @@ async function runAgent(iterations: number, agents: string[], callback: Function kaggle.on('close', (code) => { completed++; - console.log(`${completed}/${iterations}`); + console.log(`${completed}/${episodes}`); running--; const result = processResult(resultFilename); processSteps(index, result.configuration, result.steps); // ensure consistent result order results[index] = result; - if (completed === iterations) { + if (completed === episodes) { compileRunResults(cleanAgentName, results); callback(); } diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts index 1263113b..2dbd036a 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts @@ -14,7 +14,7 @@ export class Board { public readonly shipyards: Map; public readonly fleets: Map; public readonly players: Player[]; - public readonly currentPlayerId: number; + public currentPlayerId: number; public readonly configuration: Configuration; public step: number; public readonly remainingOverageTime: number; @@ -354,7 +354,7 @@ export class Board { }) let fid = fleets[0].id; for (let i = 1; i < fleets.length; i++) { - fid = this.combineFleets(board, fid, fleets[1].id); + fid = this.combineFleets(board, fid, fleets[i].id); } } diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts index 37da9aee..555319ec 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts @@ -8,6 +8,7 @@ export class Configuration { public readonly regenRate: number; public readonly maxRegenCellKore: number; public readonly randomSeed: number; + public readonly episodeSteps: number; public constructor(rawConfiguration: string) { const config = JSON.parse(rawConfiguration); @@ -19,5 +20,6 @@ export class Configuration { this.regenRate = config.regenRate; this.maxRegenCellKore = config.maxRegenCellKore; this.randomSeed = config.randomSeed; + this.episodeSteps = config.episodeSteps; } } \ No newline at end of file diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts index c2b65322..47a6476c 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts @@ -1,12 +1,11 @@ -import readline from "readline"; -import { Board } from "./Board"; -import { ShipyardAction } from "./ShipyardAction"; +import readline from 'readline'; +import { Board } from './Board'; +import { ShipyardAction } from './ShipyardAction'; export class KoreIO { public getLine: () => Promise; public _setup(): void { - // Prepare to read input const rl = readline.createInterface({ input: process.stdin, @@ -28,8 +27,7 @@ export class KoreIO { currentPromise = makePromise(); }); // The current promise for retrieving the next line - currentPromise = makePromise() - + currentPromise = makePromise(); // with await, we pause process until there is input this.getLine = async () => { @@ -52,20 +50,21 @@ export class KoreIO { this._setup(); // DO NOT REMOVE } - - public async run(loop: (board: Board) => Board): Promise { + public async run(loop: (board: Board) => Promise): Promise { while (true) { const rawObservation = await this.getLine(); const rawConfiguration = await this.getLine(); const board = Board.fromRaw(rawObservation, rawConfiguration); try { - const nextBoard = loop(board); - let actions = [] - board.currentPlayer.nextActions.forEach((action: ShipyardAction, id: string) => actions.push(`${id}:${action.toString()}`)); - console.log(actions.join(",")); + const nextBoard = await loop(board); + let actions = []; + board.currentPlayer.nextActions.forEach((action: ShipyardAction, id: string) => + actions.push(`${id}:${action.toString()}`) + ); + console.log(actions.join(',')); } catch (err) { console.log(err); } } } -} \ No newline at end of file +} diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts index b68b2328..3471771c 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts @@ -16,7 +16,8 @@ export class Point { } public mod(size: number): Point { - return new Point(this.x % size, this.y % size); + // handle cases where the point is negative due to translation offset being negative + return new Point((this.x + size) % size, (this.y + size) % size); } /** diff --git a/kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json b/kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json index 8fcdc6b9..fe9a4b71 100644 --- a/kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json +++ b/kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json @@ -11,7 +11,8 @@ "test": "mocha --require ts-node/register test/**/*.ts", "compile": "tsc", "package": "tsc && tar -czvf submission.tar.gz main.py dist/*", - "interpreter": "node --require ts-node/register interpreter.ts 2 ./main.py miner", + "interpreter:run": "node --require ts-node/register interpreter.ts run 2 ./main.py miner", + "interpreter:step": "node --require ts-node/register interpreter.ts step 2 miner do_nothing", "watch4": "kaggle-environments run --environment kore_fleets --agents ./main.py ./main.py ./main.py ./main.py --log out.log --render '{\"mode\": \"html\"}' --out replay.html && google-chrome replay.html", "watch2": "kaggle-environments run --environment kore_fleets --agents ./main.py ./main.py --log out.log --render '{\"mode\": \"html\"}' --out replay.html && google-chrome replay.html", "watch1": "kaggle-environments run --environment kore_fleets --agents ./main.py --log out.log --render '{\"mode\": \"html\"}' --out replay.html && google-chrome replay.html",