see_states = True

class block:
    def __init__(self, x, y, col, h=1, w=1, _type="normal"):
        self.x, self.y = x, y
        self.col = col
        self.h, self.w = h, w
        self.type = _type
        
    def __repr__(self):
        return f"x: {self.x}, y: {self.y}, height: {self.h}, width: {self.w}, color: {self.col}"

class power_gem(block):
    def __init__(self, x, y, col, h=2, w=2):
        assert h >= 2 and w >= 2, "Powergem cannot be formed"
        super().__init__(x, y, col.upper(), h, w)

    def __repr__(self):
        return super().__repr__()
    
class block_pair:
    def __init__(self, fst, snd):
        self.fst = fst
        self.snd = snd

    def __repr__(self):
        return f"first : {self.fst}\nsecond: {self.snd}"

    def make_move(self, t):
        assert t in "ABLR", "Invalid move!"
        if t in "LR":
            self.move_hrz(t)
        elif t in "AB":
            self.rotate(t)

    def move_hrz(self, t):
        if t == "L" and min(self.fst.y, self.snd.y) > 0:
            self.fst.y -= 1
            self.snd.y -= 1
        elif t == "R" and max(self.fst.y, self.snd.y) < 5:
            self.fst.y += 1
            self.snd.y += 1

    def rotate(self, t):
        dx, dy = self.snd.x - self.fst.x, self.snd.y - self.fst.y
        if t == "A":
            self.snd.x = self.fst.x - dy
            self.snd.y = self.fst.y + dx
        else:
            self.snd.x = self.fst.x + dy
            self.snd.y = self.fst.y - dx
        if max(self.fst.y, self.snd.y) == 6:
            self.fst.y -= 1
            self.snd.y -= 1
        elif min(self.fst.y, self.snd.y) == -1:
            self.fst.y += 1
            self.snd.y += 1            

class game:
    def __init__(self):
        self.board = [[" " for _ in range(6)] for _ in range(12)]
        self.clusters = []
        self.current_pair = None
        self.crash_gems = []
        self.power_gems = []
        self.rainbow_gems = []
        self.states = []

    def __repr__(self):
        r = ["   ___ __ "]
        for i, row in enumerate(self.board):
            r.append(str(i).rjust(2, ' ') + '|' + "".join(row) +'|')
        r.append("   " + "*" * 6)
        return "\n".join(r) + "\n"
        
    # used for real test
    def __str__(self):
        return "\n".join("".join(x) for x in self.board)
        
    def move_current_pair(self, moves):
        for move in moves:
            assert move in "LRAB", "Impossible move!"
            self.current_pair.make_move(move)

    def make_block(col, pos):
        assert pos in (0, 1), "Can't make block from pair"
        #initial height is negative before movement of pair can start
        return block(-2 + pos, 3, col)

    def fall_current_pair(self):
        #just in case...
        while min(self.current_pair.fst.x, self.current_pair.snd.x) < 0:
            self.current_pair.fst.x += 1
            self.current_pair.snd.x += 1
        x1, y1 = self.current_pair.fst.x, self.current_pair.fst.y
        x2, y2 = self.current_pair.snd.x, self.current_pair.snd.y
        if self.board[x1][y1] != ' ' or self.board[x2][y2] != ' ':
            return False
        while True:
            yf, ys = self.current_pair.fst.y, self.current_pair.snd.y
            assert 0 <= ys < 6 and 0 <= yf < 6, "Wrong position for current pair"
            m = max(self.current_pair.fst.x, self.current_pair.snd.x)
            #Stops the fall if the block hits the ground or a another block in the next step
            if m == 11 or (m + 1 < 12 and (self.board[m + 1][yf] != " " or self.board[m + 1][ys] != " ")):
                break
            #otherwise continue falling
            self.current_pair.fst.x += 1
            self.current_pair.snd.x += 1
        #the pair is blocked, the first block f is the lowest
        if self.current_pair.fst.x >= self.current_pair.snd.x:
            f, s = self.current_pair.fst, self.current_pair.snd
        else:
            f, s = self.current_pair.snd, self.current_pair.fst
        #the pair is separated...
        #move the first block independantly
        while f.x + 1 < 12 and self.board[f.x + 1][f.y] == " ":
            f.x += 1

        #update the board...
        self.board[f.x][f.y] = f.col
        #move the second block independantly
        while s.x + 1 < 12 and self.board[s.x + 1][s.y] == " ":
            s.x += 1

        #update the board...
        self.board[s.x][s.y] = s.col
#         print("after pair falls :\n", self.__repr__())
        return True
  
    def get_adj(self, i, j, col):
        # Getting pos of all adjacents color blocks (simple, crashgems and powergems)
        queue = [(i, j)]
        visited = set([(i, j)])
        power_gem_visited = set()
        while len(queue) > 0:
            x, y = queue.pop(0)
            for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                u, v = x + dx, y + dy
                if (0 <= u < 12 and 0 <= v < 6 and self.board[u][v].upper() == col.upper() and not (u, v) in visited):
                    queue.append((u, v))
                    visited.add((u, v))
        rem = set()
        for gem in self.power_gems:
            x, y, c, h, w = gem.x, gem.y, gem.col, gem.h, gem.w
            if (x, y) in visited:
                power_gem_visited.add((x, y, c, h, w))
                for u in range(x, x + h):
                    for v in range(y, y + w):
                        rem.add((u, v, c, h, w))
        visited -= set([(u, v) for (u, v, c, h, w) in rem])
        return (visited, power_gem_visited)
    
    def has_crash_gems(self):
        return any(c in "".join("".join(x) for x in self.board) for c in "rgyb")
        
    # crash gems lower case!!    
    def get_crash_gems(self):
        gems = set()
        for x in range(11, -1, -1):
            for y in range(6):
                c = self.board[x][y]
                if c in "rgyb":
                    gems.add((x, y, c)) 
        
        return gems

    # rainbow gems lower case!!    
    def get_rainbow_gems(self):
        gems = set()
        for x in range(11, -1, -1):
            for y in range(6):
                if self.board[x][y] == '0':
                    gems.add((x, y)) 
        return gems
        
    # method for crash gems
    def destroy_by_crash_gems(self):        
        for (x, y, c) in self.get_crash_gems():
            adj, pow_adj = self.get_adj(x, y, c)
            print("crashed :", c, adj, pow_adj)
            if len(adj) + len(pow_adj) > 1:
                self.board[x][y] = ' '
                if len(adj) > 1:
                    for u, v in adj:
                        self.board[u][v] = ' '
                if len(pow_adj) > 0:
                    idxs = []
                    for i, g in enumerate(self.power_gems):
                        _x, _y, _c, _h, _w = g.x, g.y, g.col, g.h, g.w
                        if (_x, _y, _c, _h, _w) in pow_adj:
                            idxs.append(i)
                            # updating board
                            for u in range(_x, _x + _h):
                                for v in range(_y, _y + _w):
                                    self.board[u][v] = ' '
                            # removing power gems
                    self.power_gems = [self.power_gems[i] for i in range(len(self.power_gems)) if not i in idxs]
                    print("after destroy procedure :\n", self.__repr__())
                    return
                    
    # method for rainbow gems
    def destroy_by_rainbow_gems(self):
        for x, y in self.get_rainbow_gems(): 
            self.board[x][y] = ' '
            if x != 11:
                # getting color of bottom block if it exists
                col = self.board[x + 1][y].upper()
                # deleting power gems
                for u in range(12):
                    for v in range(6):
                        if self.board[u][v].upper() == col:
                            self.board[u][v] = ' '
                # removing power gems
                self.power_gems = [gem for gem in self.power_gems if gem.col != col]
                    
    def get_simple_color_gems(self):
        rem = set()
        for gem in self.power_gems:
            x, y, c, h, w = gem.x, gem.y, gem.col, gem.h, gem.w
            for u in range(x, x + h):
                for v in range(y, y + w):
                    rem.add((u, v))

        gems = set()
        for x in range(12):
            for y in range(6):
                # only add simple blocks not part of power gems 
                if self.board[x][y].upper() in "RGYB" and not (x, y) in rem:
                    gems.add((x, y, self.board[x][y]))
        return gems
        
    def make_power_gems(self):
        candidates = sorted(self.get_simple_color_gems(), key=lambda x:(x[0], x[1]))
        
        new_power_gems = []
        for x, y, col in candidates:
            # square 2 * 2 of simple blocks of same color
            if x + 1 < 12 and y + 1 < 6 and all((u, v, col) in candidates for u, v in ((x + 1, y), (x + 1, y + 1), (x, y + 1))):
                # expand horizontally
                w = 2
                while y + w < 6 and (x, y + w, col) in candidates and (x + 1, y + w, col) in candidates:
                    w += 1
                # expand vertically
                h = 2
                while all((x + h, v, col) in candidates for v in range(y, y + w)):
                    h += 1
                self.power_gems.append(power_gem(x, y, col, h, w)) 
                print("new power gem from simple", self.power_gems[-1])
                return True
        return False    
                                  

    def fall_power_gems(self):
        
        idx = None
        self.power_gems.sort(key=lambda g: (-g.x, g.y))
        for i, gem in enumerate(self.power_gems):
            x, y, c, h, w = gem.x, gem.y, gem.col, gem.h, gem.w
            if x + h < 12 and all(self.board[x + h][v] == ' ' for v in range(y, y + w)):
                k = 0
                while x + h + k < 12 and all(self.board[x + h + k][v] == ' ' for v in range(y, y + w)):
                    k += 1 
                    idx = i
                if k > 0:                    
                    gem = self.power_gems[idx]
                    print("falling power gem", gem, k)
                    x, y, c, h, w = gem.x, gem.y, gem.col, gem.h, gem.w
                    self.power_gems = [gem for i, gem in enumerate(self.power_gems) if i != idx]
                    for u in range(x, x + h):
                        for v in range(y, y + w):
                            self.board[u][v] = ' '
                    self.power_gems.append(power_gem(x + k, y, c, h, w))
                    for u in range(x + k, x + k + h):
                        for v in range(y, y + w):
                            self.board[u][v] = c
                    return None
                    

    def fall_simple_gems(self):
        for x, y, c in sorted(self.get_simple_color_gems(), key=lambda e: (-e[0], e[1])):
            if x + 1 < 12 and self.board[x + 1][y] == ' ':
                k = 0
                while x + k + 1 < 12 and self.board[x + k + 1][y] == ' ':
                    k += 1
                self.board[x][y], self.board[x + k][y] = self.board[x + k][y], self.board[x][y]
                return None
                    
    def fall(self):        
        while 1:
            st = str(self)
            self.fall_simple_gems()
            self.fall_power_gems()            
            if str(self) == st:
                break
        
    def grow_power_gems_hrz(self):
        # grow with another power gem
        power_gems_sorted = sorted(self.power_gems, key=lambda e: (e.x, e.y))
        N = len(power_gems_sorted)
        ii, jj, col = None, None, None
        for i in range(N):
            for j in range(i + 1, N):
                gi, gj = power_gems_sorted[i], power_gems_sorted[j]
                xi, yi, ci, hi, wi = gi.x, gi.y, gi.col, gi.h, gi.w 
                xj, yj, cj, hj, wj = gj.x, gj.y, gj.col, gj.h, gj.w 
                if yj == yi + wi and xi == xj and ci == cj and hi == hj:
                    self.power_gems = [gem for l, gem in enumerate(power_gems_sorted) if l != i and l != j]           
                    self.power_gems.append(power_gem(gi.x, gi.y, gi.col, gi.h, gi.w + gj.w ))
                    print("p -> p (hrz)", gi, gj)
                    return True
        # grow with simple power gems 
        simple_gems = self.get_simple_color_gems()
        print("simple right", simple_gems)
        for i in range(N):
            gi = power_gems_sorted[i]
            x, y, c, h, w = gi.x, gi.y, gi.col, gi.h, gi.w 
            # growing to the right            
            k = 0
            while all((u, y + w + k, c) in simple_gems for u in range(x, x + h)):
                k += 1
            if k > 0:
                print("p -> n (right)", gi, k)
                self.power_gems = [gem for u, gem in enumerate(power_gems_sorted) if u != i]
                self.power_gems.append(power_gem(x, y, c, h, w + k))
                return True
            # growing to the left
            l = 0
            while all((u, y - (l + 1), c) in simple_gems for u in range(x, x + h)):
                l += 1            
            if l > 0:
                print("p -> n (left)", gi, l)
                self.power_gems = [gem for u, gem in enumerate(power_gems_sorted) if u != i]
                self.power_gems.append(power_gem(x, y - l, c, h, w + l))
                return True
        return False
                
    def grow_power_gems_vrt(self):
        # grow with another power gem
        power_gems_sorted = sorted(self.power_gems, key=lambda e: (e.x, e.y))
        N = len(power_gems_sorted)

        for i in range(N):
            for j in range(i + 1, N):
                gi, gj = power_gems_sorted[i], power_gems_sorted[j]
                xi, yi, ci, hi, wi = gi.x, gi.y, gi.col, gi.h, gi.w 
                xj, yj, cj, hj, wj = gj.x, gj.y, gj.col, gj.h, gj.w 
                if xj == xi + hi and yi == yj and ci == cj and wi == wj:               
                    self.power_gems = [gem for l, gem in enumerate(power_gems_sorted) if l != i and l != j]           
                    self.power_gems.append(power_gem(gi.x, gi.y, gi.col, gi.h + gj.h, gi.w))
                    print("p -> p (vrt)", gi, gj)
                    return True
        # grow with simple power gems 
        simple_gems = self.get_simple_color_gems()
        for i in range(N):
            gi = power_gems_sorted[i]
            x, y, c, h, w = gi.x, gi.y, gi.col, gi.h, gi.w 
            # growing down
            k = 0
            while all((x + h + k, v, c) in simple_gems for v in range(y, y + w)):
                k += 1
            if k > 0:
                print("p -> n (down)", gi, k)
                self.power_gems = [gem for u, gem in enumerate(power_gems_sorted) if u != i]
                self.power_gems.append(power_gem(x, y, c, h + k, w))
                return True
            # growing up
            l = 0
            while all((x - (l + 1), v, c) in simple_gems for v in range(y, y + w)):
                l += 1            
            if l > 0:                
                self.power_gems = [gem for u, gem in enumerate(power_gems_sorted) if u != i]
                self.power_gems.append(power_gem(x - l, y, c, h + l, w))
                print("p -> n (up)", gi, l)
                return True
        return False

    def play(self, instructions):
        while len(instructions) > 0:
            print("power gems :")
            print('\n'.join(str(g) for g in self.power_gems))
            pair, moves = instructions.pop(0)
            print("pair :", pair, "\nmove :", moves ,'\n')
            self.current_pair = block_pair(game.make_block(pair[0], 0), game.make_block(pair[1], 1))
            self.move_current_pair(moves)
        
            b = self.fall_current_pair()
            if not b:
                break
            
            self.destroy_by_rainbow_gems()
            
            while 1:
                st = str(self)
                self.fall()
                self.destroy_by_crash_gems()                
                b1 = self.make_power_gems()
                b2 = self.grow_power_gems_hrz()
                b3 = self.grow_power_gems_vrt()
                print(b1, b2, b3)
                if str(self) == st and not b1 and not b2 and not b3:
                    break
            
            print(self.__repr__()) 
            # self.states.append("\n".join("".join(x) for x in self.board))

def puzzle_fighter(arr):
    g = game()
    g.play(arr[:])
    return str(g)

arr = [['rB', 'B'], ['YG', 'RR'], ['bG', 'BBB'], ['GG', 'ARR'], ['GB', ''], ['BY', 'BBBL'], ['Ry', 'RR'], ['YB', 'RR'], ['GY', 'RRR'], ['GB', 'B'], ['YY', 'A'], ['YR', 'ALLL'], ['Br', 'AAALL'], ['yr', 'AALL'], ['Y0', 'ARR'], ['GB', 'AAALLL'], ['YB', 'AAARRR'], ['BR', 'BRR'], ['GY', 'AAAR'], ['BB', 'BBBR'], ['GR', 'R'], ['GG', 'AA'], ['YB', 'AAAL'], ['RY', 'ALLL'], ['YB', 'AARR'], ['YB', 'BR']]
res = puzzle_fighter(arr)

Embed on website

To embed this program on your website, copy the following code and paste it into your website's HTML: