diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..eaf91e2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2bb9140 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7c6e735 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/snake-1.iml b/.idea/snake-1.iml new file mode 100644 index 0000000..d9e6024 --- /dev/null +++ b/.idea/snake-1.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Pipfile b/Pipfile index e657dcc..e8d47a9 100644 --- a/Pipfile +++ b/Pipfile @@ -9,4 +9,4 @@ verify_ssl = true pygame = "*" [requires] -python_version = "3.9" +python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index ee91f48..11aed56 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "cdf596c2d353636ac79e464434d67c35deb7dbfbb40070019ca172b2afa30fff" + "sha256": "8367b3cf5b264019fbf1fa9a895626b8349cabeb3fc4d21326594cb54409afb4" }, "pipfile-spec": 6, "requires": { - "python_version": "3.9" + "python_version": "3.10" }, "sources": [ { @@ -18,50 +18,67 @@ "default": { "pygame": { "hashes": [ - "sha256:0571dde0277483f5060c8ee43cbfd8df5776b12505e3948eee241c8ce9b93371", - "sha256:07ed57062be4bb9741f57dab1751d95574c091c9958ed7e39cdb246d50903283", - "sha256:107d5f82f471baee4b9522a691cb320dd52dbf329ed7a0e9ab25f75cd3caf890", - "sha256:10ca736eecedadf492ba1191f9fa3a5e6f30db2b9f8882b3ee7706d5a89c14e0", - "sha256:19357c826ab94f9ae5b4ec5cb752cc806cfc29ea32cf7bdaacb65fa2615607e8", - "sha256:21475405bcdeb20b8a796a3da6704ebb816e06b29749dd64ff619e80816b7932", - "sha256:2d9b0a66034fed390ee367a549435853502c9d4fe82ac0fa3a520f0ad5648e6e", - "sha256:30eb5c7adb0b3362024cec2c461be6978fbfc99c3bca974e438b1b540cd09438", - "sha256:3270cbcff40ca2b5622a145346298a33285c91b6d50097e0b85123d9a2bc7c9b", - "sha256:328d70d40bc9a6defb9f330f5e7f3d0726af1e7c2308ebca582e69480db2950d", - "sha256:3b31d088129977885f72037c55cfa1140e9bcf3468e68b46141f6cc2b33d456b", - "sha256:3c2676b4fd278d632037eacd3b0524ce1a592c048e8e5eb5830475f83585cb3a", - "sha256:44f3ff8224d7cb998642400371c685005c8316b55e87794cbf1f6407b88ec424", - "sha256:49c2f58559c1fbf4ba258e4b141578ccb0e83da3d4f823894f6171a8f0d594ed", - "sha256:4d3135a1f8c76c3fff1ef8b7a51e4c6523748e9bdbd7bca6daa69790ce0e798a", - "sha256:4f41252dfa1e8bb95f2ea51fba710827dde9820a535623d002a65621bafe7e3f", - "sha256:5aeeb6659a7fe7760a78e449566553ae8c949ae29dd907a8eb4171fa0a274c16", - "sha256:5afc34f0af0cec09a20b6bb09090054fac5169ab01909e01b06e7e0752ab0153", - "sha256:5f057e5aa4c383fcf18560dcae2c5593e37e3fc941083a0a00a17f7cf25ee522", - "sha256:72625dc949c6d08ba7ce7c37a33163bb498d90ca0d7e626db3cfbf486df4db1d", - "sha256:7ea518d8eeb072c77c16977cdee3c59d9fffa750ed9c7c9c533ba520b6b08af7", - "sha256:81510ffb1c31a3827c6be047b1926d81caf36dc734564ca0e14903d6bce60c6f", - "sha256:845385caf99f8d941607791c60e560d24b4a35c70eef0b01c30cfde0b913ff92", - "sha256:898874521a9be1f9dbc5b036a9755803287c2664e335afd3e10963f7f4ccb853", - "sha256:8b1e7b63f47aafcdd8849933b206778747ef1802bd3d526aca45ed77141e4001", - "sha256:9aead3f2eed90260136b201f398965900c5335c974bb7b47c381d98e39284018", - "sha256:9de559462aaa68c40bb7625dcd587584b4eb85c4208528dc97b9ee7254945294", - "sha256:9f48277de1daa83fd58a722b2e3423201b5eb39842227f32702fb78e4bba5a71", - "sha256:9fd0691c4fe58b932674bb6a91d2808790e8269c3183ef16052f13e1c602ac00", - "sha256:a4e35d89b6754941e82df1ce980a1c370943d3c076938d94ed1e48165dd6a11b", - "sha256:ad230911d61f448c09886d3c92b2eae44ca7530babe9c48e74e02a0622ce2d34", - "sha256:b812285d23b5644c643a6ae30553a772f935f47f61826660b108b8727936384b", - "sha256:bf833c853a0568738ee5d88e1345c17bf3e8db626c36fb895327a35bb1827b0b", - "sha256:c188ce4bf1544f2758e8b651f4349a0f3dc441e09d8ab7c4863db1ae8f084a32", - "sha256:d8059084ce54b2c3d7b2c8bacd5f6490db849b2d2d6e7368c160b08504c87e73", - "sha256:e72cdc97a49509ca2298350c2c3a0ac26bc8e943ce003a7d245df42e91439d5d", - "sha256:eab18df58dcc8512f1b694f7218146828d7e3dd3f4e73bfd6942a11810293fd5", - "sha256:ece424c83a575c2e0ba25815871458d3bbade46d76b7997236fb51a0251229ab", - "sha256:ed80b40da839d60f4c03915bb3638e3c96ea8c30e689d0cc309b7597d82cc217", - "sha256:fd5ee0f42d59a290c049f91894e0739f62c2908e7edc028ffb847a105e68bfc3", - "sha256:fd6acd09d2a0fd3f616b18f977f399ed3dd95e2d6754f115837f026d19d62e10" + "sha256:0227728f2ef751fac43b89f4bcc5c65ce39c855b2a3391ddf2e6024dd667e6bd", + "sha256:02a26b3be6cc478f18f4efa506ee5a585f68350857ac5e68e187301e943e3d6d", + "sha256:0d2f80b501aacd74a660d4422793ea1cd4e209bee385aac18d0a07bd671511ee", + "sha256:15d4e42214f93d8c60120e16b690ad03da7f0b3b66f75db8966bccf8c66c4690", + "sha256:232e51104db0e573221660d172af8e6fc2c0fda183c5dbf2aa52170f29aa9ec9", + "sha256:2bfefabe78bda7a1bfba253cbe2131038402ce2b32e4218feeba6431fe429abb", + "sha256:32cb64627c2eb5c4c067ffe614e08ccb8987d096100d225e070dddce05725b63", + "sha256:3804476fab6ec7230aa817ee5c3b378ba956321fdd5f91f51c97452c588869d2", + "sha256:38b5a43ab02c162501e62b857ff2cb128076b0786dd4e1d8bea63db8326f9da1", + "sha256:3d5a76fa826202182d989e8399fca0c3c163fbb4f8ece773e77955a7a62cbed3", + "sha256:472b81ba6b61ffe5879ac3d0da2e5cb235e0e4da471ad4038f013a7710ab53ab", + "sha256:49e5fb589a86169aa95b83d3429ee034799792374e13dbc0da83091d86365a4b", + "sha256:4ab5aba8677d135b94c4714e8256efdfffefc164f354a4d05b846588caf43b99", + "sha256:4eff1db92d53dc2e49ed832dd6c76530e1e2b5954eef091f6af41b41d2d5c3ac", + "sha256:4f73058569573af12c8181e032745f11d85f0799510965d938b1f16c7f13afcb", + "sha256:53c6fa767e3eef52d403eda5d032e48b6040ccce03fbd64af2f71843168118da", + "sha256:594234050b50b57c538842155dc3095c9d4f994266325adb4dd008aee526157f", + "sha256:59a5461ef317e4d233d1bb5ce63311ccad3e911a652bda159d3922351050158c", + "sha256:5a3edc8211d0cf39d1e4d7ded1a0727c53aeb21205963f184199521708bbb05c", + "sha256:5d36d530a8994c5bb8889816981f82b7942d8ec7651ca1d922d01302c1feecd2", + "sha256:5eb3dede55d005adea8504f8c9230b9dc2c84c1c728efe93a9718fa1af824dc8", + "sha256:646e871ff5ab7f933cde5ea2bff7b6cd74d7369f43e84a291baebe00bb9a8f6f", + "sha256:64ec45215c2cfc4051bb0f58d26aee3b50a39b1b0a2e6fe8417bb352a6443aad", + "sha256:692fe4498c353d663d45d05354fb47c9f6bf324d10b53844b9ed7f60e6c8cefa", + "sha256:6efa3fa472acb97c784224b59a89e80da6231f0dbf54df8442ffa3352c0534d6", + "sha256:70a11eec9bae6e8970c5bc4b3d0908eb2c42d4bd4ed488e41e49774b7cb41f57", + "sha256:7281366b4ebd7f16eac8ec6a6e2adb4c729beda178ea82637d9981e93dd40c9b", + "sha256:7a305dcf44f03a8dd7baefb97dc24949d7e719fd686cd3211121639aec4ce464", + "sha256:847b4bc22edb1d77c992b5d56b19e1ab52e14687adb8bc3ed12a8a98fbd7e1ff", + "sha256:85844714f82a5379100825473b1a7b24192b4a944aed3128da9386e26adc3bed", + "sha256:86c66b917afc6330a91ac8c7169c36c77ec536578d1d7724644d41f904e2d146", + "sha256:88a2dabe617e6173003b65762c636947719da3e2d881a4ea47298e8d70886386", + "sha256:9284e76923777c21b8bea19d8528be9cd62d0915139ed3c3cde6c43f849466f5", + "sha256:987c0d5fcd7737c31b35df06f78932c48eeff2c97473001e224fdebd3292b2db", + "sha256:9a81d057a7dea95850e44118f141a892fde93c938ccb08fbc5dd7f1a26c2f1fe", + "sha256:9b2ad10ffaa226ca40ae229143b0a118426aff42e2459b626d355846c59a765d", + "sha256:a0842458b49257ab539b7b6622a242cabcddcb61178b8ae074aaceb890be75b6", + "sha256:a0ab3e4763e0cebf08c55154f4167cdae3683674604a71e1437123225f2a9b36", + "sha256:ada3d33e7e6907d5c3bf771dc58c47ee6994a1e28fed55e4f8f8b817367beb8f", + "sha256:add546fcbf8954f00647f5e7d595ab9389f6a7542a99fc5dca514e14fd799773", + "sha256:b0e405fdde643f14d60c2dd140f110a5a38f588396a8b61a1a86374f25cba589", + "sha256:b0e96c0f68f6bb88da216765920c6dbc55ae83e70435d8ebac87d271fc058646", + "sha256:b400edd7391972e75b4243113089d6ea10b032e1306e8721efabb36d33c2d0f2", + "sha256:b545634f96132af1d31dcb873cf03a9c4a5654ae39d9ee126db0b2eba2806788", + "sha256:ba5bf655c892bbf4a9bafb4fcbc4c71023cc9a65f0cae0f3eba09a11018a858e", + "sha256:bb55368d455ab9518b97febd33a8d417988397b019c9408993be034e0b5a7db6", + "sha256:c1eb91198fc47c2e4fdc19c544b5d94534a70fd877f5c342228feb05e9fc4bef", + "sha256:c28c6f764aa03a0245db12346f1da327c6f49bcc20e53aefec6eed57e4fbe1ce", + "sha256:c6ee571995527e779b46cafee7ebef2dceb1a9c375143828e019293ff0efa167", + "sha256:c84a93e6d33dafce9e25080ac557342333e15ef7e378ba84cb6181c52a8fd663", + "sha256:d4061ac4e81bb36ec8f0a7027582c1c4dd32a939882e008165627103cb0b3985", + "sha256:d5c62fbdb30082f7e1dcfa253da48e7b4be7342d275b34b2efa51f6cffc5942b", + "sha256:e533f4bf9dc1a91cfd608b9bfb028c6a92383e731c502660933f0f9b812045a6", + "sha256:e9368c105a8bccc8adfe7fd7fa5220d2b6c03979a3a57a8178c42f6fa9914ebc", + "sha256:f628f9f26c8dadf72fabc9ae0ce5fe7f60d76be71a3407abc756b4d1fd030fa0", + "sha256:f8379052cfbc278b11e31bc97f2e7f5998959c50837c4d54f4e424a541e0c5d9", + "sha256:fad7b5351931cb68d19d7ecc0b21021fe23237d8fba8c455b5af4a79e1c7c536", + "sha256:fdd488daa4ad33748d5ea806e311bfe01b9cc506def5288400072fcd66d226cf" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.1.0" } }, "develop": {} diff --git a/README.md b/README.md deleted file mode 100644 index a1ecfac..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Snake -Vorlage für eine Übungsaufgabe \ No newline at end of file diff --git a/ressources/Elronmonospace.ttf b/ressources/Elronmonospace.ttf new file mode 100644 index 0000000..c4b5f2a Binary files /dev/null and b/ressources/Elronmonospace.ttf differ diff --git a/snake.py b/snake.py index 4be7cbc..e2904fe 100644 --- a/snake.py +++ b/snake.py @@ -1,15 +1,189 @@ -import pygame import random -from typing import Optional, List, Tuple +from typing import Tuple + +import pygame + +TILE_SIZE = 30 + +# The color and direction values are saved in variables for easy access +SNAKE_HEAD_COLOR = (60, 215, 60) +SNAKE_BODY_COLOR = (40, 195, 40) +BRICK_COLOR = (200, 200, 200) +CHERRY_COLOR = (200, 0, 0) +BACKGROUND_COLOR = (70, 70, 70) + +RIGHT = (1, 0) +LEFT = (-1, 0) +UP = (0, -1) +DOWN = (0, 1) + + +# Item defines a class for objects with x and y coordinates and a method to check if they occupy a given coordinate. +class Item: + def __init__(self, x: int, y: int) -> None: + self._x = x + self._y = y + + # returns whether both given coordinates match with the objects coordinates + def occupies(self, x: int, y: int) -> bool: + if x == self._x and y == self._y: + return True + return False + + +# Brick defines a class for objects that represent walls. +# They inherit from Item and have a method to draw themselves on the given surface. +class Brick(Item): + def __init__(self, x: int, y: int) -> None: + super().__init__(x, y) + + def draw(self, surface: pygame.Surface) -> None: + # x and y have to be multiplied with Tile_size so that the scaling is properly done + # and every object on the field appears in the according size. + pygame.draw.rect(surface, + BRICK_COLOR, + [TILE_SIZE * self._x, + TILE_SIZE * self._y, + TILE_SIZE, + TILE_SIZE] + ) + + +# Cherry is a class that defines objects that represent cherrys. +# They inherit from Item and have a method to draw themselves on the given surface. +# They can also move to a random position that is not preoccupied by a wall or the snake. + +# Snake defines objects that represent snakes. +class Snake: + def __init__(self, x: int, y: int) -> None: + # _occupies stores the positions (x and y coordinates) of every snake part + self._occupies = [(x, y)] + # _direction stores the direction the snake is headed, represented by x and y vectors. e.g. (1,0) means right + self._direction = RIGHT + # _grow stores whether the snake still needs to grow aa part because it ate a cherry. + self._grow = 0 + # _last_direction stores the direction before the last direction change + # to ensure the snake doesn't turn 180° at once because this would lead to a crash that is not intended + self._last_direction = self._direction + + # grow is now set by the corresponding method. + self.grow(3) + + # get_head returns the position (x and y) of the head of the snake. + def get_head(self) -> Tuple[int, int]: + return self._occupies[0] + + # occupies checks if one of the snake parts occupies a given position on the field. + def occupies(self, x: int, y: int) -> bool: + for coordinate in self._occupies: + if x == coordinate[0] and y == coordinate[1]: + return True + return False + + # draw draws the whole snake on the given surface. + # It differentiates between the head and body parts through color and shape + def draw(self, surface: pygame.Surface) -> None: + + for ind, part in enumerate(self._occupies): + if ind == 0: + pygame.draw.ellipse(surface, + SNAKE_HEAD_COLOR, + [TILE_SIZE * part[0], + TILE_SIZE * part[1], + TILE_SIZE, + TILE_SIZE] + ) + else: + pygame.draw.rect(surface, + SNAKE_BODY_COLOR, + [TILE_SIZE * part[0], + TILE_SIZE * part[1], + TILE_SIZE, + TILE_SIZE] + ) -TILE_SIZE = 20 + # set_direction receives a direction and checks if it is a valid direction. If not it raises an exception. + # It also compares to the last direction and if the new direction is the complete opposite + # it doesn't update the direction because this would lead to a non-intended crash. + def set_direction(self, direction: tuple[int, int]) -> None: + if ((direction == RIGHT or direction == LEFT or direction == UP or direction == DOWN) and + self._last_direction != (direction[0] * -1, direction[1] * -1)): + self._direction = direction -# TODO: Spielklassen + # The grow method adds a number to grow but not if this number is less than 1 + def grow(self, g: int) -> None: + if g > 0: + self._grow += g + else: + # just to make sure that it resets if something goes wrong + self._grow = 0 + # step calculates with the head position and the direction the field the snake will now land on + # and checks whether this might lead to a crash or not and updates the game accordingly + def step(self, forbidden: list[Item]) -> bool: + new_field = (self.get_head()[0] + self._direction[0], self.get_head()[1] + self._direction[1]) -def main(): - width = 20 - height = 15 + # forbidden fields are normally walls. + for forbidden_field in forbidden: + if forbidden_field.occupies(new_field[0], new_field[1]): + return False + + # This checks whether the snake already occupies the position and would bite itself + if self.occupies(new_field[0], new_field[1]): + return False + + # This ensures the snake grows if necessary and also that it moves forward. + self._occupies.insert(0, new_field) + if self._grow <= 0: + self._occupies.pop() + else: + self._grow -= 1 + + # The now last direction is saved. + self._last_direction = self._direction + return True + + +class Cherry(Item): + def __init__(self) -> None: + super().__init__(0, 0) + + def draw(self, surface: pygame.Surface) -> None: + pygame.draw.ellipse(surface, + CHERRY_COLOR, + [TILE_SIZE * self._x, + TILE_SIZE * self._y, + TILE_SIZE, + TILE_SIZE] + ) + + def move(self, snake: Snake, wall: list[Brick], width: int, height: int, ) -> None: + occupied = True + # The loop generates random positions on the board + # as long as the cherry can't be placed on the generated position. + # If it can be placed there, because the position isn't occupied by a snake part or a brick + # it sets this position as new position. + x, y = 0, 0 + while occupied: + x = random.randrange(0, width) + y = random.randrange(0, height) + if snake.occupies(x, y): + occupied = True + else: + for brick in wall: + if brick.occupies(x, y): + occupied = True + break + else: + occupied = False + + self._x = x + self._y = y + + +def main() -> None: + width = 30 + height = 20 speed = 7 pygame.init() @@ -20,13 +194,27 @@ def main(): clock = pygame.time.Clock() - # TODO: Spielobjekte anlegen + # defines a wall on the outer coordinates of the field, a cherry + # and a snake positioned in the approximated field center + wall = [ + Brick(x, y) + for x in range(width) + for y in range(height) + if x == 0 or x == width - 1 or y == 0 or y == height - 1 + ] + cherry = Cherry() + snake = Snake(width // 2, height // 2) + + # To place the cherry on the field it needs to move once. + cherry.move(snake, wall, width, height) running = True while running: screen.fill((20, 20, 20)) - # TODO: Mauer zeichnen + # draws every brick in wall on 'screen' + for brick in wall: + brick.draw(screen) for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -35,32 +223,53 @@ def main(): break elif event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: - # TODO: Richtung aendern: nach links - pass + snake.set_direction(LEFT) elif event.key == pygame.K_RIGHT: - # TODO: Richtung aendern: nach rechts - pass + snake.set_direction(RIGHT) elif event.key == pygame.K_UP: - # TODO: Richtung aendern: nach oben - pass + snake.set_direction(UP) elif event.key == pygame.K_DOWN: - # TODO: Richtung aendern: nach unten - pass + snake.set_direction(DOWN) if not running: break - # TODO: Schlange bewegen + running = snake.step(wall) - # TODO: Schlange zeichnen + # update the snake's new position on the field + snake.draw(screen) - # TODO: Ueberpruefen, ob die Kirsche erreicht wurde, falls ja, wachsen und Kirsche bewegen. + # check if the Snake hits a cherry and if yes grow the snake and move the cherry. + if cherry.occupies(snake.get_head()[0], snake.get_head()[1]): + snake.grow(2) + cherry.move(snake, wall, width, height) - # TODO: Kirsche zeichnen + # update the cherry's new position on the field + cherry.draw(screen) pygame.display.flip() clock.tick(speed) + font = pygame.font.SysFont('ressources/Elronmonospace.ttf', TILE_SIZE * 3) + + # render loosing message + message = font.render("You Loose!", True, (200, 50, 50)) + message_w = message.get_width() + message_h = message.get_height() + + # place text in the middle of the screen + screen.blit(message, (TILE_SIZE * width / 2 - message_w / 2, TILE_SIZE * height / 2 - message_h / 2)) + pygame.display.flip() + + # If the game is finished the game doesn't quit imediately. It waits for the user to close the window. + waiting_to_quit = True + while waiting_to_quit: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + # Spiel beenden + waiting_to_quit = False + break + pygame.display.quit() pygame.quit()