joriszwart.nl

I code dreams™

Pytris

Tetris A language a year keeps the doc away Python

Introduction

Tetris written using Python in the browser.

Play it! Source code

Swiftly researching options for Python in the browser, resulted in using Brython.

PyScript was another option, but I favored Brython for the lower download size.

Controls

Use cursor keys SPACE or I J K L M SPACE to control the game.

Todo

Source code

Main source

from random import randrange

from blocks import blocks
from bucket import Bucket
from render import draw_bucket

from browser import document, bind, window, timer

class Tetris:

    def __init__(self):
        canvas = document["tetris"]
        self.ctx = canvas.getContext("2d")

        self.bucket = Bucket()

        # new game
        self.lines = 0
        self.score = 0
        self.timer = 0

        # new block
        self.new_block()

    def new_block(self):
        self.block = randrange(len(blocks))     # TODO seems not very random, lots of same blocks
        self.rotation = 0
        self.row = 0
        self.column = 3

        if not self.bucket.test_tetromino(self.row, self.column, self.block, self.rotation):
            window.alert('Game over!')
            timer.clear_interval(self.timer)
            return

        self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

        timer.clear_interval(self.timer)
        self.timer = timer.set_interval(self.move_down, 650 - self.lines)

    def move_down(self):
        self.bucket.clear_tetromino(self.row, self.column, self.block, self.rotation)

        if self.bucket.test_tetromino(self.row + 1, self.column, self.block, self.rotation):
            self.score += 1
            self.row += 1
            self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

        else:
            # land block
            self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)
            lines_cleared = self.bucket.clear_lines()

            self.score += [0, 150, 300, 700, 1500][lines_cleared]
            self.lines += lines_cleared

            # new block
            self.new_block()

    def move_left(self):
        self.bucket.clear_tetromino(self.row, self.column, self.block, self.rotation)

        if self.bucket.test_tetromino(self.row, self.column - 1, self.block, self.rotation):
            self.column -= 1

        self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

    def move_right(self):
        self.bucket.clear_tetromino(self.row, self.column, self.block, self.rotation)

        if self.bucket.test_tetromino(self.row, self.column + 1, self.block, self.rotation):
            self.column += 1

        self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

    def rotate(self):
        self.bucket.clear_tetromino(self.row, self.column, self.block, self.rotation)

        if self.bucket.test_tetromino(
            self.row, self.column, self.block, (self.rotation + 1) % 4
        ):
            self.rotation = (self.rotation + 1) % 4

        self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

    def drop(self):
        self.bucket.clear_tetromino(self.row, self.column, self.block, self.rotation)

        while self.bucket.test_tetromino(self.row + 1, self.column, self.block, self.rotation):
            self.row += 1
            self.score += 5

        self.bucket.draw_tetromino(self.row, self.column, self.block, self.rotation)

    def draw_score(self):
        self.ctx.fillStyle = "white"
        self.ctx.fillText(f"Score: {self.score}", 220, 20)
        self.ctx.fillText(f"Lines: {self.lines}", 220, 40)

    def loop(self, *args):
        self.ctx.clearRect(0, 0, self.ctx.canvas.width, self.ctx.canvas.height)
        self.draw_score()
        draw_bucket(self.ctx, self.bucket.bucket)

        # TODO optimize timer.set_timeout(self.loop, self.speed)
        window.requestAnimationFrame(self.loop)

@bind(document, "keydown")
def keyDownHandler(e):

    if e.metaKey or e.altKey or e.shiftKey or e.ctrlKey:
        return

    match e.code:
        case "ArrowLeft" | "KeyJ":
            e.preventDefault()
            tetris.move_left()
 
        case "ArrowRight" | "KeyL":
            e.preventDefault()
            tetris.move_right()
 
        case "ArrowDown" | "KeyM":
            e.preventDefault()
            tetris.move_down()
 
        case "ArrowUp" | "KeyK":
            e.preventDefault()
            tetris.rotate()

        case "Space":
            e.preventDefault()
            tetris.drop()

tetris = Tetris()
tetris.loop()