From a4a4b19d8612b019ef06ab28fee59fd47796c87f Mon Sep 17 00:00:00 2001 From: Jan Lerking Date: Mon, 21 Apr 2025 20:33:36 +0200 Subject: [PATCH] Initial game. /JL --- assets/cyan_block.png | Bin 0 -> 1739 bytes assets/magenta_block.png | Bin 0 -> 1743 bytes assets/yellow_block.png | Bin 0 -> 1741 bytes bricks.py | 78 +++++++++++++++++ dropnext.py | 15 ++++ dropzone.py | 12 +++ enums.py | 19 +++++ hud.py | 76 +++++++++++++++++ requirements.txt | 2 + tetris.py | 175 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 377 insertions(+) create mode 100644 assets/cyan_block.png create mode 100644 assets/magenta_block.png create mode 100644 assets/yellow_block.png create mode 100644 bricks.py create mode 100644 dropnext.py create mode 100644 dropzone.py create mode 100644 enums.py create mode 100644 hud.py create mode 100644 requirements.txt create mode 100644 tetris.py diff --git a/assets/cyan_block.png b/assets/cyan_block.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee211a5e1edeb0770d502db7b5a413bd8604cbf GIT binary patch literal 1739 zcmeAS@N?(olHy`uVBq!ia0y~yU~~jw4mP03OFmgc1_riCo-U3d6}R5rHsm_wAmDIO zCVWpz@6x49#inJxX^~ObyZwZzQiQgS%C4I;%|Ar9UaY)ZHrMdjj7^sB&TUrND!`)Z zD9|)Xfy2>Lpk;vyhf{zMi=qZ+lfV=fM-Hzh1)%hWLn=#;T~bnU449;#;VCdhg~LmT zMU}H@5{si}(*hO8f}?ru?J9elvFk=QJWtlqe#Mfo4LB@9R(j28gM9lUFZ=hEb5&|r zUW;`SXaYKn!x8ARmIXkkIRyaSrlE$O0!I17st}3IP$2Q#iPQZczZL1qn5+ z&r0S*69Os+8UR#I$b_pI5g;RoRu8ceqFfZW@i@b310t-jh6F~C>JX=l*a*TjZELqG zuGqv>3=Gm-C5yYy7s<>(k9w?%k-W3?+eWllY*&UP8bUguX^RMD8&p{mHE|^qtZ^)h z==QZ>@l5&a0I&|xxPH+ATW)jlzHJH2Q-hSxss~25td|uT33-| zD^m8^(6Wk{42u~PXZjR$9%>^34K;&fs&7%KPym((0l?y*zBR!tWT{66F!qQprWAkd ztdr?#TJXi5<@!Evyd@fH)GkObp0tS#cYzH{L2FtSbX2b%<3&kO7{xnCb;Rq_sw>Mt zWq$VrXsM1;*lX%FElp0G?9; BrtJU# literal 0 HcmV?d00001 diff --git a/assets/magenta_block.png b/assets/magenta_block.png new file mode 100644 index 0000000000000000000000000000000000000000..7db8d9ed9440129e2dcfa55788c582a1ea2ef438 GIT binary patch literal 1743 zcmeAS@N?(olHy`uVBq!ia0y~yU~~jw4mP03OFmgc1_riio-U3d6}R5r-pG5%fyd$E zmXaKY<7^>UUaq*&FiUqvqv|F{wi8xo7=%nc)Kv>Ce@Fh(^L}1ONrA)BQ=nym3WrmG5R0M)XOqAb7Do=RCIz7Mg+nS!k6ltyaSWKGpy4Sn zMTNslh((pNX%dU0XVU@|$AW{u-o{S4=ZL8j-Qeqe3*-7YW%=y(n?*JtbfN3Y+a;z`c|$q4nt+D0Isy%ES^zX0WJwE%0kjoljiLs~9sv=Mdw^;| zLagyuj|rg(0hI#{04gVB!m5iKKt>R)9%3UzxfX8YafVj{Cu(?M4GrXAjUXlT37Q6p zBot*?;@)^;6h$#GNN>A**sxpV=>Y->0bQG-#@2pS)Bv-R!J33HbrP>kSCPY;m?Q&_ z?-xk^K*@P{)BHgRjzdA9*kBc8*_fURi*N>%M%57VvQk1BOCo1HBG7PWc9?4pGDFLVAKEON7izgWabqcsa3-aL z=Xozjh_EdEuejq2iRIhmDW=fE8NJ*@D!UVOSrRo@n&lxCbP0l+XkK#3P@S literal 0 HcmV?d00001 diff --git a/assets/yellow_block.png b/assets/yellow_block.png new file mode 100644 index 0000000000000000000000000000000000000000..6cbafd53f296fe047f1228501c4599acd7e93271 GIT binary patch literal 1741 zcmeAS@N?(olHy`uVBq!ia0y~yU~~jw4mP03OFmgc1_rh%o-U3d6}R5r-pF|dVwMb%NDX_5kmqo+X20u>IY03jAd4bCQzH235wQr_JYSR6f@7N|G|Oj6MB z6qusI;U&bP%GorD<)G;7b=96#>{xW78GOg8sV=~CZ2~7@rE6Lf?D<@$*4_JDHs#CG zsB%uABL!N3uHR)9%3UzxfX8YaRw7G0*DVyP*_C}4s?(*BH{*zvMh0L zED?!AF)&DPyA;&T=_*P z547BfJC(O2@USFmA|-oFA0Qkd+d{PS(UOJ)vttA_iKDq1ITVp|M7uIH9n26VRV%w1 zLhB0BY=z}RM~*{6tBA>T$e9R|M-8l-Hnglq1RAoZ5XK|t=?9JA;vqobk7(1)fT>Cg zfq9tdVyflA^IwG;Tuqn$3&i}^!&{=EMeV$^o~NYn6xh&WcY|2dhAAP^=4hobdhrgF zPFSlyeaS6HV41J$1TEE33i~BHIHvWrdUAD2 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)) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..82aa9c8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pygame==2.6.1 +pygamecontrols==0.1.9 diff --git a/tetris.py b/tetris.py new file mode 100644 index 0000000..77ded75 --- /dev/null +++ b/tetris.py @@ -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() \ No newline at end of file