Spring Challenge 2021 - Feedbacks & strategies

Hello, here is my postmortem (finished around #30)

The Beam Search

I implemented a beam search exactly as described by eulerscheZahl and with a similar method for filtering moves. My eval consists in :

  • my sun - my opponent sun
  • a pondered sum of the sun income difference between my bot and the opponent for the following days,
  • the score earned,
  • a malus for trees under the shadow of my own trees,
  • a malus if the income is under a certain threshold, fixed for each day

The opponent is simulated in a very basic way:

  • every opponent tree grows every day (except the first simulated day if it is dormant or his sun is too low),
  • the opponent has a “free” complete everyday starting from day 12, reducing the amount of nutrients.

Late gold I started to manipulate the overall strategy of my bot by modifying the income threshold in the eval and the estimated nutrients for each simulated day.
I thought it would be possible to adapt this strategy depending on what my opponent plays :

my opponent completed a lot of trees early :
-> nutrients will go down more slowly as his sun income is lower, so a long term strategy involving a high sun income might be more viable

my opponent has an excessive amount of size 3 trees and did not complete much
-> nutrients will go down abruptly near the end so completing fast might be a good idea,

I’m actually not sure if this is a good idea, as I did not experiment with this much.
I went another way:

The DUCT experiment

As legend opened, I tried to implement progressively to a Decoupled UCT. The top 3 on the previous challenge did it with great success, so why not ?

But I slightly underestimated the work required to make it functional. Mixing different algorithm might be strong, but it is time consuming.

After two days of coding, my DUCT version (when activated from day 15) was performing as well as my beam search in the leaderboard, which was slightly frustrating.
So in the end, I reverted back to my beam search and fine-tuned a couple of magic numbers.

Conclusion

Pros: I learned something new
Cons: I won’t have a new T-shirt

Congrats to the winners, thanks to CG, it was fun !

21 Likes

Question for NN bots:

Have you considered using current game state + every possible move at a time to score it?
You are given the moves as input each time the state changes, you can score each move individually.

About my Bot:

Chokudai’s version of beam search with random heuristics. This is what got me best results.

My eval function wasn’t suitable for most part of the contest, didn’t focus much on them either, regret not starting with a heuristic bot instead. Each time I promoted it was due to some bugs in my eval actually, so I thought I’ll just switch to some random heuristics.

Early Sunday was at around 1000 in gold, new bot went up to 300, didn’t bother checking since then, but it proves a point.

It wasn’t my favorite board game and I didn’t find it very intuitive or fun, but thought i try some new things out, which is what kept me in the contest. CG still offers one of the best platform for contests, if not the best.

Switched a few times between standard Beam Search and Chokudai’s version and I think Chokudai’s version is great because it’s a far better mix of exploration and exploitation.

Evaluating opponent didn’t do much for me at least. So at most I evaluated WAIT. COMPLETEs were useful to evaluate, but I didn’t include this in my final bot. It’s just strange how much better things went when certain parts were left out entirely.

7 Likes

Thanks for the write up, super interesting approach!
Just a few questions:

  • On what architecture did you do the training? Even at only 5 seconds per game, 700,000 games is a cumulative ~40 days of game play, so you’d need a lot of cores to get it done quickly.
  • What structure do you use to encode your neural network’s weights? 534 inputs + 2 dense layers with 96 nodes + 38 inputs is a lot of weights to encode (probably over 60,000, so needs good compression to cram into the 100ko CG limit!)
  • Why encode the day, nutrients, and sun count in binary rather than float?
2 Likes

I’ll finish 10-20 legends with a “staged” BeamSearch for continuing actions of the current day and waits, +/- the same as eulerscheZahl.
I have “regular” actions pruning as other described, nothing fancy.

I tried to help my evaluation with some arbitrary day limits (12 & 15) where I drop the expected suns bonus, seems like it was good with the meta.
I struggle a lot with the coeffs, i’ll work on the use of (existing) tools to find best parameters next time…

For the endgame (day > 20), i simulate a greedy opponent that complete as soon as possible, that help me a lot in the end.

performance wise, I have around 40k actions in 50ms (that’s not a lot, but I’m using java, so …)

12 Likes

Training was done on my pc (i7-8700K) using 12 parallel threads of games. CPU based for generating self play data with the network training done using tensorflow-gpu in between self play batches. The self play games have a fixed number of MCTS iterations rather than using timelimits (set to 1500 iterations per turn) so turns don’t take as long. When running on cg there would be a lot more iterations per turn (around 20-30k)). My self play history size was chosen to not cause me to run out of ram (by repeatedly lowering it every time I ran of of ram - I really need to change this away from python).

They’re all 14 bit floats using 1 unicode character per weight (using base65536 but the way codingame measures characters means that most 15/16bit values would count as two characters so I use 14 bit floats). Total number of weights is 64552, leaving just under 36k characters of code space (my final bot is 98002 characters).

Something jacek showed me on oware is that when the reason you want the value isn’t linear it often trains much better (or at least require less weights / time) if you split the value into different binary inputs instead of just passing the float value (in oware I went from 14 floats to 434 binary inputs and trained a much stronger bot in 1/10th the number of games - I was also able to reduce the number of hidden layers / size of hidden layers significantly). It’s easy to optimise the first layer of the NN to skip any 0’s so more binary inputs is mostly a code size issue. Originally everything was binary, but the number of inputs was causing code space issues so I changed points / sun to be floats. Total sun was added later on after analysing lots of games and it seeming to be a good indicator of win chance. I left the sun binary inputs there as it seemed like it would probably be useful in both formats (more sun = better, but specific sun values allow certain moves).

13 Likes

Hi everyone.
Finished 4th before the final rerun.
My strategy is based on … surprise … MCTS.

My version make use of two Trees, one for each player.
The main difficulty for this is that legal moves depend on other player actions.
I handled that issue in the following way:

  • when computing childs of a node, give it all “potential” actions and not only actions that are legal for the current state. I mean by “potential” that it would be legal if opponent actions were differents. For exemple, allow a GROW even if the price is too high, or allow a SEED even if the cell is occupied.
  • when making node selection (i.e when choosing a legal move) choose between childs that are really legals for the current state.

The goal was to reduce the branching factor.
To reduce it further I also strongly pruned the SEED actions.

An other important choice (but I am not sure it was a good idea) was to impose an order in actions during a day : first COMPLETE, then GROW for trees of size 2, size 1, size 0 and eventually SEED.
It usually make actions cheaper in this order (GROW price depend on number of trees of higher size).
For the sake of simplicity I also limited the number of action during a day at only one by category (COMPLETE, GROW1, GROW2, etc.).

I noticed that several gamers have used Neural Networks with success. I can’ t wait to read their PM.

30 Likes

So how do you go about generating potential actions? I am guessing you need to prune a lot more. So how do you know what to keep?

I am a bit confused how you’d store this in the search tree format. So for example for a player you generate a tree with all possible moves while opponent is on WAIT the entire time?

1 Like

Nice!

I use bit masks to represent the state, too, and the __builtin_ctzll function to iterate through the trees when needed, although that was more of a convenience for my heuristic, since I could compute the shadows in constant time with the help of a few look up tables (I could’ve implemented my heuristic also using the bitmasks directly, but the potential speed gain was small in comparison to the effort).

Other stuff that required tree counting I did with __builtin_popcountll.

Anyway, my ultra-fast sim didn’t get me further than ~1000th in gold. I tried MCTS at first, but made the mistake of implementing the tree as a hash table instead of an actual tree: it’s easier to handle transpositions and I don’t have to think hard for reusing the tree (just plug in the new fresh state and call it a day). However, hash tables are mighty slow compared to an actual pointer-based tree, so I could only do ~300 or so simulations in the early to mid-game, which is ridiculous. If I’d made MCTS use an actual tree, maybe I’d be alongside @Magus :slight_smile:

4 Likes

I’ll finished 100-120 legend using Java,
This was the most difficult contest that I participate ever…
As a human, I didn’t understand what was going on at the board, and why i won or lose. What move was bad or good…
I started the contest like all contest, with a simple Monte Carlo strategy.
Then I tried beam search, using every play, and it was not very good, so I tried to synchronize the days, and it improved a little… But by score function was very bad, and the improvements was more based at the heuristics than the search.
Than, I think that I could try to use MCTS for the end of the game, but the garbage collector was timing out a lot… So I forget about it, and try my last approach

Genetic Algorithm

I create two population, one with my moves and one with opponent moves, in an array[day][move], with a max of 5 moves per day.
Then i play all my population against the opponent population and evolve both, based of the final board score reached.
My game simulation was not very fast, but fast enough to find good individuals…
I added at the genes, a lot of restrictions to play, like almost everyone…

15 Likes

How did you eval the genes to know what to keep? Just endgame state? Or was it sim up to certain depth and then check sun score for rest of the game?

Also

3 Likes

Day < 16, score board at the last day calculated, Day>=16 endgame score… The score board is a lot of crap things, but most important things is “Sun Collected” - “Sun Wasted” (Wasted mean grow a seed 1 to 2 with 1 tree 2 at the board, you waste 1 sun point) plus some tree values… I do not fit this constants with local play, only with felling…

2 Likes

I went with simultaneous MCTS with UCB1. Only restriction was don’t seed if you already have a seed and the evaluation function was just won-lose-tie. It showed promise but the problem was performance, not enough sims per turn. Was in the process of switching to bitboard but ran out of time.

thanks for sharing and congrats for your work !

Do you have a trick to convert quickly the state into NN inputs ?

Hi, rank 3306 here!
Some amazingly inspiring ideas already posted above, can’t wait to try them out. What could I possibly add?
Well, I’ve translated my code into a few languages on my quest to get that sweet Legendary achievement. And I think it’s a great exercise in many ways. But, for me specifically, it helped unearth how the readability of my code remains questionable still even to this day. Can recommend for sure.
I don’t know many platforms, but doing this on CodinGame surely was enjoyable. See you guys this fall in Gold, hell, maybe even in Legendary :>

update: looking through some post mortems, and seeing that I was moving in the right direction, even if I did not implement no mcts/beam search or anything like it, now that is truly inspiring :100:

10 Likes

Hello,
Rank 172 in Master with java here. This is my second competition and I think I used a beam search approach if I understand it correctly.
I did not use the back end code.
Some methods I used were:
Copying a game:
Here I had a big mistake where two dimensional arrays were not copied correctly which mattered a lot and was hard to find. Thank god to unittests.

Applying a move to a game, Calculating all possible moves for a game,
Calculating all plausible moves:
I would trim the move list by having varying maxtreenumbers [1,2,4, 6] mostly and not complete while my opponent had no size 3 tree which helped a lot when my opponent made a selloff in the midgame in the hopes of winning. (Also selling when this happens would maybe have been better).

This was my beam kind of. I simulated a whole day of my turns to reach the maximum value of a game until I reach a Wait. My opponent did nothing meanwhile.

Then I had shadowCalculation for all 6 directions and a potental shadow calculation where I simulated my opponent using all grow actions which were possible at this point.

Then there is my gameevaluating algorithm… Oh my… I think it had like 20 variable parameters, because I had not much time and had no gamebreaking fast ideas left at sunday night to work with so I made minor changes to close the gap from 60 gold to legend.
First the basic stuff everything was transformed to points so score was always 1 and sun was 1/3. I also had sunproduction for next round at 1/6 first, then 1/3, and 0 in last turn.
Lastly each tree had a complex score, depending on size [1.1,2.2,4.4,9.6] until late game. Added decreasing scores for the next turns where it was not in a shadow depending on size. And a decrease if it is in potential shadow. Seeds also had decreasing downgrades depending on neighboring trees of same player. This made my last push possible, because I totally thought that the downgrade for shadows was enough which was a big mistake. Lastly some bonusscore for the richnessvalue of the cell. All of this was multiplied by a factor which decreased when turns ran by to make completes more useful.
For endgame I had slightly different values where the basevalue of a tree was much lower since trees are kind of worthless at the end if you did not complete them.

I also started completing at earliest in turn day 11.

In total it was a fun challange where I was happy to first time reach master even with not being able to code for three days. Next time I would like to have less fixed parameters, but rather a better algorithm. I would also make even more unittests since I had like 3-4 pretty gamebreaking kind of invisible bugs. It was a little bit annoying to manage the parmeters at the end. It got late and I got tired at 02 a.m watching my last commit barely making it after an hour.

9 Likes

Nothing too clever, just the first layer of the NN being split in half to take an array of 9 uint64’s and an array of 6 floats. The uint64’s are then made from my gamestate (eg the first is 37 bits of ‘blocked’ and 27 bits of ‘active[player]’, the second is 10 bits of ‘active[player]’, 37 bits of ‘trees[0][player]’ and 17 bits of 'trees[1][player]). I precalculate all the shifts so I don’t need to do any looping through bits. The other binary inputs are a single 1 in a range so I just calculate which bit to set.

In the NN part it then just loops through the set bits in each of the 9 uint64’s and for each of the output values adds the weights relating to that input as needed (no need for multiplications as the input value is 1). It then does a normal matrix multiply for the 6 floats and combines the outputs (and the biases). In theory I should be able to directly run the first layer off the gamestate without the intermediate array of uint64’s for a small speedup but I don’t want to make it game specific and it’s not a major part of my time usage.

Cheers once again for the great contest CG team & fellow Codingamers!

It’s been a while since I’ve devoted as much time to a codingame contest and written a PM :stuck_out_tongue: Looks like I’ll finish around #90-100 in Legend. I quite liked the simplified design of the game and appreciate the discrete state space. I’ll just refer to this contest as Tororo, it sounds better that way. Also, kudos to reCurse for an absolutely dominating performance! Looking forward to your PM. Edit: really interesting PMs all around! Love it :slight_smile: thanks for sharing!

Some of you may associate me with a particular high-level interpreted language I like to contest in, but sometime in the middle of the contest I decided python just wasn’t fast enough to simulate enough moves into the future for a search-based strategy to work decently well (squeezed out ~1K simulations with evals). I also haven’t been programming much over the past year so the additional C++ practice was much welcomed.

Even though it was easier to translate over to C++ with the python bot as reference, it still took me a couple days to get it working. Once that was done, I managed to beat the Gold Boss just by increasing the search breadth/depth (~50-60K simulations with evals). Below I’ll briefly describe a couple things I learnt during this contest.

Bitboarding?

Edit2: not fully a bitboard but merely condensed state-representation…? Since I still needed to iterate through indices to generate shadows :frowning: only some operations are faster with my representation

Tororo’s game state quite intuitively translated over to a bitwise notation where the presence of a tree is just 0/1 on the i-th bit. This means we can represent any state as the following (depth & actions are for book-keeping during search):

struct State{
    uint64_t seed;          // | first 37 bits CELL has SEED        | --11 bits free-- |       16 bits Depth       |
    uint64_t sm;            // | first 37 bits CELL has size 1 TREE | --11 bits free-- |       16 bits Actions     |
    uint64_t md;            // | first 37 bits CELL has size 2 TREE | -----------------27 bits free----------------|
    uint64_t ta;            // | first 37 bits CELL has size 3 TREE | -----------------27 bits free----------------|
    uint64_t me;            // | first 37 bits CELL has OWN tree    | --11 bits free-- | 8 bits SCORE | 8 bits SUN |
    uint64_t opp;           // | first 37 bits CELL has OPP tree    | --11 bits free-- | 8 bits SCORE | 8 bits SUN |
    uint64_t dormant;       // | first 37 bits CELL is DORMANT      | --11 bits free-- | 8 bits NUTRI | 8 bits DAY |
};

With this, we can shorten some of our operations into a few bitwise operators which should speed things up significantly compared to storing and iterating over a size-37 (or more) array/vector:

// State-based macros
/* Patterns:
 * #define TREES 0x1FFFFFFFFFUL // First 37 bits
 * #define i64 ((unit64_t)1)
 *  get_(me/opp)_trees          : s.(seed/sm/md/ta)&TREES&s.(me/opp)                //Order does not matter
 *  get_(me/opp)_active_trees   : (~s.dormant)&s.(me/opp)&TREES                     //mask with inverted dormant bits
 *  cnt_(me/opp)_trees          : cntBits(s.(seed/sm/md/ta)&TREES&s.(me/opp))       //function call + first macro pattern
 *  can_seed_locations          : ((~(s.seed|s.sm|s.md|s.ta|SOIL_ZERO))&TREES)      //merge the first 4 bitmasks and places
 *                                                                                  //where SOIL is 0, invert them and
 *                                                                                  //mask again with TREES
 *  accumulating shadows        : if (state.sm&idx) // idx = (i64<<i)
 *                                    shadow.sm |= SHADOW_CELLS[d][i][1];           //For day d, tree i and size 1
 */ 

cntBits I adapted the solution from https://stackoverflow.com/a/109025 to do this in constant time.

Some Shadowing/Seeding tricks

// bitmask of adjacent cells for idx i at length l [i][l]
const u64 ADJ_CELLS[37][4]; // [0][0]: {0UL,126UL,524286UL,137438953470UL},
// bitmask of shadowed cells on day d for idx i at length l [d][i][l]
const int SHADOW_CELLS[6][37][4]; // [0][0]: {0UL,2UL,130UL,524418UL},

First we can just precompute all neighboring cells for each locations at a given distance with a 37-bit bitmask. Similarly, we can do so for any cell on a given (day%6) where the shadow would be cast. So to check whether we can seed to a location, we need only require a check for whether our tree bits intersect with the neighbors bitmask of the tree height distance around the seed_to location (an inverse check as I iterate through possible seed locations instead of own trees). However, iterating through 0-36 for which index to seed_from still took my seeding actions generation to O(n^2) :(. For shadows, we can combine all the bitmasks together in one iteration over the tree indices to get the overall shadow ‘map’ for the day.

Zobrist Hashing

Another trick was Zobrist Hashing which reduced the collisions for my hashmap and gave me a boost of ~5K more simulations (10%) even though it was more expensive to compute than the prime-multiply and add naive strategy used before. The Zobrist hashing table is statically pre-populated with random 64-bit unsigned long long integers beforehand. All in all, around 1/4 of my codebase was constants o.O

The general strategy here for Zobrist hashing is to xor each component of the board state with each other. Each cell can be in one of 9 unique states {empty,(seed,small,medium,tall)*(me,opp)}, in addition to a limit of 24 days, 21 nutrients and I assume a max of 255 possible sun/score. Chuck them all into an xor and we get back our collision-reduced hash.

Optimization tricks

As per Magus https://www.codingame.com/forum/t/c-and-the-o3-compilation-flag/1670 and _CPC_Herbert C++ and the -O3 compilation flag.

  1. #pragma GCC optimize "O3,inline,omit-frame-pointer" at the top of your file!
    • Declare functions explicitly as inline.
  2. Reuse global variables
    • struct State[] for the search and struct Shadow[] for computing shadow maps. To recover from an early-exit scenario (timeout when searching a layer), I keep pair of State arrays and just alternate between them by indexing with depth%2. So when a layer is incomplete, we can revisit the old completed layer to evaluate those nodes instead.
  3. std is slow
    • Unfortunately I did not implement my own heapq or hashmap, but the only two std datastructures I really used was a std::priority_queue and std::unordered_map in the search. It was faster to declare the pq as a function-local variable while reusing a global map and calling .clear() (heap log(n) removals I presume - but is there no way to wipe it in O(1)?).

Search Strategy (pretty broken…)

Finally, some comments on my search strategy although I think Euler’s implementation of beam search is far more accurate than the version I ended up with which definitely feels a bit wonky now that I look at it more…

Roughly I thought it was a beam search where I limited the width of propagation at each search layer but for Tororo we needed to consider two ‘layers’, actions and days. So I just gave priority in a BFS way for least actions in closest day then break ties by state evaluation function. Not ordering by the number of actions taken gave me quite bad results since a sequence with COMPLETE would always certainly be weighted heavier in my heuristic evaluation (thinking back, maybe prioritizing by more SUN may be better than length of actions?). BUT after looking at Euler’s post, I’m sure my bot’s search is pretty broken… like, really, really, only-simulates-3-actions-deep-a-day broken… lol, so I shall skip describing it further…

Edit3: Fixed my beam over ACTIONS instead of DAYS and jumped to top 50 T.T For those interested in avoiding my embarrassing mistake

Only thing of note here was that I had a cheaper heuristic state evaluation (which I cached as a ‘score’ field in my State) to determine priority in queue and a more expensive playout policy for the final layer to select the best action. This allowed for increased evals/states visited in exchange for a more approximated solution.

Heuristic Evaluations

My heuristic evaluation was a cheap next-day sun income and score (adding bonus for shading opponent trees worked worse…) + a small weighted bonus for SOIL score. The final playout function uses a naive static WAIT policy… which works… surprisingly alright:

  1. Wait each turn to generate income. Compute shadow map. Since we’re always WAIT-ing, we can optimize slightly by unrolling the loop to only iterate through at most 6 days.
  2. We have a multiplicative decay of *0.95 for each day on sun income.
  3. Pessimistically assume nutrients will decrease by opponent_tall_trees when we harvest our trees. These additions were so that my bot didn’t hold onto trees for too long and prunes the board earlier.
  4. Add a bonus score for seeds that are in various levels of shade/no-shade. Afterall, an actual MCTS playout would have us grow these trees and produce some additional sun each turn.
  5. Return the simulated final score based on game rules + num_trees/100 (tie-breaker).

Remarks

Thanks again for the wonderful contest! Hopefully the things I learnt and shared about above may be helpful to some, definitely switched me back into programming mode from reading/writing philosophy all semester… Looking forward to the Fall Contest!

29 Likes

First of all, a shout-out to @BlitzProg. Like you, I poured a lot of time into this contest, with most of my code thrown away again. In the end, I magically made it to #184.

Getting to Bronze with a few lines of code was a breeze. Then, I decided to recreate the referee in order to be able to simulate the game. I tried to implement MCTS for the first time in a contest but was frustrated after a while because nothing seemed to work and I wasn’t sure if I should start searching for bugs (which were there for sure) or making the simulation faster.

Then I tried something more like Beam Search. A look-ahead of 5 steps and opponent prediction made my bot slower, but not really better. It was annoying to me that a depth of 1 was just as good as a heavy simulation. In hindsight, I’m pretty sure, my bot wasn’t doing what I thought it was, resulting in weird behaviour. I should have tested every component more carefully and will definitely keep that in mind for the next contest.

For the sake of my rank, I eventually went back to the basics and wrote a heuristic bot. If it weren’t for watching Kung Fu Panda on Saturday, I don’t know if I’d have had enough strength to start my comeback. :panda_face: Here are the main ideas of my program:

  • an evaluation function calculates how many sun points should be expected in the next 6 turns or until the game ends; I assume, that the board doesn’t change and use a slight decay factor (0.99) to account for uncertainty in the future; the function returns mySunExpectation – oppSunExpectation; taking into account the opponent’s sun was crucial here

  • I determine the “best” seeding, growing and completing action

  • seeding: maximize richness, then maximize positional advantage by evaluating with a tree of size 3 on that position; I only allowed seeding on cells without any of my existing trees (no matter the size) within a distance of 2; this seems a good compromise between being able to plant enough trees and not losing sun points by blocking my own trees

  • growing: maximize tree size (larger trees → more sun points for me, more shadow for opponent), then minimize amount of trees of the new size, then maximize evaluation with new tree size

  • completing: maximize evaluation, then maximize cell richness (in the endgame, this is switched to avoid losing richness points)

  • completing a tree is only allowed when I have at least daysLeft / 2 fully grown trees and if no other tree has been completed on that day (or if it’s the last day)

  • seeding is only allowed if I don’t own any other seed already and I have less than 8 trees in total

  • endgame: don’t waste sun points on seeding or growing if a tree is impossible to complete

Overall, I enjoyed this contest and am happy to end it in legend. Thanks, CodinGame, for providing this awesome challenge and congratulations to the winners – especially @reCurse - and everyone who tried hard! :deciduous_tree: :evergreen_tree: :whale: :evergreen_tree: :deciduous_tree:

11 Likes

Hi,

485th global here! My best result (yet?) in CodinGame contests!

I’m both surprised and happy in that! The contest was all in all really well done and well thought, with a very beginners friendly first steps (which is definitely appreciable to bring new people to the game and create some curiosity), and as shown by the PMs here, some variety in strategies!

In my case, I not totally willingly took the lazy approach suggested by Bob in a few blogposts. I think that’s the contest where I spent the least time coding. But I took my time to find how to write my initial code (status and such) and what I wanted to do with it.

I felt like doing a simulation would probably be out of my current range (but I wanna try based on some PMs, to see what would be possible), so went with heuristics on a mix of the next days :

  • Seeding is influenced by the full sun cycle : avoid seeding in places where you’ll get shadows from yourself (didn’t take the opponent too much in account, as you can shadow a tree which shadows you)
  • Growing is influenced by the two to three next days : what happens to my tree if I don’t grow it ? Will it get shadowed or can I prevent it? And furthermore, can I shadow one of my opponent’s trees?
  • Last but not least, excepting on the last days, completion happens only when a size 3 tree is about to get shadowed.

In the end, the idea for me was that the more sun points you get, the more likely you’re to win! And in turns where a potential size 3 tree will be shadowed, I’d rather try to put a seed in that favorable position, than have a useless size 3 tree.

I think I didn’t do a big “programming job” here and kind of actually let the others do it ? My approach to this contest wouldn’t have worked with the last two, as this strategy pretty much “reacts” to the opponent, rather than being smart itself, but it was really interesting to think about strategies instead of going directly into a big simulation (which, again, I probably couldn’t have done that well)

Shoutout to @LuckyJ for the always interesting discussions about strategy and “value” of Sun Points!

And thanks again to CodinGame for the contest!

9 Likes

Thanks CG for organizing. The game was quite different from others on CG, with some interesting mechanisms that gave various strategies a chance. And all for free. :slight_smile: Good job!

Tried alpha beta pruning with NN for state evaluation. Failed because I made my NN too simple, so it converged at gold/600. Still learned a lot, and learned that I have yet a lot to learn.

Thanks!

8 Likes