unicodetiles 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/Rakefile +62 -0
- data/Readme.md +15 -0
- data/examples/01_minimal.rb +34 -0
- data/examples/02_simple_dungeon.rb +20 -0
- data/examples/03_fov_dungeon.rb +23 -0
- data/examples/04_shader_lightning.rb +29 -0
- data/examples/05_infinite_forest.rb +23 -0
- data/examples/dungeon.rb +102 -0
- data/examples/example_window.rb +47 -0
- data/examples/forest.rb +65 -0
- data/examples/fov.rb +70 -0
- data/examples/helper.rb +8 -0
- data/examples/shader.rb +46 -0
- data/fonts/DejaVuSansMono.ttf +0 -0
- data/lib/ut.rb +20 -0
- data/lib/ut/engine.rb +146 -0
- data/lib/ut/font_renderer.rb +76 -0
- data/lib/ut/tile.rb +41 -0
- data/lib/ut/version.rb +4 -0
- data/lib/ut/viewport.rb +120 -0
- data/spec/engine_spec.rb +69 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/tile_spec.rb +38 -0
- data/spec/viewport_spec.rb +98 -0
- data/ut.gemspec +15 -0
- metadata +89 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'rake/clean'
|
3
|
+
|
4
|
+
desc 'Default: run specs.'
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
|
8
|
+
# ROCCO =============================================
|
9
|
+
|
10
|
+
# Bring in Rocco tasks
|
11
|
+
require 'rocco/tasks'
|
12
|
+
Rocco::make 'docs/'
|
13
|
+
|
14
|
+
desc 'Build rocco docs'
|
15
|
+
task :docs => :rocco
|
16
|
+
directory 'docs/'
|
17
|
+
|
18
|
+
desc 'Build docs and open in browser for the reading'
|
19
|
+
task :read => :docs do
|
20
|
+
sh 'open docs/lib/ut.html'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Make index.html a copy of ut.html
|
24
|
+
file 'docs/index.html' => 'docs/lib/ut.html' do |f|
|
25
|
+
cp 'docs/lib/ut.html', 'docs/index.html', :preserve => true
|
26
|
+
end
|
27
|
+
task :docs => 'docs/index.html'
|
28
|
+
CLEAN.include 'docs/index.html'
|
29
|
+
|
30
|
+
# Alias for docs task
|
31
|
+
task :doc => :docs
|
32
|
+
|
33
|
+
# GITHUB PAGES =======================================
|
34
|
+
|
35
|
+
desc 'Update gh-pages branch'
|
36
|
+
task :pages => ['docs/.git', :docs] do
|
37
|
+
rev = `git rev-parse --short HEAD`.strip
|
38
|
+
Dir.chdir 'docs' do
|
39
|
+
sh "git add *.html"
|
40
|
+
sh "git commit -m 'rebuild pages from #{rev}'" do |ok,res|
|
41
|
+
if ok
|
42
|
+
verbose { puts "gh-pages updated" }
|
43
|
+
sh "git push -q o HEAD:gh-pages"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Update the pages/ directory clone
|
50
|
+
file 'docs/.git' => ['docs/', '.git/refs/heads/gh-pages'] do |f|
|
51
|
+
sh "cd docs && git init -q && git remote add o ../.git" if !File.exist?(f.name)
|
52
|
+
sh "cd docs && git fetch -q o && git reset -q --hard o/gh-pages && touch ."
|
53
|
+
end
|
54
|
+
CLOBBER.include 'docs/.git'
|
55
|
+
|
56
|
+
# RSPEC =============================================
|
57
|
+
|
58
|
+
desc "Run specs"
|
59
|
+
RSpec::Core::RakeTask.new do |t|
|
60
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
61
|
+
# Put spec opts in a file named .rspec in root
|
62
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Unicodetiles.rb
|
2
|
+
|
3
|
+
Ruby Port of [Unicodetiles.js](https://github.com/tapio/unicodetiles.js)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
* Install Ruby 1.9.2
|
8
|
+
* On Linux: `sudo apt-get install ruby` or [RVM](https://rvm.beginrescueend.com)
|
9
|
+
* On Windows: [Ruby Installer](http://rubyinstaller.org)
|
10
|
+
* *Windows only*: Install [Ruby DevKit](http://rubyinstaller.org/add-ons/devkit/)
|
11
|
+
* Install Bundler gem: `gem install bundler`
|
12
|
+
* Install dependencies: `bundle install`
|
13
|
+
|
14
|
+
## Examples
|
15
|
+
Examples can be found in the `examples/` dir and can be run with `ruby examples/01_minimal.rb` etc.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../lib/ut'
|
4
|
+
require_relative 'helper'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
|
8
|
+
class Window < Gosu::Window
|
9
|
+
attr_accessor :viewport
|
10
|
+
|
11
|
+
def initialize width, height
|
12
|
+
super width, height, false
|
13
|
+
end
|
14
|
+
|
15
|
+
def draw
|
16
|
+
viewport.draw
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
$window = Window.new WINDOW_WIDTH, WINDOW_HEIGHT
|
21
|
+
|
22
|
+
@renderer = UT::FontRenderer.new :font_name => "fonts/DejaVuSansMono.ttf", :tile_size => TILE_SIZE
|
23
|
+
@viewport = UT::Viewport.new :renderer => @renderer, :width => VIEWPORT_WIDTH, :height => VIEWPORT_HEIGHT
|
24
|
+
@viewport.put_string 0, 0, "Hello World !", Gosu::Color::CYAN
|
25
|
+
@viewport.put_string 0, 1, "Some unicode chars:", Gosu::Color::GREEN, Gosu::Color::GRAY
|
26
|
+
%W{☠ ☃ ⚙ ☻ ♞ ☭ ✈ ✟ ✂ ✯}.each_with_index do |c,i|
|
27
|
+
@viewport.put_tile i, 2, (UT::Tile.new :glyph => c, :foreground => Gosu::Color.from_hsv(c.ord%360, 1, 1))
|
28
|
+
end
|
29
|
+
@viewport.put_string 14, 4, "Long strings get automatically wrapped as block"
|
30
|
+
@viewport.put_string 13, 7, "or as a line if u want", nil, nil, :line
|
31
|
+
@viewport.put_string 20, 9, "or not at all", nil, nil, :none
|
32
|
+
|
33
|
+
$window.viewport = @viewport
|
34
|
+
$window.show
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../lib/ut'
|
4
|
+
require_relative 'helper'
|
5
|
+
require_relative 'dungeon'
|
6
|
+
require_relative "example_window"
|
7
|
+
|
8
|
+
$window = ExampleWindow.new
|
9
|
+
|
10
|
+
@dungeon = Dungeon.new
|
11
|
+
@renderer = UT::FontRenderer.new :font_name => "fonts/DejaVuSansMono.ttf", :tile_size => TILE_SIZE, :scale_x => SCALE_X, :scale_y => SCALE_Y
|
12
|
+
@viewport = UT::Viewport.new :renderer => @renderer, :width => VIEWPORT_WIDTH, :height => VIEWPORT_HEIGHT
|
13
|
+
@engine = UT::Engine.new :viewport => @viewport, :world_width => @dungeon.width, :world_height => @dungeon.height
|
14
|
+
@engine.set_source do |x,y|
|
15
|
+
@dungeon.get_tile x, y
|
16
|
+
end
|
17
|
+
|
18
|
+
$window.dungeon = @dungeon
|
19
|
+
$window.engine = @engine
|
20
|
+
$window.show
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../lib/ut'
|
4
|
+
require_relative 'helper'
|
5
|
+
require_relative 'dungeon'
|
6
|
+
require_relative 'example_window'
|
7
|
+
|
8
|
+
$window = ExampleWindow.new
|
9
|
+
|
10
|
+
@dungeon = Dungeon.new
|
11
|
+
@renderer = UT::FontRenderer.new :font_name => "fonts/DejaVuSansMono.ttf", :tile_size => TILE_SIZE, :scale_x => SCALE_X, :scale_y => SCALE_Y
|
12
|
+
@viewport = UT::Viewport.new :renderer => @renderer, :width => VIEWPORT_WIDTH, :height => VIEWPORT_HEIGHT
|
13
|
+
@engine = UT::Engine.new :viewport => @viewport, :world_width => @dungeon.width, :world_height => @dungeon.height
|
14
|
+
@engine.set_source do |x,y|
|
15
|
+
@dungeon.get_tile x, y
|
16
|
+
end
|
17
|
+
@engine.set_mask do |x,y|
|
18
|
+
@dungeon.is_visible? x, y
|
19
|
+
end
|
20
|
+
|
21
|
+
$window.dungeon = @dungeon
|
22
|
+
$window.engine = @engine
|
23
|
+
$window.show
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../lib/ut'
|
4
|
+
require_relative 'helper'
|
5
|
+
require_relative 'dungeon'
|
6
|
+
require_relative 'shader'
|
7
|
+
require_relative 'example_window'
|
8
|
+
|
9
|
+
$window = ExampleWindow.new
|
10
|
+
|
11
|
+
@dungeon = Dungeon.new
|
12
|
+
@shader = Shader.new
|
13
|
+
@renderer = UT::FontRenderer.new :font_name => "fonts/DejaVuSansMono.ttf", :tile_size => TILE_SIZE, :scale_x => SCALE_X, :scale_y => SCALE_Y
|
14
|
+
@viewport = UT::Viewport.new :renderer => @renderer, :width => VIEWPORT_WIDTH, :height => VIEWPORT_HEIGHT
|
15
|
+
@engine = UT::Engine.new :viewport => @viewport, :world_width => @dungeon.width, :world_height => @dungeon.height
|
16
|
+
@engine.set_source do |x,y|
|
17
|
+
@dungeon.get_tile x, y
|
18
|
+
end
|
19
|
+
@engine.set_mask do |x,y|
|
20
|
+
@dungeon.is_visible? x, y
|
21
|
+
end
|
22
|
+
@engine.set_shader do |tile, x, y|
|
23
|
+
@shader.apply tile, x, y
|
24
|
+
end
|
25
|
+
|
26
|
+
$window.dungeon = @dungeon
|
27
|
+
$window.shader = @shader
|
28
|
+
$window.engine = @engine
|
29
|
+
$window.show
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../lib/ut'
|
4
|
+
require_relative 'helper'
|
5
|
+
require_relative 'forest'
|
6
|
+
require_relative 'example_window'
|
7
|
+
|
8
|
+
$window = ExampleWindow.new
|
9
|
+
|
10
|
+
@forest = Forest.new
|
11
|
+
@renderer = UT::FontRenderer.new :font_name => "fonts/DejaVuSansMono.ttf", :tile_size => TILE_SIZE, :scale_x => SCALE_X, :scale_y => SCALE_Y
|
12
|
+
@viewport = UT::Viewport.new :renderer => @renderer, :width => VIEWPORT_WIDTH, :height => VIEWPORT_HEIGHT
|
13
|
+
@engine = UT::Engine.new :viewport => @viewport
|
14
|
+
@engine.set_source do |x,y|
|
15
|
+
@forest.get_tile x, y
|
16
|
+
end
|
17
|
+
@engine.set_mask do |x,y|
|
18
|
+
@forest.is_visible? x, y
|
19
|
+
end
|
20
|
+
|
21
|
+
$window.dungeon = @forest
|
22
|
+
$window.engine = @engine
|
23
|
+
$window.show
|
data/examples/dungeon.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require_relative 'fov'
|
3
|
+
|
4
|
+
class Dungeon
|
5
|
+
include ShadowcastingFieldOfView
|
6
|
+
attr_reader :px, :py
|
7
|
+
|
8
|
+
AT_TILE = UT::Tile.new :glyph => "@"
|
9
|
+
WALL_TILE = UT::Tile.new :glyph => "▒", :foreground => Gosu::Color.new(0xFF646464)
|
10
|
+
FLOOR_TILE = UT::Tile.new :glyph => ".", :foreground => Gosu::Color.new(0xFF323232)
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@px = 3
|
14
|
+
@py = 2
|
15
|
+
@map = [
|
16
|
+
" ##### ##### ",
|
17
|
+
" #...######## #...#### ",
|
18
|
+
" #..........# #......# ",
|
19
|
+
" #...######.# #..###.# ",
|
20
|
+
" ##### #.# ######.####",
|
21
|
+
" #.# #.....#",
|
22
|
+
" #.# #.....#",
|
23
|
+
" #.############.....#",
|
24
|
+
" #..................#",
|
25
|
+
" ####.###############",
|
26
|
+
"########## #.# #....# ",
|
27
|
+
"#........## #.# #.#..# ",
|
28
|
+
"#..####...# #.# #.#..# ",
|
29
|
+
"#.........# #.# #.###### ",
|
30
|
+
"#.........# #.# #......# ",
|
31
|
+
"##.######## #.# #......# ",
|
32
|
+
" #.# #.# #####.## ",
|
33
|
+
" #.# #.# #.# ",
|
34
|
+
" #.# #######.# #.# ",
|
35
|
+
" #.# #.......# #.# ",
|
36
|
+
" #.# #.....#.# #.# ",
|
37
|
+
" #.# #.....#.# #.# ",
|
38
|
+
" #.# #.....#.# #.# ",
|
39
|
+
" #.# #.....#.# #.# ",
|
40
|
+
" #.# #######.# #.# ",
|
41
|
+
" #.# #.###########.# ",
|
42
|
+
" #.# #.............# ",
|
43
|
+
" #.#############.########### ",
|
44
|
+
" #...............# ",
|
45
|
+
" ################# "
|
46
|
+
]
|
47
|
+
update_fov
|
48
|
+
end
|
49
|
+
|
50
|
+
def width
|
51
|
+
@map[0].length
|
52
|
+
end
|
53
|
+
|
54
|
+
def height
|
55
|
+
@map.length
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_tile x, y
|
59
|
+
return nil if x<0 || y<0
|
60
|
+
return AT_TILE if x==@px && y==@py
|
61
|
+
|
62
|
+
char = @map[y][x]
|
63
|
+
case char
|
64
|
+
when "."
|
65
|
+
FLOOR_TILE
|
66
|
+
when "#"
|
67
|
+
WALL_TILE
|
68
|
+
else
|
69
|
+
UT::NULLTILE
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def move_player dx, dy
|
76
|
+
destination = get_tile @px+dx, @py+dy
|
77
|
+
if destination == FLOOR_TILE
|
78
|
+
@px,@py = @px+dx, @py+dy
|
79
|
+
update_fov
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_visible? x, y
|
84
|
+
@mask[[x,y]] || false
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def update_fov
|
90
|
+
@mask = {}
|
91
|
+
do_fov @px, @py, 7
|
92
|
+
end
|
93
|
+
|
94
|
+
def blocked? x, y
|
95
|
+
return true if x < 0 || y < 0
|
96
|
+
return @map[y][x] == "#"
|
97
|
+
end
|
98
|
+
|
99
|
+
def light x, y
|
100
|
+
@mask[[x,y]] = true
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class ExampleWindow < Gosu::Window
|
4
|
+
attr_accessor :engine, :dungeon, :shader
|
5
|
+
|
6
|
+
REPEAT = 250
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super WINDOW_WIDTH, WINDOW_HEIGHT, false
|
10
|
+
@down_keys = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
keys = [Gosu::KbW, Gosu::KbA, Gosu::KbS, Gosu::KbD]
|
15
|
+
deltas = [[0,-1],[-1,0],[0,1],[1,0]]
|
16
|
+
keys.each_with_index do |key, i|
|
17
|
+
if button_down? key
|
18
|
+
if @down_keys.include? key
|
19
|
+
@down_keys[key] += update_interval
|
20
|
+
if @down_keys[key] > REPEAT
|
21
|
+
@dungeon.move_player deltas[i][0], deltas[i][1]
|
22
|
+
@down_keys[key] -= REPEAT
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@dungeon.move_player deltas[i][0], deltas[i][1]
|
26
|
+
@down_keys[key] = update_interval
|
27
|
+
end
|
28
|
+
else
|
29
|
+
@down_keys.delete key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
engine.update @dungeon.px, @dungeon.py
|
33
|
+
shader.center @dungeon.px, @dungeon.py unless shader.nil?
|
34
|
+
shader.update update_interval unless shader.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw
|
38
|
+
draw_quad 0, 0, Gosu::Color::BLACK,
|
39
|
+
width, 0, Gosu::Color::BLACK,
|
40
|
+
0, height, Gosu::Color::BLACK,
|
41
|
+
width, height, Gosu::Color::BLACK
|
42
|
+
|
43
|
+
engine.viewport.draw
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
data/examples/forest.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require_relative "fov"
|
3
|
+
|
4
|
+
class Forest
|
5
|
+
include ShadowcastingFieldOfView
|
6
|
+
|
7
|
+
attr_reader :px, :py
|
8
|
+
|
9
|
+
GRASS = UT::Tile.new :glyph => ".", :foreground => Gosu::Color.new(0xFF509650)
|
10
|
+
GRASS2 = UT::Tile.new :glyph => ".", :foreground => Gosu::Color.new(0xFF5AFA5A)
|
11
|
+
TREE = UT::Tile.new :glyph => "☘", :foreground => Gosu::Color.new(0xFF146414)
|
12
|
+
ROCK = UT::Tile.new :glyph => "☁", :foreground => Gosu::Color.new(0xFF646464)
|
13
|
+
AT = UT::Tile.new :glyph => "@", :foreground => Gosu::Color.new(0xFFFFFFFF)
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@px = 0
|
17
|
+
@py = 0
|
18
|
+
update_fov
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_tile x, y
|
22
|
+
return AT if x == @px && y == @py
|
23
|
+
r = deterministic_rand x, y
|
24
|
+
return TREE if (r > 0.80)
|
25
|
+
return ROCK if (r > 0.75)
|
26
|
+
return GRASS2 if (r > 0.50)
|
27
|
+
return GRASS;
|
28
|
+
end
|
29
|
+
|
30
|
+
def move_player dx, dy
|
31
|
+
destination = get_tile @px+dx, @py+dy
|
32
|
+
if destination.glyph == "."
|
33
|
+
@px,@py = @px+dx, @py+dy
|
34
|
+
update_fov
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_visible? x, y
|
39
|
+
@mask[[x,y]] || false
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def update_fov
|
45
|
+
@mask = {}
|
46
|
+
do_fov @px, @py, 13
|
47
|
+
end
|
48
|
+
|
49
|
+
def blocked? x, y
|
50
|
+
return (get_tile x, y).glyph != "."
|
51
|
+
end
|
52
|
+
|
53
|
+
def light x, y
|
54
|
+
@mask[[x,y]] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
A = 2971215073
|
58
|
+
B = 479001599
|
59
|
+
M = 1048573;
|
60
|
+
|
61
|
+
def deterministic_rand x, y
|
62
|
+
num = ((A*x)^(B*y)) % M;
|
63
|
+
return num.to_f / M;
|
64
|
+
end
|
65
|
+
end
|
data/examples/fov.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# taken from http://roguebasin.roguelikedevelopment.org/index.php/Ruby_shadowcasting_implementation
|
2
|
+
|
3
|
+
module ShadowcastingFieldOfView
|
4
|
+
# Multipliers for transforming coordinates into other octants
|
5
|
+
@@mult = [
|
6
|
+
[1, 0, 0, -1, -1, 0, 0, 1],
|
7
|
+
[0, 1, -1, 0, 0, -1, 1, 0],
|
8
|
+
[0, 1, 1, 0, 0, -1, -1, 0],
|
9
|
+
[1, 0, 0, 1, -1, 0, 0, -1],
|
10
|
+
]
|
11
|
+
|
12
|
+
# Determines which co-ordinates on a 2D grid are visible
|
13
|
+
# from a particular co-ordinate.
|
14
|
+
# start_x, start_y: center of view
|
15
|
+
# radius: how far field of view extends
|
16
|
+
def do_fov(start_x, start_y, radius)
|
17
|
+
light start_x, start_y
|
18
|
+
8.times do |oct|
|
19
|
+
cast_light start_x, start_y, 1, 1.0, 0.0, radius,
|
20
|
+
@@mult[0][oct],@@mult[1][oct],
|
21
|
+
@@mult[2][oct], @@mult[3][oct], 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# Recursive light-casting function
|
27
|
+
def cast_light(cx, cy, row, light_start, light_end, radius, xx, xy, yx, yy, id)
|
28
|
+
return if light_start < light_end
|
29
|
+
radius_sq = radius * radius
|
30
|
+
(row..radius).each do |j| # .. is inclusive
|
31
|
+
dx, dy = -j - 1, -j
|
32
|
+
blocked = false
|
33
|
+
while dx <= 0
|
34
|
+
dx += 1
|
35
|
+
# Translate the dx, dy co-ordinates into map co-ordinates
|
36
|
+
mx, my = cx + dx * xx + dy * xy, cy + dx * yx + dy * yy
|
37
|
+
# l_slope and r_slope store the slopes of the left and right
|
38
|
+
# extremities of the square we're considering:
|
39
|
+
l_slope, r_slope = (dx-0.5)/(dy+0.5), (dx+0.5)/(dy-0.5)
|
40
|
+
if light_start < r_slope
|
41
|
+
next
|
42
|
+
elsif light_end > l_slope
|
43
|
+
break
|
44
|
+
else
|
45
|
+
# Our light beam is touching this square; light it
|
46
|
+
light(mx, my) if (dx*dx + dy*dy) < radius_sq
|
47
|
+
if blocked
|
48
|
+
# We've scanning a row of blocked squares
|
49
|
+
if blocked?(mx, my)
|
50
|
+
new_start = r_slope
|
51
|
+
next
|
52
|
+
else
|
53
|
+
blocked = false
|
54
|
+
light_start = new_start
|
55
|
+
end
|
56
|
+
else
|
57
|
+
if blocked?(mx, my) and j < radius
|
58
|
+
# This is a blocking square, start a child scan
|
59
|
+
blocked = true
|
60
|
+
cast_light(cx, cy, j+1, light_start, l_slope,
|
61
|
+
radius, xx, xy, yx, yy, id+1)
|
62
|
+
new_start = r_slope
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end # while dx <= 0
|
67
|
+
break if blocked
|
68
|
+
end # (row..radius+1).each
|
69
|
+
end
|
70
|
+
end
|
data/examples/helper.rb
ADDED
data/examples/shader.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class Shader
|
2
|
+
attr_accessor :light_color, :light_intensity, :max_dist
|
3
|
+
|
4
|
+
def initialize options = {}
|
5
|
+
self.light_color = options[:light_color] || Gosu::Color.new(0xFFFF2F00)
|
6
|
+
self.light_intensity = options[:light_intensity] || 0.7
|
7
|
+
self.max_dist = options[:max_dist] || 5
|
8
|
+
@time = 0
|
9
|
+
@x = 0
|
10
|
+
@y = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply tile, x, y
|
14
|
+
return tile if tile.nil?
|
15
|
+
anim = @time/1000.0
|
16
|
+
anim = (anim - anim.floor - 0.5).abs + 0.5
|
17
|
+
dist = distance @x, @y, x, y
|
18
|
+
return tile if dist > max_dist || dist == 0
|
19
|
+
factor = (1.0 - (dist / max_dist)) * light_intensity * anim
|
20
|
+
r = (blend light_color.red, tile.foreground.red, factor).round
|
21
|
+
g = (blend light_color.green, tile.foreground.green, factor).round
|
22
|
+
b = (blend light_color.blue, tile.foreground.blue, factor).round
|
23
|
+
argb = Integer("0xFF%02X%02X%02X" % [r, g, b])
|
24
|
+
shaded = tile.clone
|
25
|
+
shaded.foreground = Gosu::Color.new argb
|
26
|
+
shaded
|
27
|
+
end
|
28
|
+
|
29
|
+
def update delta
|
30
|
+
@time += delta
|
31
|
+
end
|
32
|
+
|
33
|
+
def center x, y
|
34
|
+
@x, @y = x, y
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def distance x1, y1, x2, y2
|
40
|
+
Math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
41
|
+
end
|
42
|
+
|
43
|
+
def blend a, b, f
|
44
|
+
a*f + b*(1.0-f)
|
45
|
+
end
|
46
|
+
end
|
Binary file
|
data/lib/ut.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# # Unicodetiles.rb
|
2
|
+
#
|
3
|
+
# Ruby Port of [Unicodetiles.js](https://github.com/tapio/unicodetiles.js)
|
4
|
+
|
5
|
+
$LOAD_PATH << './lib'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'bundler/setup'
|
9
|
+
|
10
|
+
require 'gosu'
|
11
|
+
|
12
|
+
require 'ut/tile'
|
13
|
+
require 'ut/viewport'
|
14
|
+
require 'ut/engine'
|
15
|
+
require 'ut/font_renderer'
|
16
|
+
require 'ut/version'
|
17
|
+
|
18
|
+
module UT
|
19
|
+
|
20
|
+
end
|
data/lib/ut/engine.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module UT
|
2
|
+
|
3
|
+
# The *Engine* is responsible for fetching the relevant *Tile*s from the
|
4
|
+
# `source` and checking its visibility with the `mask`. The *Tile*s are also
|
5
|
+
# passed to the `shader` before being finally passed to the *Viewport*.
|
6
|
+
# `Enigne.new` takes an `options` hash as its only parameter.
|
7
|
+
# The `options` hash respects three members:
|
8
|
+
#
|
9
|
+
# * `:source`: The source used to fetch tiles. _Defaults to `DEFAULT_SOURCE`_.
|
10
|
+
#
|
11
|
+
# * `:mask`: The mask used to determine a tile's visiblity. _Defaults to
|
12
|
+
# `DEFAULT_MASK`_.
|
13
|
+
#
|
14
|
+
# * `:shader`: The shader to preprocess tiles. _Defaults to
|
15
|
+
# `DEFAULT_SOURCE`_.
|
16
|
+
#
|
17
|
+
# * `:viewport`: The *Viewport* used for rendering.
|
18
|
+
#
|
19
|
+
# * `:world_width`: The width of the world. _Default to `nil`_.
|
20
|
+
#
|
21
|
+
# * `:world_height`: The height of the world. _Defaults to `nil`_.
|
22
|
+
class Engine
|
23
|
+
# The default shader. Just returns the tile it gets.
|
24
|
+
DEFAULT_SHADER = lambda {|tile,x,y| tile}
|
25
|
+
# The default tile source. Always return `Tile::NULL`
|
26
|
+
DEFAULT_SOURCE = lambda {|x,y| Tile::NULL}
|
27
|
+
# The default mask. Always returns `true`.
|
28
|
+
DEFAULT_MASK = lambda {|x,y| true}
|
29
|
+
|
30
|
+
# The *Viewport* used for rendering
|
31
|
+
attr_accessor :viewport
|
32
|
+
# The width and height of the world. *Tile*s 'outside' of the world are
|
33
|
+
# not passed to the *Viewport* for rendering.
|
34
|
+
# If the `world_width` and `world_height` are `nil`, the world is infinte
|
35
|
+
# and all tiles are rendered.
|
36
|
+
attr_accessor :world_height, :world_width
|
37
|
+
|
38
|
+
def initialize options = {}
|
39
|
+
@source = options[:source] || DEFAULT_SOURCE
|
40
|
+
@mask = options[:mask] || DEFAULT_MASK
|
41
|
+
@shader = options[:shader] || DEFAULT_SHADER
|
42
|
+
self.viewport = options[:viewport]
|
43
|
+
self.world_width = options[:world_width]
|
44
|
+
self.world_height = options[:world_height]
|
45
|
+
@cache = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes a block that accepts a `tile`, `x` and `y` and stores it as the
|
49
|
+
# shader function.
|
50
|
+
def set_shader &blk
|
51
|
+
@shader = blk || DEFAULT_SHADER
|
52
|
+
end
|
53
|
+
|
54
|
+
# Takes a *Proc* or *lambda* which accepts a `tile`, `x` and `y` and stores
|
55
|
+
# it as the shader function.
|
56
|
+
def set_shader= shader
|
57
|
+
@shader = shader || DEFAULT_SHADER
|
58
|
+
end
|
59
|
+
|
60
|
+
# Takes a block that accepts `x` and `y` and stores it as the
|
61
|
+
# source function.
|
62
|
+
def set_source &blk
|
63
|
+
@source = blk || DEFAULT_SOURCE
|
64
|
+
end
|
65
|
+
|
66
|
+
# Takes a *Proc* or *lambda* which accepts a `x` and `y` and stores
|
67
|
+
# it as the source function.
|
68
|
+
def set_source= source
|
69
|
+
@source = source || DEFAULT_SOURCE
|
70
|
+
end
|
71
|
+
|
72
|
+
# Takes a block that accepts `x` and `y` and stores it as the
|
73
|
+
# mask function.
|
74
|
+
def set_mask &blk
|
75
|
+
@mask = blk || DEFAULT_MASK
|
76
|
+
end
|
77
|
+
|
78
|
+
# Takes a *Proc* or *lambda* which accepts a `x` and `y` and stores
|
79
|
+
# it as the mask function.
|
80
|
+
def set_mask= mask
|
81
|
+
@mask = mask || DEFAULT_MASK
|
82
|
+
end
|
83
|
+
|
84
|
+
# Fetches the *Tile* at location `[x,y]` from the source
|
85
|
+
def fetch x, y
|
86
|
+
return nil unless in_world? x, y
|
87
|
+
return nil unless is_visible? x, y
|
88
|
+
|
89
|
+
tile = @cache[[x, y]] if cache_enabled?
|
90
|
+
tile ||= @source.call x, y
|
91
|
+
@cache[[x, y]] = tile if cache_enabled?
|
92
|
+
apply_shader tile, x ,y
|
93
|
+
end
|
94
|
+
|
95
|
+
# Applies the shader to the `tile` at `[x,y]`
|
96
|
+
def apply_shader tile, x, y
|
97
|
+
@shader.call tile, x, y
|
98
|
+
end
|
99
|
+
|
100
|
+
# Determines the visibility of the *Tile* at `[x,y]`
|
101
|
+
def is_visible? x, y
|
102
|
+
@mask.call x, y
|
103
|
+
end
|
104
|
+
|
105
|
+
# Updates the *Engine*. `world_x` and `world_y` represents the center of the
|
106
|
+
# world that should be rendered in _tile coordinates_.
|
107
|
+
def update world_x, world_y
|
108
|
+
x = world_x - @viewport.center_x
|
109
|
+
y = world_y - @viewport.center_y
|
110
|
+
@viewport.width.times do |xi|
|
111
|
+
@viewport.height.times do |yi|
|
112
|
+
tx, ty = x+xi, y+yi
|
113
|
+
tile = fetch tx, ty
|
114
|
+
@viewport.put_tile xi, yi, tile
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Checks if the cache is enabled.
|
120
|
+
def cache_enabled?
|
121
|
+
@cache_enabled
|
122
|
+
end
|
123
|
+
|
124
|
+
# Enables or disables the cache.
|
125
|
+
def cache_enabled= value
|
126
|
+
@cache_enabled = value
|
127
|
+
end
|
128
|
+
|
129
|
+
# Clears the cache.
|
130
|
+
def clear_cache
|
131
|
+
@cache = {}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks if a location is in the world.
|
135
|
+
def in_world? x, y
|
136
|
+
result = true
|
137
|
+
unless world_width.nil?
|
138
|
+
result &&= (x >= 0 && x < world_width)
|
139
|
+
end
|
140
|
+
unless world_height.nil?
|
141
|
+
result &&= (y >= 0 && y< world_height)
|
142
|
+
end
|
143
|
+
result
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module UT
|
2
|
+
# The *FontRenderer* uses the `Gosu::Font` class to draw the tiles on a
|
3
|
+
# `Gosu::Window`.
|
4
|
+
# `FontRenderer.new` takes an `options` hash as its only parameter.
|
5
|
+
# The `options` hash respects three members:
|
6
|
+
#
|
7
|
+
# * `:window`: The `Gosu::Window`. _Defaults to `$window`._
|
8
|
+
#
|
9
|
+
# * `:font_name`: Specifies the name of the font. Accepts the same values
|
10
|
+
# as `Gosu::Font.new`. _Defaults to `nil`._
|
11
|
+
#
|
12
|
+
# * `:tile_size`: The size of the tile on screen in _pixels_. Equivalent to the font
|
13
|
+
# size. _Default to `0`_.
|
14
|
+
#
|
15
|
+
# * `:scale_x`: Scales the font horizontally by this factor to reduce
|
16
|
+
# spacing between the chars. _Default to 1_.
|
17
|
+
#
|
18
|
+
# * `:scale_y`: Scales the font vertically by this factor to reduce spacing
|
19
|
+
# between the chars. _Defaults to 1_.
|
20
|
+
class FontRenderer
|
21
|
+
attr_accessor :tile_size
|
22
|
+
|
23
|
+
def initialize options
|
24
|
+
@window = options[:window] || $window
|
25
|
+
@font_name = options[:font_name]
|
26
|
+
@tile_size = options[:tile_size] || 0
|
27
|
+
@scale_x = options[:scale_x] || 1
|
28
|
+
@scale_y = options[:scale_y] || 1
|
29
|
+
reload_font
|
30
|
+
end
|
31
|
+
|
32
|
+
def tile_size
|
33
|
+
@tile_size
|
34
|
+
end
|
35
|
+
|
36
|
+
def tile_size= value
|
37
|
+
@tile_size = value
|
38
|
+
reload_font
|
39
|
+
end
|
40
|
+
|
41
|
+
def font_name= value
|
42
|
+
@font_name = value
|
43
|
+
reload_font
|
44
|
+
end
|
45
|
+
|
46
|
+
def font_name
|
47
|
+
@font_name
|
48
|
+
end
|
49
|
+
|
50
|
+
# The `Gosu::Font` instance used for drawing the tiles
|
51
|
+
def font
|
52
|
+
@font
|
53
|
+
end
|
54
|
+
|
55
|
+
# Renders the `tile` width the top-left corner at `[left,top]` on the
|
56
|
+
# `window`
|
57
|
+
def render tile, left, top
|
58
|
+
@window.draw_quad left , top , tile.background,
|
59
|
+
left+tile_size, top , tile.background,
|
60
|
+
left , top + tile_size, tile.background,
|
61
|
+
left+tile_size, top + tile_size, tile.background
|
62
|
+
|
63
|
+
@font.draw_rel tile.glyph,
|
64
|
+
left+tile_size/2, top+tile_size/2,
|
65
|
+
0, 0.5, 0.5,
|
66
|
+
@scale_x, @scale_y,
|
67
|
+
tile.foreground
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def reload_font
|
73
|
+
@font = Gosu::Font.new(@window, @font_name, @tile_size)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/ut/tile.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module UT
|
2
|
+
# A *Tile* stores all attributes required by a *Viewport* to draw it.
|
3
|
+
# `Tile.new` takes an `args` hash as its only parameter.
|
4
|
+
# The `args` hash respects three members:
|
5
|
+
#
|
6
|
+
# * `:glyph`: The unicode character. _Defaults to `' '`._
|
7
|
+
#
|
8
|
+
# * `:foreground`: The foreground color represented by a `Gosu::Color`
|
9
|
+
# instance. _Defaults to `DEFAULT_FOREGROUND`._
|
10
|
+
#
|
11
|
+
# * `:background`: The background color represented by a `Gosu::Color`
|
12
|
+
# instance. _Defaults to `DEFAULT_BACKGROUND`._
|
13
|
+
class Tile
|
14
|
+
# The unicode character
|
15
|
+
attr_accessor :glyph
|
16
|
+
# Foreground and background color of the *Tile*
|
17
|
+
# Colors are stored as an instance of `Gosu::Color`
|
18
|
+
attr_accessor :foreground, :background
|
19
|
+
|
20
|
+
# The default foreground color is white
|
21
|
+
DEFAULT_FOREGROUND = Gosu::Color::WHITE
|
22
|
+
# the default background color is black
|
23
|
+
DEFAULT_BACKGROUND = Gosu::Color::BLACK
|
24
|
+
# An empty tile wih default fore- and background color
|
25
|
+
NULL = Tile.new
|
26
|
+
|
27
|
+
def initialize args = {}
|
28
|
+
self.glyph = args[:glyph] || " "
|
29
|
+
self.foreground = args[:foreground] || DEFAULT_FOREGROUND
|
30
|
+
self.background = args[:background] || DEFAULT_BACKGROUND
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clones the tile by copying all attributes to a new *Tile*. Returns the
|
34
|
+
# cloned tile.
|
35
|
+
def clone
|
36
|
+
Tile.new :glyph => glyph,
|
37
|
+
:foreground => foreground,
|
38
|
+
:background => background
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/ut/version.rb
ADDED
data/lib/ut/viewport.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module UT
|
2
|
+
|
3
|
+
# The *viewport* stores the *Tile*s that should be drawn onto the screen as well
|
4
|
+
# as their location on the screen.
|
5
|
+
# `Viewport.new` takes an `options` hash as its only parameter.
|
6
|
+
# The `options` hash respects three members:
|
7
|
+
#
|
8
|
+
# * `:renderer`: The renderer that renders the tiles. _Defaults to `nil`._
|
9
|
+
#
|
10
|
+
# * `:left`: Specifies the horizontal offset in _pixels_ from the left side
|
11
|
+
# of the screen. _Defaults to 0_.
|
12
|
+
#
|
13
|
+
# * `:top`: Specifies the vertical offset in _pixels_ from the top of the
|
14
|
+
# screen. _Defaults to 0_.
|
15
|
+
#
|
16
|
+
# * `:width`: The width of the *Viewport* in _tiles_.
|
17
|
+
#
|
18
|
+
# * `:height`: The height of the *Viewport* in _tiles_.
|
19
|
+
#
|
20
|
+
# * `:wrap_mode`: The wrap mode for strings. _Defaults to `:block`_.
|
21
|
+
class Viewport
|
22
|
+
# The renderer used to draw the tiles on screen.
|
23
|
+
# Must repond to:
|
24
|
+
#
|
25
|
+
# * `tile_size`: Returns the size of the rendered tile in _pixel_.
|
26
|
+
#
|
27
|
+
# * `render(tile, left, top)`: Renders the `tile` on screen with the
|
28
|
+
# top-left corner of the tile located at `[left, top]`.
|
29
|
+
attr_accessor :renderer
|
30
|
+
# Coordinates of the top-left corner of the *Viewport*.
|
31
|
+
attr_accessor :top, :left
|
32
|
+
# Width and height of the viewport measured in _tiles_.
|
33
|
+
attr_accessor :width, :height
|
34
|
+
# A flag which tells the *Viewport* how to handle strings that are longer
|
35
|
+
# than `width`. Accepts the following values:
|
36
|
+
#
|
37
|
+
# * `:line`: Wraps the string into the next line and starts at the
|
38
|
+
# beginning of the line.
|
39
|
+
# * `:block`: Wraps the string into the next line and starts at the same
|
40
|
+
# x-coordinate.
|
41
|
+
# * `:none`: No wrapping of strings
|
42
|
+
attr_accessor :wrap_mode
|
43
|
+
|
44
|
+
def initialize options={}
|
45
|
+
self.renderer = options[:renderer]
|
46
|
+
self.left = options[:left] || 0
|
47
|
+
self.top = options[:top] || 0
|
48
|
+
self.width = options[:width]
|
49
|
+
self.height = options[:height]
|
50
|
+
self.wrap_mode = options[:wrap_mode] || :block
|
51
|
+
|
52
|
+
@tiles = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Clears all tiles in the *Viewport*
|
56
|
+
def clear
|
57
|
+
@tiles = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Stores the `tile` at location `[x,y]`
|
61
|
+
def put_tile x, y, tile
|
62
|
+
if tile.nil?
|
63
|
+
@tiles.delete [x,y]
|
64
|
+
else
|
65
|
+
@tiles[[x,y]] = tile
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Puts a `string` into the *Viewport* by creating a tile for each character
|
70
|
+
# and storing them in a line starting at `[x,y]`. The tiles colors can be
|
71
|
+
# specified by `foreground` and `background`. `wrap_mode` overrides the
|
72
|
+
# global `wrap_mode` of the *Viewport*.
|
73
|
+
def put_string x, y, string, foreground = nil, background = nil, wrap_mode = nil
|
74
|
+
counter = 0
|
75
|
+
string.each_char do |c|
|
76
|
+
tx, ty = case wrap_mode || self.wrap_mode
|
77
|
+
when :block
|
78
|
+
[x+counter%(width-x), y+(counter/(width-x)).floor]
|
79
|
+
when :line
|
80
|
+
[(x+counter)%width, ty = y+((x+counter)/width).floor]
|
81
|
+
else
|
82
|
+
[x + counter, y]
|
83
|
+
end
|
84
|
+
put_tile tx, ty, (Tile.new :glyph => c,
|
85
|
+
:foreground => foreground,
|
86
|
+
:background => background)
|
87
|
+
counter += 1;
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Draws the tiles onto the screen using the `renderer`
|
92
|
+
def draw
|
93
|
+
@tiles.each do |coords, tile|
|
94
|
+
tleft = left + coords[0]*@renderer.tile_size
|
95
|
+
ttop = top + coords[1]*@renderer.tile_size
|
96
|
+
@renderer.render tile, tleft, ttop
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# The width of the *Viewport* on the screen in _pixels_.
|
101
|
+
def render_width
|
102
|
+
width * @renderer.tile_size
|
103
|
+
end
|
104
|
+
|
105
|
+
# The height of the *Viewport* on the screen in _pixels_.
|
106
|
+
def render_height
|
107
|
+
height * @renderer.tile_size
|
108
|
+
end
|
109
|
+
|
110
|
+
# The x-coordinate of the center of the *Viewport* in _tiles_.
|
111
|
+
def center_x
|
112
|
+
(width/2).floor
|
113
|
+
end
|
114
|
+
|
115
|
+
# The y-coordinate of the center of the *Viewport* in _tiles_.
|
116
|
+
def center_y
|
117
|
+
(height/2).floor
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module UT
|
4
|
+
describe Engine do
|
5
|
+
let(:viewport) { double(Viewport) }
|
6
|
+
subject do
|
7
|
+
Engine.new :viewport => viewport
|
8
|
+
end
|
9
|
+
|
10
|
+
its(:viewport) { should == viewport }
|
11
|
+
|
12
|
+
describe "#update" do
|
13
|
+
before do
|
14
|
+
viewport.stub(:center_x => 1, :center_y => 2)
|
15
|
+
viewport.stub(:width => 2, :height => 2)
|
16
|
+
def subject.fetch x, y
|
17
|
+
{x:x, y:y}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "updates tile in the viewport" do
|
22
|
+
subject.viewport.should_receive(:put_tile).with(0,0, {x:4,y:2})
|
23
|
+
subject.viewport.should_receive(:put_tile).with(1,0, {x:5,y:2})
|
24
|
+
subject.viewport.should_receive(:put_tile).with(0,1, {x:4,y:3})
|
25
|
+
subject.viewport.should_receive(:put_tile).with(1,1, {x:5,y:3})
|
26
|
+
|
27
|
+
subject.update 5,4
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#fetch" do
|
33
|
+
let(:source) { double("source") }
|
34
|
+
before { subject.set_source = source}
|
35
|
+
it "calls the source to fetch a tile" do
|
36
|
+
source.should_receive(:call).with(1, 2).and_return
|
37
|
+
|
38
|
+
subject.fetch 1, 2
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns the tile from the source" do
|
42
|
+
tile = Tile.new
|
43
|
+
source.stub(:call => tile)
|
44
|
+
|
45
|
+
(subject.fetch 0, 0).should == tile
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when cache is enabled" do
|
49
|
+
before do
|
50
|
+
subject.cache_enabled = true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "fetches a tile only once" do
|
54
|
+
source.should_receive(:call).with(0,0).once.and_return Tile.new
|
55
|
+
|
56
|
+
2.times { subject.fetch 0,0 }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "cached tile is same as fetched tile" do
|
60
|
+
tile = Tile.new
|
61
|
+
source.should_receive(:call).with(0,0).and_return tile
|
62
|
+
|
63
|
+
subject.fetch 0, 0
|
64
|
+
subject.fetch(0, 0).should == tile
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative '../lib/ut'
|
data/spec/tile_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module UT
|
4
|
+
describe Tile do
|
5
|
+
describe '#initialize' do
|
6
|
+
context 'without args' do
|
7
|
+
its(:glyph) { should == " " }
|
8
|
+
its(:foreground) { should == Tile::DEFAULT_FOREGROUND }
|
9
|
+
its(:background) { should == Tile::DEFAULT_BACKGROUND }
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with args' do
|
13
|
+
subject do
|
14
|
+
Tile.new :glyph => "a",
|
15
|
+
:foreground => Gosu::Color::RED,
|
16
|
+
:background => Gosu::Color::GREEN
|
17
|
+
end
|
18
|
+
|
19
|
+
its(:glyph) { should == "a" }
|
20
|
+
its(:foreground) { should == Gosu::Color::RED }
|
21
|
+
its(:background) { should == Gosu::Color::GREEN }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#clone' do
|
26
|
+
let(:tile) do
|
27
|
+
Tile.new :glyph => "a",
|
28
|
+
:foreground => Gosu::Color::RED,
|
29
|
+
:background => Gosu::Color::GREEN
|
30
|
+
end
|
31
|
+
subject { tile.clone }
|
32
|
+
|
33
|
+
its(:glyph) { should == tile.glyph }
|
34
|
+
its(:foreground) { should == tile.foreground }
|
35
|
+
its(:background) { should == tile.background }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module UT
|
4
|
+
describe Viewport do
|
5
|
+
let(:renderer) { double("renderer") }
|
6
|
+
describe '#initialize' do
|
7
|
+
context 'with options' do
|
8
|
+
let(:options) { [:renderer, :left, :top, :width, :height] }
|
9
|
+
subject { Viewport.new options.reduce({}){ |h,x| h[x]=x;h } }
|
10
|
+
|
11
|
+
it "assigns the options" do
|
12
|
+
options.each do |option|
|
13
|
+
(subject.send option).should == option
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#center_x" do
|
20
|
+
it "returns half width rounded down" do
|
21
|
+
subject.width = 3
|
22
|
+
subject.center_x.should == 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#center_y" do
|
27
|
+
it "returns half height rounded down" do
|
28
|
+
subject.height = 3
|
29
|
+
subject.center_y.should == 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#render_width" do
|
34
|
+
before do
|
35
|
+
subject.width = 2
|
36
|
+
subject.renderer = renderer
|
37
|
+
renderer.stub(:tile_size => 3)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "computes the width required by the renderer to render the tiles" do
|
41
|
+
subject.render_width.should == 6
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#render_height" do
|
46
|
+
before do
|
47
|
+
subject.height = 2
|
48
|
+
subject.renderer = renderer
|
49
|
+
renderer.stub(:tile_size => 3)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "computes the height required by the renderer to render the tiles" do
|
53
|
+
subject.render_height.should == 6
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#put_tile" do
|
58
|
+
it "updates the tile at the specified location" do
|
59
|
+
tile = double(Tile)
|
60
|
+
subject.put_tile 1, 1, tile
|
61
|
+
(subject.instance_variable_get :@tiles) == {[1,1] => tile}
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when a tile is overriden" do
|
65
|
+
before do
|
66
|
+
tile1 = double(Tile, :id => 1)
|
67
|
+
@tile2 = double(Tile, :id => 2)
|
68
|
+
subject.put_tile 1, 1, tile1
|
69
|
+
subject.put_tile 1, 1, @tile2
|
70
|
+
end
|
71
|
+
|
72
|
+
it "replaces the tile" do
|
73
|
+
(subject.instance_variable_get :@tiles) == {[1,1] => @tile2}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#draw" do
|
79
|
+
before do
|
80
|
+
subject.left = 1
|
81
|
+
subject.top = 2
|
82
|
+
renderer.stub(:tile_size => 3)
|
83
|
+
subject.renderer = renderer
|
84
|
+
@tile1 = double(Tile, :id => 1)
|
85
|
+
@tile2 = double(Tile, :id => 2)
|
86
|
+
subject.put_tile 0, 0, @tile1
|
87
|
+
subject.put_tile 1, 1, @tile2
|
88
|
+
end
|
89
|
+
|
90
|
+
it "tells the renderer to draw each tile" do
|
91
|
+
renderer.should_receive(:render).with(@tile1, 1, 2)
|
92
|
+
renderer.should_receive(:render).with(@tile2, 4, 5)
|
93
|
+
|
94
|
+
subject.draw
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/ut.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
require 'ut/version'
|
3
|
+
|
4
|
+
Gem::Specification.new 'unicodetiles', UT::VERSION do |s|
|
5
|
+
s.description = "A simple, text character based tile engine for creating roguelike games etc. The bundled font (DejaVu Sans Mono) has decent coverage (3289 glyphs) of Unicode, providing monospace characters for various miscellaneous symbols that can be useful in creating fancy looking character based games and user interface."
|
6
|
+
s.summary = "Unicode Tile Engine"
|
7
|
+
s.authors = ["Kevin Mees"]
|
8
|
+
s.email = "kev.mees@gmail.com"
|
9
|
+
s.homepage = "https://kmees.github.com/projects/unicodetiles"
|
10
|
+
s.files = `git ls-files`.split("\n") - %w[.gitignore]
|
11
|
+
s.test_files = s.files.select { |p| p =~ /^spec\/.*_spec.rb/ }
|
12
|
+
s.extra_rdoc_files = s.files.select { |p| p =~ /^Readme.md/ }
|
13
|
+
|
14
|
+
s.add_dependency 'gosu', '>= 0.7'
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unicodetiles
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kevin Mees
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gosu
|
16
|
+
requirement: &23513700 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.7'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *23513700
|
25
|
+
description: A simple, text character based tile engine for creating roguelike games
|
26
|
+
etc. The bundled font (DejaVu Sans Mono) has decent coverage (3289 glyphs) of Unicode,
|
27
|
+
providing monospace characters for various miscellaneous symbols that can be useful
|
28
|
+
in creating fancy looking character based games and user interface.
|
29
|
+
email: kev.mees@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files:
|
33
|
+
- Readme.md
|
34
|
+
files:
|
35
|
+
- .rspec
|
36
|
+
- Gemfile
|
37
|
+
- Rakefile
|
38
|
+
- Readme.md
|
39
|
+
- examples/01_minimal.rb
|
40
|
+
- examples/02_simple_dungeon.rb
|
41
|
+
- examples/03_fov_dungeon.rb
|
42
|
+
- examples/04_shader_lightning.rb
|
43
|
+
- examples/05_infinite_forest.rb
|
44
|
+
- examples/dungeon.rb
|
45
|
+
- examples/example_window.rb
|
46
|
+
- examples/forest.rb
|
47
|
+
- examples/fov.rb
|
48
|
+
- examples/helper.rb
|
49
|
+
- examples/shader.rb
|
50
|
+
- fonts/DejaVuSansMono.ttf
|
51
|
+
- lib/ut.rb
|
52
|
+
- lib/ut/engine.rb
|
53
|
+
- lib/ut/font_renderer.rb
|
54
|
+
- lib/ut/tile.rb
|
55
|
+
- lib/ut/version.rb
|
56
|
+
- lib/ut/viewport.rb
|
57
|
+
- spec/engine_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/tile_spec.rb
|
60
|
+
- spec/viewport_spec.rb
|
61
|
+
- ut.gemspec
|
62
|
+
homepage: https://kmees.github.com/projects/unicodetiles
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.8.17
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: Unicode Tile Engine
|
86
|
+
test_files:
|
87
|
+
- spec/engine_spec.rb
|
88
|
+
- spec/tile_spec.rb
|
89
|
+
- spec/viewport_spec.rb
|