pixelfaucet/src/game.cr
2022-01-06 00:36:48 -05:00

278 lines
7.7 KiB
Crystal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require "./lib_sdl"
require "./pixel"
require "./controller"
require "./game/*"
module PF
abstract class Game
FPS_INTERVAL = 1.0
SHOW_FPS = true
getter width : Int32
getter height : Int32
@viewport : Vector(Int32, 2)? = nil
property scale : Int32
property title : String
property running = true
property screen : SDL::Surface
@fps_lasttime : Float64 = Time.monotonic.total_milliseconds # the last recorded time.
@fps_current : UInt32 = 0 # the current FPS.
@fps_frames : UInt32 = 0 # frames passed since the last recorded fps.
@last_time : Float64 = Time.monotonic.total_milliseconds
def initialize(@width, @height, @scale = 1, @title = self.class.name,
flags = SDL::Renderer::Flags::ACCELERATED,
window_flags : SDL::Window::Flags = SDL::Window::Flags::SHOWN)
SDL.init(SDL::Init::VIDEO)
@window = SDL::Window.new(@title, @width * @scale, @height * @scale, flags: window_flags)
@renderer = SDL::Renderer.new(@window, flags: flags)
@renderer.scale = {@scale, @scale}
@screen = SDL::Surface.new(LibSDL.create_rgb_surface(
flags: 0, width: @width, height: @height, depth: 32,
r_mask: 0xFF000000, g_mask: 0x00FF0000, b_mask: 0x0000FF00, a_mask: 0x000000FF
))
end
abstract def update(dt : Float64, event : SDL::Event)
abstract def draw
def width=(value : Int32)
@viewport = nil
@width = value
# TODO: Resize window
end
def height=(value : Int32)
@viewport = nil
@height = value
# TODO: Resize window
end
def viewport
@viewport ||= Vector[@width, @height]
end
def elapsed_time
Time.monotonic.total_milliseconds
end
def clear(r = 0, g = 0, b = 0)
@screen.fill(r, g, b)
end
def pixel_pointer(x : Int32, y : Int32, surface = @screen)
target = surface.pixels + (y * surface.pitch) + (x * 4)
target.as(Pointer(UInt32))
end
# =================
# Drawing functions
# =================
# Unsafely draw a single point
def unsafe_draw_point(x : Int32, y : Int32, color : UInt32, surface = @screen)
pixel_pointer(x, y, surface).value = color
end
# Draw a single point
def draw_point(x : Int32, y : Int32, pixel : Pixel = Pixel.new, surface = @screen)
if x >= 0 && x < @width && y >= 0 && y < @height
pixel_pointer(x, y, surface).value = pixel.format(surface.format)
end
end
# ditto
def draw_point(point : Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_point(point.x, point.y, pixel, surface)
end
# ditto
def draw_point(point : Vector(Float, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_point(point.to_i32, pixel, surface)
end
# =================
# Draw a line using Bresenhams Algorithm
def draw_line(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# The slope for each axis
slope = Vector[(x2 - x1).abs, -(y2 - y1).abs]
# The step direction in both axis
step = Vector[x1 < x2 ? 1 : -1, y1 < y2 ? 1 : -1]
# The final decision accumulation
# Initialized to the height of x and y
decision = slope.x + slope.y
point = Vector[x1, y1]
loop do
draw_point(point.x, point.y, pixel, surface)
# Break if we've reached the ending point
break if point.x == x2 && point.y == y2
# Square the decision to avoid floating point calculations
decision_squared = decision + decision
# if decision_squared is greater than
if decision_squared >= slope.y
decision += slope.y
point.x += step.x
end
if decision_squared <= slope.x
decision += slope.x
point.y += step.y
end
end
end
# ditto
def draw_line(p1 : Vector(Int, 2), p2 : Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1.x, p1.y, p2.x, p2.y, pixel, surface)
end
# ditto
def draw_line(p1 : Vector(Float, 2), p2 : Vector(Float, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1.to_i32, p2.to_i32, pixel, surface)
end
# =================
# Draw the outline of a square rect
def draw_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
x1.upto(x2) do |x|
draw_point(x, y1, pixel, surface)
draw_point(x, y2, pixel, surface)
end
y1.upto(y2) do |y|
draw_point(x1, y, pixel, surface)
draw_point(x2, y, pixel, surface)
end
end
def draw_rect(p1 : PF::Vector(Int, 2), p2 : PF::Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_rect(p1.x, p1.y, p2.x, p2.y, pixel, surface)
end
# =================
# Draw lines enclosing a shape
def draw_shape(frame : Enumerable(Point), pixel : Pixel = Pixel.new, surface = @screen)
0.upto(frame.size - 1) do |n|
draw_line(frame[n], frame[(n + 1) % frame.size], pixel, surface)
end
end
# =================
# Draw a circle using Bresenhams Algorithm
def draw_circle(cx : Int, cy : Int, r : Int, pixel : Pixel = Pixel.new, surface = @screen)
x, y = 0, r
d = 3 - 2 * r
loop do
draw_point(cx + x, cy + y, pixel)
draw_point(cx - x, cy + y, pixel)
draw_point(cx + x, cy - y, pixel)
draw_point(cx - x, cy - y, pixel)
draw_point(cx + y, cy + x, pixel)
draw_point(cx - y, cy + x, pixel)
draw_point(cx + y, cy - x, pixel)
draw_point(cx - y, cy - x, pixel)
break if x > y
x += 1
if d > 0
y -= 1
d = d + 4 * (x - y) + 10
else
d = d + 4 * x + 6
end
end
end
def draw_circle(c : Vector(Int, 2), r : Int, pixel : Pixel = Pixel.new, surface = @screen)
draw_circle(c.x, c.y, r, pixel, surface)
end
# =================
def draw_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1, p2, pixel, surface)
draw_line(p2, p3, pixel, surface)
draw_line(p3, p1, pixel, surface)
end
# =================
# Fill a rect
def fill_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
y1.upto(y2) do |y|
x1.upto(x2) do |x|
draw_point(x, y, pixel, surface)
end
end
end
# =====================
# END drawing functions
# =====================
private def engine_update(event)
et = elapsed_time
calculate_fps(et)
update((et - @last_time) / 1000.0, event)
@last_time = et
end
private def calculate_fps(et)
return unless SHOW_FPS
@fps_frames += 1
if @fps_lasttime < et - FPS_INTERVAL * 1000
@fps_lasttime = et
@fps_current = @fps_frames
@fps_frames = 0
@window.title = String.build { |io| io << @title << " - " << @fps_current << " fps" }
end
end
private def engine_draw
@screen.lock do
draw
end
@renderer.copy(@screen)
@renderer.present
end
def run!
loop do
case event = SDL::Event.poll
when SDL::Event::Quit
break
end
engine_update(event)
engine_draw
break unless @running
end
ensure
SDL.quit
end
end
end