Generate random

My code as of now is as below:

width = 200

class G:

Welcome @millionish!

I’ll address the collision detection first –

This is kinda hacky, but it’ll work. I’m using the get() function to detect if the adjacent cells are background-coloured (therefore, empty). I’ve drawn some points (after the checks) to indicate the coordinates I’m sampling.

    ...
    if get(x+10, y+30) == -2960686:
        print('cell below is empty')
    
    if get(x+10, y+30) == -2960686:
        print('cell to the right is empty')
    
    if get(x+10, y+30) == -2960686:
        print('cell to the left is empty')
        
    stroke(5)
    point(x+10, y+30)
    point(x+30, y+10)
    point(x-10, y+10)
    
    moveDown(1)

-2960686 is the color code for background(210). You can find this out by sampling any background pixel using get().

To be honest, I’d approach this task differently – instead, use a 2D-list to store the block locations, to check for collisions, etc. You can render the tiles using this list. Conceptually, something like:

cells = [
  [ 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0 ],
  [ 0, 0, 0, 0 ],
  [ G, 0, R, 0 ],
  [ 0, B, R, 0 ],
]

So, for example, I can determine whether G (cells[3,0]) can move another row down by checking if cells[4,0] is a zero.

If you prefer your current approach, that’s cool. In that case, it’s probably a good to start with a class for each falling box:

class Box:
    
    def __init__(self):
        self.fill = (225, 0, 0)
        self.x = 0
        self.y = 0
        ...

You can use this to spawn new Box instances, in random positions, using random fills, and so on.

2 Likes

If I may, @millionish can also make use of Processing built-in functions like:

  • constrain() to keep the x and y coordinates within the boundaries of the canvas
  • createShape() to display the grid permanently and avoid computing it at each frame
  • frameCount to manage the descent rate of the moving rectangle

I would also second @tabreturn’s suggestion regarding the collision detection part. In this case, looking for the state of the cell below the currently visited cell is probably the best way to go.

Example with 1D list:

W, H = 200, 400    # dimensions of canvas
nC, nR = 10, 20    # number of cols and rows
S = W/nC           # step / size of unit cell
x, y = 80, -S      # coordinates of starting cell

disabled = False   # keyboard is NOT disabled
filled = [i for i in xrange(163,180) if random(1)>.6] + range(173, 200)  # randomly fill the bottom of the grid

def setup():
    size(W, H)
    fill(225,0,0)
    updateGrid()   # function that creates a grid showing both 'filled' and 'empty' cells

def draw():
    global y, disabled
    
    shape(grid)                # display the grid
    rect(x,y,S,S)              # draw the moving rectangle (red)
    
    t = frameCount%20 == 0     # time/tempo of descent
        
    id = (x//S)+(y//S)*nC      # convert coordinates to 1D index of currently visited cell (red)
    if id+nC not in filled:    # if no blue cell below:    (note: 'id+nC' = index of cell below)
        if t and y < H-S:      #   if time to move down and empty cell below is within canvas:
            y+=S               #     -> increment 'y' position 
            
    else:                      # else:
        disabled = True        #   -> disable keyboard
        if t:                  #   when time to move:
            filled.append(id)  #     -> put id of current cell (red) in 'filled' array list
            updateGrid()       #     -> update the grid accordingly
            y = -S             #     -> reset 'y' position
            disabled = False   #     -> re-enable keyboard
    
def keyPressed():
    global x, y
    
    if not disabled:
        k = keyCode
        if   k == 39: x+=S       # move RIGHT
        elif k == 37: x-=S       # move LEFT
        elif k == 40: y+=S       # move DOWN
        
        x = constrain(x, 0, W-S) # make sure that x and y coordinates stay within the boundaries of canvas
        y = constrain(y, 0, H-S)
        
def updateGrid():
    global grid
    
    grid = createShape(GROUP)
    for i in xrange(nC*nR):
        cell = createShape(RECT, (i%nC)*S, (i//nC)*S, S, S)
        cell.setFill(color(0, 180, 255) if i in filled else 255)
        grid.addChild(cell)

Screenshot 2020-11-06 172758

3 Likes

Hey Thank you so much for your help and efforts!!! :slight_smile:

Also, I wish to remove those random blue filled cells at bottom and start doing that when first square falls down. It should basically retain its colours and not turn blue. How do I do that?

All the code I posted is carefully annotated. If you look at line 7 I have written # randomly fill the bottom of the grid. If you don’t want anything at start (an unfilled grid), just delete the content of the filled list.

You could pick a random color at each start and save it in a list when the square is being stopped. This way, the updateGrid() function knows what color to assign to each grid cell for the next run.

To change the stroke width and color of the grid cells just add:

cell.setStrokeWeight( a float )   
cell.setStroke( an int )        

inside the updateGrid() function

Full sketch (annotated)

W, H = 200, 400         # dimensions of canvas
nC, nR = 10, 20         # number of cols and rows
S = W/nC                # step / size of unit cell
x, y = 80, -S           # coordinates of starting cell

disabled = False        # keyboard is NOT disabled
filled = []             # list storing the cells that have been stopped
colors = [255] * nC*nR  # list storing the colors of each grid cell
curcol = color(random(80,255),random(80,255),random(80,255)) # current color (picked randomly)

def setup():
    size(W, H)
    updateGrid()   # function that creates a grid showing both 'filled' and 'empty' cells

def draw():
    global y, curcol, disabled
    
    shape(grid)                                                          # display the grid
    fill(curcol)
    rect(x,y,S,S)                                                        # draw the moving rectangle (red)
    
    t = frameCount%20 == 0                                               # time/tempo of descent
        
    id = (x//S)+(y//S)*nC                                                # convert coordinates to 1D index of currently visited cell (red)
    if id+nC not in filled and id+nC<nC*nR:                              # if no blue cell below:    (note: 'id+nC' = index of cell below)
        if t and y < H-S:                                                #   if time to move down and empty cell below is within canvas:
            y+=S                                                         #     -> increment 'y' position 
            
    else:                                                                # else:
        disabled = True                                                  #   -> disable keyboard
        if t:                                                            #   when time to move:
            filled.append(id)                                            #     -> put id of current cell (red) in 'filled' array list
            colors[id] = curcol                                          #     -> store current color to 'colors' list
            curcol = color(random(80,255),random(80,255),random(80,255)) #     -> pick a new color at random
            updateGrid()                                                 #     -> update the grid accordingly
            y = -S                                                       #     -> reset 'y' position
            disabled = False                                             #     -> re-enable keyboard
    
def keyPressed():
    global x, y
    
    if not disabled:
        k = keyCode
        if   k == 39: x+=S       # move RIGHT
        elif k == 37: x-=S       # move LEFT
        elif k == 40: y+=S       # move DOWN
        
        x = constrain(x, 0, W-S) # make sure that x and y coordinates stay within the boundaries of canvas
        y = constrain(y, 0, H-S)
        
def updateGrid():
    global grid
    
    grid = createShape(GROUP)
    for i in xrange(nC*nR):
        cell = createShape(RECT, (i%nC)*S, (i//nC)*S, S, S)
        cell.setFill(colors[i])   # fill with corresponding color
        cell.setStrokeWeight(1)   # stroke width
        cell.setStroke(0)         # stroke color
        grid.addChild(cell)
2 Likes

Thanks so much!!! So suppose I want to restart the game after I click the mouse anywhere inside the window, how do I use mousePressed() function?
I’ve looked at many different topics in the forum and tried to get the code to work, but it won’t restart.

The mousePressed() works similarly to keyPressed(). There’s no conditional statement required to detect where you’re clicking (like on a button, or something) because you’re happy using the entire display window –

    ...

def keyPressed():
    ...

def mousePressed():
    global colors, filled, grid, x, y
    # code to reset the game variables
    # ...

...
2 Likes

thanks I actually figured it out myself :slight_smile: was pretty simple

Can you post your attempts (the code) for the 2 problems you’re mentioning:

  • cells removal
  • void filling

This way we can better help you understand what is not functioning or missing in your script.

Now that you have asked your question here, please try not to open another thread for it. There are just a handful of people looking at the “Python” tag so do not expect to have an answer within the hour.

If you really are in a hurry, feel free to look at the OpenProcessing website where plenty of examples for tetris game are available for inspiration.

Back to your question. Every time the moving square “collides” with another colored square below, you could:

  • get the stack of adjacent squares sharing the same color
  • if that stack exists:
    • look for a specific pattern inside (4 consecutive block either horizontally or vertically)
    • if that pattern is found:
      • remove it
      • shift down the cells above (if any)
  • if no stack:
    • “leave” the square where it is + store its color at this location
  • restart from top

Annotated example:

W, H = 200, 400    # dimensions of canvas
nC, nR = 10, 20    # number of cols and rows
S = W/nC           # step / size of unit cell
x, y = 80, -S      # coordinates of starting cell

disabled = False   # keyboard is NOT disabled
filled = set()     # set storing the id of cells that have been stopped

colors = [235] * nC*nR            # list storing the colors of each grid cell (all white-ish at start)
palette = (color(40,38,31),       # color palette
           color(95,181,179), 
           color(251,177,44),
           color(252,169,191))
curcol = palette[int(random(4))]  # current color picked randomly

patterns = (set(range(1,4)), set(range(nC, nC*4, nC)))  # a pattern = a set of distances from cell 0 (ex: horizontal block -> 0 to 1, to 2 and to 3, vertical block -> 0 to 10, to 20 and to 30)
dir = (-1, 1, -nC, nC)

def setup():
    size(W, H)
    stroke(255)
    updateGrid()   # function that creates a grid showing both 'filled' and 'empty' cells

def draw():
    global y, curcol, filled, disabled
    
    shape(grid)     # display the grid
    fill(curcol)    # draw the moving rectangle (red)
    rect(x,y,S,S)   # 
    
    t = frameCount%20 == 0   # time/tempo of descent
        
    id = (x//S)+(y//S)*nC                     # convert coordinates of currently visited cell to 1D index
    
    if id+nC not in filled and id+nC<nC*nR:   # if NO occupied cell below:    (note: 'id+nC' = index of cell below)
        if t and y < H-S:                     #   if time to move down and empty cell below is within canvas:
            y+=S                              #     -> increment 'y' position 
            
    else:                                     # else (there IS an occupied cell below!):
        disabled = True                       #   -> disable keyboard
        if t:                                 #   when time to move:
            

            s = stack(id,[])                                                    # look for cells with similar colors around (a 'stack')
            to_remove = set()
            if len(s)>1:                                                        # if stack of similar cells (same color) is found:
                for n, i1 in enumerate(s):                                      # -> check if patterns can be found in it
                    d = set(abs(i2-i1) for i2 in s[n:] if i1!=i2)
                    for p in patterns:
                        if p.issubset(d):                                       # if so:
                            to_remove.update([i1] + map(lambda x: x+i1, p))     # -> store id of their cells in 'to_remove'
        
            if to_remove:                                                       # if patterns have been found:
                filled -= to_remove                                             # -> remove the corresponding cells from 'filled'
                for i in to_remove: colors[i] = 235                             # -> make them appear white again
                            
                to_shift = set()                                                # check if there are cells above the removed cells 
                for i in filled:
                    if i<min(to_remove) and i%nC in [k%nC for k in to_remove]:
                        to_shift.add(i)
                    
                if to_shift:                                                    # if so, shift them down*
                    shifted = [i+nC for i in to_shift]
                    filled -= to_shift                                          # -> *(remove their ids form 'filled')
                    filled.update(shifted)                                      # -> *(replace with new shifted/incremented ids)                                                 
                            
                    temp = [colors[i] for i in to_shift]                        # let's not forget to shift the corresponding 'colors' as well
                    for i, n in enumerate(shifted):
                        colors[n] = temp[i]                                     # cells below inherit the color from the cell above
                    for i in to_shift.difference(shifted): 
                        colors[i] = 235                                         # cell on top becomes white
                        
                        
            else:                                 # if NO pattern to remove:
                filled.add(id)                    #  -> put id of current cell in 'filled' array list
                colors[id] = curcol               #  -> store current color to 'colors' list
                
                                                  # IN ANY CASE, at the end DO NOT FORGET TO
            curcol = palette[int(random(4))]      # -pick a new color at random
            updateGrid()                          # -update the grid
            y = -S                                # -reset 'y' position
            disabled = False                      # -re-enable keyboard
    
    
def keyPressed():
    global x, y
    
    if not disabled:
        k = keyCode
        if   k == 39: x+=S       # move RIGHT
        elif k == 37: x-=S       # move LEFT
        elif k == 40: y+=S       # move DOWN
        
        x = constrain(x, 0, W-S) # make sure that x and y coordinates stay within the boundaries of canvas
        y = constrain(y, 0, H-S)
        
        
def updateGrid():
    global grid
    
    grid = createShape(GROUP)
    for i in xrange(nC*nR):
        cell = createShape(RECT, (i%nC)*S, (i//nC)*S, S, S)
        cell.setFill(colors[i])   # fill with corresponding color
        cell.setStrokeWeight(1)   # stroke width
        cell.setStroke(255)       # stroke color
        grid.addChild(cell)
        
        
def stack(id, v):
    v.append(id)
    
    for d in dir:
        nid = id+d
        if 0<=nid<nC*nR and nid not in v:
            if colors[nid] == curcol:
                stack(nid, v)
                
    return sorted(v)
2 Likes