Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

We have been cloning 2048, analyzing 2048, but why haven't we played it yet? Write a 555 byte javascript snippet to play 2048 automatically, the best score after an hour will count (see scoring below).

Setup:

Goto 2048 and run:

 a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);

a is the object to control the game.

Rules:

After setup you may run 555 bytes of javascript from the console to control the game. The source code of the game can be found here (including comments).

  • It may only do things which are possible for the user:
    • a.move(n) to trigger a key action in any of the 4 directions.
      • 0: up, 1: right, 2: down, 3: left
    • a.restart() to restart the game. Restarting is allowed in the middle of the game.
  • Information about the state of the game can be found in a.grid.cells. This information is read-only
  • Hooking into any of the functions is allowed, altering their behaviour in any way is not (or changing any other data)
  • Moving is allowed only once every 250ms

Example

Just a very simple example to start from. Without comments and enters 181 bytes.

//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; b(); };
//number of move fails
mfs = 0;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  a.move(Math.floor(4 * Math.random()));
  m || mfs++;
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

Scoring and results

I will be running the snippets for one hour straight and the best score will count. Indeed there is a chance that randombot above will win that way, but 1 hour should be enough to beat it:

  • King Bottomstacker VII: 9912
  • Queen Bottomstacker V: 9216
  • Prince Bottomstacker II: 7520
  • Lord Bottom and Right: 6308
  • Peasant Randombot: 1413
  • Bottomstacker IV: 12320 Disqualified for making two moves in one interval (within 250ms)

FAQ

  • Why is this challenge not language agnostic through the terminal?
    • For the simple reason that it is more fun like this. Watching a game play itself graphically is simply a lot more mesmerizing than seeing a console spit out numbers. Even knowing no javascript you should be able to join into this challenge as it isn't about language features primarily (just use this tool to minimize the code)
share|improve this question
3  
This seems like it will just end up being a bunch of JavaScript implementations of the best algorithm from here, no? –  Jason C Apr 2 '14 at 16:14
2  
-1 for ...best score after an hour will count... Why just an hour? –  user80551 Apr 2 '14 at 16:18
3  
In any case, I suggest, in the name of fairness, seeding the random number generator the same for every answer's test run, and also running a hard (1 hour / 250 ms =) 14,400 moves per run to eliminate variations of that count due to timing inaccuracies. At least the results could be somewhat more deterministic and worthy of a KotH. –  Jason C Apr 2 '14 at 16:34
1  
750 bytes or 550 bytes? –  Peter Taylor Apr 2 '14 at 16:51
2  
Too restrictive. 750 bytes, 1 hour, JavaScript. –  TheDoctor Apr 2 '14 at 17:11

5 Answers 5

I can't code javascript, so I stole your answer.

//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  a.move(c)
  c++
  if (c>3) {c=1}
  m || mfs++;
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

It uses the strategy I also use.

EDIT: Nice, it just beat your score after about 5 minutes on my machine :D

EDIT: Forgot to move down twice instead of just once, this is the code you should use:

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  if (c<=3) {n=c}
  else {n=2}
  a.move(n)
  c++

  if (c>4) {c=1} 
  m || mfs++;
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

Also, there's a bug in it that it restarts when it's not needed, but I'm not sure how to fix this. EDIT: It currently has a highscore of 3116 (after 3 minutes). I think it's safe to say this algoritm is better than just doing random moves.

EDIT Newer version:

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; mfs=0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  if (c<=3) {n=c}
  else {n=2}
  a.move(n)
  c++
  if (c>4) {c=1} 
  m || mfs++;
  //up after 5 moves
  5 < mfs && (a.move(0));
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

EDIT: Another new version, this one moves down directly after moving up.

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; mfs=0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  if (c<=3) {n=c}
  else {n=2}
  a.move(n)
  c++
  if (c>4) {c=1} 
  m || mfs++;
  //up after 5 moves
  5 < mfs && (a.move(0), c=4);
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

EDIT: Update: it just broke my personal record with a pretty crazy score of 12596.

EDIT: Hey, I'm bottomstacker :D Also:

b=a.addRandomTile.bind(a);m=!1;a.addRandomTile=function(){m=!0;mfs=0;b()};mfs=0;c=1;setInterval(function(){m=!1;n=3>=c?c:2;a.move(n);c++;4<c&&(c=1);m||mfs++;5<mfs&&(a.move(0),c=4);10<mfs&&(mfs=0,a.restart())},250);

(Not actually a change, just compressed.)

5th time's a charm? Not sure. Anyways:

//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; mfs=0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  if (c<=3) {n=c}
  else {n=2}
  a.move(n)
  c++
  if (c>4) {c=1} 
  if (c==0) {c=4}
  m || mfs++;
  //up after 5 moves
  5 < mfs && (c=0);
  //restart after 10 moves failed
  10 < mfs && (mfs = 0, a.restart());
}, 250);

and:

b=a.addRandomTile.bind(a);m=!1;a.addRandomTile=function(){m=!0;mfs=0;b()};mfs=0;c=1;setInterval(function(){m=!1;n=3>=c?c:2;a.move(n);c++;4<c&&(c=1);0==c&&(c=4);m||mfs++;5<mfs&&(c=0);10<mfs&&(mfs=0,a.restart())},250);

Another new version:

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
//bind into new tile function and change m(ove) variable when a tile was moved
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() { m = !0; mfs=0; b(); };
//number of move fails
mfs = 0;
c=1;
setInterval(function() {
  //set global moved tracking variable to false
  m = !1;
  if (c<=3) {n=c}
  else {n=2}
  a.move(n)
  c++
  if (c>4) {c=1} 
  if (c==0) {c=4}
  m || mfs++;
  //up after 5 moves
  5 < mfs && (c=0);
  //Found this in the source, as the criteria for a gameover. Might as well reset then ;)
  if (!a.movesAvailable()) {
      a.restart()
  }

}, 250);

and:

a=new GameManager(4,KeyboardInputManager,HTMLActuator,LocalStorageManager);b=a.addRandomTile.bind(a);m=!1;a.addRandomTile=function(){m=!0;mfs=0;b()};mfs=0;c=1;setInterval(function(){m=!1;n=3>=c?c:2;a.move(n);c++;4<c&&(c=1);0==c&&(c=4);m||mfs++;5<mfs&&(c=0);a.movesAvailable()||a.restart()},250);

(I hope it's not too much of a problem that this continues behind the gameover screen? I think you could add an a.over=0 somewhere that gets executed often. I'll figure it out someday.)

EDIT (again): I dropped the standard gameover way and reverted to the old way of doing things. I'm now testing out an addition that'll always merge if there are 2 tiles of 16 or more together:

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
b = a.addRandomTile.bind(a);
m = !1;
a.addRandomTile = function() {
  m = !0;
  mfs = 0;
  b();
};
mfs = 0;
c = 1;
setInterval(function() {
  m = !1;
  l = 8;
  for (x = 0;x < 4;x++) {
    for (y = 0;y < 4;y++) {
      t1 = a.grid.cellContent({x:x, y:y});
      t2 = a.grid.cellContent({x:x, y:y + 1});
      t3 = a.grid.cellContent({x:x + 1, y:y + 1});
      if (t1 & t2) {
        if (t1.value == t2.value) {
          if (t1.value > l) {
            l = t1.value;
            c = 2;
          }
        }
        if (t1 & t3) {
          if (t1.value == t2.value) {
            if (t1.value > l) {
              l = t1.value;
            }
          }
        }
      }
    }
  }
  if (c <= 3) {
    n = c;
  } else {
    n = 2;
  }
  a.move(n);
  c++;
  if (c > 4) {
    c = 1;
  }
  if (c == 0) {
    c = 4;
  }
  m || mfs++;
  5 < mfs && (c = 0);
  10 < mfs && (mfs = 0, a.restart());
}, 250);
share|improve this answer
    
Add mfs=0 inside addRandomTile, that way it will restart counting after a successful move. –  David Mulder Apr 2 '14 at 18:26
    
And watching it play now, got to say, it's doing better than I expected O:) :D –  David Mulder Apr 2 '14 at 18:34
    
Just noticed that you are making two moves in one interval in the last 2 versions, so had to disqualify the 12320 score I had recorded. And yeah, I needed some kind of name :P –  David Mulder Apr 2 '14 at 19:34
    
@DavidMulder Nooooooo! (Do you have any idea where this happens, so that I can fix it?) –  ɐɔıʇǝɥʇuʎs Apr 2 '14 at 20:18
12  
Your answer is full of new version, please remove the outdated code. Or explain the differences between the versions. Old code will still be accessible in the "edit" logs if anyone is interested in. –  A.L Apr 3 '14 at 2:26

Right and Down bot: 345 bytes

Short version

b=a.addRandomTile.bind(a);m=!1;t=250;d=!0;a.addRandomTile=function(){m=!0;b();d&&setTimeout(c,t)};c=function(){d=!1;a.move(2);setTimeout(function(){m=!1;d=!0;a.move(1);m||setTimeout(function(){a.move(0);m?a.grid.cells[3][0]&&a.grid.cells[3][3]&&setTimeout(function(){a.move(1)},t):setTimeout(function(){a.move(3);m||a.restart()},t)},t)},t)};c();

Long version

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
b = a.addRandomTile.bind(a);
m = !1;
t = 250;
d = !0;
a.addRandomTile = function() {
  m = !0;
  b();
  d && setTimeout(c, t);
};
c = function() {
  d = !1;
  a.move(2);
  setTimeout(function() {
    m = !1;
    d = !0;
    a.move(1);
    m || setTimeout(function() {
      a.move(0);
      m ? a.grid.cells[3][0] && a.grid.cells[3][3] && setTimeout(function() {
        a.move(1);
      }, t) : setTimeout(function() {
        a.move(3);
        m || a.restart();
      }, t);
    }, t);
  }, t);
};
c();

In words

Move down, then right, if you can't move, move up (or if you can't, move left), if both the top right and bottom right corner is filled, move right otherwise start over.

Current highscore

My own best score was 7668, but that was run at far greater speed than t=250 (and thus indirectly longer than an hour).

share|improve this answer
    
This script has a tendency to perform multiple moves per turn. –  jdstankosky Jul 30 '14 at 20:48

Somehow I ran across this older contest this morning, and since I love 2048, I love AI, and JS is one of the few languages I currently know well, I figured I'd give it a shot.

GreedyBot (607 536 bytes)

Short version:

C=function(x,y){return a.grid.cellContent({x:x,y:y})},h=[[1,3,2,0],[2,1,3,0]],V='value',A='addRandomTile';a=new GameManager(4,KeyboardInputManager,HTMLActuator,LocalStorageManager);b=a[A].bind(a);m=!1;f=d=X=Y=0;a[A]=function(){m=!0;f=0;b()};setInterval(function(){m=!1;for(var c=X=Y=0;4>c;c++)for(var e=0;4>e;e++)if(u=C(c,e),!!u){for(q=e+1;4>q;){v=C(c,q);if(!!v){u[V]==v[V]&&(Y+=u[V]);break}q++}for(q=c+1;4>q;){v=C(q,e);if(!!v){u[V]==v[V]&&(X+=u[V]);break}q++}}f<4&&a.move(h[X>Y+4?0:1][f]);m&&(f=0);m||f++;15<f&&(f=0,a.restart())},250);

Long version (outdated):

a = new GameManager(4, KeyboardInputManager, HTMLActuator,    LocalStorageManager);
b = a.addRandomTile.bind(a);
m = !1;
f = d = X = Y = 0;
a.addRandomTile = function() { m = !0; f = 0; b(); };
setInterval(function() {
    m = !1;
    X = Y = 0;

    for(var x=0;x<4;x++) {
        for(var y=0;y<4;y++) {
            u = a.grid.cellContent({x:x, y:y});
            if(u==null){continue;}
            q = y+1;
            while(q < 4) {
                v = a.grid.cellContent({x:x,y:q});
                if(v!=null){
                    if(u.value==v.value){
                        Y+=u.value;
                    }
                    break;
                }
                q++;
            }
            q = x+1;
            while(q < 4) {
                v = a.grid.cellContent({x:q,y:y});
                if(v!=null){
                    if(u.value==v.value){
                        X+=u.value;
                    }
                    break;
                }
                q++;
            }
        }
    }

    if(X>=Y){
        if(f==0)
            a.move(1);
        else if(f==1)
            a.move(3);
        else if(f==2)
            a.move(2);
        else if(f==3)
            a.move(0);
    } else {
        if(f==0)
            a.move(2);
        else if(f==1)
            a.move(0);
        else if(f==2)
            a.move(1);
        else if(f==3)
            a.move(3);
    }
    if(m)f=0;
    m || f++;
    if(15 < f) f=0,a.restart();
}, 250);

The longer version was not golfed at all (other than shrinking variable names), so it could be shortened quite a bit while still being readable. The shorter version was created using Closure Compiler (thanks for the link!), which ended up at 650. With some custom modifications on my part, I was able to shave off another 43 114 bits.

Basically, it searches for through the grid for possible moves, and whenever it finds one, adds its value to either the horizontal or vertical total. After searching through every possible move, it determines which direction it should move, based on if the H or V total is higher, and the directions it has already tried. Right and Down are the first choices.

Looking back at this, I realize now that if either total is non-zero, the first attempt at sliding the tiles in that direction is guaranteed to succeed. Perhaps I could simplify the move-deciding section towards the end based on this.

I left this program running for an hour and ended up with a high score of 6080. However, in one of the trial runs (pre-minification), it managed a high score of 6492, only 128 behind my personal best of 6620. Its logic could be greatly improved by having it move left-down occasionally, as the numbers tend to pile up like this:

 2  4  8 16
 4  8 16 32
 8 16 32 64
16 32 64 128

(EDIT: I left it running a while longer, and it managed some 7532 points. Darn, my program is smarter than me....)

One more interesting tidbit: in one of my glitched attempts at creating something useable, somehow it ended up so that anytime any two tiles were in the same row or column, they were combined. This led to interesting developments as the random 2s or 4s would repeatedly combine with the highest tile, doubling it every time. One time, it somehow managed to score over 11,000 in 15 seconds before I shut it off.... XD

Any suggestions for improvement are very welcome!

share|improve this answer

Windshield Wipers: 454 bytes

Simply goes Right, Up, Left, Up... repeating (just like the wipers on a car) unless it gets jammed. If it gets jammed, it will attempt to turn off the wipers and turn them back on. Highest score I got in an hour was 12,156 -- However, most of the scores are somewhere between 3k - 7k.

It will output the score into the console after every attempt.

var move = !1;
var bad = 0;
var c = 0;
var b = a.addRandomTile.bind(a);
a.addRandomTile = function() {
    b();
    move=!0;
    bad=0;
}
setInterval(function() {
    if (!move) bad++;
    if (c>3) c=0;
    move = !1;
    if (c==3) {a.move(0);c++;}
    if (c==2) {a.move(3);c++;}
    if (c==1) {a.move(0);c++;}
    if (c==0) {a.move(1);c++;}
    if (bad>10) {a.move(2);}
    if (!a.movesAvailable()) {console.log("Score: "+a.score);a.restart();}
}, 250);
share|improve this answer

UpAndLeftBot

As title suggests, moves up & left by stealing David Mulder's work & swapping some numbers (I don't know jack about Javascript, so best I can do is this).

a = new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
b = a.addRandomTile.bind(a);
m = !1;
t = 250;
d = !0;
a.addRandomTile = function() {
  m = !0;
  b();
  d && setTimeout(c, t);
};
c = function() {
  d = !1;
  a.move(0); // a.move(2)
  setTimeout(function() {
    m = !1;
    d = !0;
    a.move(3); // a.move(1)
    m || setTimeout(function() {
      a.move(2);  //a.move(0)
      m ? a.grid.cells[3][0] && a.grid.cells[3][3] && setTimeout(function() {
        a.move(3); // a.move(1)
      }, t) : setTimeout(function() {
        a.move(1);  // a.move(3)
        m || a.restart();
      }, t);
    }, t);
  }, t);
};
c();
share|improve this answer
    
Just like David Mulder's script, this also performs multiple moves per turn every once in a while. –  jdstankosky Jul 30 '14 at 20:52

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.