--- /dev/null
+# Auto detect text files and perform LF normalization
+* text=auto
--- /dev/null
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
- <content url="file://$MODULE_DIR$" />
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/venv" />
+ </content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (fruit-ninja)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
--- /dev/null
+MIT License
+
+Copyright (c) 2023 Skullheadx
+
+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.
--- /dev/null
+## FRUIT ASSETS: https://ninjikin.itch.io/fruit
+
+## BLOOD EFFECTS: https://jasontomlee.itch.io/blood-fx
+
+## FONT: https://fontmeme.com/fonts/gang-of-three-font/
+
+## SWORD SLASHES: https://spikerman.itch.io/sword-slashes
+
+## BOMB EXPLOSION: https://spikerman.itch.io/punch-impacts
+
+## swish sounds https://makotohiramatsu.itch.io/swishes/download/eyJleHBpcmVzIjoxNjg2MjM1NTUzLCJpZCI6MTY5NDY4Mn0%3d%2esewaziU4pjtFHcSPEtIyLcPXOe8%3dhttps://makotohiramatsu.itch.io/swishes/download/eyJleHBpcmVzIjoxNjg2MjM1NTUzLCJpZCI6MTY5NDY4Mn0%3d%2esewaziU4pjtFHcSPEtIyLcPXOe8%3d
+
+## explosion sound effects:
+
+https://pixabay.com/sound-effects/medium-explosion-40472/
+https://pixabay.com/sound-effects/hq-explosion-6288/
+https://pixabay.com/sound-effects/sub-bass-4-secondsssss-6241/
+
+## Background music: https://fardifferent.itch.io/loops
+
+## Menu background image: https://www.freepik.com/free-photo/material-panel-plank-hardwood-texture_1173366.htm#query=wooden%20boards%20background&position=30&from_view=keyword&track=ais
+
+## Game background image made with help from https://www.deviantart.com/sadfacerl/art/WoodTexture-748548579
+
+## Blood Splatter Effect https://bluerosesonata.itch.io/free-blood-splatter-cgsfx
\ No newline at end of file
--- /dev/null
+from fruit import Fruit
+from effect import SplitEffect
+from setup import *
+
+
+class Bomb(Fruit):
+ RADIUS = 100 * SCALE.x
+
+ EXPLOSION_RADIUS = RADIUS * 10
+ POWER = 75
+
+ BOMB_IMAGE = pygame.transform.scale(pygame.image.load("assets/bomb.png").convert_alpha(),
+ (RADIUS * 2, RADIUS * 2)).convert_alpha()
+
+ EXPLOSIONS = [
+ [
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File1.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File2.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File3.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File4.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File5.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch1/File6.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha()
+ ],
+ [
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File1.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File2.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File3.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File4.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File5.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File6.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha()
+ ],
+ [
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File1.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File2.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File3.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File4.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File5.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha(),
+ pygame.transform.scale(pygame.image.load(f"assets/explosion/Punch2/File6.png"),
+ (EXPLOSION_RADIUS * 2, EXPLOSION_RADIUS * 2)).convert_alpha()
+ ],
+ ]
+ EXPLOSION_TIME = 500
+
+ explosion_sound_effects = [
+ pygame.mixer.Sound("assets/sounds/hq-explosion-6288.wav"),
+ pygame.mixer.Sound("assets/sounds/medium-explosion-40472.wav"),
+ ]
+
+ def __init__(self):
+ super().__init__()
+ self.radius = self.RADIUS
+ self.exploded = False
+ self.exploded_frame_timer = 0
+ self.image = self.BOMB_IMAGE
+ self.explosion_frame = 0
+ self.explosion = random.choice(self.EXPLOSIONS)
+
+ def update(self, delta):
+ super().update(delta)
+ if self.exploded:
+ self.exploded_frame_timer += delta
+ if self.exploded_frame_timer >= self.EXPLOSION_TIME / len(self.explosion):
+ self.exploded_frame_timer = 0
+ self.explosion_frame = min(len(self.explosion) - 1, self.explosion_frame + 1)
+ self.image = self.explosion[self.explosion_frame]
+ if self.explosion_frame == len(self.explosion) - 1:
+ return True
+
+ def explode(self, fruits, bombs, effects, depth=0):
+ if self in bombs:
+ if not self.exploded:
+ self.image = self.explosion[0]
+ if depth == 0:
+ pygame.mixer.Sound.play(random.choice(self.explosion_sound_effects))
+ self.exploded = True
+ self.velocity = pygame.Vector2(0, 0)
+ self.acceleration = pygame.Vector2(0, 0)
+
+ for fruit in fruits:
+ fruit.velocity += (fruit.position - self.position).normalize() * self.POWER
+ for effect in effects:
+ if isinstance(effect, SplitEffect):
+ effect.velocity += (effect.position - self.position).normalize() * self.POWER
+
+ for bomb in bombs:
+ if not bomb.exploded:
+ bomb.explode(fruits, bombs, effects,depth+1)
+
+ def draw(self, surf):
+ if self.exploded:
+ surf.blit(self.image, self.image.get_rect(
+ topleft=(self.position.x - self.EXPLOSION_RADIUS, self.position.y - self.EXPLOSION_RADIUS)))
+ else:
+ # pygame.draw.circle(surf, BLACK, self.position, self.radius)
+ if self.position.y - self.radius <= HEIGHT:
+ rotated_image = pygame.transform.rotate(self.image, self.angle)
+ new_rect = rotated_image.get_rect(center=self.image.get_rect(
+ topleft=(self.position.x - self.radius, self.position.y - self.radius)).center)
+ self.width, self.height = new_rect.size
+ surf.blit(rotated_image, new_rect.topleft)
--- /dev/null
+from setup import *
+
+
+class ComboCounter:
+ LIFE_TIME = 1000
+
+ def __init__(self, position, combo):
+ self.position = pygame.Vector2(position)
+ self.velocity = pygame.Vector2(0, -100)
+ self.time = self.LIFE_TIME
+ self.combo = combo
+ self.text_surface = font_large.render(self.combo, True, DARK_GRAY)
+
+ def update(self, delta):
+ self.position += self.velocity * delta / 1000
+ self.time -= delta
+ if self.time <= 0:
+ return True
+
+ def draw(self, surf):
+ surf.blit(self.text_surface, self.position)
--- /dev/null
+import pygame
+
+from setup import *
+
+
+class BloodEffect:
+ LIFE_TIME = 1000
+ SPEED_RANGE = [250, 350]
+ RADIUS_RANGE = [3, 3.75]
+ blood_frames = [
+ [pygame.image.load(f"assets/effects/blood1/{i}.png").convert_alpha() for i in range(16)],
+ [pygame.image.load(f"assets/effects/blood2/{i}.png").convert_alpha() for i in range(16)],
+ [pygame.image.load(f"assets/effects/blood3/{i}.png").convert_alpha() for i in range(16)]
+ ]
+
+ def __init__(self, position, radius, color=None):
+ self.position = pygame.Vector2(position)
+ self.radius = radius * lerp(self.RADIUS_RANGE[0], self.RADIUS_RANGE[1], random.random())
+ self.time = self.LIFE_TIME
+ self.frame_timer = 0
+ self.current_frame = 0
+ self.angle = 0
+ self.frames = [
+ pygame.transform.rotate(pygame.transform.scale(frame, (int(self.radius * 2), int(self.radius * 2))),
+ self.angle) for frame in random.choice(self.blood_frames)]
+ if color is None:
+ color = random.choice(COLORS)
+ else:
+ color = color
+ for frame in self.frames:
+ px_array = pygame.PixelArray(frame)
+ px_array.replace(pygame.Color(250, 3, 35), pygame.Color(color))
+ px_array.close()
+
+ def update(self, delta):
+ self.time -= delta
+ self.frame_timer += delta
+ if self.frame_timer >= self.LIFE_TIME / len(self.frames):
+ self.frame_timer = 0
+ self.current_frame += 1
+ if self.time <= 0:
+ return True
+
+ def draw(self, surf):
+ if self.time > 0:
+ surf.blit(self.frames[self.current_frame], (self.position.x - self.radius, self.position.y - self.radius))
+ # pygame.draw.circle(surf, BLACK, self.position, self.radius)
+
+
+class BloodSplatter:
+ LIFE_TIME = 4000
+ FADE_TIME = 1000
+ RADIUS_RANGE = [3, 3.75]
+ blood_frames = [
+ pygame.transform.rotate(pygame.image.load(
+ "assets/effects/splatter/bloodslash1.png"),
+ -35).convert_alpha(),
+ pygame.transform.rotate(pygame.image.load(
+ "assets/effects/splatter/bloodslash2.png"),
+ -35).convert_alpha()
+ ]
+
+ LIGHT_COLOR1 = pygame.Color(110, 110, 110)
+ DARK_COLOR1 = pygame.Color(84, 84, 84)
+ LIGHT_COLOR2 = pygame.Color(83, 83, 83)
+ DARK_COLOR2 = pygame.Color(74, 74, 74)
+
+
+ color_frames = [dict(), dict()]
+ for c in COLORS:
+ for i, f in enumerate(blood_frames):
+ c_f = f.copy()
+ px_array = pygame.PixelArray(c_f)
+ px_array.replace(LIGHT_COLOR1 if i == 0 else LIGHT_COLOR2, pygame.Color(c))
+ px_array.replace(DARK_COLOR1 if i == 0 else DARK_COLOR2, pygame.Color(darken(c, 0.875)))
+ px_array.close()
+ color_frames[i][c] = c_f
+
+
+ def __init__(self, position, radius, angle, color=None):
+ self.position = pygame.Vector2(position)
+ self.radius = radius * lerp(self.RADIUS_RANGE[0], self.RADIUS_RANGE[1], random.random())
+ self.time = self.LIFE_TIME
+ self.fade_time = self.FADE_TIME
+ self.angle = angle
+ img_index = random.randint(0, len(self.blood_frames) - 1)
+ # self.frame = pygame.transform.rotate(
+ # pygame.transform.scale(self.blood_frames[img_index], (int(self.radius * 2), int(self.radius * 2))),
+ # self.angle)
+ self.alpha = 255
+
+ if color is None:
+ color = random.choice(COLORS)
+
+ self.frame = pygame.transform.rotate(
+ pygame.transform.scale(self.color_frames[img_index][color], (int(self.radius * 2), int(self.radius * 2))),
+ self.angle)
+
+ # px_array = pygame.PixelArray(self.frame)
+ # px_array.replace(self.LIGHT_COLOR1 if img_index == 0 else self.LIGHT_COLOR2, pygame.Color(color))
+ # px_array.replace(self.DARK_COLOR1 if img_index == 0 else self.DARK_COLOR2, pygame.Color(darken(color, 0.875)))
+ # px_array.close()
+
+ def update(self, delta):
+ if self.time > 0:
+ self.time -= delta
+ else:
+ self.fade_time -= delta
+
+ if self.fade_time <= 0 and self.time <= 0:
+ return True
+ if self.time <= 0:
+ self.alpha = int(lerp(0, 255, self.fade_time / self.FADE_TIME))
+
+ def draw(self, surf):
+
+ self.frame.set_alpha(self.alpha)
+ surf.blit(self.frame, (self.position.x - self.radius, self.position.y - self.radius))
+
+
+class SplitEffect:
+ SPEED_PERCENT_RANGE = [65, 85]
+ gravity = 275
+
+ def __init__(self, position, frame, fruit_velocity, normal_velocity):
+ self.position = pygame.Vector2(position)
+ self.velocity = pygame.Vector2(fruit_velocity) * lerp(self.SPEED_PERCENT_RANGE[0], self.SPEED_PERCENT_RANGE[1],
+ random.random()) / 100 + pygame.Vector2(normal_velocity)
+ self.acceleration = pygame.Vector2(0, self.gravity)
+
+ # self.angle = lerp(-45, 45, random.random())
+ self.angle = 0
+ self.direction = random.choice([-1, 1])
+ # self.angle = 0
+ # self.direction = 0
+
+ self.frame = frame
+
+ self.width, self.height = self.frame.get_size()
+
+ def update(self, delta):
+ self.velocity += self.acceleration * delta / 1000
+ self.position += self.velocity * delta / 1000
+ self.angle += 360 * delta / 1000 / 10 * self.direction
+
+ if self.position.y - self.height / 2 > HEIGHT:
+ return True
+
+ def draw(self, surf):
+ rotated_image, position = rotate_center(self.frame, self.angle, self.position)
+ self.width, self.height = rotated_image.get_size()
+ surf.blit(rotated_image, position)
+
+ @staticmethod
+ def find_normals(v):
+ return pygame.Vector2(-v.y, v.x), pygame.Vector2(v.y, -v.x)
+
+ @staticmethod
+ def should_split(image, angle, image_position, mouse_position, mouse_direction):
+ img, img_pos = rotate_center(image.copy(), angle, image_position)
+ img_pos += pygame.Vector2(img.get_size()) / 2
+ if mouse_direction.x == 0:
+ mouse_direction.x += 0.0001
+ a = math.degrees(math.atan(mouse_direction.y / mouse_direction.x))
+ img = rotate_center(img, a, pygame.Vector2(0, 0))[0]
+
+ top_left = pygame.Vector2(image_position) - pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+ rot_center = pygame.Vector2(image_position) - top_left
+ mp = mouse_position - top_left
+
+ t1 = (- mp.x) / mouse_direction.x
+ p1 = mp + t1 * mouse_direction
+
+ p3 = (p1 - rot_center).rotate(-a) + rot_center
+
+ MIN_SPLIT = 0.25
+ slice_percent = clamp(p3.y, 0, img.get_height()) / img.get_height()
+ if MIN_SPLIT < slice_percent < 1 - MIN_SPLIT:
+ return True
+ return False
+
+ @staticmethod
+ def split_image(image, angle, image_position, mouse_position, mouse_direction):
+ img, img_pos = rotate_center(image.copy(), angle, image_position)
+ img_pos += pygame.Vector2(img.get_size()) / 2
+ if mouse_direction.x == 0:
+ mouse_direction.x += 0.0001
+
+ a = math.degrees(math.atan(mouse_direction.y / mouse_direction.x))
+ img = rotate_center(img, a, pygame.Vector2(0, 0))[0]
+
+ top_left = pygame.Vector2(img_pos) - pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+ rot_center = pygame.Vector2(img_pos) - top_left
+
+ # finding end and start points of the splitting line
+ # [x,y] = mouse_position + t * mouse_direction # vector equation
+ # x = mouse_position.x + t * mouse_direction.x
+ # y = mouse_position.y + t * mouse_direction.y
+
+ mp = mouse_position - top_left
+
+ t1 = (- mp.x) / mouse_direction.x
+ p1 = mp + t1 * mouse_direction
+
+ t2 = (img.get_width() - mp.x) / mouse_direction.x
+ p2 = mp + t2 * mouse_direction
+
+ p3 = (p1 - rot_center).rotate(-a) + rot_center
+ p4 = (p2 - rot_center).rotate(-a) + rot_center
+
+ half1 = img.subsurface(pygame.Rect(0, 0, img.get_width(), clamp(p3.y, 0, img.get_height())))
+ half2 = img.subsurface(pygame.Rect(0, clamp(p3.y, 0, img.get_height()), img.get_width(),
+ clamp(img.get_height() - p3.y, 0, img.get_height())))
+
+ p5 = half1.get_rect().center - rot_center
+ pos1 = (p5).rotate(a) + img_pos
+
+ p6 = half2.get_rect().center - rot_center + pygame.Vector2(0, clamp(p3.y, 0, img.get_height()))
+ pos2 = (p6).rotate(a) + img_pos
+
+ r_half1 = pygame.transform.rotate(half1, -a)
+ r_half2 = pygame.transform.rotate(half2, -a)
+
+ return r_half1, r_half2, pos1, pos2
+
+
+class SlashEffect:
+ SLASH = [pygame.image.load(f"assets/effects/sword_slashes/White_Slash_Thin/File{i}.png").convert_alpha() for i in
+ range(1, 7)]
+ GROUP_SLASH = [pygame.image.load(f"assets/effects/sword_slashes/White_Group_Slashes/File{i}.png").convert_alpha()
+ for i in
+ range(1, 21)]
+ LIFETIME = 600
+
+ def __init__(self, position, angle, combo=False):
+ self.position = pygame.Vector2(position)
+ self.angle = angle
+
+ self.is_combo = combo
+
+ self.time = 0
+ self.frame = 0
+
+ # if self.is_combo:
+ # self.slash_frames = [pygame.transform.rotate(frame, self.angle) for frame in self.GROUP_SLASH]
+ # else:
+ # self.slash_frames = [pygame.transform.rotate(frame, self.angle) for frame in self.SLASH]
+ if self.is_combo:
+ self.slash_frames = self.GROUP_SLASH
+ else:
+ self.slash_frames = [pygame.transform.rotate(frame, self.angle) for frame in self.SLASH]
+
+ def update(self, delta):
+ self.time += delta
+ if self.time >= self.LIFETIME / len(self.SLASH):
+ self.time = 0
+ self.frame += 1
+ if self.frame >= len(self.SLASH):
+ return True
+
+ def draw(self, surf):
+
+ frame = self.slash_frames[self.frame]
+ surf.blit(frame, frame.get_rect(center=self.position))
+
+
+class FadeInEffect:
+
+ def __init__(self, fade_time=500):
+ self.fade_time = fade_time
+ self.surf = pygame.Surface((WIDTH, HEIGHT))
+ self.surf.fill(BLACK)
+ self.time = self.fade_time
+ self.alpha = 255
+
+ def update(self, delta):
+ self.time -= delta
+
+ if self.time <= 0:
+ return True
+ self.alpha = int(lerp(0, 255, self.time / self.fade_time))
+
+ def draw(self, surf):
+ self.surf.set_alpha(self.alpha)
+ surf.blit(self.surf, (0, 0))
+
+
+class FadeOutEffect:
+
+ def __init__(self, fade_time=500, max_alpha=255):
+ self.fade_time = fade_time
+ self.max_alpha = max_alpha
+
+ self.surf = pygame.Surface((WIDTH, HEIGHT))
+ self.surf.fill(BLACK)
+ self.time = 0
+ self.alpha = 0
+
+ def update(self, delta):
+ self.time += delta
+ if self.time >= self.fade_time:
+ return True
+ self.alpha = int(lerp(0, self.max_alpha, self.time / self.fade_time))
+
+ def draw(self, surf):
+ self.surf.set_alpha(self.alpha)
+ surf.blit(self.surf, (0, 0))
--- /dev/null
+from setup import *
+
+
+class Fruit:
+ RADIUS_RANGE = [60 * SCALE.x, 100 * SCALE.x] # [25, 50]
+
+ HORIZONTAL_SPAWN_RANGE = [max(RADIUS_RANGE), WIDTH - max(RADIUS_RANGE)]
+ VERTICAL_SPAWN_RANGE = [HEIGHT + max(RADIUS_RANGE), HEIGHT * 2 + max(RADIUS_RANGE)]
+
+ VERTICAL_TARGET_RANGE = [max(RADIUS_RANGE), HEIGHT * 3.5 / 5]
+ HORIZONTAL_TARGET_RANGE = [WIDTH / 5.5, WIDTH * 4.5 / 5.5]
+
+ GRAVITY = 275 * SCALE.y
+ HEADS = [
+ pygame.image.load(f"assets/fruits/{file}").convert_alpha() for file in os.listdir('assets/fruits/')
+ ]
+ OUTLINE_WIDTH = 3
+
+ def __init__(self):
+ self.radius = lerp(self.RADIUS_RANGE[0], self.RADIUS_RANGE[1], random.random())
+
+ self.target = pygame.Vector2(
+ lerp(self.HORIZONTAL_TARGET_RANGE[0], self.HORIZONTAL_TARGET_RANGE[1], random.random()),
+ lerp(self.VERTICAL_TARGET_RANGE[0], self.VERTICAL_TARGET_RANGE[1], random.random()))
+ self.position = pygame.Vector2(
+ lerp(self.HORIZONTAL_SPAWN_RANGE[0], self.HORIZONTAL_SPAWN_RANGE[1], random.random()),
+ lerp(self.VERTICAL_SPAWN_RANGE[0], self.VERTICAL_SPAWN_RANGE[1], random.random()))
+ self.acceleration = pygame.Vector2(0, self.GRAVITY)
+
+ # self.previous_position = self.position
+
+ dy = self.target.y - self.position.y
+ dx = self.target.x - self.position.x
+ t = (-2 / self.GRAVITY * dy) ** 0.5
+ self.velocity = pygame.Vector2(dx / t, -(-2 * self.GRAVITY * dy) ** 0.5)
+ self.image = pygame.transform.scale(random.choice(self.HEADS), (self.radius * 2, self.radius * 2))
+ self.angle = lerp(0, 360, random.random())
+ self.direction = random.choice([-1, 1])
+ # self.angle = 0
+ # self.direction = 0
+
+ self.width, self.height = (self.radius * 2, self.radius * 2)
+
+ def update(self, delta):
+ # self.previous_position = self.position.copy() - self.velocity / 1000 * 30
+ self.velocity += self.acceleration * delta / 1000
+ self.position += self.velocity * delta / 1000
+
+ self.angle += 360 * delta / 1000 / 10 * self.direction
+
+ def get_rect(self):
+ return pygame.Rect(self.position - pygame.Vector2(self.radius / 2, self.radius / 2),
+ pygame.Vector2(self.radius, self.radius))
+
+ def draw(self, surf):
+ # rotated_image = pygame.transform.rotate(self.image, self.angle)
+ # new_rect = rotated_image.get_rect(center=self.image.get_rect(topleft=(self.position.x - self.radius, self.position.y - self.radius)).center)
+ # surf.blit(rotated_image, new_rect.topleft)
+ # pygame.draw.circle(surf, BLACK, self.position, self.radius)
+ if self.position.y - self.radius <= HEIGHT:
+ rotated_image, position = rotate_center(self.image, self.angle, self.position)
+ self.width, self.height = rotated_image.get_size()
+ surf.blit(rotated_image, position)
--- /dev/null
+import pygame
+
+from bomb import Bomb
+from combo_counter import ComboCounter
+from effect import BloodEffect, SplitEffect, SlashEffect, FadeInEffect, FadeOutEffect, BloodSplatter
+from fruit import Fruit
+from player import Player
+from setup import *
+
+
+class Game:
+ BOMB_CHANCE = 0
+ EFFECT_COUNT_PER_FRUIT = 20
+ EFFECT_COUNT_PER_BOMB = 0
+ COMBO_TIME = 250
+ GAME_OVER_TIME = 2000
+ WAVE_COOLDOWN = 500
+
+ BACKGROUND = pygame.Surface((WIDTH, HEIGHT))
+ tile_cols = 4
+ tile_rows = 4
+ background_tile = pygame.transform.scale(pygame.image.load("assets/background.png"),
+ (WIDTH / tile_cols, HEIGHT / tile_rows)).convert()
+ dark_background_tile = pygame.transform.scale(pygame.image.load("assets/dark_background.png"),
+ (WIDTH / tile_cols, HEIGHT / tile_rows)).convert()
+ for x in range(tile_cols):
+ for y in range(tile_rows):
+ if y == 0:
+ BACKGROUND.blit(dark_background_tile, (x * WIDTH / tile_cols, y * HEIGHT / tile_rows))
+ else:
+ BACKGROUND.blit(background_tile, (x * WIDTH / tile_cols, y * HEIGHT / tile_rows))
+ bass_sound_effect = pygame.mixer.Sound("assets/sounds/sub-bass-4-secondsssss-6241.wav")
+ bass_sound_effect.set_volume(0.1)
+ slash_sounds = [pygame.mixer.Sound(f"assets/sounds/Swishes/long-medium-swish-44324.wav"),
+ pygame.mixer.Sound(f"assets/sounds/Swishes/swing-6045.wav"),
+ pygame.mixer.Sound(f"assets/sounds/Swishes/swish-sound-94707.wav"),
+ ]
+
+ HIGHSCORE_FILE = "highscore.txt"
+
+ def __init__(self):
+ self.player = Player()
+ self.fruits = [Fruit()]
+ self.bombs = []
+ self.effects = [
+ [], # Blood splatter
+ [], # Blood splash
+ [], # Split Effect
+ [], # slash effect
+ [FadeInEffect(fade_time=1000)] # Fade in/fade out effects
+ ]
+ self.combo_counters = []
+ self.wave = 10
+ self.score = 0
+ self.time_since_last_hit = 0
+ self.current_combo = 0
+
+ self.cleared_wave = True
+ self.wave_cooldown_timer = 0
+
+ self.game_over = False
+ self.game_over_time = 0
+
+ try:
+ with open(self.HIGHSCORE_FILE, "r") as f:
+ self.highscore = int(f.read())
+ except:
+ self.highscore = 0
+
+ self.music_started = False
+ if pygame.mixer.music.get_busy():
+ pygame.mixer.music.fadeout(500)
+ background_music = pygame.mixer.music.load(
+ "assets/sounds/Of Far Different Nature - Friendly Trap (CC-BY).ogg")
+ pygame.mixer.music.set_volume(0.25)
+ pygame.mixer.music.play(-1)
+ self.music_started = True
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return COMMAND_EXIT
+ if event.type == pygame.KEYUP:
+ if event.key == pygame.K_ESCAPE:
+ return COMMAND_EXIT
+ elif event.key == pygame.K_m:
+ if pygame.mixer.music.get_busy():
+ pygame.mixer.music.pause()
+ else:
+ pygame.mixer.music.unpause()
+ if not self.music_started:
+ background_music = pygame.mixer.music.load(
+ "assets/sounds/Of Far Different Nature - Friendly Trap (CC-BY).ogg")
+ pygame.mixer.music.set_volume(0.25)
+ pygame.mixer.music.play(-1)
+ self.music_started = True
+ if not self.game_over:
+ self.player.update(delta)
+ else:
+ self.game_over_time += delta
+ if self.game_over_time > self.GAME_OVER_TIME:
+ return COMMAND_START
+ hits = []
+
+ for fruit in self.fruits:
+ fruit.update(delta)
+ hit_status = self.player.hits(fruit)
+ if hit_status and SplitEffect.should_split(fruit.image, fruit.angle, fruit.position, self.player.previous_mouse_pos, self.player.mouse_direction):
+ hits.append((fruit, self.player.mouse_direction, self.player.previous_mouse_pos))
+
+ fr = fruit.get_rect()
+ if ((not -fruit.width < fr.x < WIDTH + fruit.width) or fr.y - fr.height > HEIGHT) and fruit.velocity.y > 0:
+ self.fruits.remove(fruit)
+ self.cleared_wave = False
+ self.time_since_last_hit += delta
+
+ if self.time_since_last_hit < self.COMBO_TIME:
+ self.score += self.current_combo
+ else:
+ self.current_combo = 0
+
+ for hit, mouse_direction, mouse_position in hits:
+
+ color = random.choice(COLORS)
+ self.effects[0].append(
+ BloodSplatter(hit.position, hit.radius, determine_angle(hit.position, hit.position + mouse_direction),color))
+ self.effects[1].append(BloodEffect(hit.position, hit.radius,lighten(color, 0.15)))
+
+ half1, half2, pos1, pos2 = SplitEffect.split_image(hit.image, hit.angle, hit.position, mouse_position, mouse_direction)
+
+ n1, n2 = SplitEffect.find_normals(mouse_direction.normalize())
+ c = 100
+ self.effects[2].append(SplitEffect(pos1, half1, hit.velocity, n1 * c))
+ self.effects[2].append(SplitEffect(pos2, half2, hit.velocity, n2 * c))
+
+ pygame.mixer.Sound.play(random.choice(self.slash_sounds))
+
+ self.score += 1
+ if self.time_since_last_hit < self.COMBO_TIME:
+ self.current_combo += 1
+ if self.current_combo > 1:
+ self.combo_counters.append(ComboCounter(hit.position, f"x{self.current_combo + 1}"))
+ self.effects[3].append(SlashEffect(hit.position, hit.angle, combo=False))
+ else:
+ self.effects[3].append(SlashEffect(hit.position, hit.angle))
+
+ self.time_since_last_hit = 0
+
+ if hit in self.fruits:
+ self.fruits.remove(hit)
+
+ for layer in self.effects:
+ for effect in layer:
+ effect_status = effect.update(delta)
+ if effect_status:
+ layer.remove(effect)
+ for combo in self.combo_counters:
+ combo_status = combo.update(delta)
+ if combo_status:
+ self.combo_counters.remove(combo)
+
+ for bomb in self.bombs:
+ bomb_status = bomb.update(delta)
+
+ if self.player.hits(bomb):
+ bomb.explode(self.fruits, self.bombs, self.effects[2])
+ self.game_over = True
+ self.player.sliced_points.clear()
+ pygame.mixer.Sound.play(self.bass_sound_effect)
+ self.effects[4].append(FadeOutEffect(fade_time=self.GAME_OVER_TIME, max_alpha=20))
+ if self.score > self.highscore:
+ self.highscore = self.score
+ with open(self.HIGHSCORE_FILE, "w") as f:
+ f.write(str(self.highscore))
+ if bomb_status:
+ self.bombs.remove(bomb)
+ continue
+ br = bomb.get_rect()
+ if ((not -bomb.width < br.x < WIDTH + bomb.width) or br.y - br.height > HEIGHT) and bomb.velocity.y > 0:
+ self.bombs.remove(bomb)
+
+ if len(self.fruits) == 0 and len(self.bombs) == 0 and not self.game_over:
+ self.wave_cooldown_timer += delta
+ if self.wave_cooldown_timer >= self.WAVE_COOLDOWN:
+ if self.cleared_wave:
+ self.wave_cooldown_timer = 0
+ self.wave += 1
+ self.cleared_wave = True
+ for i in range(self.wave):
+ if random.random() < self.BOMB_CHANCE:
+ self.bombs.append(Bomb())
+ else:
+ self.fruits.append(Fruit())
+
+ def draw(self, surf):
+
+ screen.blit(self.BACKGROUND, (0, 0))
+
+ text_surf = font.render(f"SCORE {self.score}", True, WHITE)
+ surf.blit(text_surf, (7, 0))
+ text_surf2 = font.render(f"COMBO x{max(1, self.current_combo)}", True, WHITE)
+ surf.blit(text_surf2, text_surf2.get_rect(center=(WIDTH / 2, text_surf.get_height() / 2)))
+ # text_surf2 = font.render(f"TIME SINCE LAST HIT {round(self.time_since_last_hit / 1000, 1)}", True, BLACK)
+ # surf.blit(text_surf2, (WIDTH - text_surf2.get_width(), text_surf.get_height()))
+
+ for effect in self.effects[0]:
+ effect.draw(surf)
+ for effect in self.effects[1]:
+ effect.draw(surf)
+ for bomb in self.bombs:
+ bomb.draw(surf)
+ for effect in self.effects[2]:
+ effect.draw(surf)
+ for fruit in self.fruits:
+ fruit.draw(surf)
+ for effect in self.effects[3]:
+ effect.draw(surf)
+ for combo in self.combo_counters:
+ combo.draw(surf)
+ self.player.draw(surf)
+ for effect in self.effects[4]:
+ effect.draw(surf)
+ if self.game_over:
+ title = font_large.render("GAME OVER", True, WHITE)
+ subtitle = font.render(f"HIGHSCORE {self.highscore}", True, WHITE)
+ game_over_surf = pygame.Surface(
+ (max(title.get_width(), subtitle.get_width()), title.get_height() + subtitle.get_height()))
+ game_over_surf.fill(GRAY)
+ game_over_surf.blit(title, title.get_rect(center=(game_over_surf.get_width() / 2, title.get_height() / 2)))
+ game_over_surf.blit(subtitle, subtitle.get_rect(
+ center=(game_over_surf.get_width() / 2, title.get_height() + subtitle.get_height() / 2)))
+
+ pygame.draw.rect(surf, GRAY, game_over_surf.get_rect(center=(WIDTH / 2, HEIGHT / 2)).inflate(50, 50),
+ border_radius=10)
+ pygame.draw.rect(surf, BLACK, game_over_surf.get_rect(center=(WIDTH / 2, HEIGHT / 2)).inflate(50, 50), 5,
+ border_radius=10)
+ surf.blit(game_over_surf, game_over_surf.get_rect(center=(WIDTH / 2, HEIGHT / 2)))
\ No newline at end of file
--- /dev/null
+3850
\ No newline at end of file
--- /dev/null
+from game import Game
+from menu import Menu
+from setup import *
+
+FPS = 60
+clock = pygame.time.Clock()
+
+scene = Menu()
+
+is_running = True
+while is_running:
+ delta = clock.tick(FPS)
+ status = scene.update(delta)
+ scene.draw(screen)
+ fps_text = font.render(f"FPS {int(clock.get_fps())}", True, WHITE)
+ screen.blit(fps_text, (WIDTH - fps_text.get_width() - 7, 0))
+
+ pygame.display.update()
+
+ if status == COMMAND_EXIT:
+ is_running = False
+ elif status == COMMAND_START:
+ scene = Game()
+ elif status == COMMAND_MENU:
+ scene = Menu()
+
+pygame.quit()
--- /dev/null
+import random
+
+import pygame
+
+from setup import *
+from player import Player
+from fruit import Fruit
+from effect import SlashEffect, SplitEffect, BloodEffect, FadeOutEffect, BloodSplatter
+
+
+class Menu:
+ background = pygame.Surface((WIDTH, HEIGHT))
+ tile_cols = 4
+ tile_rows = 4
+ background_tile = pygame.transform.scale(pygame.image.load("assets/background.png"),
+ (WIDTH / tile_cols, HEIGHT / tile_rows)).convert()
+ for x in range(tile_cols):
+ for y in range(tile_rows):
+ background.blit(background_tile, (x * WIDTH / tile_cols, y * HEIGHT / tile_rows))
+
+ slash_sounds = [pygame.mixer.Sound(f"assets/sounds/Swishes/long-medium-swish-44324.wav"),
+ pygame.mixer.Sound(f"assets/sounds/Swishes/swing-6045.wav"),
+ pygame.mixer.Sound(f"assets/sounds/Swishes/swish-sound-94707.wav"),
+ ]
+
+ def __init__(self):
+ self.background_music = pygame.mixer.music.load(
+ "assets/sounds/Of Far Different Nature - Ethnic Beat (CC-BY).ogg")
+ pygame.mixer.music.set_volume(0.5)
+ pygame.mixer.music.play(-1)
+ pygame.mixer.music.pause()
+
+ self.player = Player()
+ self.fruit = Fruit()
+ self.fruit.position = pygame.Vector2(WIDTH / 2, HEIGHT * 1.5 / 2.5)
+ self.fruit.angle = 0
+ self.fruit.image = pygame.transform.scale(pygame.image.load("assets/fruits/58.png"),
+ (self.fruit.radius * 2, self.fruit.radius * 2))
+
+ self.effects = []
+
+ self.title_surface = font_large.render("Fruit Shinobi", True, WHITE)
+ self.tutorial_surface = font.render("Drag to slice the fruit", True, WHITE)
+ self.controls_surface = font_small.render("Press M to unmute music", True, WHITE)
+
+ self.credit_surface = font_small.render("Made by: Skullheadx", True, WHITE)
+ self.blacked_out = False
+
+ def update(self, delta):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
+ pygame.mixer.music.stop()
+ return COMMAND_EXIT
+ if event.type == pygame.KEYUP:
+ if event.key == pygame.K_m:
+ if pygame.mixer.music.get_busy():
+ pygame.mixer.music.pause()
+ else:
+ pygame.mixer.music.unpause()
+ self.player.update(delta)
+
+ if not self.blacked_out:
+ hit_status = self.player.hits(self.fruit)
+ if hit_status and SplitEffect.should_split(self.fruit.image, self.fruit.angle, self.fruit.position, self.player.previous_mouse_pos, self.player.mouse_direction):
+ color = random.choice(COLORS)
+ self.effects.append(BloodEffect(self.fruit.position, self.fruit.radius,lighten(color, 0.15)))
+
+ half1, half2, pos1, pos2 = SplitEffect.split_image(self.fruit.image, self.fruit.angle, self.fruit.position, self.player.previous_mouse_pos,
+ self.player.mouse_direction)
+
+ self.effects.append(BloodSplatter(self.fruit.position, self.fruit.radius,
+ determine_angle(self.fruit.position,
+ self.fruit.position + self.player.mouse_direction),color))
+ self.effects.append(SlashEffect(self.fruit.position, self.fruit.angle))
+
+ n1, n2 = SplitEffect.find_normals(self.player.mouse_direction.normalize())
+ c = 5
+ self.effects.append(SplitEffect(pos1, half1, pygame.Vector2(0,0), n1 * c))
+ self.effects.append(SplitEffect(pos2, half2, pygame.Vector2(0,0), n2 * c))
+
+ pygame.mixer.Sound.play(random.choice(self.slash_sounds))
+ self.blacked_out = True
+ self.effects.append(FadeOutEffect())
+ for effect in self.effects:
+ effect_status = effect.update(delta)
+ if effect_status:
+ if isinstance(effect, FadeOutEffect):
+ return COMMAND_START
+ self.effects.remove(effect)
+
+ def draw(self, surf):
+ surf.blit(self.background, (0, 0))
+ if not self.blacked_out:
+ self.fruit.draw(surf)
+
+ tutorial_surface_pos = (
+ WIDTH / 2, HEIGHT * 2 / 3 + self.tutorial_surface.get_height() / 2 + self.fruit.get_rect().height + 30)
+ pygame.draw.rect(surf, DARK_GRAY, self.tutorial_surface.get_rect(center=tutorial_surface_pos).inflate(25, 25),
+ border_radius=10)
+ pygame.draw.rect(surf, BLACK, self.tutorial_surface.get_rect(center=tutorial_surface_pos).inflate(25, 25), 5,
+ border_radius=10)
+ surf.blit(self.tutorial_surface, self.tutorial_surface.get_rect(
+ center=tutorial_surface_pos))
+
+ pygame.draw.rect(surf, GRAY, self.title_surface.get_rect(center=(WIDTH / 2, HEIGHT / 3)).inflate(50, 50),
+ border_radius=10)
+ pygame.draw.rect(surf, BLACK, self.title_surface.get_rect(center=(WIDTH / 2, HEIGHT / 3)).inflate(50, 50), 5,
+ border_radius=10)
+ surf.blit(self.title_surface, self.title_surface.get_rect(center=(WIDTH / 2, HEIGHT / 3)))
+
+ surf.blit(self.controls_surface, self.controls_surface.get_rect(bottomleft=(10, HEIGHT - 10)))
+ surf.blit(self.credit_surface, self.credit_surface.get_rect(bottomright=(WIDTH - 10, HEIGHT - 10)))
+ self.player.draw(surf)
+ for effect in self.effects:
+ effect.draw(surf)
--- /dev/null
+import pygame
+
+from setup import *
+
+
+class Player:
+ LIFE_TIME = 100
+ INFLATE_SCALE = 20
+
+ IMAGE = pygame.image.load("assets/effects/sword_slashes/White_Slash_Thin/File2.png").convert_alpha()
+
+ def __init__(self):
+ self.sliced_points = []
+ self.lines = []
+
+ self.previous_mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
+ self.mouse_direction = pygame.Vector2(0, 0)
+ self.angle = 0
+ self.slicing = False
+ self.display_image = self.IMAGE.copy()
+ self.position = pygame.Vector2(0, 0)
+
+
+ def update(self, delta):
+ pressed = pygame.mouse.get_pressed()
+ if pressed[0]:
+ pos = pygame.mouse.get_pos()
+ self.sliced_points.append((pygame.Vector2(pos), pygame.time.get_ticks()))
+ self.mouse_direction = pygame.Vector2(pos) - self.previous_mouse_pos
+ self.previous_mouse_pos = pygame.Vector2(pos)
+ if self.mouse_direction.x == 0:
+ x_direction = self.mouse_direction.x + 0.0001
+ else:
+ x_direction = self.mouse_direction.x
+ self.angle = math.degrees(math.atan(self.mouse_direction.y / x_direction))
+ self.display_image, self.position = rotate_center(self.IMAGE, self.angle,
+ pygame.Vector2(pos) + pygame.Vector2(
+ self.IMAGE.get_width() / 2, 0))
+ self.slicing = True
+ else:
+ self.mouse_direction = pygame.Vector2(0, 0)
+ self.previous_mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
+ self.angle = 0
+ self.slicing = False
+
+ self.lines.clear()
+ if len(self.sliced_points) > 1:
+ for i in range(len(self.sliced_points) - 1):
+ self.lines.append((pygame.Vector2(self.sliced_points[i][0]), pygame.Vector2(self.sliced_points[i + 1][0])))
+
+ for i, val in enumerate(self.sliced_points):
+ pos, time = val
+ if pygame.time.get_ticks() - time > self.LIFE_TIME:
+ self.sliced_points.pop(i)
+ break
+
+ def hits(self, fruit):
+ for line in self.lines:
+ v1 = pygame.Vector2(line[0]) - fruit.position
+ v2 = pygame.Vector2(line[1]) - fruit.position
+ v = v2 - v1
+ r = fruit.radius
+
+ if v.magnitude_squared() == 0:
+ continue
+
+ discriminant = 4 * (v1.dot(v)) ** 2 - 4 * v.magnitude_squared() * (v1.magnitude_squared() - r ** 2)
+ if discriminant >= 0:
+ t1 = (-2 * v1.dot(v) + math.sqrt(discriminant)) / (2 * v.magnitude_squared())
+ t2 = (-2 * v1.dot(v) - math.sqrt(discriminant)) / (2 * v.magnitude_squared())
+ if 0 <= t1 <= 1 or 0 <= t2 <= 1:
+ return True
+ if (t1 < 0 and t2 > 1) or (t2 < 0 and t1 > 1):
+ return True
+ return False
+
+ def draw(self, surf):
+ # for line in self.lines:
+ # pygame.draw.circle(surf, BLUE, line[0], 5)
+ # # pygame.draw.rect(surf, RED, hitbox)
+ # pygame.draw.line(surf, GREEN, line[0], line[1], 4)
+ # mx, my = line[1] - line[0]
+ # if mx == 0:
+ # mx = 0.01
+ # m = my / mx
+ # c = line[0].y - m * line[0].x
+ #
+ # x1 = line[0].x
+ # y1 = m * x1 + c
+ # x2 = line[1].x
+ # y2 = m * x2 + c
+ #
+ # pygame.draw.line(surf, RED, (x1, y1), (x2, y2), 4)
+ # pygame.draw.rect(surf, RED, hitbox)
+ # pygame.draw.line(surf, RED, line[0], line[1], 4)
+ # for pos, time in self.sliced_points:
+ # pygame.draw.circle(surf, RED, pos, 3)
+ if len(self.sliced_points) > 1:
+ pygame.draw.lines(surf, BLACK, False, [a for a, b in self.sliced_points], 6)
+ pygame.draw.lines(surf, LIGHT_GRAY, False, [a for a, b in self.sliced_points], 4)
--- /dev/null
+import pygame
+import random
+import os
+import math
+from functools import cache
+
+pygame.init()
+WIDTH, HEIGHT = pygame.display.Info().current_w, pygame.display.Info().current_h
+screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.FULLSCREEN)
+
+SCALE = pygame.Vector2(WIDTH / 1536, HEIGHT / 864)
+
+pygame.display.set_caption("Fruit Shinobi")
+icon = pygame.image.load("assets/logo.ico").convert()
+pygame.display.set_icon(icon)
+
+# fonts
+font_small = pygame.font.Font("assets/font/go3v2.ttf", int(30 * SCALE.x))
+font = pygame.font.Font("assets/font/go3v2.ttf", int(60 * SCALE.x))
+font_large = pygame.font.Font("assets/font/go3v2.ttf", int(100 * SCALE.x))
+
+# colors
+WHITE = (255, 255, 255)
+LIGHT_GRAY = (211, 211, 211)
+GRAY = (128, 128, 128)
+DARK_GRAY = (25, 25, 25)
+BLACK = (0, 0, 0)
+
+RED = (255, 0, 0)
+GREEN = (0, 255, 0)
+BLUE = (0, 0, 255)
+ORANGE = (255, 165, 0)
+YELLOW = (255, 255, 0)
+PURPLE = (128, 0, 128)
+
+BROWN = (139, 69, 19)
+DARK_BROWN = (119, 49, 0)
+
+DARK_RED = (139, 0, 0)
+DARK_GREEN = (0, 100, 0)
+DARK_BLUE = (0, 0, 139)
+DARK_ORANGE = (255, 140, 0)
+DARK_YELLOW = (255, 215, 0)
+DARK_PURPLE = (75, 0, 130)
+
+DEFAULT_COLORS = [
+ RED,
+ GREEN,
+ BLUE,
+ ORANGE,
+ YELLOW,
+ PURPLE,
+ DARK_RED,
+ DARK_GREEN,
+ DARK_BLUE,
+ DARK_ORANGE,
+ DARK_YELLOW,
+ DARK_PURPLE
+]
+
+COLORS = [
+ (252, 166, 168),
+ (247, 203, 168),
+ (203, 172, 239),
+ (160, 247, 208),
+ (222, 244, 141),
+ (205, 255, 135),
+ (174, 252, 201),
+ (247, 167, 111),
+ (225, 162, 239),
+ (209, 239, 119),
+ (211, 255, 178),
+ (119, 249, 215),
+ (252, 113, 146),
+ (204, 247, 160),
+ (247, 161, 148),
+ (218, 186, 255),
+ (112, 239, 116),
+ (237, 186, 125),
+ (198, 202, 255),
+ (197, 252, 174),
+]
+
+
+def darken(color, factor=0.5):
+ r, g, b = color
+ return (r * factor, g * factor, b * factor)
+
+
+def lighten(color, factor=0.5):
+ r, g, b = color
+ return (min(255, r * (1 + factor)), min(255, g * (1 + factor)), min(255, b * (1 + factor)))
+
+
+# commands
+COMMAND_EXIT = 0
+COMMAND_START = 1
+COMMAND_MENU = 2
+
+screen.fill(BROWN)
+loading_text = font_large.render("Loading...", True, BLACK)
+screen.blit(loading_text, loading_text.get_rect(center=(WIDTH / 2, HEIGHT / 2)))
+pygame.display.update()
+
+
+def lerp(start, end, weight):
+ return weight * (end - start) + start
+
+
+def clamp(value, minimum, maximum):
+ return min(maximum, max(minimum, value))
+
+
+@cache
+def rotate(image, angle):
+ return pygame.transform.rotate(image, angle)
+
+
+def rotate_center(image, angle, position):
+ rotated_image = rotate(image, round(angle))
+ new_rect = rotated_image.get_rect(center=image.get_rect(topleft=(
+ position.x - image.get_rect().width / 2, position.y - image.get_rect().height / 2)).center)
+ return rotated_image, new_rect.topleft
+
+
+def determine_angle(pos1, pos2):
+ pos1 = pygame.Vector2(pos1)
+ pos2 = pygame.Vector2(pos2)
+
+ if pos1.x == pos2.x:
+ pos2.x += 0.0001
+
+ a = math.degrees(math.atan((pos2.y - pos1.y) / (pos2.x - pos1.x)))
+
+ # if pos2.x < pos1.x:
+ # a += 180
+ return -a
+
+# def split_image(image, angle, image_position, mouse_position, mouse_direction):
+# img = image.copy()
+# ip = pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+# mp = mouse_position - ip
+#
+# if mouse_direction.x == 0:
+# a = 90
+# else:
+# a = math.degrees(math.atan(mouse_direction.y/mouse_direction.x))
+#
+# mp.rotate_ip(-a)
+# mp += ip
+#
+# pygame.draw.line(img, RED,mouse_position-image_position, mouse_direction * 100+ mouse_position-image_position, 20)
+#
+# rot_img,pos = rotate_center(img, -a, image_position)
+# return img, rot_img, (WIDTH/2, HEIGHT/2), (0,0)
+# crop_y = clamp(int(mp.y), 0, img.get_height())
+#
+# half1 = pygame.transform.rotate(img2.subsurface((0, 0, img.get_width(), crop_y)), a)
+# half2 = pygame.transform.rotate(img2.subsurface((0, crop_y, img.get_width(), img.get_height() - crop_y)), a)
+# #
+# # return half1, half2, half1.get_rect(topleft=pos1).center, half2.get_rect(topleft=pos2).center
+
+# good one :D
+# def split_image(image, angle, image_position, mouse_position, mouse_direction):
+# img = image.copy()
+#
+# if mouse_direction.x == 0:
+# mouse_direction.x += 0.0001
+#
+# a = math.degrees(math.atan(mouse_direction.y / mouse_direction.x))
+#
+# img = rotate_center(img, a, pygame.Vector2(0, 0))[0]
+#
+# top_left = pygame.Vector2(image_position) - pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+# rot_center = pygame.Vector2(image_position) - top_left
+#
+# # finding end and start points of the splitting line
+# # [x,y] = mouse_position + t * mouse_direction # vector equation
+# # x = mouse_position.x + t * mouse_direction.x
+# # y = mouse_position.y + t * mouse_direction.y
+#
+# mp = mouse_position - top_left
+#
+# t1 = (- mp.x) / mouse_direction.x
+# p1 = mp + t1 * mouse_direction
+#
+# t2 = (img.get_width() - mp.x) / mouse_direction.x
+# p2 = mp + t2 * mouse_direction
+#
+# p3 = (p1 - rot_center).rotate(-a) + rot_center
+# p4 = (p2-rot_center).rotate(-a) + rot_center
+#
+# half1 = img.subsurface(pygame.Rect(0, 0, img.get_width(), clamp(p3.y,0, img.get_height())))
+# half2 = img.subsurface(pygame.Rect(0, clamp(p3.y,0, img.get_height()), img.get_width(), clamp(img.get_height() - p3.y, 0, img.get_height())))
+#
+# p5 = half1.get_rect().center - rot_center
+# pos1 = (p5).rotate(a) + image_position
+#
+# p6 = half2.get_rect().center - rot_center + pygame.Vector2(0, clamp(p3.y,0, img.get_height()))
+# pos2 = (p6).rotate(a) + image_position
+#
+# r_half1 = pygame.transform.rotate(half1, -a)
+# r_half2 = pygame.transform.rotate(half2, -a)
+#
+# return r_half1, r_half2, pos1, pos2
+
+
+# def split_image(image, angle, pos1, pos2, image_position):
+# pos1 = pygame.Vector2(pos1) - image_position + pygame.Vector2(image.get_width() / 2, image.get_height() / 2)
+# pos2 = pygame.Vector2(pos2) - image_position + pygame.Vector2(image.get_width() / 2, image.get_height() / 2)
+#
+# if pos1.x == pos2.x:
+# pos2.x += 0.0001
+#
+# img = image.copy()
+#
+# center = pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+#
+# # pygame.draw.circle(img, BLACK, center, 5)
+# # pygame.draw.circle(img, RED, pos1, 15)
+# # pygame.draw.circle(img, GREEN, pos2, 15)
+# # pygame.draw.line(img, BLACK, pos1, pos2, 5)
+#
+# a = math.degrees(math.atan((pos2.y - pos1.y) / (pos2.x - pos1.x)))
+# img = rotate_center(img, a, pygame.Vector2(0, 0))[0]
+# p1 = (pos1 - center).rotate(-a) + img.get_rect().center
+# p2 = (pos2 - center).rotate(-a) + img.get_rect().center
+#
+# # pygame.draw.circle(img, BLACK, center, 5)
+# # pygame.draw.circle(img, BLUE, p1, 15)
+# # pygame.draw.circle(img, WHITE, p2, 15)
+#
+# rot_center = pygame.Vector2(img.get_width() / 2, img.get_height() / 2)
+# p3 = (-rot_center.copy()).rotate(-a) + center
+# p4 = pygame.Vector2(-image.get_width() / 2, 0).rotate(-a) + center
+#
+# half1 = pygame.transform.rotate(img.subsurface(pygame.Rect(0, 0, img.get_width(), min(p1.y, img.get_height()))),
+# -a).convert_alpha()
+# half2 = pygame.transform.rotate(
+# img.subsurface(pygame.Rect(0, min(p1.y, img.get_height()), img.get_width(), max(img.get_height() - p1.y, 0))),
+# -a).convert_alpha()
+# #
+# if 0 < a < 90:
+# p3, p4 = p4, p3
+#
+# half1, pos1 = rotate_center(half1, angle,
+# p3 + image_position + pygame.Vector2(half1.get_width() / 2, half1.get_height() / 2))
+# half2, pos2 = rotate_center(half2, angle,
+# p4 + image_position + pygame.Vector2(half2.get_width() / 2, half2.get_height() / 2))
+#
+# return half1, half2, pos1, pos2