The Tetris Project

@vijolica9 I used the code @Glenn had posted as a starting point for the “F” shape in four rotation positions. I am not sure if this on the right track or if what I am doing will be a mess later… But I will be working on the other shapes anyway.

Here is @Glenn’s code that I modified:

# tetris.py mod from @Glenn code
# May 18, 2015
from graphics import *

### function that draws an F shape on the board
### x, y is board location, in pixels, of upper left corner of the shape
### x is distance in pixels from left margin
### y is distance in pixels from top margin
### position is whether it is up, down, right, or left
def draw_F_shape(x, y, position):
    BLOCKSIZE = 50
    if position == "down":
        for i in range(3):
            rect = Rectangle(Point(x + BLOCKSIZE * i, y), Point(x + BLOCKSIZE * i + BLOCKSIZE, y + BLOCKSIZE))
            rect.setFill("red")
            rect.draw(win)
        rect2 = Rectangle(Point(x, y + BLOCKSIZE), Point(x + BLOCKSIZE, y + BLOCKSIZE + BLOCKSIZE))
        rect2.setFill("red")
        rect2.draw(win)
    elif position == "left":
        for i in range(3):
            rect = Rectangle(Point(x, y + BLOCKSIZE * i), Point(x + BLOCKSIZE, y + BLOCKSIZE * i + BLOCKSIZE))
            rect.setFill("blue")
            rect.draw(win)
        rect2 = Rectangle(Point(x - BLOCKSIZE, y), Point(x, y + BLOCKSIZE))
        rect2.setFill("blue")
        rect2.draw(win)
    elif position == "up":
        for i in range(3):
            rect = Rectangle(Point(x + BLOCKSIZE * i, y), Point(x + BLOCKSIZE * i + BLOCKSIZE, y + BLOCKSIZE))
            rect.setFill("green")
            rect.draw(win)
        rect2 = Rectangle(Point(x + BLOCKSIZE * i, y - BLOCKSIZE), Point(x + BLOCKSIZE * i + BLOCKSIZE, y))
        rect2.setFill("green")
        rect2.draw(win)
    elif position == "right":
        for i in range(3):
            rect = Rectangle(Point(x, y + BLOCKSIZE * i), Point(x + BLOCKSIZE, y + BLOCKSIZE * i + BLOCKSIZE))
            rect.setFill("yellow")
            rect.draw(win)
        rect2 = Rectangle(Point(x + BLOCKSIZE, y + BLOCKSIZE * 2), Point(x + BLOCKSIZE * 2, y + BLOCKSIZE * 3))
        rect2.setFill("yellow")
        rect2.draw(win)
            
win = GraphWin('Rect', 800, 500)

draw_F_shape(50, 50, "down")
draw_F_shape(300, 50, "left")
draw_F_shape(400, 100, "up")
draw_F_shape(600, 50, "right")

win.mainloop()

The code we are currently discussing for drawing Tetris shapes is helpful for our learning Python and graphics, but if we are going to switch to an object-oriented approach later, which I have reason to believe will happen, we are going to need to make major revisions to any code we write now. So, we should keep @bkglass’s concern in mind, when he writes “… if what I am doing will be a mess later …”. Well - his code is actually well-written, and not at all a mess, but since we will likely be taking a different approach later on, there will, in fact, be lots of rewriting to do.

What we are doing now regarding Tetris is good practice, and, in my opinion, we should proceed in the direction that the MMOOC has set for us, but should do so with the expectation that whatever we do now will need to be changed later, as we learn new programming techniques. After all, we began by drawing shapes with the console, then advanced toward drawing shapes with graphics, and will advance again toward using classes to represent each type of shape.

We have already been given a taste of object oriented programming. The Rectangle type is defined as a class in the graphics.py file that we were given. In any case, the Tetris project , as it was originally designed in MIT 6.189, provided students with a template that offered lots of guidance for how to complete the project by using an object oriented approach, including detailed instructions for developing each class. So, it will work out well.

To gain a perspective on how Tetris might be implemented, we could look at how MIT 6.189 presented it, but since we have not been assigned the task of doing this yet, we do so without a guarantee that this will be the final form of our project.

Here are some links to the past project:

To put the above material in proper perspective, though, we need a good grounding in object oriented programming. We may be getting a little ahead of the MMOOC sequence, though.

1 Like

@Glenn I agree… But for me, I need to resist looking too far ahead. I need to work through the ‘frustration’ of figuring things out in order to really learn this stuff.

When I was working through Codecademy, there was one person in the forums I found the most helpful. He would never flat out give me the solution for why my code was not working… But instead, he would say things like, “Take a look at how line 14 is actually being evaluated.”

This approach, as frustrating as it was sometimes, really forced me to learn what I was doing. Like the old proverb says - give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime. So no ‘peeking’ for me. :see_no_evil:

However, I do have a question about how this code from the email works:

shapes = [(Point(Point(center.x - 1, center.y),
           Point(center.x, center.y),
           Point(center.x + 1, center.y),
           Point(center.x + 1, center.y + 1)),

I did a search in the graphics.py for ‘center’.

Search "center" (10 hits in 1 file)
  C:\Users\Bernard\Desktop\pymooc\graphics.py (10 hits)
	Line 21: 10 centered in a 100x100 window:
	Line 318:           "justify":"center",
	Line 457:     def getCenter(self):
	Line 498:     def __init__(self, center, radius):
	Line 499:         p1 = Point(center.x-radius, center.y-radius)
	Line 499:         p1 = Point(center.x-radius, center.y-radius)
	Line 500:         p2 = Point(center.x+radius, center.y+radius)
	Line 500:         p2 = Point(center.x+radius, center.y+radius)
	Line 505:         other = Circle(self.getCenter(), self.radius)
	Line 853:     t = Text(Point(5,5), "Centered Text")

I narrowed down to this part:

Line 438:  class _BBox(GraphicsObject):
    		# Internal base class for objects represented by bounding box
    		# (opposite corners) Line segment is a degenerate case.

I understand how the ‘center’ point applies to a circle, or even a square… But how is this working for something like the “J” shape?

This code …

shapes = [(Point(Point(center.x - 1, center.y),
           Point(center.x, center.y),
           Point(center.x + 1, center.y),
           Point(center.x + 1, center.y + 1)),
           	           .
           	           .
           	           .
]

… appears to assume that we have set up a game board in which each Point represents a position on the board in x, y coordinates. In the case of Tetris, each position would be a square. The shapes variable represents a list of positions of squares that make up a single Tetris shape.

Here, the variable center represents a Point and has an x and y coordinate. This center is an arbitrarily chosen Point among the four that make up the shape. Note that the locations of all the other Points are defined relative to that one by adding or subtracting offsets. So, the second Point in the above list would be at coordinates center.x and center.y. The adjacent first, third, and fourth Points also form parts of the shape, which appears to be a J.

This code seems to be converging in the direction of the code for the MIT version of Tetris.

@Glenn I had not looked at the tetris_template.py until now… Apparently that code can came from a child Class of the parent base - class Shape()… Which seems to be getting the block size, outline, and color from the class Block(Rectangle)… Which seems to be a child Class of the class Rectangle() from the graphics.py

It looks like I’m going to need to chew on this template for awhile… a long while.

Yes, that code is from the J_shape class, which is a child of the Shape class. The J_shape and its siblings primarily describe the Tetris shapes by specifying the relative coordinates of their component Blocks, but they inherit lots of functionality (or should we say methodology :wink: ) from Shape, for example, get_blocks, draw, move, and other methods that implement behavior.

Check out the __init__ method of the J_shape class. It calls the __init__ method of its parent Shape class, passing it the coords of its component Blocks and the string, 'orange'. In the __init__ method of Shape, a loop at the end initializes a list of Blocks, each of the specified color, which is 'orange' in the case of the J_shape, and each with its specified coordinates.

I have found that it helps to have a printed copy of the template file, as a reference.

Note how the Instruction handout, 6.189 Final Project – Tetris!, specifies, in detail, the order in which we should add functionality to the various components, so that right from the start, and at every step along the way, the program is functional, until finally we having a working Tetris game.

1 Like

In 1. Tetris Class Overview, within the instructions, we are asked to examine the tetris_template.py file, presumably in IDLE, run it, and make sure an empty board appears on the screen. The board is a gray rectangle, and might get rendered behind the console window, so if you don’t see it, drag the console window to another part of the screen.

Then, in 2. Creating a random shape, we need to add some code to the file to create and draw a shape.

Notice that the class Tetris() code contains a list of shapes:

SHAPES = [I_shape, J_shape, L_shape, O_shape, S_shape, T_shape, Z_shape]

In the create_new_shape method of the Tetris class, we need to replace pass with code that randomly selects a shape from that list, and returns it:

#YOUR CODE HERE
shape_index = random.randint(0, len(self.SHAPES) - 1)
return self.SHAPES[shape_index](Point(int(self.BOARD_WIDTH/2), 0))

Note that the __init()__ method of the Tetris class already contains the line:

self.current_shape = self.create_new_shape()

So, the returned instance has been assigned to self.current_shape. Now, we need to draw that shape on the board. The Board class contains a draw_shape method that we can call:

# draw_shape method in the Board class)
####  YOUR CODE HERE ####
self.board.draw_shape(self.current_shape)

Then, when we run the program, we should see a shape at the top of the board:

If anyone has trouble getting this to happen, we can discuss and debug here.

I’m doing a bit of catch up, I’m still on drawing the picture that @vijolica9 has. However, I just wanted to share that I’ve found the programming to go much smoother if I first drew a picture of the thing I wanted to draw with each point marked out in terms of the constants and variables represented in the function. Because of that, it was easier for me to translate what I wanted into code.

Just did the thing I talked about in the previous post.

It was really rather repetitive, 108 lines worth of code. Kinda bloated. It will probably be much easier with object oriented programming.

And the code:

from graphics import *

win = GraphWin('Rect', 1300, 500)
BLOCKSIZE = 40

def draw_i_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(4):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('blue')
           rect.setWidth(3)
    
draw_i_shape(20, 20, 'horizontal')


def draw_o_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('red')
           rect.setWidth(3)
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y + BLOCKSIZE), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE*2))
           rect.draw(win)
           rect.setFill('red')
           rect.setWidth(3)

draw_o_shape(20 + 13*BLOCKSIZE, 20, 'horizontal')


def draw_t_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(3):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('yellow')
           rect.setWidth(3)
        rect = Rectangle(Point(x + BLOCKSIZE, y + BLOCKSIZE), Point(x + BLOCKSIZE*(2), y + BLOCKSIZE*2))
        rect.draw(win)
        rect.setFill('yellow')
        rect.setWidth(3)

draw_t_shape(20 + 21*BLOCKSIZE, 20, 'horizontal')

def draw_j_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(3):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('orange')
           rect.setWidth(3)
        rect = Rectangle(Point(x + BLOCKSIZE*2, y + BLOCKSIZE), Point(x + BLOCKSIZE*(3), y + BLOCKSIZE*2))
        rect.draw(win)
        rect.setFill('orange')
        rect.setWidth(3)


draw_j_shape(20 + 4* BLOCKSIZE, 20, 'horizontal')

def draw_l_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(3):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('cyan')
           rect.setWidth(3)
        rect = Rectangle(Point(x, y + BLOCKSIZE), Point(x + BLOCKSIZE, y + BLOCKSIZE*2))
        rect.draw(win)
        rect.setFill('cyan')
        rect.setWidth(3)

draw_l_shape(20 + 9*BLOCKSIZE, 20, 'horizontal')
def draw_s_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('lawn green')
           rect.setWidth(3)
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*(i - 1), y + BLOCKSIZE), Point(x + BLOCKSIZE*i, y + BLOCKSIZE*2))
           rect.draw(win)
           rect.setFill('lawn green')
           rect.setWidth(3)

draw_s_shape(20 + 18*BLOCKSIZE, 20, 'horizontal')


def draw_z_shape(x, y, rotation):
    if rotation == 'horizontal':
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*i, y), Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE))
           rect.draw(win)
           rect.setFill('deep pink')
           rect.setWidth(3)
        for i in range(2):
           rect = Rectangle(Point(x + BLOCKSIZE*(i + 1), y + BLOCKSIZE), Point(x + BLOCKSIZE*(i + 2), y + BLOCKSIZE*2))
           rect.draw(win)
           rect.setFill('deep pink')
           rect.setWidth(3)

draw_z_shape(20 + 25*BLOCKSIZE, 20, 'horizontal')

win.mainloop()

Here is a picture of some of the tools I used to help program:

2 Likes

Is anyone working on Section 1 & 2 of 6.189 Final Project – Tetris! handout?

This involves adding Python code to the tetris_template.py file, which, by the time we have completed Section 11. Game Over, will provide us with a working Tetris game.

But, first, it would be a good idea to complete the Codecademy Introduction to Classes and Classes exercises. The Tetris file is completely object oriented, and the Codecademy exercises provide concepts the pervade that implementation of the project.

For this phase of the project, I don’t think the best strategy would be to paste in the same code that we have previously written for Tetris. It would be much more effective to arrive with the concepts we have learned from our previous work, and then to implement them with the structure and guidance provided by the handout instructions and the template.

Hi again!

I somehow cannot find the original code for tetris.py from Week One (I think at least). What is the URL address to find it? Thank you in advance,

Rose K.

I don’t think there was any downloaded code for Week One of the Tetris project. There is the template that Glenn mentioned in his post, but I don’t think that’s what you meant.

Hi, @Rose_Ketring,

As @Cedar_Mora mentioned, there was no download regarding the Tetris project in Week One. During the early weeks, the MMOOC essentially asked us to try printing some Tetris shapes composed of text characters. For example, to make a J shape, we could do this …

J_shape = "_|"
print J_shape

Various participants in the MMOOC did some web searching, and came up with means of printing characters in different colors.

Ultimately, though, as the organizers of the MMOOC undoubtedly expected would happen, it became obvious that we needed a means of drawing actual shapes on the screen. That’s when they gave us a graphics.py file to download. We could then write a Python program that began with the statement …

from graphics import *

… and save it in the same folder as the graphics.py file. That enabled us to use some of the objects defined in the graphics.py in our programs to draw and color shapes. Many of the posts in this thread are focused on how to do that.

However, in the Tetris game, we need to be able to have new randomly chosen shapes appear at the top of the game board, have them gradually drop to the bottom of the board, and enable the player of the game to move and rotate the shapes. Shapes are not allowed to overlap or move off the board. There’s a lot of programming required to make all of this happen. So, we were given a second code download, tetris_template.py, which is a skeleton of a program that we need to fill in with some functionality, along with a handout: 6.189 Final Project – Tetris!. We need to save tetris_template.py in the same folder as graphics.py, and follow the instructions in the handout to complete the game. So, that’s where we are now.

The tetris_template.py file makes extensive use of object oriented programming techniques. So, before attempting to do anything with that file, we should complete the following two Codecademy units:

In fact, object oriented programming is important enough to merit some attention. So, let’s start a new discussion on that topic.

1 Like

Well, I finished the basics of the Tetris Project. I finished it a couple of days ago because my college is on hold for now due to a technicality and I was very bored, so I thought I should do something productive besides web surf my whole life. The whole code is too long for me to include here, so I’ll just include my implementations of the functions. You have the template, of course. This isn’t complete code, so it may not be quite complete in itself. When included with the template, it works. If you would like whole version, I can include it.

class Block(Rectangle):
    def can_move(self, board, dx, dy):
        if board.can_move(self.x + dx, self.y + dy):
            return True
        else:
            return False

    def move(self, dx, dy):
        self.x += dx
        self.y += dy

        Rectangle.move(self, dx*Block.BLOCK_SIZE, dy*Block.BLOCK_SIZE)

class Shape():
    def get_blocks(self):
        return self.blocks

    def draw(self, win):
        for block in self.blocks:
            block.draw(win)

    def move(self, dx, dy):
        for block in self.blocks:
            block.move(dx, dy)

    def can_move(self, board, dx, dy):
        for block in self.blocks:
            if not block.can_move(board, dx, dy):
                return False
        return True

    def can_rotate(self, board):
        dir_ = self.get_rotation_dir()
        center = self.center_block
        for block in self.blocks:
            if not board.can_move(center.x - dir_*center.y + dir_*block.y,
                                  center.y + dir_*center.x - dir_*block.x):
                return False
        return True

    def rotate(self, board):
        center = self.center_block
        dir_ = self.get_rotation_dir()
        if self.can_rotate(board):
            for block in self.blocks:
                block.move(center.x - dir_*center.y + dir_*block.y - block.x, center.y + dir_*center.x - dir_*block.x - block.y)

        if self.shift_rotation_dir:
            self.rotation_dir *= -1

class Board():
    def draw_shape(self, shape):
        if shape.can_move(self, 0, 0):
            shape.draw(self.canvas)
            return True
        return False

    def can_move(self, x, y):
        if (0 <= x < self.width) and (0 <= y < self.height) and (x, y) not in self.grid:
            return True
        else:
            return False

    def add_shape(self, shape):
        blocks = shape.get_blocks()
        for block in blocks:
            self.grid[(block.x, block.y)] = block

    def delete_row(self, y):
        for i in range(0, self.width):
            self.grid[i, y].undraw()
            del(self.grid[i, y])

    def is_row_complete(self, y):
        for i in range(0, self.width):
            if (i, y) not in self.grid:
                return False
        return True

    def move_down_rows(self, y_start):
        for j in range(y_start, 0, -1):
            for i in range(0, self.width):
                if (i, j) in self.grid:
                    self.grid[i,j].move(0, 1)
                    self.grid[i, j + 1] = self.grid[i,j]
                    del(self.grid[i,j])

    def remove_complete_rows(self):
        for y in range(0, self.height):
            if self.is_row_complete(y):
                self.delete_row(y)
                self.move_down_rows(y - 1)

    def game_over(self):
        self.youjustlostthegame = Text(Point(self.width * Block.BLOCK_SIZE / 2, self.height * Block.BLOCK_SIZE / 8), "You just lost the game.")
        self.youjustlostthegame.setFill("blue")
        self.youjustlostthegame.draw(self.canvas)

class Tetris():
    def create_new_shape(self):
        new_shape = Tetris.SHAPES[random.randint(0,6)](Point(int(self.BOARD_WIDTH/2), 0))
        return new_shape

    def animate_shape(self):
        self.do_move('Down')
        self.win.after(self.delay, self.animate_shape)

    def do_move(self, direction):
        dx = self.DIRECTION[direction][0]
        dy = self.DIRECTION[direction][1]
        if self.current_shape.can_move(self.board, dx, dy):
            self.current_shape.move(dx, dy)
            return True
        elif direction == 'Down':
            self.board.add_shape(self.current_shape)
            self.board.remove_complete_rows()
            self.current_shape = self.create_new_shape()
            if not self.board.draw_shape(self.current_shape):
                self.board.game_over()
            return False
        else:
            return False

    def do_rotate(self):
        if self.current_shape.can_rotate(self.board):
            self.current_shape.rotate(self.board)

    def key_pressed(self, event):
        key = event.keysym
        if key == 'space':
            while self.do_move('Down'):
                pass
        elif key == 'Up':
            self.do_rotate()
        else:
            self.do_move(key)
        print key

win = Window("Tetris")
game = Tetris(win)
win.mainloop()

Some fun details about this have been:
  • It took me forever to implement Block.can_move() because I had forgotten that you can get position by adding the derivative. The arguments they gave seemed odd to me. It seems like x and y instead dx and dy would have been easier to work with, but I wasn't sure of that.
  • I still don't understand however the Shape.shift_rotation_direction works or what it is in there for.
  • Shape.can_move() was fun too. It took me a bit to realize that I had to check if a block couldn't move, not if it could.
  • I don't really understand why Shape.get_rotation_dir() was necessary. We could have just accessed the attribute directly. Maybe it's something to do with public/private, I don't know.
  • Shape.rotate() really messed with me, since I had to provide the derivative of the block for Block.move, while the formula they gave us gave only the position.
  • It was hard to figure out exactly what the bounds of Board.can_move() should be. Finally figured it out mostly by testing and debugging more than hard thought, but it makes sense now.
  • It was really weird thinking through the whole thing how what is actually shown and what is under the hood are completely separate things. It felt redundant at times, but it makes sense.
  • Board.remove_complete_rows() really messed with me. On my first implementation, if you cleared multiple rows at once, it only cleared the bottom row. After the next shape joined the grid, the next leftover complete row would be cleared. They should all have cleared at once. After a laborious hour or so I figured out that my algorithm checked if rows were complete from bottom to top. That way, after it cleared a row, the next complete row became the current row, but unfortunately, it checked the next row, since it thought the current row was already cleared. In other words, instead of range(0, self.height), I had range(self.height, 0, -1). As a separate problem, I didn't have the -1 originally, which meant it checked zero rows, but that got fixed.
  • I wanted to indicate the location of the Board.game_over() by the coordinate system instead of pixel location, since it would have been simpler to write, but I don't think it works that way.
  • Tetris.create_new_shape() was a very dense one liner that took me some time to figure out how to access a class via a dictionary. I didn't know that was possible before.
  • When I went to write this out, I discovered that I had implemented the rotate method in the wrong place. Even though it worked, I had to go back to refactor that.
  • blablabblablab Thanks for reading this far maybe.

Have fun with your version. Any advice is welcome bla bla bla bla

1 Like

Great work, @Cedar_Mora!

It’s lucky that your college offers some “bored” time, :wink: so you can get some real work done.

Some of my code is similar to yours; some of it is different.

I also had to put a lot of time into Board.remove_complete_rows(self):, and did lots of testing on move_down_rows(self, y_start):. This was to make sure that unusual cases got handled correctly. For example, as illustrated in the attached images, two rows that are not adjacent to each other may need to get removed at the same time. Note that when the falling upside-down “L” on the right side of the first image fell into place, the 5th and 7th rows from the bottom both became complete, but the sixth did not, because it still had a missing block in the 3rd column from the right.

The code passed the test, though.

            

Scoring has also been implemented.

Below is code for some of the methods.

    def move_down_rows(self, y_start):
        for y in range(y_start, -1, -1):
            for x in range(self.width):
                if (x, y) in self.grid:
                    block = self.grid[x, y]
                    block.undraw()
                    del self.grid[x, y]
                    block.move(0, 1)
                    self.grid[block.x, block.y] = block
                    block.draw(self.canvas)
    def remove_complete_rows(self):
        num_removed = 0
        for y in range(self.height):
            if self.is_row_complete(y):
                self.delete_row(y)
                self.move_down_rows(y - 1)
                num_removed += 1
        return num_removed

Following is code for a ScoreBoard class.

class ScoreBoard():
    def __init__(self, win, width, height, score):
        self.width = width
        self.height = height
        self.score = score
        self.canvas = CanvasFrame(win, self.width, self.height)
        self.canvas.setBackground('white')
        self.score_message = Text(Point(150, 25), "Score: " + str(self.score))
        self.score_message.setSize(20)
        self.score_message.setTextColor("black")
        self.score_message.draw(self.canvas)
    def set_score(self, score):
        self.score_message.setText("<p>ause / score: " + str(score))
    def set_final_score(self, score):
        self.score_message.setText("final score: " + str(score))
    def pause_score(self, score):
        self.score_message.setText("un<p>ause / score: " + str(score))

So as not to be a “spoiler”, I have refrained from posting all my code at this time, but will post it in the coming weeks, if anyone would like to see it.

If anyone else has code, whether complete, incomplete, or in need of debugging, it would be great to see it.

Thank you, @Glenn for being here and participating. This course has become such a ghost town. I guess that’s the way of the MOOC though.

I almost wish that this Gentle Introduction to Python wasn’t so gentle. They didn’t really teach how to make stuff without a template and instructions.

I have my college coming up so I will learn some of this stuff, but are some more intermediate games programming courses online? I’m looking to become a professional games developer. Or maybe an iOS developer. Or maybe just whatever will get me work haha. Anyways, any resources that come to mind?

I don’t actually know a lot about game development, but a good way to get a sense of the “landscape” would be to browse through some of the subreddits that relate to the topic. A few examples are listed below:

You can then click links and use Google to follow leads that look promising.

Preparing to become a professional games developer will give you an opportunity to get into lots of interesting topics, such as graphics, analysis of algorithms, data structures, artificial intelligence, GUI design, and game theory.

1 Like

ScoreBoard class implemented, with levels and score displayed. As levels go on, pieces drop faster, but clearing rows gets more points. I was having trouble figuring out what system to use for scoring and levels, so I based it loosely upon the original Nintendo scoring system. I will post code and details later, since now I need to go to bed.


1 Like

For scoring, I made up a simple system.

For each shape that appears on the board, one point is awarded. The rationale is that the player can make room for new shapes by moving the current shape on the board.

Then, whenever rows are removed after a shape can no longer move, the player receives a number of points equal to the square of the number of rows that get removed at that time.

Most of the scoring is done in the do_move and an increment_score method of the Tetris class.

    def do_move(self, direction):
        dx, dy = self.DIRECTION[direction]
        if self.current_shape.can_move(self.board, dx, dy):
            self.current_shape.move(dx, dy)
            return True
        else:
            if direction == "Down":
                self.board.add_shape(self.current_shape)
                removed = self.board.remove_complete_rows()
                self.increment_score(removed * removed)
                self.current_shape = self.create_new_shape()
                if not self.board.draw_shape(self.current_shape):
                    # self.board.game_over()
                    self.scoreBoard.set_final_score(self.score)
                else:
                    self.increment_score(1)
            return False
    def increment_score(self, inc):
        self.score += inc
        self.scoreBoard.set_score(self.score)
        return self.score
1 Like

Smart move. I’ve done it different: check in loop and one move as separate method - do_shift().
I don’t like if/else statements, so i use dictionary:

    do = {"Left": self.do_move, "Right": self.do_move,
          "Up": self.do_rotate, "Down": self.do_move,
          "space": self.do_shift}
    if key in do.keys():
        do[key](key)
2 Likes