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&a7zc34PVLx
K~-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