From 703312b0ac3c7698a69489bb7f637c8d5137ca3e Mon Sep 17 00:00:00 2001 From: eval Nya <11857526-nexplorer-3e@users.noreply.gitlab.com> Date: Fri, 19 Jan 2024 16:39:03 +0800 Subject: [PATCH] upload --- .coverage | Bin 0 -> 53248 bytes .vscode/settings.json | 4 + LICENSE.txt | 9 ++ ...tetris_pygame_hatch-0.0.1-py3-none-any.whl | Bin 0 -> 7064 bytes dist/tetris_pygame_hatch-0.0.1.tar.gz | Bin 0 -> 7518 bytes pyproject.toml | 63 +++++++++++ scratch.py | 88 +++++++++++++++ tests/__init__.py | 3 + tetris_pygame_hatch/__about__.py | 4 + tetris_pygame_hatch/__init__.py | 8 ++ .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 331 bytes .../__pycache__/app.cpython-311.pyc | Bin 0 -> 4501 bytes .../__pycache__/settings.cpython-311.pyc | Bin 0 -> 2966 bytes .../__pycache__/tetris.cpython-311.pyc | Bin 0 -> 6468 bytes .../__pycache__/tetromino.cpython-311.pyc | Bin 0 -> 7311 bytes tetris_pygame_hatch/app.py | 71 ++++++++++++ tetris_pygame_hatch/settings.py | 68 ++++++++++++ tetris_pygame_hatch/tetris.py | 101 +++++++++++++++++ tetris_pygame_hatch/tetromino.py | 105 ++++++++++++++++++ 19 files changed, 524 insertions(+) create mode 100644 .coverage create mode 100644 .vscode/settings.json create mode 100644 LICENSE.txt create mode 100644 dist/tetris_pygame_hatch-0.0.1-py3-none-any.whl create mode 100644 dist/tetris_pygame_hatch-0.0.1.tar.gz create mode 100644 pyproject.toml create mode 100644 scratch.py create mode 100644 tests/__init__.py create mode 100644 tetris_pygame_hatch/__about__.py create mode 100644 tetris_pygame_hatch/__init__.py create mode 100644 tetris_pygame_hatch/__pycache__/__init__.cpython-311.pyc create mode 100644 tetris_pygame_hatch/__pycache__/app.cpython-311.pyc create mode 100644 tetris_pygame_hatch/__pycache__/settings.cpython-311.pyc create mode 100644 tetris_pygame_hatch/__pycache__/tetris.cpython-311.pyc create mode 100644 tetris_pygame_hatch/__pycache__/tetromino.cpython-311.pyc create mode 100644 tetris_pygame_hatch/app.py create mode 100644 tetris_pygame_hatch/settings.py create mode 100644 tetris_pygame_hatch/tetris.py create mode 100644 tetris_pygame_hatch/tetromino.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..71c9e893b5cdcd56847707c57781882e8727d5f4 GIT binary patch literal 53248 zcmeI)O>f&a7zc34PVLxK~-9&6K$5{ zNOE2d!{By6fC2jk!w%ag+9&C0yY#Z>5hcGQaSfB*zOY=QpM`C@fx$$a&5ESnt_htg4@eyu!uw!OKx zE%r9Qd9p3^F|k?}v^5%HQ-r~Z=&De(-HsBj-*O!pyZ(WQ4^@`B7pWF6baafCI!?Hr zHzaM!qyyFIg~?RI1VP|oRl{X(#Br9Z?5ReVSC5AGb867 z)wz1Ud(=~d&aaDM&U(@eKd1|C1Vz9nbA{>`3#LI)sZOJ#4t?ITWiJkr_cnFDy+JSY zzkWPhtgft>=SNAPFpu4I<7lu`8Vj1i;R&I`=O@PvkCO2q;ZfA-h@hPe8O|+q=TJ0{ zj+wg|h75V2i6Bi#HGQwq#+9Wyh-N2fj>lROF_EGHLUTcutlje+9x4&H!3mC4C=b+n zdR9*h?>mqvbT&$b>h{W{&>4h)y)oD=_m>LA>hiMrs+5FL+FR+L;#de}J$c16nw|2L zjCx@E3$K63B~?COO7uON^!b?|t1=%M zR{2|X);N|OnoYXW4Qa^I9IRIW!3kTE63b@Li<9`}w@G;N+B2-LE@1dRv!_&RNpc$A z-v?3T(z43SR`eh{Dr$!^;#nxFiDnRVl=Su5Ksg>qbWJxBNgXb)N?#4jh|ai-gX3CK zr)+WIX?~9eT{gaG$MdA$O4lo1hIOel_w(T7>*z^@GKv}v%7}*Og)*~3)3ZEdW#zAT z$%ISwWJ?cs?v#-dPY%_k;`C~?C1Vx4o)VlSsZ0`BCo&RFsOTiErE9hPyTX8b%iiEu z{+2w;ALaThX0dwbj@j>}gHg{jc0`N1C$nm&Z*yZoa^d7KUUq20rB6Gfal&>Z9Q;;a zfhRZJgH5fFrAj~Rl-KwRzG=NR=z$Fa5P$##AOHafKmY;|fB*y_0Dd+WjeR^aqJ<;T(fz0V#!->2Uf2;HdBJv*SEglITH_w2$U`r!iS zkd8%^!?s=DjcuEL(O}=C@*duxJie^0FKg>-jFisz|8v%^VeMLfU$cQo1_1~_00Izz z00bZa0SG_<0uX?JVeHKGZ<{yxzJB5Re{O%K|FCp}^4|IVpYQ+EkN#tW00bZa0SG_< z0uX=z1Rwwb2tZ(p1^E6yzW+~g2cu~afB*y_009U<00Izz00bZa0TRIde}n-9AOHaf zKmY;|fB*y_009U + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/dist/tetris_pygame_hatch-0.0.1-py3-none-any.whl b/dist/tetris_pygame_hatch-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..9a0a0eca48137d0d0f2d057a8c73e070333ad753 GIT binary patch literal 7064 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 literal 0 HcmV?d00001 diff --git a/dist/tetris_pygame_hatch-0.0.1.tar.gz b/dist/tetris_pygame_hatch-0.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..4588eb82d03cbd0c61a68e1b964bd3b9d2f5c6e5 GIT binary patch literal 7518 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 bpI`d_902_H{NpMA9LaxOC;q|?<;8yiXTipp literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..95ec3d8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "tetris-pygame-hatch" +description = '' +readme = "README.md" +requires-python = ">=3.7" +license = "MIT" +keywords = [] +authors = [ + { name = "eval Nya", email = "11857526-nexplorer-3e@users.noreply.gitlab.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "pygame" +] +dynamic = ["version"] + +[project.urls] +Documentation = "https://github.com/unknown/tetris-pygame-hatch#readme" +Issues = "https://github.com/unknown/tetris-pygame-hatch/issues" +Source = "https://github.com/unknown/tetris-pygame-hatch" + +[tool.hatch.version] +path = "tetris_pygame_hatch/__about__.py" + +[tool.hatch.envs.default] +dependencies = [ + "pytest", + "pytest-cov", +] +[tool.hatch.envs.default.scripts] +cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=tetris_pygame_hatch --cov=tests {args}" +no-cov = "cov --no-cov {args}" + +[[tool.hatch.envs.test.matrix]] +python = ["37", "38", "39", "310", "311"] + +[tool.coverage.run] +branch = true +parallel = true +omit = [ + "tetris_pygame_hatch/__about__.py", +] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] diff --git a/scratch.py b/scratch.py new file mode 100644 index 0000000..1089e46 --- /dev/null +++ b/scratch.py @@ -0,0 +1,88 @@ +import pygame +import math +import itertools + +# Define constants +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +WIDTH, HEIGHT = 800, 600 +CENTER = (WIDTH // 2, HEIGHT // 2) + +# Define the tesseract vertices in 4D space +vertices = [-1, 1] +tesseract_vertices = [[x, y, z, w] for x in vertices for y in vertices + for z in vertices for w in vertices] + +# Define the edges between vertices +edges = [(n, n ^ 1) for n in range(16) for bit in range(4) if n & (1 << bit)] + +# Initialize Pygame +pygame.init() +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +clock = pygame.time.Clock() +running = True + + +# Projection functions +def project_4D_to_3D(x, y, z, w, rotation_xy, rotation_xz, rotation_xw): + # Rotate in 4D space + xz = x * math.cos(rotation_xz) - z * math.sin(rotation_xz) + zx = z * math.cos(rotation_xz) + x * math.sin(rotation_xz) + xy = xz * math.cos(rotation_xy) - y * math.sin(rotation_xy) + yx = y * math.cos(rotation_xy) + xz * math.sin(rotation_xy) + xw = xy * math.cos(rotation_xw) - w * math.sin(rotation_xw) + wx = w * math.cos(rotation_xw) + xy * math.sin(rotation_xw) + + # 4D to 3D projection + distance = 2 + factor = distance / (distance + wx) + x3d = factor * yx + y3d = factor * zx + z3d = factor * w + return x3d, y3d, z3d + + +def project_3D_to_2D(x, y, z): + # 3D to 2D projection (ignoring z for simple orthographic projection) + return int(CENTER[0] + x * 100), int(CENTER[1] + y * 100) + + +# Main loop +angle_xy = angle_xz = angle_xw = 0.0 +while running: + # Handle events + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + # Update the rotation angles + angle_xy += 0.01 + angle_xz += 0.01 + angle_xw += 0.01 + + # Clear screen + screen.fill(BLACK) + + # Draw the tesseract + projected_2d_points: list = [] + for vertex in tesseract_vertices: + x, y, z, w = vertex + # Project the 4D point to 3D + x3d, y3d, z3d = project_4D_to_3D(x, y, z, w, angle_xy, angle_xz, + angle_xw) + # Project the 3D point to 2D and store it + projected_2d_points.append(project_3D_to_2D(x3d, y3d, z3d)) + + # Draw the edges + for edge in edges: + start_pos = projected_2d_points[edge[0]] + end_pos = projected_2d_points[edge[1]] + pygame.draw.line(screen, WHITE, start_pos, end_pos, 1) + + # Update display + pygame.display.flip() + + # Cap the frame rate + clock.tick(30) + +pygame.quit() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..802fac4 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2023-present eval Nya <11857526-nexplorer-3e@users.noreply.gitlab.com> +# +# SPDX-License-Identifier: MIT diff --git a/tetris_pygame_hatch/__about__.py b/tetris_pygame_hatch/__about__.py new file mode 100644 index 0000000..88ed54c --- /dev/null +++ b/tetris_pygame_hatch/__about__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2023-present eval Nya <11857526-nexplorer-3e@users.noreply.gitlab.com> +# +# SPDX-License-Identifier: MIT +__version__ = '0.0.1' diff --git a/tetris_pygame_hatch/__init__.py b/tetris_pygame_hatch/__init__.py new file mode 100644 index 0000000..ce568e6 --- /dev/null +++ b/tetris_pygame_hatch/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2023-present eval Nya <11857526-nexplorer-3e@users.noreply.gitlab.com> +# +# SPDX-License-Identifier: MIT + +from .app import App + +app = App() +app.run() \ No newline at end of file diff --git a/tetris_pygame_hatch/__pycache__/__init__.cpython-311.pyc b/tetris_pygame_hatch/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e03c56a824c0eddd5fbe9d8b5e0889441b78069a GIT binary patch literal 331 zcmZ3^%ge<81csFrsat{cV-N=hn4pZ$en7@_h7^Vr#vF!R#wbQc1}277#$`a!)iAXT zQ4FaJ3t&3nq96vUI!1;RreFq5=9i2>DNV*(%#H;Gewxg;m=g;MikN|7x0s7c^Hwr^ z25J2j;bIlzlAm0fo0?Zr9AjkS92QfOT2ho*tXoi-o|v1eo19-zi6)ehSdyFp6^n-` zi-(BC$7kkcmc+;F6;%G>u*uC&Da}c>E8+xN1aeHVDUkTU%*e?2fs27r^a6tjGJ3$q e*^u0r+LGG9e1n0xoxO>@k+X%f0St@SfjR-()lX^w literal 0 HcmV?d00001 diff --git a/tetris_pygame_hatch/__pycache__/app.cpython-311.pyc b/tetris_pygame_hatch/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82eb84e9b9b93afeeb6a1fb3bf3c5bab91853f9f GIT binary patch literal 4501 zcma)9U2Gf25#A$@2q=s#ga%DoXs7mrTgV21$N>Qvfs7J?>@=_q=)nbApm}Rw`g9}+h&Vt%fT9n1qv7@;dFsp^ z@5G~GM9bsc?C$LB?(8?Sb6@S<+d`oHVdJLgaT4-xtmuWUGq2tTW|as;V3K5knPC{@ zElJCaRj>+dl8q7zxj+QlT_V_@SO|Fvf33{e1j{rDIKIPbfKjaHQcIEV7_Gt_iEC0C z)d#&AhWaWI$qXZq8H?Z)tan+MioHu_Seyiu+JV{y2T+H=0p-+bT0nm$qu8k^Ez(pZ zj`Qfn3Jm^V{SjJWGYo71HuEWLM$$J5-M0vjQfd*bP&4|ZJ$gGKb9SA70?bprLO_>l zPOV|ip?4HHoojqc`?hOOU{z0z^)j%IVuPjzIbe4&U&C6r$4b6!-aH@dbFbcK+QA!o zYq3@58sFyKgBG{mYazFoTV%&9dN%+FV8KQxnip_kkBSmaWJd#Sid|A!irZJbqvyuQ z`RLe0X!25&zYvOqrp`qtrxe%qSTe~=i3KseC@J<#Y%wbeiX}6vu!&ScQXE1en@Pr& zls#Ed;^VQ5lt`x($H=*NqGOX0#f?I|_>q{BR3%j5P;MbDh>CM`EHobEr$W<;JsYQ@ zm{M4rNU@D1)A4!57ROh>qOe&pd0ky*7NP6+|1cf=^_5^czPJE-vsX?IkGyw9+ss>; zrPTSFx#%pb{OlB~%q;Nd1u9nZa0NjIm4H1x;+YV_4ra5L|bOUf&BBKFL2O3Sx&WezH;<@i5sbKBeMR`gP_860%#NU1U>?06|DhptrU&G zqqpGO?A#cVw$G+$Sh#V7)DY&(7OvZri3DsEW0PWE%m^__q+M76{{qLdj=nQZe+KnN zLk&PZ*Mf%92B7t_$AKX1UHLtmTvwUvDskNvu3P51v90B^*88oGtVSC&!?V*M2p-iS z{{m(=25Gz};QnA{EfT`IUcO|e$TLebl)z{|0oP_TODA|3QcZzQt*1yQ<@7X0Z{a>-8@Q^3Ts*YsTh|c0VnN%W`(_;NG6p%T9h4`9G{$` zhK#TlNM z4DutKo{d3JrC>!`wIN3ZQerHBf6F=w*}<3K(*ah_|;>}y1m?fSZ+UDJ$z(+ zzBsfJEQD9W)y|%^12w|5ovM0#h47|lpzIm=v+bF)C{Ci+li4#1o2;9!n?EN6O%gm>!#@l9%&HD1c2{5^CGq%mB4p`|~ z&_V~0;5F8Kw<%V*igOOc4=^a3Jq-k5V|DtfF88LZr|jw}xq2(EUfIm!1jKA z;NI3ua)w@n4unQ_9!Sl~Fie$rW%E-d?Xv!SOFCrzsoBpm3@q};=}Wc!zpv?47xRC+ C=UoN> literal 0 HcmV?d00001 diff --git a/tetris_pygame_hatch/__pycache__/settings.cpython-311.pyc b/tetris_pygame_hatch/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d388e7649ac5c7ac6a4886ffb11ba1bb61001a1b GIT binary patch literal 2966 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 N@38IrM|9AY{vT%{0@eTk literal 0 HcmV?d00001 diff --git a/tetris_pygame_hatch/app.py b/tetris_pygame_hatch/app.py new file mode 100644 index 0000000..fb8d190 --- /dev/null +++ b/tetris_pygame_hatch/app.py @@ -0,0 +1,71 @@ +from typing import Any +import pygame as pg + +from .settings import * + + +class App: + def __init__(self) -> None: + from .tetris import Tetris + + # TODO: dynamic timeout + self.fall_timeout: Generator[int, Any, Any] = FALL_TIMEOUT_GENERATOR + self.paused = False + + pg.init() + pg.display.set_caption(CAPTION) + self.init_event_timer() + self.screen = pg.display.set_mode(FIELD_RES) + self.clock = pg.time.Clock() + self.tetris = Tetris(self) + + def init_event_timer(self): + # when timeout, set trigger to true to make tetromino fall + self.fall_trigger = False + self.fall_event = pg.USEREVENT + pg.time.set_timer(self.fall_event, next(self.fall_timeout)) + + def update(self): + self.tetris.update() + self.clock.tick(FPS) + + def draw(self): + ''' + main app frame drawer. This method only run once. + draw app background color + draw tetris grids + ''' + self.screen.fill(color=FIELD_COLOR, rect=(0, 0, *FIELD_RES)) + self.tetris.draw() # draw grid + pg.display.flip() + + def check_events(self): + ''' + Main event loop. + ''' + self.fall_trigger = False # fall_event set in set_event_timer + for event in pg.event.get(): + if event.type == pg.QUIT or ( + event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE + or event.type == pg.KEYDOWN and event.key == pg.K_c + and pg.key.get_mods() & pg.KMOD_CTRL + ): # handle ctrl+c, https://stackoverflow.com/a/46506753/20419707 + pg.quit() + exit() + elif event.type == pg.KEYDOWN: + if event.key == pg.K_p: + self.paused = not self.paused + elif not self.paused: + self.tetris.control(event.key) + elif event.type == self.fall_event and not self.paused: + self.fall_trigger = True + pg.time.set_timer(self.fall_event, next(self.fall_timeout)) + + def run(self): + ''' + Main loop. + ''' + while True: + self.check_events() + self.update() + self.draw() diff --git a/tetris_pygame_hatch/settings.py b/tetris_pygame_hatch/settings.py new file mode 100644 index 0000000..2de7299 --- /dev/null +++ b/tetris_pygame_hatch/settings.py @@ -0,0 +1,68 @@ +from math import log +import random +from typing import Any, Generator, Tuple + +import pygame as pg + +vec = pg.math.Vector2 + +TETROMINO_COLORS: dict[str, str] # TODO pg.Color +TETROMINOES: dict[str, Tuple[tuple[int, int] | vec, tuple[int, int] | vec, + tuple[int, int] | vec, tuple[int, int] | vec]] +CAPTION: str = 'Tetris Pygame Demo' +FPS: float = 60 +FIELD_COLOR: Tuple[int, int, int] = (48, 39, 32) + +TILE_SIZE = 20 +FIELD_SIZE = FIELD_W, FIELD_H = 20, 30 +FIELD_RES = FIELD_W * TILE_SIZE, FIELD_H * TILE_SIZE +FALL_TIMEOUT_GENERATOR: Generator[int, Any, + Any] # millseconds, tetromino fall countdown + +MOVE_DIRECTIONS = {'l': vec(-1, 0), 'r': vec(1, 0), 'd': vec(0, 1)} + +# https://www.pygame.org/docs/ref/color_list.html +TETROMINO_COLORS = { + 'T': 'mediumorchid', + 'O': 'khaki1', + 'J': 'royalblue3', + 'L': 'orange', + 'I': 'cyan1', + 'S': 'seagreen2', + 'Z': 'indianred2', + '.': 'azure4', +} +TETROMINOES = { + 'T': ((0, 0), (-1, 0), (1, 0), (0, -1)), + 'O': ((0, 0), (0, -1), (-1, 0), (-1, -1)), + 'J': ((0, 0), (-1, 0), (0, -1), (0, -2)), + 'L': ((0, 0), (-1, 0), (-1, -1), (-1, -2)), + 'I': ((0, 0), (0, 1), (0, -1), (0, -2)), + 'S': ((0, 0), (-1, 0), (0, -1), (1, -1)), + 'Z': ((0, 0), (1, 0), (0, -1), (-1, -1)) +} + +__bag_count: int = 0 + + +def _7bag_picker() -> Generator[str, Any, None]: + global __bag_count + _7bag = list(TETROMINOES.keys()) + random.shuffle(_7bag) + while True: + for tetromino in _7bag: + yield tetromino + random.shuffle(_7bag) + if __bag_count: + __bag_count += 1 + + +def countdown_gen() -> Generator[int, Any, None]: + DEFAULT_COUNTDOWN: float = 1 * 1000 # millseconds + while True: + yield int(DEFAULT_COUNTDOWN - log(__bag_count + 1) * 10) + + +# define your generator which invoked by Tetris class. +TETROMINO_GENERATOR = _7bag_picker() +FALL_TIMEOUT_GENERATOR = countdown_gen() diff --git a/tetris_pygame_hatch/tetris.py b/tetris_pygame_hatch/tetris.py new file mode 100644 index 0000000..c80713a --- /dev/null +++ b/tetris_pygame_hatch/tetris.py @@ -0,0 +1,101 @@ +from typing import List, Optional +import pygame as pg +from tetris_pygame_hatch import tetromino + +from tetris_pygame_hatch.tetromino import Tetromino + +from .settings import * + + +class Tetris: + def __init__(self, app) -> None: + from .app import App + from .tetromino import Tetromino, Block + self.app: App + self.sprite_group: pg.sprite.Group + self.tetromino: Tetromino + self.blocks: List[List[Optional[Block]]] + + self.app = app + self.tetromino_shaper = TETROMINO_GENERATOR + # TODO Advanced sprites + # initialze before tetromino for dependency + self.sprite_group = pg.sprite.Group() + self.blocks = [[None for i in range(FIELD_W)] for j in range(FIELD_H)] + self.tetromino = Tetromino(self, next(self.tetromino_shaper)) + + def control(self, pressed_key): + ''' + Game control. Note that pause is in app check_events. + + Parameters + ---------- + pressed_key : int + pygame pressed_key + ''' + if pressed_key == pg.K_LEFT or pressed_key == pg.K_a: + self.tetromino.move('l') + elif pressed_key == pg.K_RIGHT or pressed_key == pg.K_d: + self.tetromino.move('r') + elif pressed_key == pg.K_DOWN or pressed_key == pg.K_s: + self.tetromino.move('d') + elif pressed_key == pg.K_w or pressed_key == pg.K_UP: + self.tetromino.rotate() + elif pressed_key == pg.K_SPACE: + # FIXME race condition maybe + while not self.tetromino.landed: + self.tetromino.move('d') + self.app.fall_trigger = True + + def remove_line(self, y: int) -> bool: + if all(self.blocks[y]): + for block_on_y in self.blocks[y]: + if block_on_y is not None: + block_on_y.dead = True + for i in range(y, 0, -1): + for j in range(FIELD_W): + block = self.blocks[i - 1][j] + if block is not None: + block.pos += vec(0, 1) + if self.blocks[i - 1][j] is not None: + self.blocks[i][j] = self.blocks[i - 1][j] + else: self.blocks[i][j] = None + return True + else: return False + + def draw_grid(self): + for x in range(FIELD_W): + for y in range(FIELD_H): + pg.draw.rect( + self.app.screen, 'black', + (x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE), 1) + + def post_tetromino_landed(self): + if self.tetromino.landed: + # save block status into self.blocks + # Note: after removal the affect line above the operation line would fell by one line. + # this would affect the detection of block removal. + affect_line: List[int] = [] + for block in self.tetromino.blocks: + x, y = tuple(map(int, block.pos)) + self.blocks[y][x] = block + affect_line.append(y) + for y in affect_line: + while self.remove_line(y): pass + self.tetromino = Tetromino(self, next(self.tetromino_shaper)) + + def update(self): + ''' + events to update, + mainly handle at every 'app.fall_timeout' seconds + ''' + if self.app.fall_trigger: + self.tetromino.update() + self.post_tetromino_landed() + + # update the tetromino location since it may changed by self.tetris.control in MainApp event handler + self.sprite_group.update() + + def draw(self): + self.draw_grid() + self.sprite_group.draw(self.app.screen) diff --git a/tetris_pygame_hatch/tetromino.py b/tetris_pygame_hatch/tetromino.py new file mode 100644 index 0000000..2e5bae3 --- /dev/null +++ b/tetris_pygame_hatch/tetromino.py @@ -0,0 +1,105 @@ +import random +import pygame as pg + +from .settings import * + +class Block(pg.sprite.Sprite): + def __init__(self, tetromino, pos, color) -> None: + from .tetromino import Tetromino + self.tetromino: Tetromino = tetromino + self.pos = vec(pos) + vec((FIELD_W//2, 0)) + self.dead = False + + super().__init__(tetromino.tetris.sprite_group) + self.image = pg.Surface([TILE_SIZE, TILE_SIZE]) + # TODO user custom border radius + pg.draw.rect(self.image, color, (1, 1, TILE_SIZE, TILE_SIZE), border_radius=TILE_SIZE//2-5) + self.rect = self.image.get_rect() + + def rotate(self, pivot: vec) -> vec: # because pygame rotates by origin, need to specify pivot + ''' + Rotate 90 degress with pivot as origin + + Parameters + ---------- + pivot : vec + origin port for block rotation. + + Returns + ------- + Vector2 + rotated position. + ''' + return (self.pos - pivot).rotate(90) + pivot + + def set_rect_pos(self): + self.rect.topleft = int(self.pos[0]) * TILE_SIZE, int(self.pos[1]) * TILE_SIZE + + def is_collide(self, dt: vec) -> bool: + x, y = tuple(map(int, self.pos + dt)) + if (0 <= x < FIELD_W and y < FIELD_H) and (y < 0 or not self.tetromino.tetris.blocks[y][x]): + return False + return True + + def update(self): + ''' + make pos update ease, should update in tetromino class though + ''' + if self.dead: + self.kill() + else: + self.set_rect_pos() + +class Tetromino: + def __init__(self, tetris, shape: str) -> None: + from .tetris import Tetris + self.tetris: Tetris = tetris + + self.shape = shape + self.color = TETROMINO_COLORS[shape] + self.blocks = [Block(self, pos, self.color) for pos in TETROMINOES[self.shape]] + self.landed = False + + # hack to display full block + for i in range(-min((pos_y for _pos_x, pos_y in TETROMINOES[self.shape])) - 1): + self.move('d') + min((float(i) for i in range(5))) + + def is_collide(self, dt: vec) -> bool: + ''' + check every block if is collided. + + Returns + ------- + bool + True if collide + ''' + return any((i.is_collide(dt) for i in self.blocks)) + + def rotate(self): + ''' + Rotate the tetromino if possible, say not collide. + ''' + rotatable = True + pivot_pos = self.blocks[0].pos + for i in self.blocks[1:]: + if i.is_collide(i.rotate(pivot_pos) - i.pos): + rotatable = False + break + if rotatable: + for i in self.blocks: i.pos = i.rotate(pivot_pos) + + def move(self, direction): + movevec = MOVE_DIRECTIONS[direction] + # landing check + if not self.is_collide(movevec): + for block in self.blocks: + block.pos += movevec + elif direction == 'd': + self.landed = True + + def update(self): + ''' + update function called every time need to fall a cell + ''' + self.move('d') \ No newline at end of file