Forum Archive

2048

henryiii

Here is a little 2048 clone. It is focused more on being a simple example than being graphically advanced. Swipes would be nice, as would sliding tiles, but the later would complicate the class code, and the former I'm not sure how to do cleanly, except maybe with a custom view for the top level view. It also has a text only mode for consoles.

Writing a 2048 clone is a good programming exercise. :)

JonB

Fun game. I'm addicted.

Fwiw, I added swipes here , though I was starting from the version you posted, not the latest.

henryiii

Thanks, I've merged your addition. :) I was wondering how hard that would be, I'm rather impressed at how easy it looks.

techteej

Would you mind if I took a stab at making the design closer to 2048's?

LawAbidingCactus

Techteej, if it would help, I made a version of 2048 for CPython a few months back, but I haven't tested it out on Pythonista, probably requires a little tweaking to get it to work.

Screenshot

Source

techteej

That actually helps a lot, even though Im getting the colors through github then converting them the old fashioned way to Rgb.

henryiii

Of course I wouldn't mind. :)

Mine does support different sizes, btw.

@LawAbidingCactus, what language is that? Doesn't look like Python.

techteej

@henryiii Almost finished, but I can't quite seem to get the color changing based on numbers. Which line would I find this on?

Here's my completed progress so far.

henryiii

@techteej, I think you want line 110. That's where I set up the board after every move.

techteej

@henryiii thats the line, but when I change the color on that line, it forces all non-empty tile colors to change to that color.

henryiii

@techteej You use the value of the tile to set the color. For example, make a dictionary or list of the colors you want, then set it based on the value 'lab'. If you need the the value as a number 0-11, that's how it is stored internally.

For example:
At the top:

COLORS = ('white','blue', (0,1,1,1), ...) # Add 12 colors here, the first is empty cell

In the function:

simplelabel = board.traditional_data[i,j]
labels[i,j].background_color = COLORS[simplelabel]
ccc

It would be cooler if this project was a repo on GitHub instead of a gist so that multiple folks could contribute code.

techteej

I get an IndexError: tuple index out of range.

henryiii

Oops, don't use traditional_data. Replace that woth data. My bad. Data goes from 0-11. Traditional data is the printed number (2,4,8,...)

@ccc Agreed, but gistcheck is simple and easy to use from a menu. Is there something (not using shellista) for git? If there's a good library, I don't mind making a UI.

JonB

shellista has the best git support -- it is basically a wrapper around dulwich (there is some gittle in there, but I think you can do everything with dulwich.porcelain). If you look at the git stuff in shellista, it would not be hard to convert those scripts to be usable in the action menu.

techteej

I tried to implement a delay, but I'm not getting the results I would like to. Looks like I'll stick to making the design closer xD.

LawAbidingCactus

Quick tweak to ask the user how many tiles, x by x, the board should be:

```# -- coding: utf-8 --
"""
Created on Sat Jul 12 09:33:29 2014

@author: henryii
"""

import console, random
import numpy as np
from functools import partial

myBoard = int(input("Enter the number of tiles, x by x, you want the board to be: "))

size = myBoard
COLORS = ((0.93333333333, 0.89411764705, 0.85490196078), (0.93333333333, 0.89411764705, 0.85490196078), (0.9294117647, 0.87843137254, 0.78431372549), (0.94901960784, 0.69411764705, 0.47450980392), (0.96078431372, 0.58431372549, 0.38823529411),(0.96470588235, 0.4862745098, 0.3725490196), (0.96470588235, 0.36862745098, 0.23137254902),(0.9294117647, 0.81176470588, 0.44705882352), (0.9294117647, 0.8, 0.38039215686),(0.9294117647, 0.78431372549, 0.31372549019), (0.9294117647, 0.7725490196, 0.24705882352), (0.9294117647, 0.76078431372, 0.18039215686),) # Add 12 colors here, the first is empty cell

class Board (object):
def init(self, data=np.zeros((size,size),dtype=int)):
self.data = data

def add_tile(self):
    data = self.data.reshape(-1)
    locs = np.nonzero(data == 0)[0]
    if len(locs):
        data[random.choice(locs)] = random.randint(1,2)
        return True
    return False

def check_move(self):
    'Checks to see if move available'
    if np.count_nonzero(self.data == 0):
        return True
    for row in self.data:
        if np.count_nonzero(np.ediff1d(row) == 0):
            return True
    for row in self.data.T:
        if np.count_nonzero(np.ediff1d(row) == 0):
            return True
    return False

def __repr__(self):
    return self.__class__.__name__+ '(' +repr(self.data)  + ')'

def __str__(self):
    return str(self.traditional_data) + '\nScore: {}'.format(self.score)

@property
def traditional_data(self):
    data = 2**self.data
    data[data == 1] = 0
    return data

@property
def score(self):
    return (self.traditional_data).sum()

def move(self, dir, test = False):
    'right,up,left,down as 0,1,2,3'
    made_move = False
    data = self.data
    if dir%2:
        data = data.T
    if (dir-1)//2 != 0:
        data = np.fliplr(data)

    for row in data:
        vals = row[row!=0]
        for n in range(len(vals)-1):
            if vals[n]==vals[n+1]:
                vals[n]+=1
                vals[n+1]=0
        vals = vals[vals!=0]
        vals.resize(row.shape)
        made_move |= not np.all(vals == row)
        if not test:
            row[:] = vals

    return made_move

def directions(self):
    return {x for x in range(4) if self.move(x,True)}

def auto_simple(self, pattern=(0,1,2,3)):
    for d in pattern:
        if self.move(d):
            return True
    return False

def run_console():
board = Board()
board.add_tile()
while True:
print board
x = raw_input('wasd:').lower()
if len(x)==1 and x in 'wasd':
v = 'dwas'.find(x)
if board.move(v):
board.add_tile()
if not board.check_move():
console.hud_alert('Game over.', 'error', 2.5)
break
else:
break
print 'Done'
return board

def _ui_setup(board, labels, v):
for i in range(board.data.shape[0]):
for j in range(board.data.shape[1]):
lab = board.traditional_data[i,j]
simplelabel = board.data[i,j]
labels[i,j].background_color = COLORS[simplelabel]
labels[i,j].text = str(lab) if lab else ''
#labels[i,j].background_color = (0.9294117647, 0.76078431372, 0.18039215686) if lab else (0.93333333333, 0.89411764705, 0.85490196078)

v.name = 'Score: {}'.format(board.score)

def _ui_move(board, labels, v, dir, buts, sender):
if board.move(dir):
board.add_tile()
if not board.check_move():
console.hud_alert('Game over.', 'error', 2.5)
v.close()
_ui_setup(board, labels, v)
_setup_buts(board, buts)

def _ui_move_auto(board, labels, v, buts, sender):
if board.auto_simple():
board.add_tile()
if not board.check_move():
console.hud_alert('Game over.', 'error', 2.5)
v.close()
_ui_setup(board, labels, v)
_setup_buts(board, buts)

def _setup_buts(board,buts):
valid_dirs = board.directions()
for d in range(4):
buts[d].enabled = d in valid_dirs

def run_ui():
import ui

class MyView(ui.View):
    moves=[None for i in range(4)]   #list containing function handles
    old=np.array((0,0))  #old touch location

    def touch_began(self, touch):
        # Called when a touch begins.  store    old location
        self.old[:]=touch.location

    def touch_ended(self, touch):
        # Called when a touch ends.
        #   compute vector from old to new touch
        u=np.array(touch.location)-self.old
        # first, determine whether x or y moved the most
        if abs(u[0])>abs(u[1]):
            dir=0
        else:
            dir=1
        # then, determine sign
        if u[dir]>0:
            dir+=2 if dir else 0
        else:
            dir+=0 if dir else 2
        # call the appropriate move function
        if u[0]**2 + u[1]**2 > 30**2:
            self.moves[dir](self)

v = MyView(frame=(0,0,100,140))
v.background_color = (0.98039215686, 0.9725490196, 0.93725490196)

board=Board()
board.add_tile()
labels = np.empty_like(board.data,dtype=object)
for i in range(board.data.shape[0]):
    for j in range(board.data.shape[1]):
        labels[i,j] = ui.Label()
        labels[i,j].flex = 'HWLRTB'
        labels[i,j].alignment = ui.ALIGN_CENTER
        labels[i,j].border_width = 1
        labels[i,j].corner_radius = 2
        labels[i,j].font = ('<system-bold>',24)
        labels[i,j].frame = (
            100/board.data.shape[1]*(j+.04),
            100/board.data.shape[0]*(i+.04),
            100/board.data.shape[1]*.92,
            100/board.data.shape[0]*.92)
        v.add_subview(labels[i,j])
_ui_setup(board,labels,v)

buts = []
for i in range(4):
    but = ui.Button()
    but.title = ('RIGHT','UP','LEFT','DOWN')[i]
    but.frame = ((60,110,20,20),
                            (40,100,20,20),
                            (20,110,20,20),
                            (40,120,20,20))[i]
    but.flex = 'WHTBLR'
    but.action = partial(_ui_move,board,labels,v,i,buts)
    v.add_subview(but)
    v.moves[i]=but.action   #to allow swipes
    buts.append(but)

but = ui.ButtonItem()
but.title = 'Auto Move'
but.action = partial(_ui_move_auto,board,labels,v,buts)
v.right_button_items = [but]

_setup_buts(board,buts)

v.present()

if name == 'main':
#self = run_console()
run_ui()
```

LawAbidingCactus

I apologize for the poorly posted code.... If anyone can fix it, can they please notify me of it?

henryiii

I'll be merging changes tomorrow, if I have time. Thanks! Did you surround the code with three backticks (`)? See github style fenced code.

henryiii

Updated, combined everyone's work so far, plus a few clean ups. I implemented a slightly nicer size dialog (but it currently won't run unmodified on a PC).

siddbudd

A crucial feature of the original and also of the kivy-python implementation of this game is that when you quickly swipe in a direction and then in the opposite direction, then numbers are NOT added both times, only after the second swipe. This allows keeping the largest number in a corner at ALL times. With this strategy and some practice it is easy to reach 4096 or even 8192.

henryiii

What is the original? I tested that on both the official github game and the iOS version, and it added numbers on the first swipe / keypress, even when they were pretty fast.