import pygame import pygameControls as PC import globals globals.init() from globals import GRID_WIDTH, GRID_HEIGHT, TILE_SIZE, BRICKS import enums from bricks import Brick import random from dropzone import DropZone from dropnext import DropNext from hud import Hud __version__ = "0.2.0" # Constants HAT_REPEAT_DELAY = 0 # milliseconds before first repeat HAT_REPEAT_INTERVAL = 200 # milliseconds between repeats RUMBLE_TIMEOUT = 200 class Tetris: def __init__(self): self.setup() self.running = True while self.running: self.main_loop() if self.joysticks: self.joysticks[self.joy.get_instance_id()].controllers[0].close() pygame.quit() def setup(self): pygame.init() pygame.key.set_repeat(200) self.joystick_count = pygame.joystick.get_count() self.joysticks = {} self.screen_width, self.screen_height = (20 * TILE_SIZE), (20 * TILE_SIZE) self.screen = pygame.display.set_mode((self.screen_width, self.screen_height)) pygame.display.set_caption("Tetris " + __version__) self.hud = Hud(self.screen_width, self.screen_height) self.dropzone = DropZone(GRID_WIDTH, GRID_HEIGHT) self.dropnext = DropNext(width = TILE_SIZE * 4, height = TILE_SIZE * 4) self.current = Brick(brick = random.choice(list(BRICKS.keys())), state = enums.BrickState.Current) self.next = Brick(brick = random.choice(list(BRICKS.keys()))) self.clock = pygame.time.Clock() self.rumble_timer = pygame.time.get_ticks() self.fall_speed = 1000 # in milliseconds self.fall_timer = pygame.USEREVENT + 1 pygame.time.set_timer(self.fall_timer, self.get_fall_speed(self.hud.level)) def get_fall_speed(self, level): return max(100, 1000 - (level - 1) * 100) def main_loop(self): if self.joysticks: if pygame.time.get_ticks() - self.rumble_timer > RUMBLE_TIMEOUT: self.joysticks[self.joy.get_instance_id()].controllers[0].stop_rumble() self.screen.fill(enums.BrickColor.Cyan.value) self.hud.draw(self.screen) self.dropzone.draw(self.screen) self.dropzone.draw_brick(self.current.brick, self.current.location) self.dropnext.draw(self.screen) self.dropnext.draw_block(self.next.brick) self.handle_input() pygame.display.flip() self.clock.tick(60) def handle_hat_repeat(self): now = pygame.time.get_ticks() if self.hat_direction != (0, 0): if self.hat_first_press: if now - self.hat_timer >= HAT_REPEAT_DELAY: self.hat_timer = now self.hat_first_press = False self.post_hat_repeat_event() else: if now - self.hat_timer >= HAT_REPEAT_INTERVAL: self.hat_timer = now self.post_hat_repeat_event() def post_hat_repeat_event(self): pygame.event.post(pygame.event.Event(pygame.USEREVENT, { "type_name": "JOYHATREPEAT", "value": self.hat_direction })) def handle_input(self): for event in pygame.event.get(): match event.type: case pygame.QUIT: self.running = False case pygame.KEYDOWN: new_pos = pos[:] match event.key: case pygame.K_RIGHT: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Right self.current.move_right() self.dropzone.draw_brick(self.current.brick, self.current.location) case pygame.K_LEFT: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Left self.current.move_left() self.dropzone.draw_brick(self.current.brick, self.current.location) case pygame.K_SPACE: if self.current.direction == enums.BrickDirection.Dropped: break self.current.rotate() self.dropzone.draw_brick(self.current.brick, self.current.location) case pygame.K_RETURN: if self.current.direction == enums.BrickDirection.Dropped: break self.current.drop() case pygame.JOYHATMOTION: self.hat_direction = event.value self.hat_x, self.hat_y = self.hat_direction self.hat_timer = pygame.time.get_ticks() self.hat_first_press = True if self.hat_x == 1: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Right self.current.move_right() self.dropzone.draw_brick(self.current.brick, self.current.location) elif self.hat_x == -1: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Left self.current.move_left() self.dropzone.draw_brick(self.current.brick, self.current.location) case pygame.JOYBUTTONDOWN: match event.button: case self.left_button: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Left self.current.move_left() self.dropzone.draw_brick(self.current.brick, self.current.location) case self.right_button: if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Right self.current.move_right() self.dropzone.draw_brick(self.current.brick, self.current.location) case self.rotate_button: if self.current.direction == enums.BrickDirection.Dropped: break self.current.rotate() self.dropzone.draw_brick(self.current.brick, self.current.location) case self.drop_button: if self.current.direction == enums.BrickDirection.Dropped: break self.current.drop() case pygame.USEREVENT: if event.dict.get("type_name") == "JOYHATREPEAT": match event.dict['value']: case (1, 0): if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Right self.current.move_right() case (-1, 0): if self.current.direction == enums.BrickDirection.Dropped: break self.current.direction = enums.BrickDirection.Left self.current.move_left() case (0, 1): if self.current.direction == enums.BrickDirection.Dropped: break self.current.rotate() case (0, -1): if self.current.direction == enums.BrickDirection.Dropped: break self.current.drop() case self.fall_timer: if not self.current.update(): self.dropzone.lock(self.current) self.current = self.next self.next = Brick(brick = randrange(0, len(BRICKS))) # Handle hotplugging case pygame.JOYDEVICEADDED: # This event will be generated when the program starts for every # joystick, filling up the list without needing to create them manually. self.joy = pygame.joystick.Joystick(event.device_index) self.joysticks[self.joy.get_instance_id()] = PC.controller.Controllers(self.joy) # Get joypad mappings if "triangle button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.rotate_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["triangle button"] elif "Y button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.rotate_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["Y button"] print(f"Rotate button :{self.rotate_button}") if "cross button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.drop_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["cross button"] elif "A button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.drop_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["A button"] print(f"Drop button :{self.drop_button}") if "left button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.left_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["left button"] if "right button" in self.joysticks[self.joy.get_instance_id()].controllers[0].mapping: self.right_button = self.joysticks[self.joy.get_instance_id()].controllers[0].mapping["right button"] case pygame.JOYDEVICEREMOVED: del self.joysticks[event.instance_id] print(f"Joystick {event.instance_id} disconnected") def exit(self): self.running = False if __name__ == "__main__": game = Tetris()