Parsing the raw engine output


#1

If you’re anything like me, you find reading Starter Kits extremely hard.

Here is a bot that – as far as I know – correctly reads the engine output, correctly saves it in some internal data structures, and then correctly does nothing with it.

The point is, this might help people understand how to parse the raw engine output, if (like me) you prefer not to use a starter kit.

import json

class TokenReader:
	def __init__(self):
		self.tokens = []

	def get_token(self):
		if len(self.tokens) == 0:
			self.tokens = input().split()
		ret = self.tokens[0]
		self.tokens = self.tokens[1:]
		return ret

	def get_int(self):
		return int(self.get_token())


reader = TokenReader()


class Factory:
	def __init__(self, player, x, y):
		self.player = player
		self.x = x
		self.y = y

class Dropoff:
	def __init__(self, player, x, y):
		self.player = player
		self.x = x
		self.y = y

class Ship:
	def __init__(self, player, ship_id, x, y, halite):
		self.player = player
		self.ship_id = ship_id
		self.x = x
		self.y = y
		self.halite = halite

class Game:
	def __init__(self):
		self.map = []
		self.factories = []
		self.ships = []
		self.dropoffs = []
		self.energy = dict()	# player ID --> energy (stored halite)

	def pre_parse(self):

		self.constants = json.loads(reader.get_token())

		self.players = reader.get_int()
		self.pid = reader.get_int()		# Our own ID

		for n in range(self.players):
			player = reader.get_int()
			x = reader.get_int()
			y = reader.get_int()
			self.factories.append(Factory(player, x, y))

		self.width = reader.get_int()
		self.height = reader.get_int()

		# Create our map array and zero it...

		self.map = [[0 for y in range(self.height)] for x in range(self.width)]

		# Fill it up with actual data from stdin...

		for y in range(self.height):
			for x in range(self.width):
				self.map[x][y] = reader.get_int()

	def parse(self):

		# We will get sent all ships/dropoffs, so clear those arrays.
		# Note: any AI that has references to individual ships
		# will need to update its references to the new ones.

		self.ships = []
		self.dropoffs = []

		self.turn = reader.get_int() - 1	# Engine is out by 1, imo

		for n in range(self.players):

			player = reader.get_int()
			ships = reader.get_int()
			dropoffs = reader.get_int()

			self.energy[player] = reader.get_int()

			for i in range(ships):
				ship_id = reader.get_int()
				x = reader.get_int()
				y = reader.get_int()
				halite = reader.get_int()
				self.ships.append(Ship(player, ship_id, x, y, halite))

			for i in range(dropoffs):
				__ = reader.get_int()		# Dropoff ID (useless info)
				x = reader.get_int()
				y = reader.get_int()
				self.dropoffs.append(Dropoff(player, x, y))

		map_updates = reader.get_int()

		for n in range(map_updates):
			x = reader.get_int()
			y = reader.get_int()
			value = reader.get_int()

			self.map[x][y] = value


def main():

	game = Game()
	game.pre_parse()

	print("DoNothingBot")

	while 1:
		game.parse()
		print()


if __name__ == "__main__":
	main()

Not using starter kit
#2

Thank you for posting this! I was struggling to understand the engine output while writing my ocaml starter, your code makes it much easier!


#3

Note that you do not have to actually treat the constants as JSON if it makes it easier in your language. For example in C++ and Java there is no JSON parsing available in the language itself. What you can do is strip all of the JSON special characters and treat whole thing as series of space separated pairs. For example of the parsing check out C++ kit: https://github.com/HaliteChallenge/Halite-III/blob/master/starter_kits/C%2B%2B/hlt/constants.cpp


#4

Yes, I agree about treating the JSON as a flat string and parsing it like anything else. I went searching for a suitable JSON parser, but then looked at the actual string and since it’s not a nested structure it’s easy to process.

It was the long string of integers which I was having trouble with - @fohristiwhirl’s code was very helpful in understanding the sequence.