Загрузка данных
https://drive.google.com/drive/u/0/folders/1IcvS0LwuyEIEuzeF_hav0L_I3g0_cnXQ
import random
import sys
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Color, Rectangle, Line
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.audio import SoundLoader
# --- ШАБЛОНЫ ФИГУР ---
S = [['.....', '.....', '..00.', '.00..', '.....'], ['.....', '..0..', '..00.', '...0.', '.....']]
Z = [['.....', '.....', '.00..', '..00.', '.....'], ['.....', '...0.', '..00.', '..0..', '.....']]
I = [['..0..', '..0..', '..0..', '..0..', '.....'], ['.....', '0000.', '.....', '.....', '.....']]
O = [['.....', '.....', '.00..', '.00..', '.....']]
J = [['.....', '.0...', '.000.', '.....', '.....'], ['.....', '..00.', '..0..', '..0..', '.....'], ['.....', '.....', '.000.', '...0.', '.....'], ['.....', '..0..', '..0..', '.00..', '.....']]
L = [['.....', '...0.', '.000.', '.....', '.....'], ['.....', '..0..', '..0..', '..00.', '.....'], ['.....', '.....', '.000.', '.0...', '.....'], ['.....', '.00..', '..0..', '..0..', '.....']]
T = [['.....', '..0..', '.000.', '.....', '.....'], ['.....', '..0..', '..00.', '..0..', '.....'], ['.....', '.....', '.000.', '..0..', '.....'], ['.....', '..0..', '.00..', '..0..', '.....']]
SHAPES = [S, Z, I, O, J, L, T]
COLORS = [
(0, 1, 0.6, 1), # Салатовый
(1, 0.2, 0.4, 1), # Розово-красный
(0, 0.8, 1, 1), # Голубой
(1, 0.8, 0, 1), # Золотой
(1, 0.6, 0, 1), # Оранжевый
(0.4, 0.4, 1, 1), # Синий
(0.8, 0.3, 1, 1) # Фиолетовый
]
class TetrisGame(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols, self.rows = 10, 20
self.block_size = 28
self.grid = [[None for _ in range(self.cols)] for _ in range(self.rows)]
self.score = 0
self.level = 1
# Загрузка звуков (оставлены твои названия файлов)
self.bg_music = SoundLoader.load('bg_music.mp3.mp3')
self.drop_sound = SoundLoader.load('drop.mp3.mp3')
self.level_up_sound = SoundLoader.load('level_up.mp3.mp3')
if self.bg_music:
self.bg_music.loop = True
self.bg_music.volume = 0.5
self.current_piece = self.get_new_piece()
self.next_piece = self.get_new_piece()
self.held_piece = None
self.can_hold = True
self.score_label = Label(text="SCORE: 0 | LEVEL: 1", font_size=18, bold=True, color=(1, 1, 1, 1))
self.hold_label = Label(text="HOLD (C)\nROT (Z)", font_size=14, color=(0, 1, 1, 1), halign='center')
self.next_label = Label(text="NEXT", font_size=14, color=(0, 1, 1, 1))
self.add_widget(self.score_label)
self.add_widget(self.hold_label)
self.add_widget(self.next_label)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
self.fall_speed = 0.4
self.event = None # Ивент запускается только когда экран активен
def start_game(self):
if not self.event:
self.event = Clock.schedule_interval(self.update, self.fall_speed)
if self.bg_music:
self.bg_music.play()
def pause_game(self):
if self.event:
self.event.cancel()
self.event = None
if self.bg_music:
self.bg_music.stop()
def get_new_piece(self):
shape = random.choice(SHAPES)
color = COLORS[SHAPES.index(shape)]
return {'x': 3, 'y': 0, 'shape': shape, 'rotation': 0, 'color': color}
def convert_shape(self, piece):
positions = []
format = piece['shape'][piece['rotation'] % len(piece['shape'])]
for i, line in enumerate(format):
for j, column in enumerate(list(line)):
if column == '0':
positions.append((piece['x'] + j, piece['y'] + i))
return [(p[0], p[1] - 2) for p in positions]
def valid_space(self, piece):
for x, y in self.convert_shape(piece):
if x < 0 or x >= self.cols or y >= self.rows:
return False
if y >= 0 and self.grid[y][x] is not None:
return False
return True
def lock_piece(self):
if self.drop_sound: self.drop_sound.play()
for x, y in self.convert_shape(self.current_piece):
if y >= 0:
self.grid[y][x] = self.current_piece['color']
else:
self.game_over()
return
lines_cleared = 0
for i in range(self.rows - 1, -1, -1):
if None not in self.grid[i]:
del self.grid[i]
self.grid.insert(0, [None for _ in range(self.cols)])
lines_cleared += 1
if lines_cleared > 0:
self.score += lines_cleared * 15
new_level = self.score // 100 + 1
if new_level > self.level:
self.level = new_level
if self.level_up_sound: self.level_up_sound.play()
self.score_label.text = f"SCORE: {self.score} | LEVEL: {self.level}"
self.fall_speed = max(0.08, 0.4 - (self.level - 1) * 0.04)
if self.event:
self.event.cancel()
self.event = Clock.schedule_interval(self.update, self.fall_speed)
self.current_piece = self.next_piece
self.next_piece = self.get_new_piece()
self.can_hold = True
def update(self, dt):
self.current_piece['y'] += 1
if not self.valid_space(self.current_piece):
self.current_piece['y'] -= 1
self.lock_piece()
self.draw_board()
def game_over(self):
if self.event:
self.event.cancel()
self.event = None
if self.bg_music:
self.bg_music.stop()
self.score_label.text = f"GAME OVER! SCORE: {self.score}"
self.score_label.color = (1, 0.2, 0.2, 1)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
if not self.event: return True # Не реагируем на кнопки, если игра на паузе
key = keycode[1]
if key == 'left':
self.current_piece['x'] -= 1
if not self.valid_space(self.current_piece): self.current_piece['x'] += 1
elif key == 'right':
self.current_piece['x'] += 1
if not self.valid_space(self.current_piece): self.current_piece['x'] -= 1
elif key == 'down':
self.current_piece['y'] += 1
if not self.valid_space(self.current_piece): self.current_piece['y'] -= 1
self.score += 1
elif key == 'up':
self.current_piece['rotation'] += 1
if not self.valid_space(self.current_piece): self.current_piece['rotation'] -= 1
elif key == 'z':
self.current_piece['rotation'] -= 1 # Крутим в обратную сторону
if not self.valid_space(self.current_piece): self.current_piece['rotation'] += 1
elif key == 'spacebar':
while self.valid_space(self.current_piece):
self.current_piece['y'] += 1
self.current_piece['y'] -= 1
self.lock_piece()
elif key == 'c' and self.can_hold:
if self.held_piece is None:
self.held_piece = {'x': 3, 'y': 0, 'shape': self.current_piece['shape'], 'rotation': 0, 'color': self.current_piece['color']}
self.current_piece = self.next_piece
self.next_piece = self.get_new_piece()
else:
temp = {'x': 3, 'y': 0, 'shape': self.current_piece['shape'], 'rotation': 0, 'color': self.current_piece['color']}
self.current_piece = {'x': 3, 'y': 0, 'shape': self.held_piece['shape'], 'rotation': 0, 'color': self.held_piece['color']}
self.held_piece = temp
self.can_hold = False
self.draw_board()
return True
def draw_mini_piece(self, piece, offset_x, offset_y):
if not piece: return
Color(*piece['color'])
format = piece['shape'][piece['rotation'] % len(piece['shape'])]
p_size = 14
for i, line in enumerate(format):
for j, column in enumerate(list(line)):
if column == '0':
rect_y = offset_y - i * p_size
rect_x = offset_x + j * p_size
Rectangle(pos=(rect_x, rect_y), size=(p_size - 1, p_size - 1))
def draw_board(self):
self.canvas.before.clear()
self.canvas.clear()
self.score_label.size = self.score_label.texture_size
self.score_label.pos = (Window.width // 2 - self.score_label.width // 2, Window.height - 60)
self.hold_label.size = self.hold_label.texture_size
self.hold_label.pos = (40, Window.height - 120)
self.next_label.size = self.next_label.texture_size
self.next_label.pos = (Window.width - 80, Window.height - 120)
board_w = self.cols * self.block_size
board_h = self.rows * self.block_size
offset_x = (Window.width - board_w) / 2
offset_y = (Window.height - board_h) / 2 - 40
with self.canvas:
Color(0.08, 0.08, 0.12, 1)
Rectangle(pos=(0, 0), size=(Window.width, Window.height))
Color(0.12, 0.12, 0.18, 1)
Rectangle(pos=(offset_x, offset_y), size=(board_w, board_h))
Color(0.2, 0.8, 1, 1)
Line(rectangle=(offset_x, offset_y, board_w, board_h), width=1.2)
for y in range(self.rows):
for x in range(self.cols):
if self.grid[y][x]:
Color(*self.grid[y][x])
rect_y = offset_y + (self.rows - 1 - y) * self.block_size
rect_x = offset_x + x * self.block_size
Rectangle(pos=(rect_x + 1, rect_y + 1), size=(self.block_size - 2, self.block_size - 2))
Color(*self.current_piece['color'])
for x, y in self.convert_shape(self.current_piece):
if y >= 0:
rect_y = offset_y + (self.rows - 1 - y) * self.block_size
rect_x = offset_x + x * self.block_size
Rectangle(pos=(rect_x + 1, rect_y + 1), size=(self.block_size - 2, self.block_size - 2))
if self.held_piece:
self.draw_mini_piece(self.held_piece, 30, Window.height - 150)
self.draw_mini_piece(self.next_piece, Window.width - 90, Window.height - 150)
# --- ЭКРАНЫ ПРИЛОЖЕНИЯ ---
class MenuScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Темный фон меню
with self.canvas.before:
Color(0.08, 0.08, 0.12, 1)
self.bg = Rectangle(pos=self.pos, size=Window.size)
self.bind(pos=self.update_bg, size=self.update_bg)
layout = BoxLayout(orientation='vertical', padding=60, spacing=25)
title = Label(text="TETRIS", font_size=58, bold=True, color=(0, 1, 0.8, 1), size_hint=(1, 0.4))
btn_play = Button(text="Играть", font_size=24, bold=True, background_color=(0, 0.8, 0.6, 1), color=(1,1,1,1), size_hint=(1, 0.2))
btn_play.bind(on_press=self.start_game)
btn_settings = Button(text="Настройки", font_size=20, background_color=(0.3, 0.4, 0.5, 1), color=(1,1,1,1), size_hint=(1, 0.2))
btn_settings.bind(on_press=self.go_settings)
btn_exit = Button(text="Выход", font_size=20, background_color=(0.8, 0.2, 0.2, 1), color=(1,1,1,1), size_hint=(1, 0.2))
btn_exit.bind(on_press=self.exit_app)
layout.add_widget(title)
layout.add_widget(btn_play)
layout.add_widget(btn_settings)
layout.add_widget(btn_exit)
self.add_widget(layout)
def update_bg(self, *args):
self.bg.pos = self.pos
self.bg.size = self.size
def start_game(self, instance):
self.manager.current = 'game'
def go_settings(self, instance):
self.manager.current = 'settings'
def exit_app(self, instance):
sys.exit()
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas.before:
Color(0.08, 0.08, 0.12, 1)
self.bg = Rectangle(pos=self.pos, size=Window.size)
self.bind(pos=self.update_bg, size=self.update_bg)
layout = BoxLayout(orientation='vertical', padding=60, spacing=20)
title = Label(text="НАСТРОЙКИ\n(В разработке)", font_size=32, bold=True, halign="center", color=(1, 1, 1, 1), size_hint=(1, 0.6))
btn_back = Button(text="Назад", font_size=20, background_color=(0.4, 0.4, 0.4, 1), size_hint=(1, 0.2))
btn_back.bind(on_press=self.go_back)
layout.add_widget(title)
layout.add_widget(btn_back)
self.add_widget(layout)
def update_bg(self, *args):
self.bg.pos = self.pos
self.bg.size = self.size
def go_back(self, instance):
self.manager.current = 'menu'
class GameScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
layout = FloatLayout()
self.game_widget = TetrisGame()
# Кнопка возврата поверх игры (мобильный UI)
self.btn_back = Button(
text="< Меню",
font_size=14,
bold=True,
size_hint=(None, None),
size=(80, 40),
pos_hint={'x': 0.02, 'top': 0.98},
background_color=(0.8, 0.2, 0.2, 1),
color=(1, 1, 1, 1)
)
self.btn_back.bind(on_press=self.go_back)
layout.add_widget(self.game_widget)
layout.add_widget(self.btn_back)
self.add_widget(layout)
def on_enter(self):
# Запускаем логику и музыку только когда перешли на экран игры
self.game_widget.start_game()
def on_leave(self):
# Ставим на паузу при выходе в меню
self.game_widget.pause_game()
def go_back(self, instance):
self.manager.current = 'menu'
class TetrisApp(App):
def build(self):
Window.size = (480, 760)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))
return sm
if __name__ == '__main__':
TetrisApp().run()