I am working on a school project to create a basic, educational game. I started with the amazing code written by cactusbin and revised by Gareth Ress found Here for the basics of a bejeweled style game.

The game objective is to match letters of DNA to make correct pairs while including two blank squares with the letter to clear it. There are three different colors (red, blue, yellow) and five different letters (a, c, g, t, u).

Right now the game will clear matches of four based on color regardless of letter. What I have been banging my head against the keyboard trying to figure out is how to change the code so the ONLY way matches are cleared is if they include two letters that are a part of a valid pair. Unlike Bejeweled, squares can be swapped with the square next to it regardless of if it makes a valid match.

For example, if a user moves four blank squares of the same color next to each other, they will not clear. However, if they move two (or more) blank red squares next to a red A square and a red T square, that will be a valid match and will clear. If they get a red A, red T, and red U, they will clear but be an invalid "pair" and give the user a strike. If they get 5 blank reds and a red T, they will not clear, but if they manage to get a red A somewhere in the line of 5 blanks and T, the whole set will clear.

I hope that makes it clear what we are trying to do. I have made some changes trying to get it to work but it either ends up clearing ALL blank squares regardless of them being grouped next to each other or I made it so only blank squares clear and if a letter is part of the four, it will not clear. Running out of time to get this Alpha build finished with working gameplay, so any help you can provide would be awesome!

I had the suggestion of looping through the "match" variable and see if it fits requirements for a score, but I am not sure that would work. There are a handful of different correct ways to score (BlueA, BlueT, 2 blue blanks) (YellowA, YellowT, 2 yellow blanks) (RedA, RedT, 2 red blanks) are just three of the different possible valid "matches". Any ideas on how to do this?

The full code as it stands right now (the vertical cursor switching is not working so don't press "F" or you will get an error...another problem I am trying to fix).

import pygame, random, time, sys
from pygame.locals import *
import itertools
import os

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

SHAPE_WIDTH = 64                # Width of each shape (pixels).
SHAPE_HEIGHT = 64               # Height of each shape (pixels).
PUZZLE_COLUMNS = 10              # Number of columns on the board.
PUZZLE_ROWS = 11                # Number of rows on the board.
MARGIN = 118                     # Margin around the board (pixels).
WINDOW_WIDTH = PUZZLE_COLUMNS * SHAPE_WIDTH + 2 * MARGIN + 485
WINDOW_HEIGHT = PUZZLE_ROWS * SHAPE_HEIGHT + 2 * MARGIN - 150
FONT_SIZE = 60
TEXT_OFFSET = MARGIN + 950

# Map from number of matches to points scored.
MINIMUM_MATCH = 4
EXTRA_LENGTH_POINTS = .1
RANDOM_POINTS = .3
DELAY_PENALTY_SECONDS = 1
DELAY_PENALTY_POINTS = 0

FPS = 30
EXPLOSION_SPEED = 15            # In frames per second.
SPIN_SPEED = 15
REFILL_SPEED = 10               # In cells per second.

VERTICAL = False

class Cell(object):
    """
    A cell on the board, with properties:
    `image` -- a `Surface` object containing the sprite to draw here.
    `offset` -- vertical offset in pixels for drawing this cell.
    """
    def __init__(self, image):
        self.offset = 0.0
        self.image = image

    def tick(self, dt):
        self.offset = max(0.0, self.offset - dt * REFILL_SPEED)

class Board(object):
    """
    A rectangular board of cells, with properties:
    `w` -- width in cells.
    `h` -- height in cells.
    `size` -- total number of cells.
    `board` -- list of cells.
    `matches` -- list of matches, each being a list of exploding cells.
    `refill` -- list of cells that are moving up to refill the board.
    `score` -- score due to chain reactions.
    """
    def __init__(self, width, height):
        self.explosion = [pygame.image.load('images/explosion{}.png'.format(i))
                          for i in range(1, 7)]
        self.spin = [pygame.image.load('images/powerframe{}.png'.format(i))
                      for i in range (1, 12)]
        self.image_color = {}
        self.shapes = []
        self.rareshapes = []

        colors = 'red blue yellow'
        letters = 'acgtu'

        for c in colors.split():
            im = pygame.image.load('images/{}.png'.format(c))
            self.shapes.append(im)
            self.image_color[im] = c
            for l in letters:
                im = pygame.image.load('rareimages/{}{}.png'.format(c, l))
                self.rareshapes.append(im)
                self.image_color[im] = l

        self.background = pygame.image.load("images/bg.png")
        self.blank = pygame.image.load("images/blank.png")
        self.x = pygame.image.load("images/x.png")
        self.w = width
        self.h = height
        self.size = width * height
        self.board = [Cell(self.blank) for _ in range(self.size)]
        self.matches = []
        self.refill = []
        self.score = 0.0
        self.spin_time = 15


    def randomize(self):
        """
        Replace the entire board with fresh shapes.
        """
        rareshapecount = 0
        for i in range(self.size):
            if rareshapecount <= 8:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 9:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <=22:
                self.board[i] = Cell(random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 23:
                self.board[i] = Cell(random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 26:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 27:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 40:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 41:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 45:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 46:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 57:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 59:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 70:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount == 71:
                self.board[i] = Cell (random.choice(self.rareshapes))
                rareshapecount = rareshapecount + 1

            elif rareshapecount <= 200:
                self.board[i] = Cell (random.choice(self.shapes))
                rareshapecount = rareshapecount + 1



    def pos(self, i, j):
        """
        Return the index of the cell at position (i, j).
        """
        assert(0 <= i < self.w)
        assert(0 <= j < self.h)
        return j * self.w + i

    def busy(self):
        """
        Return `True` if the board is busy animating an explosion or a
        refill and so no further swaps should be permitted.
        """
        return self.refill or self.matches

    def tick(self, dt):
        """
        Advance the board by `dt` seconds: move rising blocks (if
        any); otherwise animate explosions for the matches (if any);
        otherwise check for matches.
        """
        if self.refill:
            for c in self.refill:
                c.tick(dt)
            self.refill = [c for c in self.refill if c.offset > 0]
            if self.refill:
                return
        elif self.matches:
            self.explosion_time += dt
            f = int(self.explosion_time * EXPLOSION_SPEED)
            if f < len(self.explosion):
                self.update_matches(self.explosion[f])
                return
            self.update_matches(self.blank)
            self.refill = list(self.refill_columns())
        self.explosion_time = 0
        self.matches = self.find_matches()

    def draw(self, display):
        """
        Draw the board on the pygame surface `display`.
        """
        display.blit(self.background, (0, 0))
        for i, c in enumerate(self.board):
            display.blit(c.image,
                         (MARGIN + SHAPE_WIDTH * (i % self.w),
                          MARGIN + SHAPE_HEIGHT * (i // self.w - c.offset) - 68))
        display.blit(self.x, (995, 735))
        display.blit(self.x, (1112, 735))
        display.blit(self.x, (1228, 735))

    def swap(self, cursor):
        """
        Swap the two board cells covered by `cursor` and update the
        matches.
        """
        i = self.pos(*cursor)
        b = self.board
        b[i], b[i+1] = b[i+1], b[i]
        self.matches = self.find_matches()


    def find_matches(self):
        """
        Search for matches (lines of cells with identical images) and
        return a list of them, each match being represented as a list
        of board positions.
        """
        def lines():
            for j in range(self.h):
                yield range(j * self.w, (j + 1) * self.w)
            for i in range(self.w):
                yield range(i, self.size, self.w)
        def key(i):
            return self.image_color.get(self.board[i].image)
        def matches():
            for line in lines():
                for _, group in itertools.groupby(line, key):
                    match = list(group)
                    if len(match) >= MINIMUM_MATCH:
                        yield match
                        self.score = self.score + 1
        return list(matches())

    def update_matches(self, image):
        """
        Replace all the cells in any of the matches with `image`.
        """
        for match in self.matches:
            for position in match:
                self.board[position].image = image

    def refill_columns(self):
        """
        Move cells downwards in columns to fill blank cells, and
        create new cells as necessary so that each column is full. Set
        appropriate offsets for the cells to animate into place.
        """
        for i in range(self.w):
            target = self.size - i - 1
            for pos in range(target, -1, -self.w):
                if self.board[pos].image != self.blank:
                    c = self.board[target]
                    c.image = self.board[pos].image
                    c.offset = (target - pos) // self.w
                    target -= self.w
                    yield c
            offset = 1 + (target - pos) // self.w
            for pos in range(target, -1, -self.w):
                c = self.board[pos]
                c.image = random.choice(self.shapes)
                c.offset = offset
                yield c

class Game(object):
    """
    The state of the game, with properties:
    `clock` -- the pygame clock.
    `display` -- the window to draw into.
    `font` -- a font for drawing the score.
    `board` -- the board of cells.
    `cursor` -- the current position of the (left half of) the cursor.
    `score` -- the player's score.
    `last_swap_ticks` -- 
    `swap_time` -- time since last swap (in seconds).
    """
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("Nucleotide")
        self.clock = pygame.time.Clock()
        self.display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT),
                                               DOUBLEBUF)
        self.board = Board(PUZZLE_COLUMNS, PUZZLE_ROWS)
        self.font = pygame.font.Font(None, FONT_SIZE)

    def start(self):
        """
        Start a new game with a random board.
        """
        self.board.randomize()
        self.cursor = [0, 0]
        self.cursor2 = [0, 0]
        self.score = 0.0
        self.swap_time = 10

    def quit(self):
        """
        Quit the game and exit the program.
        """
        pygame.quit()
        sys.exit()

    def play(self):
        """
        Play a game: repeatedly tick, draw and respond to input until
        the QUIT event is received.
        """
        self.start()
        while True:
            self.draw()
            dt = min(self.clock.tick(FPS) / 1000.0, 1.0 / FPS)
            self.swap_time -= dt
            for event in pygame.event.get():
                if event.type == KEYUP:
                    self.input(event.key)
                elif event.type == QUIT:
                    self.quit()
                elif self.swap_time == 0.0:
                    self.quit()
            self.board.tick(dt)

    def input(self, key):
        """
        Respond to the player pressing `key`.
        """
        if key == K_q:
            self.quit()
        elif key == K_f:
            if VERTICAL == False:
                VERTICAL = True
            elif VERTICAL == True:
                VERTICAL = False
        elif key == K_RIGHT and self.cursor[0] < self.board.w - 2:
            self.cursor[0] += 1
        elif key == K_LEFT and self.cursor[0] > 0:
            self.cursor[0] -= 1
        elif key == K_DOWN and self.cursor[1] < self.board.h - 1:
            self.cursor[1] += 1
        elif key == K_UP and self.cursor[1] > 0:
            self.cursor[1] -= 1
        elif key == K_SPACE and not self.board.busy():
            self.swap()

    def swap(self):
        """
        Swap the two cells under the cursor and update the player's score.
        """
        swap_penalties = int(self.swap_time / DELAY_PENALTY_SECONDS)
        self.swap_time = 1
        self.board.swap(self.cursor)

    def draw(self):
        self.board.draw(self.display)
        self.draw_score()
        self.draw_time()
        if VERTICAL == False:
            self.draw_cursor()
        elif VERTICAL == True:
            self.draw_cursor2()
        pygame.display.update()

    def draw_time(self):
        s = int(self.swap_time)
        text = self.font.render('{}:{:02}'.format(s / 60, s % 60),
                                True, BLACK)
        self.display.blit(text, (TEXT_OFFSET, WINDOW_HEIGHT - 170))

    def draw_score(self):
        total_score = self.score

    def draw_cursor(self):
        topLeft = (MARGIN + self.cursor[0] * SHAPE_WIDTH,
                MARGIN + self.cursor[1] * SHAPE_HEIGHT - 68)
        topRight = (topLeft[0] + SHAPE_WIDTH * 2, topLeft[1])
        bottomLeft = (topLeft[0], topLeft[1] + SHAPE_HEIGHT)
        bottomRight = (topRight[0], topRight[1] + SHAPE_HEIGHT)
        pygame.draw.lines(self.display, WHITE, True,
                [topLeft, topRight, bottomRight, bottomLeft], 3)

    def draw_cursor2(self):
          topLeft = (MARGIN + self.cursor[0] * SHAPE_WIDTH,
                   MARGIN + self.cursor[1] * SHAPE_HEIGHT - 68)
          topRight = (topLeft[0] + SHAPE_WIDTH, topLeft[1])
          bottomLeft = (topLeft[0], topLeft[1] + SHAPE_HEIGHT * 2)
          bottomRight = (topRight[0], topRight[1] + SHAPE_HEIGHT * 2)
          pygame.draw.lines(self.display, WHITE, True,
                      [topLeft, topRight, bottomRight, bottomLeft], 3)

if __name__ == '__main__':
    Game().play()

Edited 3 Years Ago by Badgeking

This article has been dead for over six months. Start a new discussion instead.