File splitting.
parent
01c835567e
commit
cafa63e6ab
135
src/main.cr
135
src/main.cr
|
@ -3,99 +3,6 @@ require "json"
|
|||
|
||||
require "./naka.cr"
|
||||
|
||||
class Map
|
||||
@save_directory : String
|
||||
getter generator
|
||||
def initialize(@save_directory, &block : Proc(Tile, Int32, Int32, Tile?))
|
||||
Dir.mkdir_p @save_directory
|
||||
|
||||
@generator = block
|
||||
end
|
||||
|
||||
class Object
|
||||
getter type : String
|
||||
def initialize(@type)
|
||||
end
|
||||
|
||||
def to_json(builder)
|
||||
builder.object do
|
||||
builder.field "type", @type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Tile < Array(Object)
|
||||
def initialize(element)
|
||||
initialize
|
||||
|
||||
self << element
|
||||
end
|
||||
end
|
||||
|
||||
CHUNK_SIZE = 32
|
||||
class Chunk
|
||||
property tiles : Array(Array(Tile))
|
||||
getter x : Int32
|
||||
getter y : Int32
|
||||
|
||||
def initialize(@x, @y, map)
|
||||
p "Creating Map::Chunk"
|
||||
|
||||
@tiles = Array.new(CHUNK_SIZE) do |x|
|
||||
x += @x * CHUNK_SIZE
|
||||
|
||||
Array.new(CHUNK_SIZE) do |y|
|
||||
y += @y * CHUNK_SIZE
|
||||
|
||||
Tile.new(Object.new "stone").tap do |tile|
|
||||
map.generator.call tile, x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_json
|
||||
{
|
||||
:x => @x,
|
||||
:y => @y,
|
||||
:tiles => @tiles
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def get(x, y)
|
||||
@tiles[x][y]
|
||||
end
|
||||
end
|
||||
|
||||
@chunks = Hash(Int32, Hash(Int32, Chunk)).new
|
||||
|
||||
def get_chunk(x, y)
|
||||
chunks_list = @chunks[x]?
|
||||
if chunks_list.nil?
|
||||
chunks_list = Hash(Int32, Chunk).new
|
||||
@chunks[x] = chunks_list
|
||||
end
|
||||
|
||||
chunk = chunks_list[y]?
|
||||
if chunk.nil?
|
||||
chunk = Chunk.new x, y, self
|
||||
chunks_list[y] = chunk
|
||||
|
||||
save chunk
|
||||
end
|
||||
|
||||
chunk
|
||||
end
|
||||
|
||||
def save(chunk : Chunk)
|
||||
File.write "#{@save_directory}/chunk-#{chunk.x}-#{chunk.y}.json", chunk.to_json
|
||||
end
|
||||
|
||||
def get(x, y)
|
||||
get_chunk(x / CHUNK_SIZE, y / CHUNK_SIZE).get(x % CHUNK_SIZE, y % CHUNK_SIZE)
|
||||
end
|
||||
end
|
||||
|
||||
class ImagesLoader
|
||||
getter grass : SDL::Texture
|
||||
getter stone : SDL::Texture
|
||||
|
@ -131,7 +38,7 @@ images = ImagesLoader.new window
|
|||
connection_font = window.newFont "Connection.otf", 30.5
|
||||
window.set_font connection_font
|
||||
|
||||
altitude_map = Naka::Noise2D.new.tap do |a|
|
||||
altitude_noise = Naka::Noise2D.new.tap do |a|
|
||||
a.add_octave(32, 1)
|
||||
a.add_octave(16, 0.5)
|
||||
a.add_octave(8, 0.25)
|
||||
|
@ -139,7 +46,7 @@ altitude_map = Naka::Noise2D.new.tap do |a|
|
|||
a.add_octave(2, 1/16) # wtf, too smol
|
||||
end
|
||||
|
||||
humidity_map = Naka::Noise2D.new.tap do |a|
|
||||
humidity_noise = Naka::Noise2D.new.tap do |a|
|
||||
a.add_octave(64, 1)
|
||||
a.add_octave(32, 0.5)
|
||||
a.add_octave(16, 0.25)
|
||||
|
@ -148,63 +55,61 @@ humidity_map = Naka::Noise2D.new.tap do |a|
|
|||
a.add_octave(2, 0.10)
|
||||
end
|
||||
|
||||
evil_map = Naka::Noise2D.new.tap do |a|
|
||||
evil_noise = Naka::Noise2D.new.tap do |a|
|
||||
a.add_octave(48, 0.6)
|
||||
a.add_octave(32, 0.6)
|
||||
a.add_octave(16, 0.3)
|
||||
end
|
||||
|
||||
flowers_map = Naka::Noise2D.new(1, 1)
|
||||
map = Naka::Map.new "map-test-01" do |tile, x, y|
|
||||
altitude = altitude_noise.get(x, y) + 0.5
|
||||
|
||||
map = Map.new "map-test-01" do |tile, x, y|
|
||||
altitude = altitude_map.get(x, y) + 0.5
|
||||
humidity = humidity_noise.get(x, y) + 0.5
|
||||
|
||||
humidity = humidity_map.get(x, y) + 0.5
|
||||
|
||||
evil = evil_map.get(x, y)
|
||||
evil = evil_noise.get(x, y)
|
||||
|
||||
if altitude < 0.25
|
||||
tile << Map::Object.new "sand"
|
||||
tile << Naka::Map::Object.new "sand"
|
||||
|
||||
if altitude < 0.175
|
||||
tile << Map::Object.new "water"
|
||||
tile << Naka::Map::Object.new "water"
|
||||
else
|
||||
if humidity < 0.33
|
||||
tile << Map::Object.new "sand"
|
||||
tile << Naka::Map::Object.new "sand"
|
||||
else
|
||||
tile << Map::Object.new "dirt"
|
||||
tile << Naka::Map::Object.new "dirt"
|
||||
end
|
||||
end
|
||||
elsif altitude < 0.75
|
||||
tile << Map::Object.new "dirt"
|
||||
tile << Naka::Map::Object.new "dirt"
|
||||
|
||||
if evil > 0.5
|
||||
tile << Map::Object.new "haunted"
|
||||
tile << Naka::Map::Object.new "haunted"
|
||||
else
|
||||
tile << Map::Object.new "grass"
|
||||
tile << Naka::Map::Object.new "grass"
|
||||
end
|
||||
|
||||
if altitude > 0.35 && altitude < 0.65
|
||||
if humidity > 0.4
|
||||
if humidity > 0.8
|
||||
tile << Map::Object.new "tree"
|
||||
tile << Naka::Map::Object.new "tree"
|
||||
elsif humidity < 0.6
|
||||
tile << Map::Object.new "pine"
|
||||
tile << Naka::Map::Object.new "pine"
|
||||
else
|
||||
if Random.rand(2) == 0
|
||||
tile << Map::Object.new "tree"
|
||||
tile << Naka::Map::Object.new "tree"
|
||||
else
|
||||
tile << Map::Object.new "pine"
|
||||
tile << Naka::Map::Object.new "pine"
|
||||
end
|
||||
end
|
||||
else
|
||||
if Random.rand(10) == 0
|
||||
tile << Map::Object.new "flowers"
|
||||
tile << Naka::Map::Object.new "flowers"
|
||||
end
|
||||
end
|
||||
end
|
||||
elsif altitude > 0.85
|
||||
tile << Map::Object.new "rocks"
|
||||
tile << Naka::Map::Object.new "rocks"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
200
src/naka.cr
200
src/naka.cr
|
@ -1,203 +1,7 @@
|
|||
|
||||
require "sdl"
|
||||
require "sdl/image"
|
||||
require "sdl/ttf"
|
||||
|
||||
# Not sure we’d even need that, buuut…
|
||||
class Naka
|
||||
end
|
||||
|
||||
require "./noise.cr"
|
||||
|
||||
## Helper code comes here
|
||||
|
||||
class Naka
|
||||
def self.init
|
||||
SDL.init SDL::Init::VIDEO
|
||||
SDL::IMG.init SDL::IMG::Init::PNG
|
||||
SDL::TTF.init
|
||||
|
||||
at_exit {
|
||||
SDL.quit
|
||||
SDL::TTF.quit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Renderer < SDL::Renderer
|
||||
end
|
||||
|
||||
class Naka::Window
|
||||
alias Flags = LibSDL::WindowFlags
|
||||
alias Position = LibSDL::WindowPosition
|
||||
|
||||
getter renderer
|
||||
|
||||
@current_font : SDL::TTF::Font? = nil
|
||||
|
||||
def initialize(title, width, height,
|
||||
x : Position = Position::UNDEFINED,
|
||||
y : Position = Position::UNDEFINED,
|
||||
flags : Flags = Flags::SHOWN)
|
||||
|
||||
@window = SDL::Window.new(title, width, height, x, y, flags)
|
||||
@renderer = Naka::Renderer.new @window
|
||||
|
||||
@renderer.draw_blend_mode = SDL::BlendMode::BLEND
|
||||
end
|
||||
|
||||
def newImage(file_path) : SDL::Texture
|
||||
SDL::IMG.load(file_path, @renderer).tap do |texture|
|
||||
texture.blend_mode = SDL::BlendMode::BLEND
|
||||
end
|
||||
end
|
||||
|
||||
def newFont(file_path, font_size = 12) : SDL::TTF::Font
|
||||
SDL::TTF::Font.new file_path, font_size
|
||||
end
|
||||
|
||||
# FIXME: We’ll probably want options for scaling, rotations, and so on.
|
||||
def draw(texture : SDL::Texture, x : Int32, y : Int32, scale_x = 1, scale_y = 1)
|
||||
@renderer.copy texture,
|
||||
dstrect: SDL::Rect[x, y, (texture.width * scale_x).to_i, (texture.height * scale_y).to_i]
|
||||
end
|
||||
|
||||
def draw(surface : SDL::Surface, x : Int32, y : Int32)
|
||||
@renderer.copy surface,
|
||||
dstrect: SDL::Rect[x, y, surface.width, surface.height]
|
||||
end
|
||||
|
||||
def set_font(@current_font)
|
||||
end
|
||||
|
||||
# FIXME:
|
||||
# - This only prints properly “solid” text. Shaded text (depixelated)
|
||||
# would need some additional OpenGL manipulations.
|
||||
# - This is very inefficient resources-wise. If we are willing to drop
|
||||
# ligatures support (is it even supported in SDL::TTF?), we should
|
||||
# build a textures atlas of all glyphs and use that to render text.
|
||||
# - Alternatively, it may be worth it to let users generate a texture of
|
||||
# some text and draw it, manually.
|
||||
def print(text, x, y)
|
||||
font = @current_font
|
||||
|
||||
return if font.nil?
|
||||
|
||||
surface = font.render_solid(text, SDL::Color[0, 0, 0, 255], @renderer.draw_color)
|
||||
|
||||
draw surface, x, y
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Timer
|
||||
getter start
|
||||
getter last_update
|
||||
|
||||
def initialize
|
||||
@start = Time.now
|
||||
@last_update = @start
|
||||
end
|
||||
|
||||
def step
|
||||
now = Time.now
|
||||
|
||||
dt = now.epoch_ms - @last_update.epoch_ms
|
||||
|
||||
@last_update = now
|
||||
|
||||
dt
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Event
|
||||
class Quit
|
||||
end
|
||||
|
||||
class Draw
|
||||
getter window : Naka::Window
|
||||
|
||||
def initialize(@window)
|
||||
end
|
||||
end
|
||||
|
||||
class Update
|
||||
getter dt : Int64
|
||||
|
||||
def initialize(@dt)
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: Split in KeyUp and KeyDown.
|
||||
class KeyUp
|
||||
getter key : LibSDL::Keycode
|
||||
getter scan_code : LibSDL::Scancode
|
||||
|
||||
def initialize(@key, @scan_code)
|
||||
end
|
||||
end
|
||||
class KeyDown
|
||||
getter key : LibSDL::Keycode
|
||||
getter scan_code : LibSDL::Scancode
|
||||
getter repeat : Bool
|
||||
|
||||
def initialize(@key, @scan_code, @repeat)
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION
|
||||
# Update events are still missing, and both Draw and Update should
|
||||
# be time-based.
|
||||
# Why having a `window` parameter instead of making a Window#loop method?
|
||||
# Because we may want to have a `windows` parameter in the future, although
|
||||
# unlikely. But still possible.
|
||||
def self.loop(window, &block)
|
||||
max_fps = 60
|
||||
max_dt = 1000. / 60
|
||||
|
||||
timer = Naka::Timer.new
|
||||
|
||||
renderer = window.renderer
|
||||
|
||||
::loop do
|
||||
exit_requested = false
|
||||
|
||||
while event = SDL::Event.poll
|
||||
r_value = yield case event
|
||||
when SDL::Event::Quit
|
||||
Naka::Event::Quit.new
|
||||
when SDL::Event::Keyboard
|
||||
keysym = event.keysym
|
||||
if event.type == LibSDL::EventType::KEYUP
|
||||
Naka::Event::KeyUp.new keysym.sym, keysym.scancode
|
||||
elsif event.type == LibSDL::EventType::KEYDOWN
|
||||
Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0
|
||||
end
|
||||
else # FIXME: Most event types may need proper Naka:: classes.
|
||||
event
|
||||
end
|
||||
|
||||
if r_value == Naka::Event::Quit
|
||||
exit_requested = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
break if exit_requested
|
||||
|
||||
dt = timer.step
|
||||
|
||||
r_value = yield Naka::Event::Update.new dt
|
||||
|
||||
break if r_value == Naka::Event::Quit
|
||||
|
||||
renderer.draw_color = SDL::Color[255, 255, 255, 255]
|
||||
renderer.clear
|
||||
|
||||
yield Naka::Event::Draw.new window
|
||||
|
||||
renderer.present
|
||||
|
||||
sleep 0.001
|
||||
end
|
||||
end
|
||||
end
|
||||
require "./naka/*"
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
|
||||
require "json"
|
||||
|
||||
class Naka::Map
|
||||
@save_directory : String
|
||||
getter generator
|
||||
def initialize(@save_directory, &block : Proc(Tile, Int32, Int32, Tile?))
|
||||
Dir.mkdir_p @save_directory
|
||||
|
||||
@generator = block
|
||||
end
|
||||
|
||||
class Object
|
||||
getter type : String
|
||||
def initialize(@type)
|
||||
end
|
||||
|
||||
def to_json(builder)
|
||||
builder.object do
|
||||
builder.field "type", @type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Tile < Array(Object)
|
||||
def initialize(element)
|
||||
initialize
|
||||
|
||||
self << element
|
||||
end
|
||||
end
|
||||
|
||||
CHUNK_SIZE = 32
|
||||
class Chunk
|
||||
property tiles : Array(Array(Tile))
|
||||
getter x : Int32
|
||||
getter y : Int32
|
||||
|
||||
def initialize(@x, @y, map)
|
||||
p "Creating Map::Chunk"
|
||||
|
||||
@tiles = Array.new(CHUNK_SIZE) do |x|
|
||||
x += @x * CHUNK_SIZE
|
||||
|
||||
Array.new(CHUNK_SIZE) do |y|
|
||||
y += @y * CHUNK_SIZE
|
||||
|
||||
Tile.new(Object.new "stone").tap do |tile|
|
||||
map.generator.call tile, x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_json
|
||||
{
|
||||
:x => @x,
|
||||
:y => @y,
|
||||
:tiles => @tiles
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def get(x, y)
|
||||
@tiles[x][y]
|
||||
end
|
||||
end
|
||||
|
||||
@chunks = Hash(Int32, Hash(Int32, Chunk)).new
|
||||
|
||||
def get_chunk(x, y)
|
||||
chunks_list = @chunks[x]?
|
||||
if chunks_list.nil?
|
||||
chunks_list = Hash(Int32, Chunk).new
|
||||
@chunks[x] = chunks_list
|
||||
end
|
||||
|
||||
chunk = chunks_list[y]?
|
||||
if chunk.nil?
|
||||
chunk = Chunk.new x, y, self
|
||||
chunks_list[y] = chunk
|
||||
|
||||
save chunk
|
||||
end
|
||||
|
||||
chunk
|
||||
end
|
||||
|
||||
def save(chunk : Chunk)
|
||||
File.write "#{@save_directory}/chunk-#{chunk.x}-#{chunk.y}.json", chunk.to_json
|
||||
end
|
||||
|
||||
def get(x, y)
|
||||
get_chunk(x / CHUNK_SIZE, y / CHUNK_SIZE).get(x % CHUNK_SIZE, y % CHUNK_SIZE)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
|
||||
require "sdl"
|
||||
require "sdl/image"
|
||||
require "sdl/ttf"
|
||||
|
||||
## Helper code comes here
|
||||
|
||||
class Naka
|
||||
def self.init
|
||||
SDL.init SDL::Init::VIDEO
|
||||
SDL::IMG.init SDL::IMG::Init::PNG
|
||||
SDL::TTF.init
|
||||
|
||||
at_exit {
|
||||
SDL.quit
|
||||
SDL::TTF.quit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Renderer < SDL::Renderer
|
||||
end
|
||||
|
||||
class Naka::Window
|
||||
alias Flags = LibSDL::WindowFlags
|
||||
alias Position = LibSDL::WindowPosition
|
||||
|
||||
getter renderer
|
||||
|
||||
@current_font : SDL::TTF::Font? = nil
|
||||
|
||||
def initialize(title, width, height,
|
||||
x : Position = Position::UNDEFINED,
|
||||
y : Position = Position::UNDEFINED,
|
||||
flags : Flags = Flags::SHOWN)
|
||||
|
||||
@window = SDL::Window.new(title, width, height, x, y, flags)
|
||||
@renderer = Naka::Renderer.new @window
|
||||
|
||||
@renderer.draw_blend_mode = SDL::BlendMode::BLEND
|
||||
end
|
||||
|
||||
def newImage(file_path) : SDL::Texture
|
||||
SDL::IMG.load(file_path, @renderer).tap do |texture|
|
||||
texture.blend_mode = SDL::BlendMode::BLEND
|
||||
end
|
||||
end
|
||||
|
||||
def newFont(file_path, font_size = 12) : SDL::TTF::Font
|
||||
SDL::TTF::Font.new file_path, font_size
|
||||
end
|
||||
|
||||
# FIXME: We’ll probably want options for scaling, rotations, and so on.
|
||||
def draw(texture : SDL::Texture, x : Int32, y : Int32, scale_x = 1, scale_y = 1)
|
||||
@renderer.copy texture,
|
||||
dstrect: SDL::Rect[x, y, (texture.width * scale_x).to_i, (texture.height * scale_y).to_i]
|
||||
end
|
||||
|
||||
def draw(surface : SDL::Surface, x : Int32, y : Int32)
|
||||
@renderer.copy surface,
|
||||
dstrect: SDL::Rect[x, y, surface.width, surface.height]
|
||||
end
|
||||
|
||||
def set_font(@current_font)
|
||||
end
|
||||
|
||||
# FIXME:
|
||||
# - This only prints properly “solid” text. Shaded text (depixelated)
|
||||
# would need some additional OpenGL manipulations.
|
||||
# - This is very inefficient resources-wise. If we are willing to drop
|
||||
# ligatures support (is it even supported in SDL::TTF?), we should
|
||||
# build a textures atlas of all glyphs and use that to render text.
|
||||
# - Alternatively, it may be worth it to let users generate a texture of
|
||||
# some text and draw it, manually.
|
||||
def print(text, x, y)
|
||||
font = @current_font
|
||||
|
||||
return if font.nil?
|
||||
|
||||
surface = font.render_solid(text, SDL::Color[0, 0, 0, 255], @renderer.draw_color)
|
||||
|
||||
draw surface, x, y
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Timer
|
||||
getter start
|
||||
getter last_update
|
||||
|
||||
def initialize
|
||||
@start = Time.now
|
||||
@last_update = @start
|
||||
end
|
||||
|
||||
def step
|
||||
now = Time.now
|
||||
|
||||
dt = now.epoch_ms - @last_update.epoch_ms
|
||||
|
||||
@last_update = now
|
||||
|
||||
dt
|
||||
end
|
||||
end
|
||||
|
||||
class Naka::Event
|
||||
class Quit
|
||||
end
|
||||
|
||||
class Draw
|
||||
getter window : Naka::Window
|
||||
|
||||
def initialize(@window)
|
||||
end
|
||||
end
|
||||
|
||||
class Update
|
||||
getter dt : Int64
|
||||
|
||||
def initialize(@dt)
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: Split in KeyUp and KeyDown.
|
||||
class KeyUp
|
||||
getter key : LibSDL::Keycode
|
||||
getter scan_code : LibSDL::Scancode
|
||||
|
||||
def initialize(@key, @scan_code)
|
||||
end
|
||||
end
|
||||
class KeyDown
|
||||
getter key : LibSDL::Keycode
|
||||
getter scan_code : LibSDL::Scancode
|
||||
getter repeat : Bool
|
||||
|
||||
def initialize(@key, @scan_code, @repeat)
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION
|
||||
# Update events are still missing, and both Draw and Update should
|
||||
# be time-based.
|
||||
# Why having a `window` parameter instead of making a Window#loop method?
|
||||
# Because we may want to have a `windows` parameter in the future, although
|
||||
# unlikely. But still possible.
|
||||
def self.loop(window, &block)
|
||||
max_fps = 60
|
||||
max_dt = 1000. / 60
|
||||
|
||||
timer = Naka::Timer.new
|
||||
|
||||
renderer = window.renderer
|
||||
|
||||
::loop do
|
||||
exit_requested = false
|
||||
|
||||
while event = SDL::Event.poll
|
||||
r_value = yield case event
|
||||
when SDL::Event::Quit
|
||||
Naka::Event::Quit.new
|
||||
when SDL::Event::Keyboard
|
||||
keysym = event.keysym
|
||||
if event.type == LibSDL::EventType::KEYUP
|
||||
Naka::Event::KeyUp.new keysym.sym, keysym.scancode
|
||||
elsif event.type == LibSDL::EventType::KEYDOWN
|
||||
Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0
|
||||
end
|
||||
else # FIXME: Most event types may need proper Naka:: classes.
|
||||
event
|
||||
end
|
||||
|
||||
if r_value == Naka::Event::Quit
|
||||
exit_requested = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
break if exit_requested
|
||||
|
||||
dt = timer.step
|
||||
|
||||
r_value = yield Naka::Event::Update.new dt
|
||||
|
||||
break if r_value == Naka::Event::Quit
|
||||
|
||||
renderer.draw_color = SDL::Color[255, 255, 255, 255]
|
||||
renderer.clear
|
||||
|
||||
yield Naka::Event::Draw.new window
|
||||
|
||||
renderer.present
|
||||
|
||||
sleep 0.001
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue