diff --git a/pedal/sandbox/library/__init__.py b/pedal/sandbox/library/__init__.py index 64bfe1db..df17c7a7 100644 --- a/pedal/sandbox/library/__init__.py +++ b/pedal/sandbox/library/__init__.py @@ -3,3 +3,5 @@ from pedal.sandbox.library.turtles import MockTurtle from pedal.sandbox.library.microbit import MockMicrobit from pedal.sandbox.library.drafter_library import MockDrafter +from pedal.sandbox.library.arcade import MockArcade +from pedal.sandbox.library.pygame import MockPygame diff --git a/pedal/sandbox/library/arcade.py b/pedal/sandbox/library/arcade.py new file mode 100644 index 00000000..164a9b0c --- /dev/null +++ b/pedal/sandbox/library/arcade.py @@ -0,0 +1,352 @@ +from pedal.sandbox.mocked import MockModule + + +class MockArcade(MockModule): + """ + Mock Arcade library that can be used to capture game development data. + + Arcade is a modern Python library for creating 2D games, designed to be + more beginner-friendly and Pythonic than Pygame. This mock captures + common game development patterns for educational purposes. + + Attributes: + window (dict): Information about the game window + sprites (list): List of sprites created + sounds (list): List of sounds played + scenes (list): List of scenes created + draw_commands (list): List of drawing commands executed + physics_engines (list): List of physics engines created + """ + + def __init__(self): + super().__init__() + self._reset_game_state() + + def _reset_game_state(self): + """Reset all game state tracking""" + self.window = { + 'width': 800, + 'height': 600, + 'title': 'Arcade Window', + 'background_color': (255, 255, 255), + 'open': False + } + self.sprites = [] + self.sounds = [] + self.scenes = [] + self.draw_commands = [] + self.physics_engines = [] + self.textures = [] + self.current_view = None + self.delta_time = 1/60 # Default 60 FPS + + # Window Management + def open_window(self, width, height, title, resizable=False, **kwargs): + """Open a game window""" + self.window.update({ + 'width': width, + 'height': height, + 'title': title, + 'resizable': resizable, + 'open': True, + 'kwargs': kwargs + }) + + def set_background_color(self, color): + """Set the background color""" + self.window['background_color'] = color + + def close_window(self): + """Close the game window""" + self.window['open'] = False + + # Drawing Functions + def start_render(self): + """Start the rendering process""" + self.draw_commands.append({'type': 'start_render'}) + + def finish_render(self): + """Finish the rendering process""" + self.draw_commands.append({'type': 'finish_render'}) + + def draw_circle_filled(self, center_x, center_y, radius, color): + """Draw a filled circle""" + self.draw_commands.append({ + 'type': 'circle_filled', + 'center_x': center_x, + 'center_y': center_y, + 'radius': radius, + 'color': color + }) + + def draw_circle_outline(self, center_x, center_y, radius, color, border_width=1): + """Draw a circle outline""" + self.draw_commands.append({ + 'type': 'circle_outline', + 'center_x': center_x, + 'center_y': center_y, + 'radius': radius, + 'color': color, + 'border_width': border_width + }) + + def draw_rectangle_filled(self, center_x, center_y, width, height, color, tilt_angle=0): + """Draw a filled rectangle""" + self.draw_commands.append({ + 'type': 'rectangle_filled', + 'center_x': center_x, + 'center_y': center_y, + 'width': width, + 'height': height, + 'color': color, + 'tilt_angle': tilt_angle + }) + + def draw_rectangle_outline(self, center_x, center_y, width, height, color, border_width=1, tilt_angle=0): + """Draw a rectangle outline""" + self.draw_commands.append({ + 'type': 'rectangle_outline', + 'center_x': center_x, + 'center_y': center_y, + 'width': width, + 'height': height, + 'color': color, + 'border_width': border_width, + 'tilt_angle': tilt_angle + }) + + def draw_line(self, start_x, start_y, end_x, end_y, color, line_width=1): + """Draw a line""" + self.draw_commands.append({ + 'type': 'line', + 'start_x': start_x, + 'start_y': start_y, + 'end_x': end_x, + 'end_y': end_y, + 'color': color, + 'line_width': line_width + }) + + def draw_text(self, text, start_x, start_y, color, font_size=12, **kwargs): + """Draw text""" + self.draw_commands.append({ + 'type': 'text', + 'text': text, + 'start_x': start_x, + 'start_y': start_y, + 'color': color, + 'font_size': font_size, + 'kwargs': kwargs + }) + + # Sprite Management + def Sprite(self, filename=None, scale=1.0, **kwargs): + """Create a sprite mock""" + sprite_data = { + 'type': 'sprite', + 'filename': filename, + 'scale': scale, + 'center_x': 0, + 'center_y': 0, + 'change_x': 0, + 'change_y': 0, + 'angle': 0, + 'kwargs': kwargs + } + self.sprites.append(sprite_data) + return MockSprite(sprite_data) + + def SpriteList(self, **kwargs): + """Create a sprite list mock""" + sprite_list_data = { + 'type': 'sprite_list', + 'sprites': [], + 'kwargs': kwargs + } + return MockSpriteList(sprite_list_data) + + # Physics + def PhysicsEngineSimple(self, player_sprite, walls, **kwargs): + """Create a simple physics engine mock""" + physics_data = { + 'type': 'physics_simple', + 'player_sprite': player_sprite, + 'walls': walls, + 'kwargs': kwargs + } + self.physics_engines.append(physics_data) + return MockPhysicsEngine(physics_data) + + # Sound + def load_sound(self, filename): + """Load a sound file""" + sound_data = { + 'type': 'load_sound', + 'filename': filename + } + self.sounds.append(sound_data) + return MockSound(sound_data) + + def play_sound(self, sound, **kwargs): + """Play a sound""" + self.sounds.append({ + 'type': 'play_sound', + 'sound': sound, + 'kwargs': kwargs + }) + + # Game Loop and Events + def run(self): + """Run the game loop""" + self.draw_commands.append({'type': 'run_game'}) + + def schedule(self, function, interval): + """Schedule a function to run at intervals""" + self.draw_commands.append({ + 'type': 'schedule', + 'function': function.__name__ if hasattr(function, '__name__') else str(function), + 'interval': interval + }) + + # Utility Functions + def check_for_collision(self, sprite1, sprite2): + """Check if two sprites collide""" + return False # Mock collision detection + + def check_for_collision_with_list(self, sprite, sprite_list): + """Check collision between sprite and sprite list""" + return [] # Mock collision detection + + def get_sprites_at_point(self, point, sprite_list): + """Get sprites at a specific point""" + return [] # Mock point detection + + # Color constants (common colors used in educational projects) + @property + def color(self): + """Mock color module""" + return MockColor() + + def _generate_patches(self): + """Generate patches for the arcade module""" + return { + 'open_window': self.open_window, + 'set_background_color': self.set_background_color, + 'close_window': self.close_window, + 'start_render': self.start_render, + 'finish_render': self.finish_render, + 'draw_circle_filled': self.draw_circle_filled, + 'draw_circle_outline': self.draw_circle_outline, + 'draw_rectangle_filled': self.draw_rectangle_filled, + 'draw_rectangle_outline': self.draw_rectangle_outline, + 'draw_line': self.draw_line, + 'draw_text': self.draw_text, + 'Sprite': self.Sprite, + 'SpriteList': self.SpriteList, + 'PhysicsEngineSimple': self.PhysicsEngineSimple, + 'load_sound': self.load_sound, + 'play_sound': self.play_sound, + 'run': self.run, + 'schedule': self.schedule, + 'check_for_collision': self.check_for_collision, + 'check_for_collision_with_list': self.check_for_collision_with_list, + 'get_sprites_at_point': self.get_sprites_at_point, + 'color': self.color, + } + + +class MockSprite: + """Mock arcade sprite""" + def __init__(self, data): + self.data = data + + @property + def center_x(self): + return self.data['center_x'] + + @center_x.setter + def center_x(self, value): + self.data['center_x'] = value + + @property + def center_y(self): + return self.data['center_y'] + + @center_y.setter + def center_y(self, value): + self.data['center_y'] = value + + @property + def change_x(self): + return self.data['change_x'] + + @change_x.setter + def change_x(self, value): + self.data['change_x'] = value + + @property + def change_y(self): + return self.data['change_y'] + + @change_y.setter + def change_y(self, value): + self.data['change_y'] = value + + def update(self): + """Update sprite position""" + self.data['center_x'] += self.data['change_x'] + self.data['center_y'] += self.data['change_y'] + + +class MockSpriteList: + """Mock arcade sprite list""" + def __init__(self, data): + self.data = data + + def append(self, sprite): + """Add sprite to list""" + self.data['sprites'].append(sprite) + + def update(self): + """Update all sprites in list""" + for sprite in self.data['sprites']: + if hasattr(sprite, 'update'): + sprite.update() + + def draw(self): + """Draw all sprites in list""" + pass # Mock drawing + + +class MockPhysicsEngine: + """Mock arcade physics engine""" + def __init__(self, data): + self.data = data + + def update(self): + """Update physics""" + pass # Mock physics update + + +class MockSound: + """Mock arcade sound""" + def __init__(self, data): + self.data = data + + def play(self, **kwargs): + """Play the sound""" + pass # Mock sound playback + + +class MockColor: + """Mock arcade color constants""" + WHITE = (255, 255, 255) + BLACK = (0, 0, 0) + RED = (255, 0, 0) + GREEN = (0, 255, 0) + BLUE = (0, 0, 255) + YELLOW = (255, 255, 0) + PURPLE = (255, 0, 255) + CYAN = (0, 255, 255) + ORANGE = (255, 165, 0) + GRAY = (128, 128, 128) \ No newline at end of file diff --git a/pedal/sandbox/library/pygame.py b/pedal/sandbox/library/pygame.py new file mode 100644 index 00000000..8038e5e9 --- /dev/null +++ b/pedal/sandbox/library/pygame.py @@ -0,0 +1,416 @@ +from pedal.sandbox.mocked import MockModule + + +class MockPygame(MockModule): + """ + Mock Pygame library that can be used to capture game development data. + + Pygame is the classic Python library for creating 2D games. This mock captures + common game development patterns for educational purposes, focusing on the + most commonly used pygame functions in educational settings. + + Attributes: + display_info (dict): Information about the pygame display + events (list): List of events created + surfaces (list): List of surfaces created + sounds (list): List of sounds loaded/played + draw_calls (list): List of drawing operations + clock_ticks (list): List of clock tick operations + """ + + def __init__(self): + super().__init__() + self._reset_pygame_state() + + def _reset_pygame_state(self): + """Reset all pygame state tracking""" + self.display_info = { + 'initialized': False, + 'width': 0, + 'height': 0, + 'caption': '', + 'flip_count': 0 + } + self.events = [] + self.surfaces = [] + self.sounds = [] + self.draw_calls = [] + self.clock_ticks = [] + self.keys_pressed = {} + + # Core pygame functions + def init(self): + """Initialize pygame""" + self.display_info['initialized'] = True + return (6, 0) # Mock return: (successful, failed) + + def quit(self): + """Quit pygame""" + self.display_info['initialized'] = False + + # Display module + @property + def display(self): + """Mock display module""" + return MockPygameDisplay(self) + + # Event module + @property + def event(self): + """Mock event module""" + return MockPygameEvent(self) + + # Draw module + @property + def draw(self): + """Mock draw module""" + return MockPygameDraw(self) + + # Mixer module (sound) + @property + def mixer(self): + """Mock mixer module""" + return MockPygameMixer(self) + + # Time module + @property + def time(self): + """Mock time module""" + return MockPygameTime(self) + + # Key module + @property + def key(self): + """Mock key module""" + return MockPygameKey(self) + + # Mouse module + @property + def mouse(self): + """Mock mouse module""" + return MockPygameMouse(self) + + # Image module + @property + def image(self): + """Mock image module""" + return MockPygameImage(self) + + # Transform module + @property + def transform(self): + """Mock transform module""" + return MockPygameTransform(self) + + # Surface creation + def Surface(self, size, flags=0, depth=0): + """Create a surface mock""" + surface_data = { + 'type': 'surface', + 'size': size, + 'flags': flags, + 'depth': depth, + 'fills': [], + 'blits': [] + } + self.surfaces.append(surface_data) + return MockSurface(surface_data) + + # Color constants + @property + def Color(self): + """Mock Color class""" + return MockColor + + # Rectangle + @property + def Rect(self): + """Mock Rect class""" + return MockRect + + def _generate_patches(self): + """Generate patches for the pygame module""" + return { + 'init': self.init, + 'quit': self.quit, + 'display': self.display, + 'event': self.event, + 'draw': self.draw, + 'mixer': self.mixer, + 'time': self.time, + 'key': self.key, + 'mouse': self.mouse, + 'image': self.image, + 'transform': self.transform, + 'Surface': self.Surface, + 'Color': self.Color, + 'Rect': self.Rect, + } + + +class MockPygameDisplay: + """Mock pygame.display module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def set_mode(self, resolution, flags=0, depth=0): + """Set display mode""" + self.pygame_mock.display_info.update({ + 'width': resolution[0], + 'height': resolution[1], + 'flags': flags, + 'depth': depth + }) + surface_data = {'size': resolution, 'type': 'display_surface', 'fills': [], 'blits': []} + self.pygame_mock.surfaces.append(surface_data) + return MockSurface(surface_data) + + def flip(self): + """Flip the display""" + self.pygame_mock.display_info['flip_count'] += 1 + + def update(self, rectangle=None): + """Update display""" + pass + + def set_caption(self, title, icontitle=None): + """Set window caption""" + self.pygame_mock.display_info['caption'] = title + + def get_caption(self): + """Get window caption""" + return self.pygame_mock.display_info['caption'] + + +class MockPygameEvent: + """Mock pygame.event module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def get(self, eventtype=None): + """Get events""" + if eventtype is None: + events = self.pygame_mock.events[:] + self.pygame_mock.events.clear() + return events + else: + matching_events = [e for e in self.pygame_mock.events if e.type == eventtype] + self.pygame_mock.events = [e for e in self.pygame_mock.events if e.type != eventtype] + return matching_events + + def pump(self): + """Process events""" + pass + + +class MockPygameDraw: + """Mock pygame.draw module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def rect(self, surface, color, rect, width=0): + """Draw rectangle""" + self.pygame_mock.draw_calls.append({ + 'type': 'rect', + 'surface': surface, + 'color': color, + 'rect': rect, + 'width': width + }) + + def circle(self, surface, color, pos, radius, width=0): + """Draw circle""" + self.pygame_mock.draw_calls.append({ + 'type': 'circle', + 'surface': surface, + 'color': color, + 'pos': pos, + 'radius': radius, + 'width': width + }) + + def line(self, surface, color, start_pos, end_pos, width=1): + """Draw line""" + self.pygame_mock.draw_calls.append({ + 'type': 'line', + 'surface': surface, + 'color': color, + 'start_pos': start_pos, + 'end_pos': end_pos, + 'width': width + }) + + +class MockPygameMixer: + """Mock pygame.mixer module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def init(self): + """Initialize mixer""" + pass + + def Sound(self, filename): + """Create sound object""" + sound_data = { + 'type': 'sound', + 'filename': filename, + 'plays': 0 + } + self.pygame_mock.sounds.append(sound_data) + return MockSound(sound_data) + + +class MockPygameTime: + """Mock pygame.time module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def Clock(self): + """Create clock object""" + return MockClock(self.pygame_mock) + + +class MockPygameKey: + """Mock pygame.key module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def get_pressed(self): + """Get pressed keys""" + return self.pygame_mock.keys_pressed + + +class MockPygameMouse: + """Mock pygame.mouse module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def get_pos(self): + """Get mouse position""" + return (0, 0) + + def get_pressed(self): + """Get mouse button states""" + return (False, False, False) + + +class MockPygameImage: + """Mock pygame.image module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def load(self, filename): + """Load image""" + surface_data = { + 'type': 'image_surface', + 'filename': filename, + 'size': (32, 32) # Default size + } + self.pygame_mock.surfaces.append(surface_data) + return MockSurface(surface_data) + + +class MockPygameTransform: + """Mock pygame.transform module""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def scale(self, surface, size): + """Scale surface""" + return surface # Return same surface for simplicity + + +class MockSurface: + """Mock pygame Surface""" + def __init__(self, data): + self.data = data + + def fill(self, color): + """Fill surface with color""" + self.data.setdefault('fills', []).append(color) + + def blit(self, source, dest): + """Blit surface onto this surface""" + self.data.setdefault('blits', []).append({ + 'source': source, + 'dest': dest + }) + + def get_rect(self): + """Get surface rectangle""" + size = self.data.get('size', (0, 0)) + return MockRect(0, 0, size[0], size[1]) + + +class MockSound: + """Mock pygame Sound""" + def __init__(self, data): + self.data = data + + def play(self): + """Play sound""" + self.data['plays'] += 1 + + +class MockClock: + """Mock pygame Clock""" + def __init__(self, pygame_mock): + self.pygame_mock = pygame_mock + + def tick(self, framerate=0): + """Tick clock""" + self.pygame_mock.clock_ticks.append(framerate) + return 16 # Return mock milliseconds + + +class MockColor: + """Mock pygame Color constants""" + def __init__(self, r, g=None, b=None, a=255): + if g is None: + # Single argument - could be another color or string + if isinstance(r, str): + # Named color + color_map = { + 'red': (255, 0, 0), + 'green': (0, 255, 0), + 'blue': (0, 0, 255), + 'white': (255, 255, 255), + 'black': (0, 0, 0), + 'yellow': (255, 255, 0), + 'purple': (255, 0, 255), + 'cyan': (0, 255, 255) + } + r, g, b = color_map.get(r.lower(), (0, 0, 0)) + else: + # Grayscale + g = b = r + self.r, self.g, self.b, self.a = r, g, b, a + + def __iter__(self): + return iter((self.r, self.g, self.b, self.a)) + + def __getitem__(self, index): + return (self.r, self.g, self.b, self.a)[index] + + +class MockRect: + """Mock pygame Rect""" + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.width = width + self.height = height + + @property + def center(self): + return (self.x + self.width // 2, self.y + self.height // 2) + + @center.setter + def center(self, pos): + self.x = pos[0] - self.width // 2 + self.y = pos[1] - self.height // 2 + + def colliderect(self, other): + """Check collision with another rect""" + return False # Mock collision detection \ No newline at end of file diff --git a/pedal/sandbox/sandbox.py b/pedal/sandbox/sandbox.py index f9bbeb1c..650e992a 100644 --- a/pedal/sandbox/sandbox.py +++ b/pedal/sandbox/sandbox.py @@ -601,6 +601,8 @@ def reset_default_overrides(self): self.mock_module('designer', mocked.MockDesigner(), 'designer') self.mock_module('drafter', mocked.MockDrafter(), 'drafter') self.mock_module('microbit', mocked.MockMicrobit(), 'microbit') + self.mock_module('arcade', mocked.MockArcade(), 'arcade') + self.mock_module('pygame', mocked.MockPygame(), 'pygame') def mock_function(self, function_name, new_version): self._module_overrides['__builtins__'][function_name] = new_version diff --git a/pedal/types/library/arcade.py b/pedal/types/library/arcade.py new file mode 100644 index 00000000..be56256a --- /dev/null +++ b/pedal/types/library/arcade.py @@ -0,0 +1,68 @@ +from pedal.types.new_types import ModuleType, FunctionType, NoneType, ListType, BoolType, NumType, register_builtin_module + + +# Arcade Support +def load_arcade_module(): + """Load type definitions for the Arcade game library.""" + + # Color module types + _COLOR_MODULE = ModuleType('color', fields={ + 'WHITE': NumType, + 'BLACK': NumType, + 'RED': NumType, + 'GREEN': NumType, + 'BLUE': NumType, + 'YELLOW': NumType, + 'PURPLE': NumType, + 'CYAN': NumType, + 'ORANGE': NumType, + 'GRAY': NumType, + }) + + # Main arcade module types + _ARCADE_MODULE = ModuleType('arcade', fields={ + # Window management + 'open_window': FunctionType(name='open_window', returns=NoneType), + 'set_background_color': FunctionType(name='set_background_color', returns=NoneType), + 'close_window': FunctionType(name='close_window', returns=NoneType), + + # Rendering + 'start_render': FunctionType(name='start_render', returns=NoneType), + 'finish_render': FunctionType(name='finish_render', returns=NoneType), + + # Drawing functions + 'draw_circle_filled': FunctionType(name='draw_circle_filled', returns=NoneType), + 'draw_circle_outline': FunctionType(name='draw_circle_outline', returns=NoneType), + 'draw_rectangle_filled': FunctionType(name='draw_rectangle_filled', returns=NoneType), + 'draw_rectangle_outline': FunctionType(name='draw_rectangle_outline', returns=NoneType), + 'draw_line': FunctionType(name='draw_line', returns=NoneType), + 'draw_text': FunctionType(name='draw_text', returns=NoneType), + + # Sprite management + 'Sprite': FunctionType(name='Sprite', returns=ModuleType('Sprite')), + 'SpriteList': FunctionType(name='SpriteList', returns=ListType), + + # Physics + 'PhysicsEngineSimple': FunctionType(name='PhysicsEngineSimple', returns=ModuleType('PhysicsEngine')), + + # Sound + 'load_sound': FunctionType(name='load_sound', returns=ModuleType('Sound')), + 'play_sound': FunctionType(name='play_sound', returns=NoneType), + + # Game loop + 'run': FunctionType(name='run', returns=NoneType), + 'schedule': FunctionType(name='schedule', returns=NoneType), + + # Collision detection + 'check_for_collision': FunctionType(name='check_for_collision', returns=BoolType), + 'check_for_collision_with_list': FunctionType(name='check_for_collision_with_list', returns=ListType), + 'get_sprites_at_point': FunctionType(name='get_sprites_at_point', returns=ListType), + + # Color constants + 'color': _COLOR_MODULE, + }) + + return _ARCADE_MODULE + + +register_builtin_module('arcade', load_arcade_module) \ No newline at end of file diff --git a/pedal/types/library/pygame.py b/pedal/types/library/pygame.py new file mode 100644 index 00000000..05f91930 --- /dev/null +++ b/pedal/types/library/pygame.py @@ -0,0 +1,88 @@ +from pedal.types.new_types import ModuleType, FunctionType, NoneType, ListType, BoolType, NumType, register_builtin_module + + +# Pygame Support +def load_pygame_module(): + """Load type definitions for the Pygame game library.""" + + # Display module types + _DISPLAY_MODULE = ModuleType('display', fields={ + 'set_mode': FunctionType(name='set_mode', returns=ModuleType('Surface')), + 'flip': FunctionType(name='flip', returns=NoneType), + 'update': FunctionType(name='update', returns=NoneType), + 'set_caption': FunctionType(name='set_caption', returns=NoneType), + 'get_caption': FunctionType(name='get_caption', returns=str), + }) + + # Event module types + _EVENT_MODULE = ModuleType('event', fields={ + 'get': FunctionType(name='get', returns=ListType), + 'pump': FunctionType(name='pump', returns=NoneType), + }) + + # Draw module types + _DRAW_MODULE = ModuleType('draw', fields={ + 'rect': FunctionType(name='rect', returns=NoneType), + 'circle': FunctionType(name='circle', returns=NoneType), + 'line': FunctionType(name='line', returns=NoneType), + }) + + # Mixer module types + _MIXER_MODULE = ModuleType('mixer', fields={ + 'init': FunctionType(name='init', returns=NoneType), + 'Sound': FunctionType(name='Sound', returns=ModuleType('Sound')), + }) + + # Time module types + _TIME_MODULE = ModuleType('time', fields={ + 'Clock': FunctionType(name='Clock', returns=ModuleType('Clock')), + }) + + # Key module types + _KEY_MODULE = ModuleType('key', fields={ + 'get_pressed': FunctionType(name='get_pressed', returns=ListType), + }) + + # Mouse module types + _MOUSE_MODULE = ModuleType('mouse', fields={ + 'get_pos': FunctionType(name='get_pos', returns=ListType), + 'get_pressed': FunctionType(name='get_pressed', returns=ListType), + }) + + # Image module types + _IMAGE_MODULE = ModuleType('image', fields={ + 'load': FunctionType(name='load', returns=ModuleType('Surface')), + }) + + # Transform module types + _TRANSFORM_MODULE = ModuleType('transform', fields={ + 'scale': FunctionType(name='scale', returns=ModuleType('Surface')), + }) + + # Main pygame module types + _PYGAME_MODULE = ModuleType('pygame', fields={ + # Core functions + 'init': FunctionType(name='init', returns=NumType), + 'quit': FunctionType(name='quit', returns=NoneType), + + # Submodules + 'display': _DISPLAY_MODULE, + 'event': _EVENT_MODULE, + 'draw': _DRAW_MODULE, + 'mixer': _MIXER_MODULE, + 'time': _TIME_MODULE, + 'key': _KEY_MODULE, + 'mouse': _MOUSE_MODULE, + 'image': _IMAGE_MODULE, + 'transform': _TRANSFORM_MODULE, + + # Classes + 'Surface': FunctionType(name='Surface', returns=ModuleType('Surface')), + 'Color': FunctionType(name='Color', returns=ModuleType('Color')), + 'Rect': FunctionType(name='Rect', returns=ModuleType('Rect')), + }) + + return _PYGAME_MODULE + + +register_builtin_module('pygame', load_pygame_module) \ No newline at end of file diff --git a/tests/test_arcade_integration.py b/tests/test_arcade_integration.py new file mode 100644 index 00000000..54db09c8 --- /dev/null +++ b/tests/test_arcade_integration.py @@ -0,0 +1,185 @@ +""" +Test the Arcade mock integration with the sandbox system. +""" +import unittest +from textwrap import dedent +from tests.execution_helper import Execution, ExecutionTestCase, SUCCESS_MESSAGE + + +# Sample arcade code that might be used in educational settings +sample_arcade_code = dedent(''' +import arcade + +# Open window +arcade.open_window(800, 600, "My Game") +arcade.set_background_color(arcade.color.WHITE) + +# Start rendering +arcade.start_render() + +# Draw some shapes +arcade.draw_circle_filled(100, 100, 50, arcade.color.RED) +arcade.draw_rectangle_filled(200, 200, 100, 80, arcade.color.BLUE) +arcade.draw_text("Score: 100", 10, 580, arcade.color.BLACK, 16) + +# Finish rendering +arcade.finish_render() + +# Create a sprite +player = arcade.Sprite("player.png", 0.5) +player.center_x = 400 +player.center_y = 300 + +# Create sprite list +sprite_list = arcade.SpriteList() +sprite_list.append(player) + +# Load and play sound +coin_sound = arcade.load_sound("coin.wav") +arcade.play_sound(coin_sound) +''').strip() + + +class TestArcadeIntegration(ExecutionTestCase): + """Test Arcade mock integration with pedal sandbox""" + + def test_arcade_basic_execution(self): + """Test that basic arcade code executes without errors""" + with Execution(sample_arcade_code) as e: + # Should execute without error + pass + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_arcade_mock_captures_data(self): + """Test that the arcade mock captures drawing and game data""" + with Execution(sample_arcade_code) as e: + student = e.student + # Access the mocked arcade module + arcade_mock = student.modules.arcade + + # Check window creation + self.assertTrue(arcade_mock.window['open']) + self.assertEqual(arcade_mock.window['width'], 800) + self.assertEqual(arcade_mock.window['height'], 600) + self.assertEqual(arcade_mock.window['title'], "My Game") + + # Check drawing commands were captured + self.assertGreater(len(arcade_mock.draw_commands), 0) + + # Check specific draw commands + draw_types = [cmd['type'] for cmd in arcade_mock.draw_commands] + self.assertIn('start_render', draw_types) + self.assertIn('circle_filled', draw_types) + self.assertIn('rectangle_filled', draw_types) + self.assertIn('text', draw_types) + self.assertIn('finish_render', draw_types) + + # Check sprites were created + self.assertEqual(len(arcade_mock.sprites), 1) + + # Check sounds were loaded and played + self.assertEqual(len(arcade_mock.sounds), 2) # load_sound + play_sound + + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_arcade_sprite_interaction(self): + """Test sprite creation and manipulation""" + sprite_code = dedent(''' + import arcade + + # Create sprite + player = arcade.Sprite("player.png") + player.center_x = 100 + player.center_y = 200 + player.change_x = 5 + player.change_y = -3 + + # Update sprite position + player.update() + + # Check final position + final_x = player.center_x + final_y = player.center_y + + # Use the variables to avoid unused variable warnings + print(final_x, final_y) + ''').strip() + + with Execution(sprite_code) as e: + student = e.student + arcade_mock = student.modules.arcade + + # Verify sprite was created and updated correctly + self.assertEqual(len(arcade_mock.sprites), 1) + sprite_data = arcade_mock.sprites[0] + self.assertEqual(sprite_data['center_x'], 105) # 100 + 5 + self.assertEqual(sprite_data['center_y'], 197) # 200 + (-3) + + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_arcade_collision_detection(self): + """Test collision detection functionality""" + collision_code = dedent(''' + import arcade + + sprite1 = arcade.Sprite("sprite1.png") + sprite2 = arcade.Sprite("sprite2.png") + sprite_list = arcade.SpriteList() + + # Test collision functions + collision = arcade.check_for_collision(sprite1, sprite2) + collisions = arcade.check_for_collision_with_list(sprite1, sprite_list) + sprites_at_point = arcade.get_sprites_at_point((100, 100), sprite_list) + + # Use the variables to avoid unused variable warnings + print(collision, len(collisions), len(sprites_at_point)) + ''').strip() + + with Execution(collision_code) as e: + # Should execute without error + pass + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_arcade_physics_engine(self): + """Test physics engine creation""" + physics_code = dedent(''' + import arcade + + player = arcade.Sprite("player.png") + walls = arcade.SpriteList() + + physics_engine = arcade.PhysicsEngineSimple(player, walls) + physics_engine.update() + ''').strip() + + with Execution(physics_code) as e: + student = e.student + arcade_mock = student.modules.arcade + + # Verify physics engine was created + self.assertEqual(len(arcade_mock.physics_engines), 1) + self.assertEqual(arcade_mock.physics_engines[0]['type'], 'physics_simple') + + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_arcade_color_constants(self): + """Test color constant access""" + color_code = dedent(''' + import arcade + + white = arcade.color.WHITE + red = arcade.color.RED + blue = arcade.color.BLUE + + # Use the colors to avoid unused variable warnings + print(white, red, blue) + ''').strip() + + with Execution(color_code) as e: + # Should execute without error + pass + self.assertFeedback(e, SUCCESS_MESSAGE) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_arcade_mock.py b/tests/test_arcade_mock.py new file mode 100644 index 00000000..3cfafb38 --- /dev/null +++ b/tests/test_arcade_mock.py @@ -0,0 +1,190 @@ +""" +Test cases for the Arcade mock library. +""" +import unittest +from pedal.sandbox.library.arcade import MockArcade + + +class TestMockArcade(unittest.TestCase): + """Test the MockArcade implementation""" + + def setUp(self): + """Set up test fixtures""" + self.mock_arcade = MockArcade() + + def test_window_management(self): + """Test window creation and management""" + # Test opening window + self.mock_arcade.open_window(800, 600, "Test Window") + self.assertTrue(self.mock_arcade.window['open']) + self.assertEqual(self.mock_arcade.window['width'], 800) + self.assertEqual(self.mock_arcade.window['height'], 600) + self.assertEqual(self.mock_arcade.window['title'], "Test Window") + + # Test background color + self.mock_arcade.set_background_color((255, 0, 0)) + self.assertEqual(self.mock_arcade.window['background_color'], (255, 0, 0)) + + # Test closing window + self.mock_arcade.close_window() + self.assertFalse(self.mock_arcade.window['open']) + + def test_drawing_functions(self): + """Test drawing function tracking""" + # Test circle drawing + self.mock_arcade.draw_circle_filled(100, 100, 50, (255, 0, 0)) + self.mock_arcade.draw_circle_outline(200, 200, 30, (0, 255, 0), 2) + + # Test rectangle drawing + self.mock_arcade.draw_rectangle_filled(150, 150, 100, 80, (0, 0, 255)) + self.mock_arcade.draw_rectangle_outline(250, 250, 120, 90, (255, 255, 0), 3) + + # Test line drawing + self.mock_arcade.draw_line(0, 0, 100, 100, (255, 255, 255), 2) + + # Test text drawing + self.mock_arcade.draw_text("Hello World", 50, 50, (0, 0, 0), 16) + + # Verify commands were tracked + self.assertEqual(len(self.mock_arcade.draw_commands), 6) + + # Check specific draw command + circle_cmd = self.mock_arcade.draw_commands[0] + self.assertEqual(circle_cmd['type'], 'circle_filled') + self.assertEqual(circle_cmd['center_x'], 100) + self.assertEqual(circle_cmd['center_y'], 100) + self.assertEqual(circle_cmd['radius'], 50) + self.assertEqual(circle_cmd['color'], (255, 0, 0)) + + def test_sprite_creation(self): + """Test sprite creation and management""" + # Create sprite + sprite = self.mock_arcade.Sprite("player.png", 0.5) + self.assertEqual(len(self.mock_arcade.sprites), 1) + + # Test sprite properties + sprite.center_x = 100 + sprite.center_y = 200 + sprite.change_x = 5 + sprite.change_y = -3 + + self.assertEqual(sprite.center_x, 100) + self.assertEqual(sprite.center_y, 200) + self.assertEqual(sprite.change_x, 5) + self.assertEqual(sprite.change_y, -3) + + # Test sprite update + sprite.update() + self.assertEqual(sprite.center_x, 105) # 100 + 5 + self.assertEqual(sprite.center_y, 197) # 200 + (-3) + + def test_sprite_list(self): + """Test sprite list functionality""" + sprite_list = self.mock_arcade.SpriteList() + + # Create and add sprites + sprite1 = self.mock_arcade.Sprite("sprite1.png") + sprite2 = self.mock_arcade.Sprite("sprite2.png") + + sprite_list.append(sprite1) + sprite_list.append(sprite2) + + self.assertEqual(len(sprite_list.data['sprites']), 2) + + def test_physics_engine(self): + """Test physics engine creation""" + player = self.mock_arcade.Sprite("player.png") + walls = self.mock_arcade.SpriteList() + + physics_engine = self.mock_arcade.PhysicsEngineSimple(player, walls) + + self.assertEqual(len(self.mock_arcade.physics_engines), 1) + self.assertEqual(self.mock_arcade.physics_engines[0]['type'], 'physics_simple') + + def test_sound_functionality(self): + """Test sound loading and playing""" + # Load sound + sound = self.mock_arcade.load_sound("coin.wav") + self.assertEqual(len(self.mock_arcade.sounds), 1) + self.assertEqual(self.mock_arcade.sounds[0]['type'], 'load_sound') + self.assertEqual(self.mock_arcade.sounds[0]['filename'], "coin.wav") + + # Play sound + self.mock_arcade.play_sound(sound, volume=0.5) + self.assertEqual(len(self.mock_arcade.sounds), 2) + self.assertEqual(self.mock_arcade.sounds[1]['type'], 'play_sound') + + def test_game_loop_functions(self): + """Test game loop and scheduling""" + def dummy_function(): + pass + + # Test run + self.mock_arcade.run() + self.assertEqual(self.mock_arcade.draw_commands[-1]['type'], 'run_game') + + # Test schedule + self.mock_arcade.schedule(dummy_function, 1/60) + schedule_cmd = self.mock_arcade.draw_commands[-1] + self.assertEqual(schedule_cmd['type'], 'schedule') + self.assertEqual(schedule_cmd['function'], 'dummy_function') + self.assertEqual(schedule_cmd['interval'], 1/60) + + def test_collision_detection(self): + """Test collision detection functions""" + sprite1 = self.mock_arcade.Sprite("sprite1.png") + sprite2 = self.mock_arcade.Sprite("sprite2.png") + sprite_list = self.mock_arcade.SpriteList() + + # Test collision between sprites (mock returns False) + collision = self.mock_arcade.check_for_collision(sprite1, sprite2) + self.assertFalse(collision) + + # Test collision with list (mock returns empty list) + collisions = self.mock_arcade.check_for_collision_with_list(sprite1, sprite_list) + self.assertEqual(collisions, []) + + # Test sprites at point (mock returns empty list) + sprites_at_point = self.mock_arcade.get_sprites_at_point((100, 100), sprite_list) + self.assertEqual(sprites_at_point, []) + + def test_color_constants(self): + """Test color constant access""" + color_module = self.mock_arcade.color + self.assertEqual(color_module.WHITE, (255, 255, 255)) + self.assertEqual(color_module.BLACK, (0, 0, 0)) + self.assertEqual(color_module.RED, (255, 0, 0)) + self.assertEqual(color_module.GREEN, (0, 255, 0)) + self.assertEqual(color_module.BLUE, (0, 0, 255)) + + def test_render_cycle(self): + """Test complete render cycle tracking""" + self.mock_arcade.start_render() + self.mock_arcade.draw_circle_filled(100, 100, 50, (255, 0, 0)) + self.mock_arcade.draw_text("Score: 100", 10, 10, (0, 0, 0)) + self.mock_arcade.finish_render() + + # Check that all commands were tracked + commands = self.mock_arcade.draw_commands + self.assertEqual(len(commands), 4) + self.assertEqual(commands[0]['type'], 'start_render') + self.assertEqual(commands[1]['type'], 'circle_filled') + self.assertEqual(commands[2]['type'], 'text') + self.assertEqual(commands[3]['type'], 'finish_render') + + def test_patches_generation(self): + """Test that _generate_patches returns expected functions""" + patches = self.mock_arcade._generate_patches() + + # Check that key functions are included + expected_functions = [ + 'open_window', 'draw_circle_filled', 'Sprite', 'SpriteList', + 'load_sound', 'run', 'check_for_collision', 'color' + ] + + for func_name in expected_functions: + self.assertIn(func_name, patches) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_pygame_integration.py b/tests/test_pygame_integration.py new file mode 100644 index 00000000..86d4aa67 --- /dev/null +++ b/tests/test_pygame_integration.py @@ -0,0 +1,97 @@ +""" +Test the Pygame mock integration with the sandbox system. +""" +import unittest +from textwrap import dedent +from tests.execution_helper import Execution, ExecutionTestCase, SUCCESS_MESSAGE + + +# Sample pygame code that might be used in educational settings +sample_pygame_code = dedent(''' +import pygame + +# Initialize pygame +pygame.init() + +# Set up display +screen = pygame.display.set_mode((800, 600)) +pygame.display.set_caption("My Pygame Game") + +# Create clock +clock = pygame.time.Clock() + +# Fill screen with white +screen.fill((255, 255, 255)) + +# Draw some shapes +pygame.draw.rect(screen, (255, 0, 0), (100, 100, 200, 150)) +pygame.draw.circle(screen, (0, 255, 0), (400, 300), 50) +pygame.draw.line(screen, (0, 0, 255), (0, 0), (100, 100), 5) + +# Update display +pygame.display.flip() + +# Load and play sound +sound = pygame.mixer.Sound("coin.wav") +sound.play() + +# Tick clock +clock.tick(60) + +# Get events +events = pygame.event.get() +keys = pygame.key.get_pressed() + +# Use variables to avoid unused warnings +print(len(events), len(keys)) + +# Don't call pygame.quit() so we can inspect the state +''').strip() + + +class TestPygameIntegration(ExecutionTestCase): + """Test Pygame mock integration with pedal sandbox""" + + def test_pygame_basic_execution(self): + """Test that basic pygame code executes without errors""" + with Execution(sample_pygame_code) as e: + # Should execute without error + pass + self.assertFeedback(e, SUCCESS_MESSAGE) + + def test_pygame_mock_captures_data(self): + """Test that the pygame mock captures game data""" + with Execution(sample_pygame_code) as e: + student = e.student + # Access the mocked pygame module + pygame_mock = student.modules.pygame + + # Check initialization + self.assertTrue(pygame_mock.display_info['initialized']) + + # Check display setup + self.assertEqual(pygame_mock.display_info['width'], 800) + self.assertEqual(pygame_mock.display_info['height'], 600) + self.assertEqual(pygame_mock.display_info['caption'], "My Pygame Game") + + # Check drawing commands were captured + self.assertGreater(len(pygame_mock.draw_calls), 0) + self.assertEqual(len(pygame_mock.draw_calls), 3) # rect, circle, line + + # Check surfaces were created + self.assertGreater(len(pygame_mock.surfaces), 0) + + # Check sounds were loaded + self.assertEqual(len(pygame_mock.sounds), 1) + self.assertEqual(pygame_mock.sounds[0]['filename'], "coin.wav") + self.assertEqual(pygame_mock.sounds[0]['plays'], 1) + + # Check clock ticks + self.assertEqual(len(pygame_mock.clock_ticks), 1) + self.assertEqual(pygame_mock.clock_ticks[0], 60) + + self.assertFeedback(e, SUCCESS_MESSAGE) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_pygame_mock.py b/tests/test_pygame_mock.py new file mode 100644 index 00000000..efa7c8ad --- /dev/null +++ b/tests/test_pygame_mock.py @@ -0,0 +1,165 @@ +""" +Test cases for the Pygame mock library. +""" +import unittest +from pedal.sandbox.library.pygame import MockPygame + + +class TestMockPygame(unittest.TestCase): + """Test the MockPygame implementation""" + + def setUp(self): + """Set up test fixtures""" + self.mock_pygame = MockPygame() + + def test_initialization(self): + """Test pygame initialization""" + result = self.mock_pygame.init() + self.assertTrue(self.mock_pygame.display_info['initialized']) + self.assertEqual(result, (6, 0)) + + self.mock_pygame.quit() + self.assertFalse(self.mock_pygame.display_info['initialized']) + + def test_display_functions(self): + """Test display functionality""" + display = self.mock_pygame.display + + # Test set_mode + surface = display.set_mode((800, 600)) + self.assertEqual(self.mock_pygame.display_info['width'], 800) + self.assertEqual(self.mock_pygame.display_info['height'], 600) + + # Test caption + display.set_caption("Test Game") + self.assertEqual(self.mock_pygame.display_info['caption'], "Test Game") + self.assertEqual(display.get_caption(), "Test Game") + + # Test flip + initial_flips = self.mock_pygame.display_info['flip_count'] + display.flip() + self.assertEqual(self.mock_pygame.display_info['flip_count'], initial_flips + 1) + + def test_drawing_functions(self): + """Test drawing function tracking""" + draw = self.mock_pygame.draw + surface = self.mock_pygame.Surface((400, 300)) + + # Test rectangle drawing + draw.rect(surface, (255, 0, 0), (10, 10, 100, 50)) + + # Test circle drawing + draw.circle(surface, (0, 255, 0), (200, 150), 25) + + # Test line drawing + draw.line(surface, (0, 0, 255), (0, 0), (100, 100)) + + # Verify draw calls were tracked + self.assertEqual(len(self.mock_pygame.draw_calls), 3) + + rect_call = self.mock_pygame.draw_calls[0] + self.assertEqual(rect_call['type'], 'rect') + self.assertEqual(rect_call['color'], (255, 0, 0)) + + def test_surface_creation(self): + """Test surface creation and manipulation""" + surface = self.mock_pygame.Surface((200, 100)) + self.assertEqual(len(self.mock_pygame.surfaces), 1) + + # Test surface methods + surface.fill((255, 255, 255)) + self.assertEqual(len(surface.data['fills']), 1) + self.assertEqual(surface.data['fills'][0], (255, 255, 255)) + + # Test blit + other_surface = self.mock_pygame.Surface((50, 50)) + surface.blit(other_surface, (10, 10)) + self.assertEqual(len(surface.data['blits']), 1) + + def test_sound_functionality(self): + """Test sound loading and playing""" + mixer = self.mock_pygame.mixer + mixer.init() + + # Load sound + sound = mixer.Sound("test.wav") + self.assertEqual(len(self.mock_pygame.sounds), 1) + self.assertEqual(self.mock_pygame.sounds[0]['filename'], "test.wav") + + # Play sound + sound.play() + self.assertEqual(self.mock_pygame.sounds[0]['plays'], 1) + + def test_time_clock(self): + """Test clock functionality""" + time_module = self.mock_pygame.time + clock = time_module.Clock() + + # Test tick + milliseconds = clock.tick(60) + self.assertEqual(len(self.mock_pygame.clock_ticks), 1) + self.assertEqual(self.mock_pygame.clock_ticks[0], 60) + self.assertEqual(milliseconds, 16) + + def test_event_handling(self): + """Test event handling""" + event_module = self.mock_pygame.event + + # Test get events + events = event_module.get() + self.assertEqual(events, []) + + # Test pump + event_module.pump() # Should not raise error + + def test_color_creation(self): + """Test Color functionality""" + Color = self.mock_pygame.Color + + # Test RGB color + red = Color(255, 0, 0) + self.assertEqual(red.r, 255) + self.assertEqual(red.g, 0) + self.assertEqual(red.b, 0) + + # Test iteration + color_tuple = tuple(red) + self.assertEqual(color_tuple, (255, 0, 0, 255)) + + def test_rect_creation(self): + """Test Rect functionality""" + Rect = self.mock_pygame.Rect + + rect = Rect(10, 20, 100, 50) + self.assertEqual(rect.x, 10) + self.assertEqual(rect.y, 20) + self.assertEqual(rect.width, 100) + self.assertEqual(rect.height, 50) + + # Test center property + self.assertEqual(rect.center, (60, 45)) + + def test_image_loading(self): + """Test image loading""" + image_module = self.mock_pygame.image + + surface = image_module.load("player.png") + self.assertEqual(len(self.mock_pygame.surfaces), 1) + self.assertEqual(self.mock_pygame.surfaces[0]['filename'], "player.png") + + def test_patches_generation(self): + """Test that _generate_patches returns expected functions""" + patches = self.mock_pygame._generate_patches() + + # Check that key functions are included + expected_functions = [ + 'init', 'quit', 'display', 'event', 'draw', 'mixer', + 'time', 'Surface', 'Color', 'Rect' + ] + + for func_name in expected_functions: + self.assertIn(func_name, patches) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file