Search code examples
artificial-intelligencenetlogo2048

NetLogo: 2048 bot optimisation


I am trying to make a Netlogo simulation of a 2048 game. I have implemented three heuristic functions determined by weight parameters and want to use behaviour space to run simulations and check what is the best strategy for winning this game.

Procedure search uses export/import-world primitives to search over possible moves and chooses the move for which the heuristic function has the highest value.

The problem is that this procedure is very slow (due to the import-world function which is being called four times each turn). Do you have any ideas how to implement this without exporting and importing world so often?

This is a project for my Introduction to AI class. It is due in a couple of days and I can't seem to find any solutions.

The relevant part of the code is below. Procedures move-(direction) all work properly and variable moveable? is true if the square can move in said direction and false otherwise. It is checked in procedure moveable-check called by move-(direction).

I would very much appreciate your help. :)

to search

  let x 0
  let direction "down"

  export-world "state.csv"
  move-up
  ifelse not any? squares with [moveable?]
     [set h-value -5000]
     [set x h-value
     set direction "up"
     import-world "state.csv"]


  export-world "state.csv"
  move-down
  ifelse not any? squares with [moveable?]
     [set h-value -5000]
     [if h-value > x
        [set x h-value
        set direction "down"]
     import-world "state.csv"]


    export-world "state.csv"
  move-left
  ifelse not any? squares with [moveable?]
     [set h-value -5000]
     [if h-value > x
        [set x h-value
        set direction "left"]
     import-world "state.csv"]

   export-world "state.csv"
  move-right
  ifelse not any? squares with [moveable?]
     [set h-value -5000]
     [if h-value > x
        [set x h-value
        set direction "right"]
     import-world "state.csv"]

  ifelse direction = "up"
    [move-up
      print "up"]
    [ifelse direction = "down"
      [move-down
        print "down"]
      [ifelse direction = "right"
        [move-right
          print "right"]
        [move-left
          print "left"]]]
   if not any? squares with [moveable?]
     [
       ask squares [set heading heading + 90]
       moveable-check
       if not any? squares with [moveable?]
          [ask squares [set heading heading + 90]
           moveable-check
           if not any? squares with [moveable?]
              [ask squares [set heading heading + 90]
               moveable-check
               if not any? squares with [moveable?]
                  [stop]]]
       ]
end

Solution

  • The most important, and difficult, information you need to be able to save and restore is the squares. This is pretty easy to do without import-world and export-world (note that the following uses NetLogo 6 syntax; if you're still on NetLogo 5, you'll need to use the old task syntax in the foreach):

    to-report serialize-state
      report [(list xcor ycor value)] of squares
    end
    
    to restore-state [ state ]
      clear-squares
      foreach state [ [sq] ->
        create-squares 1 [
          setxy (item 0 sq) (item 1 sq)
          set heading 0 ;; or whatever
          set value item 2 sq
        ]
      ]
    end
    

    value above just shows how to store arbitrary variables of your squares. I'm not sure what data you have associated with them or need to restore. The idea behind this code is that you're storing the information about the squares in a list of lists, where each inner list contains the data for one square. The way you use this then is:

    let state serialize-state
    ;; make changes to state that you want to investigate
    restore-state state
    

    You may need to store some globals and such as well. Those can be stored in local variables or in the state list (which is more general, but more difficult to implement).

    A few other ideas:

    • Right now it looks like you're only looking one state ahead, and at only one possible position for the new square that's going to be placed (make sure you're not cheating by know where exactly the new square is going to be). Eventually, you may want to do arbitrary look ahead using a kind of tree search. This tree gets really big really fast. If you do that, you'll want to use pruning strategies such as: https://en.wikipedia.org/wiki/Alpha%E2%80%93beta_pruning . Also, that makes the state restoration stuff more difficult, but still doable. You'll be storing a stack of states rather than a single state.
    • Instead of set heading heading + 90 you can just do right 90 or rt 90.