Hrvoje’s Blog

It’s only ones and zeroes.

A Simple 2048 Clone in Python

Every now and then a viral game shows up. It is difficult to name a particular quality which makes a game popular, but most people would probably describe it as addictive. 2048 is just that, despite its remarkable simplicity.

Soon, I started wondering how many lines of code would it take to write it and what are the bare essentials of the game, and tried writing my own version. To keep things short, movements are implemented in a single direction only. For the rest of the moves to work you have to rotate the matrix a few times first, perform the move and rotate it some more to return to your starting point. Not ideal, but simple it is. Game over detect simply checks if movement in any direction affects the board.

Tile creation needed to be short, so a list of zero-valued matrix elements’ coordinates was made, one picked out at random and a 2 or 4 placed there. To color the backgrounds differently, its background color is derived from its value. A dictionary could have been used as well, but it would be slightly longer.

That’s pretty much it - no score, no menu, no restart, no keypress error handling… just the bare essentials, but - it works. And it’s kind of fun, too!

Short attempt with Tkinter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import random,Tkinter
def shift(c): return list([i for i in c if i>0] + [0]*c.count(0))
def sms(l):
        l=shift(l)
        for i in range(len(l)-1):
                if l[i+1]==l[i]: l[i], l[i+1] = 2*l[i], 0
        return shift(l)
class igra(Tkinter.Tk):
        b = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
        def __init__(self,parent):
                Tkinter.Tk.__init__(self,parent)
                self.parent = parent
                self.grid()
                self.bl = [Tkinter.Button(self, height=2, width=4, state=Tkinter.DISABLED, font=("Helvetica", 24)) for i in range(16)]
                for i in range(16): self.bl[i].grid(row=i//4, column=i%4)
                self.dodaj_novu()
                self.bind_all('<Key>', self.key)
                self.mainloop()
        def rot(self): self.b = list(map(list, zip(*self.b[::-1])))
        def move(self, n):
                diff = self.b[:]
                for i in range(n): self.rot()
                for i in range(4): self.b[i] = sms(self.b[i])
                for i in range(4-n): self.rot()
                return 1 if self.b != diff else 0
        def dodaj_novu(self):
                k = [(i//4, i%4) for i,j in enumerate(sum(self.b, [])) if j == 0][random.randint(0, sum(self.b, []).count(0)-1)]
                self.b[k[0]][k[1]] = random.randint(1,2)*2
                for i in range(16):
                        d = self.b[i//4][i%4]
                        self.bl[i].config(text=d if d else ' ', bg='#%06x'% ((2**24-1) - (d*1500) ))
        def key(self, event):
                direction={'Left': 0, 'Down': 1, 'Right': 2, 'Up': 3}
                if self.move(direction[event.keysym]): self.dodaj_novu()
                p = self.b[:]
                for i in range(4):
                        self.move(i)
                        if self.b != p:
                                self.b = p[:]
                                return
                for i in range(16): self.bl[i].config(bg='red', text=':(')
a = igra(None)

…and it looks like this: