diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5fd267b Binary files /dev/null and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/input_handler.cpython-311.pyc b/core/__pycache__/input_handler.cpython-311.pyc new file mode 100644 index 0000000..186a1c5 Binary files /dev/null and b/core/__pycache__/input_handler.cpython-311.pyc differ diff --git a/core/__pycache__/lua_api.cpython-311.pyc b/core/__pycache__/lua_api.cpython-311.pyc new file mode 100644 index 0000000..c8f2fe8 Binary files /dev/null and b/core/__pycache__/lua_api.cpython-311.pyc differ diff --git a/core/__pycache__/renderer.cpython-311.pyc b/core/__pycache__/renderer.cpython-311.pyc new file mode 100644 index 0000000..b3d8856 Binary files /dev/null and b/core/__pycache__/renderer.cpython-311.pyc differ diff --git a/core/input_handler.py b/core/input_handler.py new file mode 100644 index 0000000..b7e05dc --- /dev/null +++ b/core/input_handler.py @@ -0,0 +1,15 @@ +# engine/input_handler.py +import pygame + +class InputHandler: + def __init__(self): + self.keys = {} + + def process_event(self, event): + if event.type == pygame.KEYDOWN: + self.keys[event.key] = True + elif event.type == pygame.KEYUP: + self.keys[event.key] = False + + def is_key_pressed(self, key): + return self.keys.get(key, False) diff --git a/core/lua_api.py b/core/lua_api.py new file mode 100644 index 0000000..64ee9c6 --- /dev/null +++ b/core/lua_api.py @@ -0,0 +1,64 @@ +# engine/lua_api.py +import pygame + +class LuaAPI: + def __init__(self, renderer, input_handler): + self.renderer = renderer + self.input_handler = input_handler + + def get_api(self, lua): + # Expose functions to Lua + lua.globals()['cls'] = self.cls + lua.globals()['px'] = self.px + lua.globals()['spr'] = self.spr + lua.globals()['btn'] = self.btn + + def cls(self, color=0): + self.renderer.clear(color) + + def px(self, x, y, color=1): + self.renderer.draw_pixel(x, y, color) + + def spr(self, x, y, sprite, color=1): + """ + Draws a sprite at position (x, y). + + :param x: X-coordinate + :param y: Y-coordinate + :param sprite: 2D list representing the sprite + :param color: Color index + """ + # Convert Lua table to Python list if necessary + if hasattr(sprite, 'keys'): + # If sprite is a Lua table, convert it to a Python list + sprite = [sprite[i + 1] for i in range(len(sprite))] + self.renderer.draw_sprite(x, y, sprite, color) + + def btn(self, button): + """ + Checks if a specific button is pressed. + + Button Mapping: + 0: Left Arrow + 1: Right Arrow + 2: Up Arrow + 3: Down Arrow + 4: Z (Button A) + 5: X (Button B) + + :param button: Button index + :return: Boolean indicating if the button is pressed + """ + # Map button indices to Pygame keys + key_map = { + 0: pygame.K_LEFT, + 1: pygame.K_RIGHT, + 2: pygame.K_UP, + 3: pygame.K_DOWN, + 4: pygame.K_z, # Button A + 5: pygame.K_x, # Button B + } + key = key_map.get(button) + if key: + return self.input_handler.is_key_pressed(key) + return False diff --git a/core/renderer.py b/core/renderer.py new file mode 100644 index 0000000..97c901e --- /dev/null +++ b/core/renderer.py @@ -0,0 +1,50 @@ +# engine/renderer.py +import pygame + +class Renderer: + def __init__(self, surface): + self.surface = surface + # Define a simple 8-bit palette (e.g., NES palette) + self.palette = [ + (84, 84, 84), # Color 0 + (0, 30, 116), # Color 1 + (8, 16, 144), # Color 2 + (48, 0, 136), # Color 3 + (68, 0, 100), # Color 4 + (92, 0, 48), # Color 5 + (84, 4, 0), # Color 6 + (60, 24, 0), # Color 7 + (32, 42, 0), # Color 8 + (8, 58, 0), # Color 9 + (0, 64, 0), # Color 10 + (0, 60, 0), # Color 11 + (0, 50, 60), # Color 12 + (0, 0, 0), # Color 13 + (0, 0, 0), # Color 14 + (0, 0, 0), # Color 15 + ] + + def clear(self, color=0): + if 0 <= color < len(self.palette): + self.surface.fill(self.palette[color]) + else: + self.surface.fill(self.palette[0]) # Default to background color + + def draw_pixel(self, x, y, color_index): + if 0 <= color_index < len(self.palette): + if 0 <= x < self.surface.get_width() and 0 <= y < self.surface.get_height(): + self.surface.set_at((int(x), int(y)), self.palette[color_index]) + + def draw_sprite(self, x, y, sprite, color_index=1): + """ + Draws a sprite at position (x, y). + + :param x: X-coordinate + :param y: Y-coordinate + :param sprite: 2D list representing the sprite + :param color_index: Color index to use for the sprite + """ + for row_idx, row in enumerate(sprite): + for col_idx, pixel in enumerate(row): + if pixel: + self.draw_pixel(x + col_idx, y + row_idx, color_index) diff --git a/main.py b/main.py new file mode 100644 index 0000000..1ef49fe --- /dev/null +++ b/main.py @@ -0,0 +1,89 @@ +# main.py +import pygame +from core.lua_api import LuaAPI +from core.renderer import Renderer +from core.input_handler import InputHandler +from lupa import LuaRuntime +import os +import sys + +# Constants for 8-bit style +SCREEN_WIDTH = 160 +SCREEN_HEIGHT = 144 +SCALE_FACTOR = 5 # Scale up for visibility +FPS = 60 + +def main(): + pygame.init() + pygame.display.set_caption("8-Bit Python Engine") + window = pygame.display.set_mode((SCREEN_WIDTH * SCALE_FACTOR, SCREEN_HEIGHT * SCALE_FACTOR)) + clock = pygame.time.Clock() + + # Create a surface for the 8-bit display + display_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT)) + + # Initialize components + renderer = Renderer(display_surface) + input_handler = InputHandler() + lua = LuaRuntime(unpack_returned_tuples=True) + api = LuaAPI(renderer, input_handler) + api.get_api(lua) # Expose API to Lua + + # Load Lua game script + script_path = os.path.join("scripts", "game.lua") + if not os.path.exists(script_path): + print(f"Error: Lua script '{script_path}' not found.") + pygame.quit() + sys.exit(1) + + with open(script_path, "r") as f: + lua_script = f.read() + + try: + lua.execute(lua_script) + except Exception as e: + print(f"Error executing Lua script: {e}") + pygame.quit() + sys.exit(1) + + # Retrieve update and draw functions from Lua + lua_globals = lua.globals() + if not hasattr(lua_globals, 'update') or not hasattr(lua_globals, 'draw'): + print("Error: Lua script must define 'update' and 'draw' functions.") + pygame.quit() + sys.exit(1) + + update = lua_globals.update + draw = lua_globals.draw + + running = True + while running: + delta_time = clock.tick(FPS) / 1000.0 # Seconds since last frame + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + input_handler.process_event(event) + + try: + # Update game logic via Lua + update(delta_time) + + # Clear the display + renderer.clear() + + # Draw game elements via Lua + draw() + except Exception as e: + print(f"Error during Lua execution: {e}") + running = False + + # Scale and blit to the window + scaled_surface = pygame.transform.scale(display_surface, (SCREEN_WIDTH * SCALE_FACTOR, SCREEN_HEIGHT * SCALE_FACTOR)) + window.blit(scaled_surface, (0, 0)) + pygame.display.flip() + + pygame.quit() + +if __name__ == "__main__": + main() diff --git a/scripts/game.lua b/scripts/game.lua new file mode 100644 index 0000000..90c5b04 --- /dev/null +++ b/scripts/game.lua @@ -0,0 +1,70 @@ +-- scripts/game.lua + +-- Define a simple sprite (e.g., 8x8 pixel player) +player_sprite = { + {0,1,1,0,0,1,1,0}, + {1,0,0,1,1,0,0,1}, + {1,0,0,1,1,0,0,1}, + {0,1,1,0,0,1,1,0}, + {0,1,1,0,0,1,1,0}, + {1,0,0,1,1,0,0,1}, + {1,0,0,1,1,0,0,1}, + {0,1,1,0,0,1,1,0}, +} + +-- Define a simple enemy sprite +enemy_sprite = { + {2,2,2,2,2,2,2,2}, + {2,0,0,0,0,0,0,2}, + {2,0,2,2,2,2,0,2}, + {2,0,2,0,0,2,0,2}, + {2,0,2,2,2,2,0,2}, + {2,0,0,0,0,0,0,2}, + {2,2,2,2,2,2,2,2}, + {0,0,0,0,0,0,0,0}, +} + +player_x = 72 +player_y = 68 +player_speed = 60 -- pixels per second + +enemy_x = 40 +enemy_y = 40 +enemy_speed = 30 -- pixels per second + +function update(dt) + -- Player Movement + if btn(0) then -- Left + player_x = player_x - player_speed * dt + end + if btn(1) then -- Right + player_x = player_x + player_speed * dt + end + if btn(2) then -- Up + player_y = player_y - player_speed * dt + end + if btn(3) then -- Down + player_y = player_y + player_speed * dt + end + + -- Enemy Movement: simple back and forth + enemy_x = enemy_x + enemy_speed * dt + if enemy_x > 120 then + enemy_speed = -enemy_speed + elseif enemy_x < 40 then + enemy_speed = -enemy_speed + end +end + +function draw() + -- Draw Player + spr(math.floor(player_x), math.floor(player_y), player_sprite, 11) + + -- Draw Enemy + spr(math.floor(enemy_x), math.floor(enemy_y), enemy_sprite, 9) + + -- Draw some pixels as obstacles or decorations + px(80, 72, 5) + px(81, 72, 5) + px(82, 72, 5) +end