Halite Home

Alternative Halite Starter Package for Python3


#1

Python bots timing out? Want to write bots in actual Python, you know, pythonic-ly?

I would have done a pull-request for the official Python3 starter package, but it was almost a complete rewrite.

This is the same hlt.py code that I use for my "production" bots. I rewrote the starter bots and translated the @nmalaguti bots from https://2016.forums.halite.io/t/so-youve-improved-the-random-bot-now-what/482/8 as demonstrations.


So you've Improved the Random Bot. Now what?
Tips for making Python bots faster
Tips for climbing the ladder - spoiler free
Timing Out Frequently
#2

Moved to Python category.


#3

Excellent package, my code runs so much faster now, thanks a lot! One question, for testing I like to create a made-up game map but haven't been able to figure out the format for the production_string and map_string parameters. Could you give me a simple example for a say 3x3 map?


#4

@FigiMon take a look at Writing Your Own Starter Package for details on the input formats.


#5

@FigiMon, glad someone's finding it useful ... can you estimate / quantify the speedup you experienced?


#6

@erdman I'm using this game to actually improve my python-fu (yes a serious use :slight_smile: ) so your pythonic library is extremely useful, many thanks.


#7

I wish I was 1/2 as pythonic as erdman.
Continuing on from @FigiMon earlier query, if I load a game from a file which are in json format, how do I elegantly 'stuff' it into the new starter package. I can get the production string with

production = (' '.join([' '.join([str(node) for node in row]) for row in game['productions']]))

but to translate the game['frame'] frame to map_string I end up with code that looks like the original starter code. I know @erdman has already written something to do this that is so pretty it will make me sick when I look at my own crude inept code later.

I can get the strength part of the frame string with

strengths = (' '.join([' '.join([str(node[1]) for node in row]) for row in d['frames'][0]]))

but the first part....
Of course I probably should put the frame straight into the contents, I promise to do this as soon as I comprehend the enumerated ziped grouper thingie. I have until Feburary, I might get there.


#8

I never considered using it this way, but OK. Do this:

import hlt        # my version
#usually you would:
# myID, game_map = hlt.get_init()
#where the game environment fills in the map for you.

#To create the game_map manually, do this instead:

game_map = hlt.GameMap(size_string, production_string, map_string)

where those three strings are in the same Halite compressed format as spec'd on the page that @nmalaguti linked above. Hopefully, those same three strings are also saved in the .hlt replay file, so you can plug and play?


#9

the size string is easy enough to recreate, the production string can be created with the code I used above, I was having trouble recreating the map string which is stored in the replay as a 'frame' with basically the map with pairs of [owner, strength]


#10

Oh, I see, they used a different format there. Frustrating.

So they have list of lists of (owner, strength) tuples ...

This should get you there ...

self.contents = [[Square(x, y, owner_strength_tuple[0], owner_strength_tuple[1], production)
                  for x, (owner_strength_tuple, production)
                  in enumerate(zip(owner_strength_row, production_row))]
                 for y, (owner_strength_row, production_row)
                 in enumerate(zip(owner_strength_list_of_lists,
                                  self.production))]

#11

Why do you want to do what you're doing? If you want to replay the same map with different botcode, you can launch the game environment on your local machine with the same seed, and you'll get the same map. So, I'm stumped on where you're going.

One idea that would make this a lot easier might be to write the three strings to a file in the init portion of the GameMap class, with the seed as part of the name. Then you could just read them back in from the file if you want to recreate it.


#12

Excellent code @erdman, thanks a lot for sharing, I have now started using it instead of my heavily tweaked hlt.py.

I suggest a very small change in your hlt.py that might be useful for some:

Square.__hash__ = lambda self:hash((self.x,self.y))
Square.__eq__ = lambda self,other:self.x==other.x and self.y==other.y

This will make indexing a dictionary by a square stable over time, because the hash of the square only depends of its position. I think that was one of the main reasons for the decoupling of Location ans Site in the original hlt. I can't see any downside to this. What do you think?


#13

Yeah, I found the reference and can get it to work, at least for smaller maps. But I also tried exactly what you are suggesting, saving the strings by modifying the init function, and somehow the strings get mangled in the process. Obviously I'm doing something wrong though not sure what.

Regarding speed-up it's hard to say. Not being very secure in the use of generators I find myself reintroducing loops everywhere and so in part not getting the full boost. Looking at the output scrolling by when I let my bots compete locally it is _a lot_ faster though. Also I haven't seen the timeouts I saw with the original framework.

Finally, what I am trying to do is simply make sure that my own functions and extensions actually return what I intend. I find that very hard and tedious to do by writing to log files. Much easier to make up a simple map and see the results in the IDE.


#14

You can run your bot from an IDE. See here.


#15

@Maximophone: You're welcome, I'm pleased it's useful. Your code change seems sensible for that usecase -- and TIL that __hash__ exists, and that you can override it on a namedtuple without creating a custom class. And if it works for you or others, by all means, of course run with it. I will point out that if you wanted to create the special dict you described (time invariant) without changing the hlt.py, you could also just use (square.x, square.y) tuples as the keys -- manually rather than automatically. So, right now, I do use a dictionary with the full Squares as keys for a certain part of my botcode. After wrestling with the official-starter too many times bc I had a Site and needed Location info (or vice versa), I took an oath that I would never pass around the Square data without all 5 datapoints intact. Your change would break my particular usecase because if/when I iterate over the keys or items, I no longer have a full square to refer to. I've done some programming in languages where all data is immutable, and that has impacted some of my coding choices in other languages.

@FigiMon, thanks, that helps me understand where you and others are coming from. Hope @truell20's link helps. Re generators / iterators, this is a classic resource. Read through his slides there (I've been through them in detail three times over the years). I like to use list comprehensions and generator expressions whenever it's sensible, bc the resulting code is usually much more compact and readable (though the monstrosity of the self.contents = line in my alt-starter __init__ is pushing it for sure!) I was under the impression that list-comp's and gen-expr's were also faster than regular for-loops, but the internet says I am wrong about that. But for readability (or working with very large data like dabeaz's usecase), they are a win for sure.


#16

Are the coordinates formatted the same as they used to be? For example, I'm having trouble here:

# Old
for y in range(gameMap.height):
    for x in range(gameMap.width):
        location = Location(x, y)
        if gameMap.getSite(location).owner == myID:
            moves.append(Move(location, actions[y][x]))
sendFrame(moves)

# New
for y in range(gameMap.height):
    for x in range(gameMap.width):
        location = Square(x, y, None, None, None)
        if gameMap.contents[y][x].owner == myID:
            moves.append(Move(location, actions[y][x]))
send_frame(moves)

I would expect this to be a seamless change, however my agent acts incorrectly with the change. Am I missing something?

Edit:
Nevermind; I was able to figure it out. I realized your framework uses a different direction scheme than that listed in the documentation, and I just had to remove translate_cardinal to make it work as before.


#17

is he using contents[y][x] or the more natural contents[x][y] ?


#18

Thanks a lot, the running is like 10x faster now ! :sunglasses:


Saved just in time!
#19

We'd like to make this starter package (with maybe some tweaks) the official halite python starter package. If you send over a PR @erdman (so that you can be credited as the contributor), we'll get to work on that.


#20

This is now the official Python starter package.


We've changed the official Python Starter Package