start of Menu implementation

This commit is contained in:
IM23a-bachmannj2
2025-02-02 09:16:50 +01:00
parent b8887598d4
commit fc31ab5912
12 changed files with 296 additions and 51 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ This isn't just about coding; it's about building something I love from the grou
--- ---
### **Step 1: Speed and Difficulty Progression** ### **Step 1: Speed and Difficulty Progression**
1. [ ] **Soft Drop**: Implement gradual acceleration when the player holds the down arrow. 1. [x] **Soft Drop**: Implement gradual acceleration when the player holds the down arrow.
2. [x] **Hard Drop**: Allow instant dropping with the spacebar. 2. [x] **Hard Drop**: Allow instant dropping with the spacebar.
3. [ ] **Leveling Up**: 3. [ ] **Leveling Up**:
- [x] Increase the falling speed after clearing a fixed number of lines. - [x] Increase the falling speed after clearing a fixed number of lines.
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+11 -6
View File
@@ -1,7 +1,7 @@
import pygame as pg import pygame as pg
from game.settings import * from game.settings import *
from game.game_manager import Grid from game.game_manager import Grid
from game.util import draw_text, get_fall_interval from game.util.util import *
from game.tetrominos import Tetrominos from game.tetrominos import Tetrominos
import game.settings as settings import game.settings as settings
import sys import sys
@@ -25,17 +25,20 @@ class Game:
self.next_tetromino = Tetrominos(cols, cell_size) self.next_tetromino = Tetrominos(cols, cell_size)
self.hold_tetromino = None self.hold_tetromino = None
self.hold_used = False # Flag to track if hold has been used for the current tetromino self.hold_used = False
self.fall_timer = 0 self.fall_timer = 0
self.move_timer = 0 self.move_timer = 0
self.move_interval = 100 self.move_interval = 100
self.playing = None
def run(self): def run(self):
"""Main game loop.""" """Main game loop."""
self.playing = True self.playing = True
if settings.gamestate == False: if not settings.gamestate:
self.game_grid = Grid(rows, cols, cell_size) self.game_grid = Grid(rows, cols, cell_size)
settings.game_score = 0
settings.gamestate = True settings.gamestate = True
while self.playing: while self.playing:
@@ -79,6 +82,8 @@ class Game:
if not self.game_grid.check_collision(self.current_tetromino.shape, self.current_tetromino.x - 1, self.current_tetromino.y): if not self.game_grid.check_collision(self.current_tetromino.shape, self.current_tetromino.x - 1, self.current_tetromino.y):
self.current_tetromino.move(-1, 0) # Move left self.current_tetromino.move(-1, 0) # Move left
self.move_timer = current_time # Reset timer self.move_timer = current_time # Reset timer
elif keys[pg.K_DOWN]:
fall_interval = 40
# Handle automatic falling based on timer # Handle automatic falling based on timer
if current_time - self.fall_timer > fall_interval: if current_time - self.fall_timer > fall_interval:
@@ -98,9 +103,8 @@ class Game:
# Draw the grid and tetromino # Draw the grid and tetromino
self.game_grid.draw_grid(self.grid_surface) self.game_grid.draw_grid(self.grid_surface)
self.current_tetromino.draw(self.grid_surface)
self.ghost_tetromino.draw_ghost_tetromino(self.grid_surface) self.ghost_tetromino.draw_ghost_tetromino(self.grid_surface)
self.current_tetromino.draw(self.grid_surface)
# Draw next and hold previews # Draw next and hold previews
self.next_surface.fill((0, 0, 0)) self.next_surface.fill((0, 0, 0))
@@ -111,7 +115,7 @@ class Game:
self.hold_tetromino.draw_next_tetromino(self.hold_surface) self.hold_tetromino.draw_next_tetromino(self.hold_surface)
# Draw score # Draw score
draw_text(self.screen, f'Score: {settings.game_score}', 30, self.width - 75, 100, (255, 255, 255)) draw_text(self.screen, f'Score: {settings.game_score}', get_font(20), self.width - 150, 100, (255, 255, 255))
# Render surfaces # Render surfaces
self.screen.blit(self.grid_surface, (0, 0)) self.screen.blit(self.grid_surface, (0, 0))
@@ -155,5 +159,6 @@ class Game:
def show_game_over(self): def show_game_over(self):
"""Handle game over screen.""" """Handle game over screen."""
add_score("gamescore.json", settings.game_score)
settings.gamestate = False settings.gamestate = False
self.playing = False self.playing = False
+78 -9
View File
@@ -1,17 +1,23 @@
import pygame as pg import pygame as pg
from game.settings import *
import game.settings as setting import game.settings as setting
from game.util import draw_text from game.util.button import Button
from game.util.util import *
import sys import sys
class StartMenu: class StartMenu:
def __init__(self, screen, clock): def __init__(self, screen, clock):
self.menu_running = None
self.screen = screen self.screen = screen
self.clock = clock self.clock = clock
self.screen_size = self.screen.get_size() self.screen_size = self.screen.get_size()
self.mouse_pos = pg.mouse.get_pos()
self.score = load_score("gamescore.json")
self.option = False
self.options_menu = OptionMenu(self.screen, self.clock)
def run(self): def run(self):
self.menu_running = True self.menu_running = True
@@ -22,6 +28,7 @@ class StartMenu:
return True return True
def update(self): def update(self):
self.mouse_pos = pg.mouse.get_pos()
for event in pg.event.get(): for event in pg.event.get():
if event.type == pg.QUIT: if event.type == pg.QUIT:
@@ -33,17 +40,50 @@ class StartMenu:
if event.key == pg.K_ESCAPE: if event.key == pg.K_ESCAPE:
pg.quit() pg.quit()
sys.exit() sys.exit()
if event.type == pg.MOUSEBUTTONDOWN:
if self.play.checkForInput(self.mouse_pos):
self.menu_running = False
if self.options.checkForInput(self.mouse_pos):
self.option = True
while self.option:
self.option = self.options_menu.run()
if self.quit.checkForInput(self.mouse_pos):
pg.quit()
sys.exit()
def draw(self): def draw(self):
self.screen.fill((0, 0, 0)) self.screen.blit(get_background(), (0, 0))
self.play = Button(image=None, pos=(setting.width // 2, setting.height // 3), text_input="Play", font=get_font(50), base_color=(255, 255, 255), hovering_color=(200, 200, 200))
self.play.changeColor(self.mouse_pos)
self.quit = Button(image=None, pos=(setting.width // 2, setting.height // 3 * 2), text_input="Quit", font=get_font(50), base_color=(255, 255, 255), hovering_color=(200, 200, 200))
self.quit.changeColor(self.mouse_pos)
self.options = Button(image=None, pos=(setting.width // 2, setting.height // 2), text_input="Options", font=get_font(50), base_color=(255, 255, 255), hovering_color=(200, 200, 200))
self.options.changeColor(self.mouse_pos)
draw_text(self.screen, "TETRIS", get_font(30), setting.width // 2, setting.height // 3 - 50, (255, 255, 255))
position = 0
for item in self.score["score"]:
draw_text(self.screen, f"Score: {item}", get_font(30), setting.width // 5, setting.height // 3 - 50 + position, (255, 255, 255))
position += 50
self.play.update(self.screen)
self.options.update(self.screen)
self.quit.update(self.screen)
pg.display.update()
def draw_options(self):
self.screen.blit(get_background(), (0, 0))
draw_text(self.screen, "TETRIS", 50, width // 2, HEIGHT // 2 - 50, (255, 255, 255))
draw_text(self.screen, "Press ENTER to Start", 30, width // 2, HEIGHT // 2 + 50, (200, 200, 200))
pg.display.update() pg.display.update()
class GameMenu: class GameMenu:
def __init__(self, screen, clock): def __init__(self, screen, clock):
self.menu_running = None
self.screen = screen self.screen = screen
self.clock = clock self.clock = clock
self.screen_size = self.screen.get_size() self.screen_size = self.screen.get_size()
@@ -74,10 +114,39 @@ class GameMenu:
self.screen.fill((0, 0, 0)) self.screen.fill((0, 0, 0))
if setting.gamestate: if setting.gamestate:
draw_text(self.screen, "Game Paused", 50, width // 2, HEIGHT // 2 - 50, (255, 255, 255)) draw_text(self.screen, "Game Paused", get_font(50), setting.width // 2, setting.height // 2 - 50, (255, 255, 255))
draw_text(self.screen, "Press ESC to Resume", 30, width // 2, HEIGHT // 2 + 10, (200, 200, 200)) draw_text(self.screen, "Press ESC to Resume", get_font(30), setting.width // 2, setting.height // 2 + 10, (200, 200, 200))
else: else:
draw_text(self.screen, "Game Over", 50, width // 2, HEIGHT // 2 - 50, (255, 0, 0)) draw_text(self.screen, "Game Over", get_font(50), setting.width // 2, setting.height // 2 - 50, (255, 0, 0))
draw_text(self.screen, "Press Enter to Restart", 30, width // 2, HEIGHT // 2 + 50, (200, 200, 200)) draw_text(self.screen, "Press Enter to Restart", get_font(30), setting.width // 2, setting.height // 2 + 50, (200, 200, 200))
pg.display.update()
class OptionMenu:
def __init__(self, screen, clock):
self.menu_running = None
self.screen = screen
self.clock = clock
self.screen_size = self.screen.get_size()
def run(self):
self.menu_running = True
while self.menu_running:
self.clock.tick(60)
self.update()
self.draw()
return False
def update(self):
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
self.menu_running = False
def draw(self):
self.screen.blit(get_background(), (0, 0))
pg.display.update() pg.display.update()
+1 -9
View File
@@ -1,15 +1,8 @@
# Variables width, height = 0, 0
#screen size
global height, width
height, width = 600, 450
#grid surface #grid surface
g_height, g_width = 600, 300 g_height, g_width = 600, 300
#start surface size
HEIGHT, WIDTH = 600, 300
#score #score
game_score = 0 game_score = 0
@@ -19,5 +12,4 @@ cell_size = 30
fps = 60 fps = 60
global gamestate # Add game state variable
gamestate = True gamestate = True
-23
View File
@@ -1,23 +0,0 @@
import pygame
def get_fall_interval(score):
"""
Calculate the fall interval based on the current score.
"""
base_interval = 1000 # Base interval in milliseconds
decrease_per_1000_points = 50 # Decrease in ms per 1000 points
min_interval = 100 # Minimum interval in milliseconds
# Calculate the fall interval
interval = base_interval - (score // 400) * decrease_per_1000_points
# Ensure the interval doesn't go below the minimum
return max(interval, min_interval)
#game states
def draw_text(surface, text, size, x, y, color):
"""Utility function to render text onto a surface."""
font = pygame.font.Font(None, size) # Use default font
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect(center=(x, y))
surface.blit(text_surface, text_rect)
+29
View File
@@ -0,0 +1,29 @@
class Button():
def __init__(self, image, pos, text_input, font, base_color, hovering_color):
self.image = image
self.x_pos = pos[0]
self.y_pos = pos[1]
self.font = font
self.base_color, self.hovering_color = base_color, hovering_color
self.text_input = text_input
self.text = self.font.render(self.text_input, True, self.base_color)
if self.image is None:
self.image = self.text
self.rect = self.image.get_rect(center=(self.x_pos, self.y_pos))
self.text_rect = self.text.get_rect(center=(self.x_pos, self.y_pos))
def update(self, screen):
if self.image is not None:
screen.blit(self.image, self.rect)
screen.blit(self.text, self.text_rect)
def checkForInput(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
return True
return False
def changeColor(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
self.text = self.font.render(self.text_input, True, self.hovering_color)
else:
self.text = self.font.render(self.text_input, True, self.base_color)
+83
View File
@@ -0,0 +1,83 @@
import pygame, sys
from util import write_save
class Controls_Handler():
def __init__(self, save):
self.save_file = save
self.curr_block = save["current_profile"]
self.controls = self.save_file["controls"][str(self.curr_block)]
self.setup()
def update(self, actions):
if self.selected: self.set_new_control()
else: self.navigate_menu(actions)
def render(self, surface):
self.draw_text(surface, "Control Profile " + str(self.curr_block+1) , 20, pygame.Color((0,0,0)), 480 / 2, 270/8)
self.display_controls(surface, self.save_file["controls"][str(self.curr_block)])
if self.curr_block == self.save_file["current_profile"]: self.draw_text(surface, "*" , 20, pygame.Color((0,0,0)), 20, 20)
def navigate_menu(self, actions):
# Move the cursor up and down
if actions["Down"]: self.curr_index = (self.curr_index + 1) % (len(self.save_file["controls"][str(self.curr_block)]) + 1)
if actions["Up"]: self.curr_index = (self.curr_index - 1) % (len(self.save_file["controls"][str(self.curr_block)]) + 1)
# Switch between profiles
if actions["Left"]: self.curr_block = (self.curr_block -1) % len(self.save_file["controls"])
if actions["Right"]: self.curr_block = (self.curr_block +1) % len(self.save_file["controls"])
# Handle Selection
if actions["Action1"] or actions["Start"]:
# Set the current profile to be the main one
if self.cursor_dict[self.curr_index] == "Set":
self.controls = self.save_file["controls"][str(self.curr_block)]
self.save_file["current_profile"] = self.curr_block
write_save(self.save_file)
else:
self.selected = True
def set_new_control(self):
selected_control = self.cursor_dict[self.curr_index]
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
pygame.quit()
sys.exit()
elif event.key not in self.save_file["controls"][str(self.curr_block)].values():
self.save_file["controls"][str(self.curr_block)][selected_control] = event.key
write_save(self.save_file)
self.selected = False
done = True
def display_controls(self,surface, controls):
color = (255,13,5) if self.selected else (255,250,239)
pygame.draw.rect(surface, color, (80 , 270/4 - 10 + (self.curr_index*30), 320, 20) )
i = 0
for control in controls:
self.draw_text(surface, control + ' - ' + pygame.key.name(controls[control]),20,
pygame.Color((0,0,0)), 480 / 2, 270/4 + i)
i += 30
self.draw_text(surface, "Set Current Profile",20, pygame.Color((0,0,0)), 480 / 2, 270/4 + i)
def setup(self):
self.selected = False
self.font = pygame.font.Font("RetroFont.ttf", 20)
self.cursor_dict = {}
self.curr_index = 0
i = 0
for control in self.controls:
self.cursor_dict[i] = control
i += 1
self.cursor_dict[i] = "Set"
def draw_text(self,surface, text, size, color, x, y):
text_surface = self.font.render(text, True, color, size)
text_surface.set_colorkey((0,0,0))
text_rect = text_surface.get_rect()
text_rect.center = (x, y)
surface.blit(text_surface, text_rect)
+86
View File
@@ -0,0 +1,86 @@
import pygame
import json, os
# calculate fall speed
def get_fall_interval(score):
"""
Calculate the fall interval based on the current score.
"""
base_interval = 1000 # Base interval in milliseconds
decrease_per_1000_points = 50 # Decrease in ms per 1000 points
min_interval = 100 # Minimum interval in milliseconds
# Calculate the fall interval
interval = base_interval - (score // 400) * decrease_per_1000_points
# Ensure the interval doesn't go below the minimum
return max(interval, min_interval)
# Draw text simplified
def draw_text(surface, text, font, x, y, color):
"""Utility function to render text onto a surface."""
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect(center=(x, y))
surface.blit(text_surface, text_rect)
def get_font(size):
return pygame.font.Font("assets/font.ttf", size)
def get_background():
return pygame.image.load('assets/menu_background.png')
#For the Leaderboard
def load_score(filename):
"""Load JSON data only if the file is not empty."""
if os.path.exists(filename) and os.path.getsize(filename) > 0: # Check if file exists & not empty
with open(filename, "r") as file:
return json.load(file)
return {}
def add_score(filename, score):
if os.path.exists(filename):
with open(os.path.join(os.getcwd(), filename), "w") as file:
json.dump(score, file)
# For keybinds (control.py)
def load_existing_save(savefile):
with open(os.path.join(savefile), 'r+') as file:
controls = json.load(file)
return controls
def write_save(data):
with open(os.path.join(os.getcwd(),'save.json'), 'w') as file:
json.dump(data, file)
def load_save():
try:
# Save is loaded
save = load_existing_save('save.json')
except:
# No save file, so create one
save = create_save()
write_save(save)
return save
def create_save():
new_save = {
"controls":{
"0" :{"Left": pygame.K_a, "Right": pygame.K_d, "Up": pygame.K_w, "Down": pygame.K_s,
"Start": pygame.K_RETURN, "Action1": pygame.K_SPACE},
"1" :{"Left": pygame.K_a, "Right": pygame.K_d, "Up": pygame.K_w, "Down": pygame.K_s,
"Start": pygame.K_RETURN, "Action1": pygame.K_SPACE}
},
"current_profile": 0
}
return new_save
def reset_keys(actions):
for action in actions:
actions[action] = False
return actions
+3
View File
@@ -0,0 +1,3 @@
{
"score": [1000, 700, 500, 6000, 5000]
}
+3 -2
View File
@@ -2,14 +2,15 @@ import pygame
from game.menu import StartMenu, GameMenu from game.menu import StartMenu, GameMenu
from game.game import Game from game.game import Game
from game.settings import width, height import game.settings as setting
def main(): def main():
running = True running = True
pygame.init() pygame.init()
screen = pygame.display.set_mode((width, height)) screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
setting.width, setting.height = pygame.display.get_window_size()
clock = pygame.time.Clock() clock = pygame.time.Clock()
pygame.display.set_caption("Tetris") pygame.display.set_caption("Tetris")