Initial game. /JL
This commit is contained in:
BIN
assets/cyan_block.png
Normal file
BIN
assets/cyan_block.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/magenta_block.png
Normal file
BIN
assets/magenta_block.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/yellow_block.png
Normal file
BIN
assets/yellow_block.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
78
bricks.py
Normal file
78
bricks.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import pygame
|
||||||
|
import enums
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
BRICKS = [
|
||||||
|
" X " + "\n" + "XXX" + "\n" + " X ",
|
||||||
|
" X" + "\n" + "XXX",
|
||||||
|
"X " + "\n" + "XXX",
|
||||||
|
"X",
|
||||||
|
"XXXX",
|
||||||
|
"XX" + "\n" + "XX",
|
||||||
|
" XX" + "\n" + "XX ",
|
||||||
|
"XX " + "\n" + " XX",
|
||||||
|
"X X" + "\n" + "XXX",
|
||||||
|
"XXX" + "\n" + " X "
|
||||||
|
]
|
||||||
|
|
||||||
|
TILE_SIZE = 48
|
||||||
|
|
||||||
|
class Brick:
|
||||||
|
def __init__(self, brick, state = enums.BrickState.Next):
|
||||||
|
self.layout = []
|
||||||
|
self.color = choice(list(enums.BrickColor))
|
||||||
|
self.set_state(state)
|
||||||
|
self.angle = 0
|
||||||
|
self.direction = None
|
||||||
|
self.load_brick(brick)
|
||||||
|
self.get_image()
|
||||||
|
self.block_image = pygame.transform.scale(self.img, (48, 48)) if self.img else None
|
||||||
|
self.draw_brick()
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
match self.color:
|
||||||
|
case enums.BrickColor.Magenta:
|
||||||
|
self.img = pygame.image.load("assets/magenta_block.png").convert_alpha()
|
||||||
|
case enums.BrickColor.Cyan:
|
||||||
|
self.img = pygame.image.load("assets/cyan_block.png").convert_alpha()
|
||||||
|
case enums.BrickColor.Yellow:
|
||||||
|
self.img = pygame.image.load("assets/yellow_block.png").convert_alpha()
|
||||||
|
case _:
|
||||||
|
self.img = pygame.image.load("assets/magenta_block.png").convert_alpha()
|
||||||
|
|
||||||
|
def load_brick(self, brick):
|
||||||
|
self.layout = [l for l in BRICKS[brick].splitlines()]
|
||||||
|
|
||||||
|
self.rows = len(self.layout)
|
||||||
|
self.cols = len(self.layout[0])
|
||||||
|
self.width = self.cols * TILE_SIZE
|
||||||
|
self.height = self.rows * TILE_SIZE
|
||||||
|
|
||||||
|
def draw_brick(self):
|
||||||
|
self.brick = pygame.Surface((self.width, self.height))
|
||||||
|
for y, row in enumerate(self.layout):
|
||||||
|
for x, char in enumerate(row):
|
||||||
|
if char == "X":
|
||||||
|
self.brick.blit(self.block_image, (1 + x * TILE_SIZE, 1 + y * TILE_SIZE))
|
||||||
|
|
||||||
|
def is_current(self):
|
||||||
|
return True if self.state == enums.BrickState.Current else False
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def rotate(self):
|
||||||
|
print("Rotating")
|
||||||
|
|
||||||
|
def set_state(self, state):
|
||||||
|
self.state = state
|
||||||
|
print(f"State set to {self.state}")
|
||||||
|
|
||||||
|
def move_right(self):
|
||||||
|
print("Moving right")
|
||||||
|
|
||||||
|
def move_left(self):
|
||||||
|
print("Moving left")
|
||||||
|
|
||||||
|
def drop(self):
|
||||||
|
print("Dropping")
|
15
dropnext.py
Normal file
15
dropnext.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
class DropNext():
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.dropnext = pygame.Surface((width, height))
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.dropnext.fill((0, 0, 0)) # Fill with black
|
||||||
|
|
||||||
|
def draw(self, screen, tile_size):
|
||||||
|
screen.blit(self.dropnext, (tile_size * 15, tile_size * 2))
|
||||||
|
|
||||||
|
def draw_block(self, brick):
|
||||||
|
self.dropnext.blit(brick, ((self.width - brick.get_width()) / 2, (self.height - brick.get_height()) / 2))
|
||||||
|
|
12
dropzone.py
Normal file
12
dropzone.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
class DropZone():
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.dropzone = pygame.Surface((width, height))
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.dropzone.fill((0, 0, 0)) # Fill with black
|
||||||
|
|
||||||
|
def draw(self, screen, tile_size):
|
||||||
|
screen.blit(self.dropzone, (tile_size * 4, tile_size * 1))
|
||||||
|
|
19
enums.py
Normal file
19
enums.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class BrickColor(Enum):
|
||||||
|
White = (255, 255, 255)
|
||||||
|
Red = (255, 0, 0)
|
||||||
|
Green = (0, 255, 0)
|
||||||
|
Blue = (0, 0, 255)
|
||||||
|
Magenta = (255, 0, 255)
|
||||||
|
Yellow = (255, 255, 0)
|
||||||
|
Cyan = (0, 255, 255)
|
||||||
|
|
||||||
|
class BrickState(Enum):
|
||||||
|
Current = 0
|
||||||
|
Next = 1
|
||||||
|
|
||||||
|
class BrickDirection(Enum):
|
||||||
|
Left = 0
|
||||||
|
Right = 1
|
||||||
|
Dropped = 2
|
76
hud.py
Normal file
76
hud.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import pygame
|
||||||
|
import os
|
||||||
|
from enums import BrickColor
|
||||||
|
|
||||||
|
class Hud:
|
||||||
|
def __init__(self, screen_width, screen_height, tile_size, font_size=36, highscore_file="highscore.txt"):
|
||||||
|
self.score = 0
|
||||||
|
self.lines = 0
|
||||||
|
self.level = 0
|
||||||
|
self.highscore_file = highscore_file
|
||||||
|
self.highscore = self.load_highscore()
|
||||||
|
|
||||||
|
self.font = pygame.font.Font(None, font_size)
|
||||||
|
self.color = BrickColor.Red.value
|
||||||
|
self.screen_width = screen_width
|
||||||
|
self.screen_height = screen_height
|
||||||
|
self.tile_size = tile_size
|
||||||
|
|
||||||
|
def load_highscore(self):
|
||||||
|
if os.path.exists(self.highscore_file):
|
||||||
|
with open(self.highscore_file, 'r') as file:
|
||||||
|
try:
|
||||||
|
return int(file.read())
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def save_highscore(self):
|
||||||
|
with open(self.highscore_file, 'w') as file:
|
||||||
|
file.write(str(self.highscore))
|
||||||
|
|
||||||
|
def add_points(self, points):
|
||||||
|
self.score += points
|
||||||
|
if self.score > self.highscore:
|
||||||
|
self.highscore = self.score
|
||||||
|
self.save_highscore()
|
||||||
|
|
||||||
|
def add_lines(self, lines):
|
||||||
|
self.lines += lines
|
||||||
|
if lines > 2:
|
||||||
|
self.add_points(lines * lines * 100)
|
||||||
|
else:
|
||||||
|
self.add_points(lines * 100)
|
||||||
|
|
||||||
|
def level_up(self):
|
||||||
|
self.level += 1
|
||||||
|
|
||||||
|
def reset(self, reset_score=True):
|
||||||
|
if reset_score:
|
||||||
|
self.score = 0
|
||||||
|
|
||||||
|
def draw(self, screen):
|
||||||
|
# Score (top-left)
|
||||||
|
score_text = self.font.render(f"Score:", True, self.color)
|
||||||
|
score = self.font.render(f"{self.score}", True, self.color)
|
||||||
|
screen.blit(score_text, (self.tile_size / 2, self.tile_size))
|
||||||
|
screen.blit(score,(self.tile_size / 2, self.tile_size + 24))
|
||||||
|
|
||||||
|
lines_text = self.font.render(f"Lines:", True, self.color)
|
||||||
|
lines = self.font.render(f"{self.lines}", True, self.color)
|
||||||
|
screen.blit(lines_text, (self.tile_size / 2, self.tile_size * 2 + 24))
|
||||||
|
screen.blit(lines, (self.tile_size / 2, self.tile_size * 3))
|
||||||
|
|
||||||
|
level_text = self.font.render(f"Level:", True, self.color)
|
||||||
|
level = self.font.render(f"{self.level}", True, self.color)
|
||||||
|
screen.blit(level_text, (self.tile_size / 2, self.tile_size * 4))
|
||||||
|
screen.blit(level, (self.tile_size / 2, self.tile_size * 4 + 24))
|
||||||
|
|
||||||
|
# Highscore (top-center)
|
||||||
|
highscore_text = self.font.render(f"High Score:", True, self.color)
|
||||||
|
highscore = self.font.render(f"{self.highscore}", True, self.color)
|
||||||
|
screen.blit(highscore_text, (self.tile_size / 2, self.tile_size * 5 + 24))
|
||||||
|
screen.blit(highscore, (self.tile_size / 2, self.tile_size * 6))
|
||||||
|
|
||||||
|
next_text = self.font.render("Next:", True, self.color)
|
||||||
|
screen.blit(next_text, (self.tile_size * 15, self.tile_size))
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pygame==2.6.1
|
||||||
|
pygamecontrols==0.1.9
|
175
tetris.py
Normal file
175
tetris.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import pygame
|
||||||
|
import pygameControls as PC
|
||||||
|
import enums
|
||||||
|
from bricks import Brick, BRICKS, TILE_SIZE
|
||||||
|
from random import randrange
|
||||||
|
from dropzone import DropZone
|
||||||
|
from dropnext import DropNext
|
||||||
|
from hud import Hud
|
||||||
|
|
||||||
|
__version__ = "0.0.1"
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
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.current = Brick(brick = randrange(0, len(BRICKS)), state = enums.BrickState.Current)
|
||||||
|
print(self.current.layout)
|
||||||
|
print(self.current.color)
|
||||||
|
self.next = Brick(brick = randrange(0, len(BRICKS)))
|
||||||
|
print(self.next.layout)
|
||||||
|
print(self.next.color)
|
||||||
|
|
||||||
|
self.hud = Hud(self.screen_width, self.screen_height, TILE_SIZE)
|
||||||
|
self.dropzone = DropZone(width = TILE_SIZE * 10, height = TILE_SIZE * 18)
|
||||||
|
self.dropnext = DropNext(width = TILE_SIZE * 4, height = TILE_SIZE * 4)
|
||||||
|
|
||||||
|
self.clock = pygame.time.Clock()
|
||||||
|
self.rumble_timer = pygame.time.get_ticks()
|
||||||
|
|
||||||
|
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, TILE_SIZE)
|
||||||
|
self.dropnext.draw(self.screen, TILE_SIZE)
|
||||||
|
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:
|
||||||
|
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()
|
||||||
|
case pygame.K_LEFT:
|
||||||
|
if self.current.direction == enums.BrickDirection.Dropped:
|
||||||
|
break
|
||||||
|
self.current.direction = enums.BrickDirection.Left
|
||||||
|
self.current.move_left()
|
||||||
|
case pygame.K_UP:
|
||||||
|
if self.current.direction == enums.BrickDirection.Dropped:
|
||||||
|
break
|
||||||
|
self.current.rotate()
|
||||||
|
case pygame.K_DOWN:
|
||||||
|
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()
|
||||||
|
elif self.hat_x == -1:
|
||||||
|
if self.current.direction == enums.BrickDirection.Dropped:
|
||||||
|
break
|
||||||
|
self.current.direction = enums.BrickDirection.Left
|
||||||
|
self.current.move_left()
|
||||||
|
elif self.hat_y == 1:
|
||||||
|
if self.current.direction == enums.BrickDirection.Dropped:
|
||||||
|
break
|
||||||
|
self.current.rotate()
|
||||||
|
elif self.hat_y == -1:
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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()
|
Reference in New Issue
Block a user