unicodetiles 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gem "gosu"
4
+
5
+ group :test do
6
+ gem "rspec"
7
+ end
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
@@ -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
+
@@ -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
@@ -0,0 +1,8 @@
1
+ TILE_SIZE = 20
2
+ SCALE_X = 1.85
3
+ SCALE_Y = 1.25
4
+ WINDOW_WIDTH = 640
5
+ WINDOW_HEIGHT = 480
6
+
7
+ VIEWPORT_WIDTH = WINDOW_WIDTH/TILE_SIZE
8
+ VIEWPORT_HEIGHT = WINDOW_HEIGHT/TILE_SIZE
@@ -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
@@ -0,0 +1,4 @@
1
+ module UT
2
+ # The current version of *unicodetiles.rb*
3
+ VERSION = "1.0.0"
4
+ end
@@ -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
@@ -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
@@ -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