Projeto iniciado e finalizado

This commit is contained in:
LucasKalil-Programador 2024-07-22 17:07:41 -03:00
commit eda1f9f891
10 changed files with 565 additions and 0 deletions

185
.gitignore vendored Normal file
View file

@ -0,0 +1,185 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# 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/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
### VirtualEnv template
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
.venv
pip-selfcheck.json
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# idea folder, uncomment if you don't need it
# .idea

3
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/SandBoxPyGame.iml generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (SandBoxPyGame)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (SandBoxPyGame)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (SandBoxPyGame)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/SandBoxPyGame.iml" filepath="$PROJECT_DIR$/.idea/SandBoxPyGame.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

190
SandBox.py Normal file
View file

@ -0,0 +1,190 @@
import random
from enum import Enum
import pygame
import numpy as np
from numba import njit
class Types(Enum):
BG = (0, 0, 0, 0, 0)
SAND = (203, 189, 147, 1, 0)
WATER = (28, 163, 236, 2, 0)
STONE = (115, 112, 112, 3, 0)
VACUUM = (20, 20, 20, 4, 0)
CLONER = (108, 60, 12, 5, 0)
@njit
def run_physics(data, size):
if random.random() > 0.5:
data = data[::-1]
for x in range(len(data)):
skip_y = False
for y in range(len(data[x])):
if skip_y or y + 1 >= size[1]:
skip_y = False
elif data[x, y, 3] == Types.SAND.value[3]:
skip_y = sand_physics(data, size, x, y)
elif data[x, y, 3] == Types.WATER.value[3]:
skip_y = water_physics(data, size, x, y)
elif data[x, y, 3] == Types.VACUUM.value[3]:
skip_y = vacuum_physics(data, size, x, y)
elif data[x, y, 3] == Types.CLONER.value[3]:
skip_y = cloner_physics(data, size, x, y)
@njit
def cloner_physics(data, size, x, y):
adjacent = ((x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1))
type_id = data[x, y, 4]
for adj_x, adj_y in adjacent:
if adj_x < 0 or adj_y < 0 or adj_x >= size[0] or adj_y >= size[1]:
continue
if type_id == Types.BG.value[3]:
if data[adj_x, adj_y, 3] not in (Types.CLONER.value[3], Types.BG.value[3], Types.VACUUM.value[3]):
data[x, y, 4] = data[adj_x, adj_y, 3]
else:
if data[adj_x, adj_y, 3] == Types.BG.value[3]:
type_b = Types.BG
if data[x, y, 4] == Types.SAND.value[3]:
type_b = Types.SAND
elif data[x, y, 4] == Types.WATER.value[3]:
type_b = Types.WATER
elif data[x, y, 4] == Types.STONE.value[3]:
type_b = Types.STONE
if type_b.value[3] in (Types.SAND.value[3], Types.WATER.value[3], Types.STONE.value[3]):
spawn_pixel(data, adj_x, adj_y, color=type_b.value, cmod=10)
elif type_b.value[3] in (Types.BG.value[3], Types.VACUUM.value[3], Types.CLONER.value[3]):
spawn_pixel(data, adj_x, adj_y, color=type_b.value)
return False
@njit
def vacuum_physics(data, size, x, y):
adjacent = ((x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1))
for adj_x, adj_y in adjacent:
if adj_x < 0 or adj_y < 0 or adj_x >= size[0] or adj_y >= size[1]:
continue
if data[adj_x, adj_y, 3] not in (Types.VACUUM.value[3], Types.BG.value[3]):
spawn_pixel(data, adj_x, adj_y, color=Types.BG.value)
return False
@njit
def sand_physics(data, size, x, y):
# region Gravity
if data[x, y + 1, 3] == Types.BG.value[3]:
data[x, y + 1] = data[x, y]
data[x, y] = Types.BG.value
return True
# endregion Gravity
# region Drop down left or right
random_side = -1 if random.random() > 0.5 else 1
if x + random_side < 0 or x + random_side >= size[0]:
random_side *= -1
if data[x + random_side, y + 1, 3] in (Types.BG.value[3], Types.WATER.value[3]):
data[x + random_side, y + 1], data[x, y] = data[x, y], data[x + random_side, y + 1].copy()
return False
random_side *= -1
if x + random_side < 0 or x + random_side >= size[0]:
return False
if data[x + random_side * -1, y + 1, 3] in (Types.BG.value[3], Types.WATER.value[3]):
data[x + random_side * -1, y + 1], data[x, y] = data[x, y], data[x + random_side * -1, y + 1].copy()
return False
# endregion Drop down left or right
return False
@njit
def water_physics(data, size, x, y):
# region Gravity
if data[x, y + 1, 3] == Types.BG.value[3]:
data[x, y + 1], data[x, y] = data[x, y], Types.BG.value
return True
# endregion Gravity
# region Drop down left or Right
random_side = -1 if random.random() > 0.5 else 1
if x + random_side < 0 or x + random_side >= size[0]:
random_side *= -1
if data[x + random_side, y + 1, 3] == Types.BG.value[3]:
data[x + random_side, y + 1], data[x, y] = data[x, y], Types.BG.value
return False
# endregion Drop down left or Right
# region Sand fall over water
if y - 1 > 0 and data[x, y - 1, 3] == Types.SAND.value[3]:
data[x, y - 1], data[x, y] = data[x, y], data[x, y - 1].copy()
return True
# endregion Sand fall over water
# region Moving horizontal
max_steps = size[0]
if data[x, y + 1][3] != Types.BG.value[3]:
for new_x in range(x + 1, min(x + max_steps, size[0])):
if data[new_x, y, 3] == Types.BG.value[3]:
if data[new_x, y + 1, 3] == Types.BG.value[3]:
data[new_x, y], data[x, y] = data[x, y], Types.BG.value
return False
else:
break
for new_x in range(x - 1, max(x - max_steps, 0), -1):
if data[new_x, y, 3] == Types.BG.value[3]:
if data[new_x, y + 1, 3] == Types.BG.value[3]:
data[new_x, y], data[x, y] = data[x, y], Types.BG.value
return False
else:
break
# endregion Moving horizontal
return False
@njit
def spawn_pixel(data, x, y, color=Types.BG.value, cmod=0):
color = (color[0] + random.randint(-cmod, cmod),
color[1] + random.randint(-cmod, cmod),
color[2] + random.randint(-cmod, cmod),
color[3], color[4])
data[x, y] = color
@njit
def spawn(data, size, center, radius, type_b=Types.BG):
for x in range(center[0] - radius, center[0] + radius):
for y in range(center[1] - radius, center[1] + radius):
if y < 0 or x < 0 or y >= size[1] or x >= size[0]:
continue
if (y - center[1]) ** 2 + (x - center[0]) ** 2 <= radius ** 2 and data[x, y, 3] != type_b.value[3]:
if type_b.value[3] in (Types.SAND.value[3], Types.WATER.value[3], Types.STONE.value[3]):
spawn_pixel(data, x, y, color=type_b.value, cmod=10)
elif type_b.value[3] in (Types.BG.value[3], Types.VACUUM.value[3], Types.CLONER.value[3]):
spawn_pixel(data, x, y, color=type_b.value)
class SandBox:
def __init__(self, size: tuple[int, int]):
self.__size = size
self.__data: np.ndarray = np.empty(0)
self.reset()
self.__surface = pygame.Surface(size)
self.update_surface()
def spawn(self, center, radius, type_b=Types.BG):
spawn(self.__data, self.__size, center, radius, type_b)
def run_physics(self):
run_physics(self.__data, self.__size)
def reset(self):
self.__data = np.full((self.__size[0], self.__size[1], 5), Types.BG.value, dtype=np.uint8)
def update_surface(self):
pygame.surfarray.blit_array(self.__surface, self.__data[:, :, :3])
def get_surface(self):
self.update_surface()
return self.__surface

150
main.py Normal file
View file

@ -0,0 +1,150 @@
import random
import threading
import time
import pygame
import sys
import SandBox
def handle_actions(keys_pressed: dict[any, bool], on_key_click):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
keys_pressed[event.key] = True
elif event.type == pygame.KEYUP:
if keys_pressed.get(event.key, False):
keys_pressed[event.key] = False
on_key_click(event)
class Game:
def __init__(self, size=(160, 160), scale=4.0, fps=60, font_size=30, test_mode=False):
# Start variables
self.performance_str = None
self.size, self.scale, self.fps = size, scale, fps
self.selected_item, self.spawn_radius = SandBox.Types.SAND, 10
self.screen_size = (self.size[0] * self.scale, self.size[1] * self.scale)
self.test_mode, self.performance_data, self.performance_str = test_mode, {}, ""
self.paused = False
self.keys_pressed = {}
# Start
self.sandBox = SandBox.SandBox(self.size)
# Start pygame
pygame.init()
self.screen = pygame.display.set_mode(self.screen_size, pygame.RESIZABLE)
pygame.display.set_caption("Sand box game")
# Start pygame utils
self.font = pygame.font.Font(None, font_size)
self.fps_clock = pygame.time.Clock()
def handle_key_inputs(self, event):
key_to_item = {
pygame.K_1: SandBox.Types.BG,
pygame.K_2: SandBox.Types.SAND,
pygame.K_3: SandBox.Types.WATER,
pygame.K_4: SandBox.Types.STONE,
pygame.K_5: SandBox.Types.VACUUM,
pygame.K_6: SandBox.Types.CLONER,
}
if event.key == pygame.K_KP_PLUS and self.spawn_radius < min(self.size[0], self.size[1]):
self.spawn_radius += 10 if pygame.key.get_pressed()[pygame.K_LCTRL] else 1
elif event.key == pygame.K_KP_MINUS and self.spawn_radius > 1:
self.spawn_radius -= 10 if pygame.key.get_pressed()[pygame.K_LCTRL] else 1
elif event.key == pygame.K_p:
self.paused = not self.paused
elif event.key == pygame.K_r:
self.performance_data = {}
self.selected_item = SandBox.Types.SAND
self.spawn_radius = 10
self.sandBox.reset()
elif event.key in key_to_item:
for key, item in key_to_item.items():
if key == event.key:
self.selected_item = item
break
def handle_mouse_inputs(self):
buttons_pressed = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
if buttons_pressed[0]:
pos = (int(mouse_pos[0] / self.scale), int(mouse_pos[1] / self.scale))
self.sandBox.spawn(pos, self.spawn_radius, type_b=self.selected_item)
def perform_random_spawn(self):
type_b = random.choice(list(SandBox.Types))
x, y = random.randint(0, self.size[0]), random.randint(0, self.size[1])
self.sandBox.spawn((x, y), self.spawn_radius, type_b)
def performance_monitor(self):
current_fps = int(self.fps_clock.get_fps())
if current_fps in self.performance_data:
self.performance_data[current_fps] = self.performance_data[current_fps] + 1
else:
self.performance_data[current_fps] = 1
fps_sum, occurrences_count = 0, 0
for fps, count in self.performance_data.items():
occurrences_count += count
fps_sum += count * fps
sorted_keys = sorted(self.performance_data.keys())
self.performance_str = f"MIN: {sorted_keys[0]}, MAX: {sorted_keys[-1]}, AVG: {fps_sum // occurrences_count}"
def run(self):
self.__loading()
while True:
handle_actions(self.keys_pressed, self.handle_key_inputs)
self.handle_mouse_inputs()
if self.test_mode and not self.paused:
self.performance_monitor()
self.perform_random_spawn()
self.__render_sand_box()
self.__render_gui()
pygame.display.flip()
self.fps_clock.tick(self.fps)
def __loading(self):
# First run to force numba compilation
thread1 = threading.Thread(target=self.sandBox.spawn, args=((0, 0), 10))
thread2 = threading.Thread(target=self.sandBox.run_physics)
thread1.start()
thread2.start()
while thread1.is_alive() or thread2.is_alive():
for i in range(4):
self.__render_message("Loading" + "".ljust(i, "."), (50, 255, 50))
time.sleep(.25)
def __render_message(self, text="", color=(0, 0, 0)):
fps_text = self.font.render(text, True, color)
self.screen.fill((0, 0, 0))
self.screen.blit(fps_text, (5, 5))
pygame.display.flip()
def __render_sand_box(self):
if not self.paused:
self.sandBox.run_physics()
sb_surface = pygame.transform.scale(self.sandBox.get_surface(), self.screen_size)
self.screen.blit(sb_surface, (0, 0))
def __render_gui(self):
fps_str = f'Item: {self.selected_item.name}, Pencil size: {self.spawn_radius}, Fps: {int(self.fps_clock.get_fps())} '
if self.test_mode:
fps_str += self.performance_str
fps_text = self.font.render(fps_str, True, (50, 255, 50))
self.screen.blit(fps_text, (5, 5))
if __name__ == '__main__':
game = Game(size=(1000, 1000), fps=300, scale=1, test_mode=True)
game.run()

0
readme.md Normal file
View file