commit 8d643a3842244d23a50c2988d7df478a797cf35a Author: Daanish Date: Wed Mar 25 07:13:37 2026 +0000 initial commit diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fcc22d7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true +} \ No newline at end of file diff --git a/assets/fonts/charybdis.ttf b/assets/fonts/charybdis.ttf new file mode 100644 index 0000000..b6325cd Binary files /dev/null and b/assets/fonts/charybdis.ttf differ diff --git a/assets/fonts/hardpixel.otf b/assets/fonts/hardpixel.otf new file mode 100644 index 0000000..0912b3e Binary files /dev/null and b/assets/fonts/hardpixel.otf differ diff --git a/assets/fonts/tn.ttf b/assets/fonts/tn.ttf new file mode 100644 index 0000000..9d12258 Binary files /dev/null and b/assets/fonts/tn.ttf differ diff --git a/assets/images/floor.aseprite b/assets/images/floor.aseprite new file mode 100644 index 0000000..206cd5a Binary files /dev/null and b/assets/images/floor.aseprite differ diff --git a/assets/images/floor.png b/assets/images/floor.png new file mode 100644 index 0000000..8b5be20 Binary files /dev/null and b/assets/images/floor.png differ diff --git a/assets/images/menu_bg.png b/assets/images/menu_bg.png new file mode 100644 index 0000000..91a1f55 Binary files /dev/null and b/assets/images/menu_bg.png differ diff --git a/assets/images/player.aseprite b/assets/images/player.aseprite new file mode 100644 index 0000000..69dc3ae Binary files /dev/null and b/assets/images/player.aseprite differ diff --git a/assets/images/player.png b/assets/images/player.png new file mode 100644 index 0000000..1f3e631 Binary files /dev/null and b/assets/images/player.png differ diff --git a/assets/images/walls.aseprite b/assets/images/walls.aseprite new file mode 100644 index 0000000..3d1926e Binary files /dev/null and b/assets/images/walls.aseprite differ diff --git a/assets/images/walls.png b/assets/images/walls.png new file mode 100644 index 0000000..d0193ff Binary files /dev/null and b/assets/images/walls.png differ diff --git a/game/character.rb b/game/character.rb new file mode 100644 index 0000000..e6a576e --- /dev/null +++ b/game/character.rb @@ -0,0 +1,113 @@ +class Character + SPEED = 2.5 + SIZE = [25, 30] + + def initialize + @world_x = 60.0 + @world_y = 60.0 + + # Load the full sheet as tiles + sheet = Gosu::Image.load_tiles("assets/images/player.png", 20, 40, retro: true) + + rows = [ + :idle_front, + :idle_back, + :idle_right, + :idle_left, + :walk_front, + :walk_back, + :walk_right, + :walk_left + ] + + @animations = {} + + rows.each_with_index do |name, row| + start_index = row * 6 + @animations[name] = sheet[start_index, 6] # slice 6 frames + end + + @current_animation = :idle_front + @facing = :front + end + + def rect + w, h = SIZE + [@world_x - w / 2, @world_y - h / 2, w, h] + end + + def collides?(rect) + return true if intersects?(self.rect, rect) + false + end + + def update + dx = 0.0 + dy = 0.0 + + dx -= 1 if Gosu.button_down?(Gosu::KB_A) + dx += 1 if Gosu.button_down?(Gosu::KB_D) + dy -= 1 if Gosu.button_down?(Gosu::KB_W) + dy += 1 if Gosu.button_down?(Gosu::KB_S) + + moving = dx != 0 || dy != 0 + + if moving + if dy < 0 + @facing = :back + elsif dy > 0 + @facing = :front + elsif dx < 0 + @facing = :left + elsif dx > 0 + @facing = :right + end + end + + @current_animation = moving ? :"walk_#{@facing}" : :"idle_#{@facing}" + + # Normalize vector if moving diagonally + if dx != 0 && dy != 0 + length = Math.sqrt(dx**2 + dy**2) + dx /= length + dy /= length + end + + # Apply speed + dx *= SPEED + dy *= SPEED + + # Horizontal + @world_x += dx + if $bus.get(:collides?, rect)&.include?(:wall) + @world_x -= dx + end + + # Vertical + @world_y += dy + if $bus.get(:collides?, rect)&.include?(:wall) + @world_y -= dy + end + + # Camera follow + cam_x = @world_x - SCREEN_SIZE[0] / 2.0 + cam_y = @world_y - SCREEN_SIZE[1] / 2.0 + $bus.emit(:player_move, [cam_x, cam_y]) + end + + def draw + cx = SCREEN_SIZE[0] / 2.0 - SIZE[0] / 2.0 + cy = SCREEN_SIZE[1] / 2.0 - SIZE[1] / 2.0 + + sprite_x = cx + SIZE[0] / 2.0 - 20 * 1.8 / 2 + sprite_y = cy + SIZE[1] / 2.0 - 50 * 1.8 / 2 + + elapsed = Gosu.milliseconds / 1000.0 + frame_index = ((elapsed * 7).floor) % 6 + @animations[@current_animation][frame_index].draw(sprite_x, sprite_y, sprite_y, 1.8, 1.8) + + return unless DEBUG + + Gosu.draw_rect(cx, cy, SIZE[0], SIZE[1], Gosu::Color.new(0x8000FF00), cy) + end +end \ No newline at end of file diff --git a/game/enemy/ai.rb b/game/enemy/ai.rb new file mode 100644 index 0000000..e69de29 diff --git a/game/enemy/base.rb b/game/enemy/base.rb new file mode 100644 index 0000000..e69de29 diff --git a/game/maze.rb b/game/maze.rb new file mode 100644 index 0000000..28d2e08 --- /dev/null +++ b/game/maze.rb @@ -0,0 +1,106 @@ +require_relative "../utils/maze" + +class Maze + def initialize + @maze = MazeData.new(80, 80) + @spritesheet = Gosu::Image.load_tiles("assets/images/walls.png", 20, 40, retro: true) + puts @spritesheet.size + end + + def collides?(rect) + x1 = ((rect[0] - 10) / 60).floor + y1 = ((rect[1] - 10) / 60).floor + x2 = ((rect[0] + rect[2] + 10) / 60).floor + y2 = ((rect[1] + rect[3] + 10) / 60).floor + + (x1..x2).each do |x| + (y1..y2).each do |y| + shapes = shapes_for(@maze.wall_type(x, y), x * 60, y * 60) + shapes.each do |shape| + return true if intersects?(shape, rect) + end + end + end + + false + end + + def shapes_for(mask, tx, ty) + return [] if mask == 0 + + half = 30 + band = 10 + + shapes = [] + + case mask & (W | E) + when W | E + shapes << [tx, ty + 30 - band / 2, 60, band] + when E + if (mask & (N | S)) == 0 + shapes << [tx, ty + 30 - band / 2, 60, band] + else + shapes << [tx + half, ty + 30 - band / 2, half, band] + end + when W + if (mask & (N | S)) == 0 + shapes << [tx, ty + 30 - band / 2, 60, band] + else + shapes << [tx, ty + 30 - band / 2, half, band] + end + end + + band = 20 + + case mask & (N | S) + when (N | S) + shapes << [tx + 30 - band / 2, ty, band, 60] + when S + shapes << [tx + 30 - band / 2, ty + half, band, half] + when N + shapes << [tx + 30 - band / 2, ty, band, half] + end + + shapes + end + + def draw(camera) + cam_x, cam_y = camera + tile_size = 60 + scale = 3 + screen_width, screen_height = SCREEN_SIZE + + # How many tiles fit on screen (+1 for partially visible tiles) + tiles_x = (screen_width / tile_size) + 2 + tiles_y = (screen_height / tile_size) + 2 + + # Which tile to start drawing (top-left corner of camera) + start_x = (cam_x / tile_size).floor - 1 + start_y = (cam_y / tile_size).floor - 1 + + (0...tiles_y).each do |j| + (0...tiles_x).each do |i| + gx = start_x + i + gy = start_y + j + + next if gx < 0 || gy < 0 || gx >= @maze.width || gy >= @maze.height + + index = @maze.wall_type(gx, gy) + next if index == 0 + + # Pixel position on screen + screen_x = gx * tile_size - cam_x + screen_y = gy * tile_size - cam_y + + @spritesheet[index - 1].draw(screen_x, screen_y, screen_y, scale, scale) + + next unless DEBUG + + shapes = shapes_for(index, gx * tile_size, gy * tile_size) + shapes.each do |shape| + Gosu.draw_rect(shape[0] - cam_x, shape[1] - cam_y, shape[2], shape[3], 0x44ff0000, shape[1] - cam_y) + end + end + end + end +end \ No newline at end of file diff --git a/game/scene.rb b/game/scene.rb new file mode 100644 index 0000000..303199d --- /dev/null +++ b/game/scene.rb @@ -0,0 +1,123 @@ +require_relative 'state' +require_relative 'maze' +require_relative 'character' + +require 'perlin' + +class Game < Scene + def initialize + super + @font = Gosu::Font.new(24) + @noise = Perlin::Generator.new(rand(1000), 1.0, 1) + @floor_image = Gosu::Image.new("assets/images/floor.png", retro: true) + + @maze = Maze.new + @character = Character.new + + @camera = [0, 0] + + $bus.on(:player_move) do |pos| + @camera = pos + end + + $bus.on_retrievable(:collides?) do |rect| + collisions = [] + collisions << :wall if @maze.collides?(rect) + collisions << :character if @character.collides?(rect) + next collisions + end + end + + def draw + draw_floor + @maze.draw(@camera) + @character.draw + draw_fog(@camera) + draw_debug! if DEBUG + end + + def draw_debug! + now = Gosu.milliseconds + @last_draw_time ||= now + @draw_count ||= 0 + @fps ||= 0 + + @draw_count += 1 + + if now - @last_draw_time >= 1000 + @fps = @draw_count + @draw_count = 0 + @last_draw_time = now + end + + @font.draw_text("FPS: #{@fps}", 5, 5, Float::INFINITY, 1, 1, Gosu::Color::YELLOW) + end + + def draw_fog(camera) + cam_x, cam_y = camera + cell = 10 + i_radius = 80.0 + o_radius = 360.0 + + # Player screen position + px = SCREEN_SIZE[0] / 2.0 + py = SCREEN_SIZE[1] / 2.0 + + tiles_x = (SCREEN_SIZE[0] / cell) + 2 + tiles_y = (SCREEN_SIZE[1] / cell) + 2 + + tiles_y.times do |j| + tiles_x.times do |i| + sx = i * cell + sy = j * cell + + + dist = Math.sqrt((sx - px)**2 + (sy - py)**2) + + if dist < i_radius + next # fully clear + elsif dist > o_radius + alpha = 255 + else + t = (dist - i_radius) / (o_radius - i_radius) # 0..1 + + world_x = (sx + cam_x) * 0.005 + world_y = (sy + cam_y) * 0.005 + + # Sample noise at world position so it scrolls with camera + noise = (@noise[world_x + Gosu.milliseconds / 5000.0, world_y + Gosu.milliseconds / 6000.0] + 1.0) / 2.0 + alpha = (t * (1.0 + noise) * 255).to_i.clamp(0, 255) + end + + Gosu.draw_rect(sx, sy, cell, cell, Gosu::Color.new(alpha, 0, 0, 0), 10000 + sy) + end + end + end + + def draw_floor + cam_x, cam_y = @camera + tile_size = 60 + + offset_x = cam_x % tile_size + offset_y = cam_y % tile_size + + tiles_x = (SCREEN_SIZE[0] / tile_size) + 2 + tiles_y = (SCREEN_SIZE[1] / tile_size) + 2 + + (0...tiles_y).each do |j| + (0...tiles_x).each do |i| + sx = i * tile_size - offset_x + sy = j * tile_size - offset_y + @floor_image.draw(sx, sy, -1000, 3, 3) + end + end + end + + def update + @character.update + end + + def button_down(id, _pos) + return $bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE + end +end \ No newline at end of file diff --git a/game/state.rb b/game/state.rb new file mode 100644 index 0000000..9c1e414 --- /dev/null +++ b/game/state.rb @@ -0,0 +1,7 @@ +class State + # This class holds state information + # for example the player inventory / items + # also their health, stealth, and memory bars + # it can handle mouse input for inventory management and item usage + # it can also handle keyboard input for quick item usage +end \ No newline at end of file diff --git a/main.rb b/main.rb new file mode 100644 index 0000000..bce9d98 --- /dev/null +++ b/main.rb @@ -0,0 +1,92 @@ +require 'gosu' + +require_relative 'settings/configuration' +require_relative 'utils/bus' +require_relative 'utils/utils' +require_relative 'utils/scene' +require_relative 'settings/scene' +require_relative 'menu/scene' +require_relative 'game/scene' + +$bus = EventBus.new + +# Main window +class Window < Gosu::Window + def initialize + super *SCREEN_SIZE, resizable: true + self.caption = "Mist" + + @scene = Menu.new + + $bus.on(:quit_game) { close! } + + $bus.on_retrievable(:mouse_pos) do + next mouse_relative(mouse_x, mouse_y) + end + + $bus.on(:change_scene) do |new_scene| + @scene.close + @scene = new_scene + end + end + + def button_down(id) + @scene.button_down(id, mouse_relative(mouse_x, mouse_y)) + end + + def update + @scene.update + end + + def compute_transform + scale_x = width.to_f / SCREEN_SIZE[0] + scale_y = height.to_f / SCREEN_SIZE[1] + + scale = [scale_x, scale_y].min + + if scale_x < scale_y + offset_x = 0 + offset_y = (height - SCREEN_SIZE[1] * scale) / 2 + else + offset_x = (width - SCREEN_SIZE[0] * scale) / 2 + offset_y = 0 + end + + [scale, offset_x, offset_y] + end + + def mouse_relative(sx, sy) + scale, offset_x, offset_y = compute_transform + + virtual_x = (sx - offset_x) / scale + virtual_y = (sy - offset_y) / scale + + return nil if virtual_x < 0 || virtual_x > SCREEN_SIZE[0] || + virtual_y < 0 || virtual_y > SCREEN_SIZE[1] + + return [virtual_x, virtual_y] + end + + def draw + scale, offset_x, offset_y = compute_transform + + Gosu.translate(offset_x, offset_y) do + Gosu.scale(scale) do + @scene.draw + end + end + + padding_color = Gosu::Color::BLACK + if offset_y > 0 + # Letterbox: top and bottom + Gosu.draw_rect(0, 0, width, offset_y, padding_color, Float::INFINITY) + Gosu.draw_rect(0, height - offset_y, width, offset_y, padding_color, Float::INFINITY) + else + # Pillarbox: left and right + Gosu.draw_rect(0, 0, offset_x, height, padding_color, Float::INFINITY) + Gosu.draw_rect(width - offset_x, 0, offset_x, height, padding_color, Float::INFINITY) + end + end +end + +Window.new.show \ No newline at end of file diff --git a/menu/scene.rb b/menu/scene.rb new file mode 100644 index 0000000..7b9164c --- /dev/null +++ b/menu/scene.rb @@ -0,0 +1,67 @@ +class Menu < Scene + OPTIONS = { + "Start" => -> { $bus.emit(:change_scene, Game.new) }, + "Settings" => -> { $bus.emit(:change_scene, Settings.new) }, + "Quit" => -> { $bus.emit(:quit_game) } + }.freeze + + def initialize + super + @font = Gosu::Font.new(32, name: "assets/fonts/tn.ttf") + @selected_index = 0 + @bg_image = Gosu::Image.new("assets/images/menu_bg.png", retro: true) + @last_mouse_pos = nil + end + + def update + pos = $bus.get(:mouse_pos) + return unless pos + + if @last_mouse_pos.nil? || ( (pos[0] - @last_mouse_pos[0]).abs > 2 || (pos[1] - @last_mouse_pos[1]).abs > 2 ) + hovered_option = item_at(pos) + @selected_index = hovered_option[0] if hovered_option + @last_mouse_pos = pos.dup + end + end + + def draw + @bg_image.draw(0, 0, 1) + + OPTIONS.each_with_index do |(text, _), index| + y = 260 + index * 50 + if index == @selected_index + @font.draw_text("[ #{text.ljust(10)} ]", 50, y, 1, 1, 1, Gosu::Color::YELLOW) + else + @font.draw_text(" #{text.ljust(10)} ", 50, y, 1, 1, 1, Gosu::Color::WHITE) + end + end + end + + def button_down(id, pos) + case id + when Gosu::KB_UP + @selected_index = (@selected_index - 1) % OPTIONS.size + when Gosu::KB_DOWN + @selected_index = (@selected_index + 1) % OPTIONS.size + when Gosu::KB_RETURN + OPTIONS.values[@selected_index].call + end + + return unless id == Gosu::MS_LEFT && pos + + selected_option = item_at(pos) + selected_option[1].call if selected_option + end + + private + + def item_at(pos) + OPTIONS.each_with_index do |(_, action), index| + y = 260 + index * 50 + if pos[1].between?(y, y + 32) && pos[0].between?(50, 50 + @font.text_width(" " * 14)) + return [index, action] + end + end + nil + end +end \ No newline at end of file diff --git a/objects/items.rb b/objects/items.rb new file mode 100644 index 0000000..e69de29 diff --git a/objects/traps.rb b/objects/traps.rb new file mode 100644 index 0000000..e69de29 diff --git a/run b/run new file mode 100755 index 0000000..6b031bb --- /dev/null +++ b/run @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +# Dependencies +# Gosu for rendering and input +# Perlin noise for fog effect + +load 'main.rb' \ No newline at end of file diff --git a/settings/configuration.rb b/settings/configuration.rb new file mode 100644 index 0000000..116017c --- /dev/null +++ b/settings/configuration.rb @@ -0,0 +1,6 @@ +# Global constants + +SCREEN_SIZE = [720, 480].freeze +N, S, W, E = 1, 2, 4, 8 + +DEBUG = ARGV.include?("--debug") diff --git a/settings/scene.rb b/settings/scene.rb new file mode 100644 index 0000000..bca37e1 --- /dev/null +++ b/settings/scene.rb @@ -0,0 +1,15 @@ +class Settings < Scene + def initialize + super + @font = Gosu::Font.new(24) + end + + def draw + Gosu.draw_rect(0, 0, SCREEN_SIZE[0], SCREEN_SIZE[1], Gosu::Color::BLACK) + @font.draw_text("Settings Scene - Press ESC to return to Menu", 50, 50, 1, 1, 1, Gosu::Color::WHITE) + end + + def button_down(id, _pos) + $bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE + end +end \ No newline at end of file diff --git a/utils/bus.rb b/utils/bus.rb new file mode 100644 index 0000000..a485352 --- /dev/null +++ b/utils/bus.rb @@ -0,0 +1,57 @@ +class EventBus + def initialize + # store callbacks with owner: {event => [ {callback:, owner:} ] } + @events = Hash.new { |h, k| h[k] = [] } + @retrievable_events = {} + end + + # Emit an event + def emit(event, *args) + @events[event].each do |h| + h[:callback].call(*args) + end + end + + # Subscribe to an event + def on(event, owner: nil, &callback) + owner ||= callback.binding.eval("self") + @events[event] << { callback: callback, owner: owner } + end + + # Subscribe to an event that should only be called once + def once(event, owner = nil, &callback) + wrapper = nil + wrapper = proc do |*args| + callback.call(*args) + off(event, &wrapper) + end + on(event, owner: owner, &wrapper) + end + + def off(event, &callback) + @events[event].reject! { |h| h[:callback] == callback } + end + + # Register a retrievable event (only one allowed) + def on_retrievable(event, owner = nil, &callback) + owner ||= callback.binding.eval("self") + @retrievable_events[event] = { callback: callback, owner: owner } + end + + # Get data from a retrievable event + def get(event, *args) + if @retrievable_events[event] + @retrievable_events[event][:callback].call(*args) + else + nil + end + end + + # Unsubscribe from an object's events + def unlisten_owner(owner) + @events.each do |event, handlers| + handlers.reject! { |h| h[:owner] == owner } + end + @retrievable_events.delete_if { |_, h| h[:owner] == owner } + end +end \ No newline at end of file diff --git a/utils/maze.rb b/utils/maze.rb new file mode 100644 index 0000000..179e840 --- /dev/null +++ b/utils/maze.rb @@ -0,0 +1,162 @@ +class MazeData + def initialize(width, height) + @width = width + @height = height + + @grid = Array.new(height) { Array.new(width, 0) } + + @boss_rooms = [] + + crate_rooms! + carve_passages_from(0, 0) + fix_room_entrances! + end + + def width + @width * 2 + 1 + end + + def height + @height * 2 + 1 + end + + def wall_type(gx, gy) + mask = 0 + + return mask unless wall_at?(gx, gy) + + mask |= N if gy > 0 && wall_at?(gx, gy - 1) + mask |= S if gy < height - 1 && wall_at?(gx, gy + 1) + mask |= E if gx < width - 1 && wall_at?(gx + 1, gy ) + mask |= W if gx > 0 && wall_at?(gx - 1, gy ) + + return mask + end + + def wall_at?(gx, gy) + return true if gx == 0 || gy == 0 || gx == width - 1 || gy == height - 1 + + @boss_rooms.each do |rx, ry| + display_x1 = rx * 2 + 1 + display_y1 = ry * 2 + 1 + display_x2 = display_x1 + (BOSS_ROOM_SIZE - 1) * 2 + display_y2 = display_y1 + (BOSS_ROOM_SIZE - 1) * 2 + return false if gx.between?(display_x1, display_x2) && gy.between?(display_y1, display_y2) + end + + return false if gx.odd? && gy.odd? + + if gx.odd? && gy.even? + cx = (gx - 1) / 2 + cy = (gy - 1) / 2 + return (@grid[cy][cx] & S) == 0 + end + + if gx.even? && gy.odd? + cx = (gx - 1) / 2 + cy = (gy - 1) / 2 + return (@grid[cy][cx] & E) == 0 + end + + true + end + + private + + BOSS_ROOM_SIZE = 5 + BOSS_ROOM = 16 + N, S, W, E = 1, 2, 4, 8 + DX = { E => 1, W => -1, N => 0, S => 0 } + DY = { E => 0, W => 0, N => -1, S => 1 } + OPPOSITE = { E => W, W => E, N => S, S => N } + + def room_free?(x, y) + (y - 2...(y + BOSS_ROOM_SIZE + 2)).each do |j| + (x - 2...(x + BOSS_ROOM_SIZE + 2)).each do |i| + return false if @grid[j][i] & BOSS_ROOM != 0 + end + end + true + end + + def fix_room_entrances! + @boss_rooms.each do |x, y| + @grid[y][x + 2] |= N + @grid[y - 1][x + 2] |= S + end + end + + def crate_rooms! + base = Math.sqrt(@width * @height / 200.0).round + num_rooms = rand((base - 1)..(base)) + num_rooms = 1 if num_rooms < 1 + attempts = 0 + + while @boss_rooms.size < num_rooms && attempts < 10 + attempts += 1 + + x = rand(2..(@width - BOSS_ROOM_SIZE - 2)) + y = rand(2..(@height - BOSS_ROOM_SIZE - 2)) + + next unless room_free?(x, y) + + (y...(y + BOSS_ROOM_SIZE)).each do |j| + (x...(x + BOSS_ROOM_SIZE)).each do |i| + cell = N | S | E | W + cell |= BOSS_ROOM + + cell &= ~N if j == y + cell &= ~S if j == y + BOSS_ROOM_SIZE - 1 + cell &= ~W if i == x + cell &= ~E if i == x + BOSS_ROOM_SIZE - 1 + + @grid[j][i] = cell + end + end + + @boss_rooms << [x, y] + end + end + + def carve_passages_from(cx, cy) + directions = [N, S, E, W].shuffle + + directions.each do |direction| + nx, ny = cx + DX[direction], cy + DY[direction] + + if ny.between?(0, @height - 1) && nx.between?(0, @width - 1) && @grid[ny][nx] == 0 + @grid[cy][cx] |= direction + @grid[ny][nx] |= OPPOSITE[direction] + carve_passages_from(nx, ny) + end + end + end +end + +if __FILE__ == $0 + WALL_CHARS = { + 0 => " ", # NONE + 1 => "╵", # N + 2 => "╷", # S + 3 => "│", # NS + 4 => "╴", # W + 5 => "┘", # NW + 6 => "┐", # WS + 7 => "┤", # WNS + 8 => "╶", # E + 9 => "└", # NE + 10 => "┌", # ES + 11 => "├", # ENS + 12 => "─", # EW + 13 => "┴", # EWN + 14 => "┬", # EWS + 15 => "┼" # EWNS + } + maze = MazeData.new(80, 80) + (0...maze.height).each do |y| + (0...maze.width).each do |x| + print WALL_CHARS[maze.wall_type(x, y)] + end + puts + end +end \ No newline at end of file diff --git a/utils/scene.rb b/utils/scene.rb new file mode 100644 index 0000000..a730797 --- /dev/null +++ b/utils/scene.rb @@ -0,0 +1,19 @@ +class Scene + # Base class for all scenes. + + def initialize + end + + def update + end + + def draw + end + + def button_down(id, pos) + end + + def close + $bus.unlisten_owner(self) + end +end \ No newline at end of file diff --git a/utils/utils.rb b/utils/utils.rb new file mode 100644 index 0000000..d8118eb --- /dev/null +++ b/utils/utils.rb @@ -0,0 +1,12 @@ +def intersects?(rect1, rect2) + x1, y1, w1, h1 = rect1 + x2, y2, w2, h2 = rect2 + + # No overlap conditions + return false if x1 + w1 <= x2 # rect1 is left of rect2 + return false if x1 >= x2 + w2 # rect1 is right of rect2 + return false if y1 + h1 <= y2 # rect1 is above rect2 + return false if y1 >= y2 + h2 # rect1 is below rect2 + + true # overlap exists +end \ No newline at end of file