├── test ├── fonts │ ├── maybefont2 │ ├── maybefont.ttc │ ├── maybefont.ttf │ ├── maybefont2.ttc │ ├── maybefont2.ttf │ ├── about.txt │ └── ORANGEKI.TTF ├── images │ ├── not_image.txt │ ├── _ruby.png │ ├── ruby.png │ ├── ruby8.png │ ├── ruby_.png │ ├── rubypng │ ├── star.png │ ├── foo.bar.png │ ├── ruby16.png │ ├── ruby32.png │ ├── sample.png │ ├── sample2.png │ ├── ambiguous.gif │ ├── ambiguous.png │ ├── hello_world.png │ ├── ruby8_16colors.png │ ├── about.txt │ ├── ruby32_interlace.png │ └── ruby8_without_alpha.png ├── sounds │ ├── music.ogg │ ├── sample.wav │ ├── sample2.wav │ └── about.txt ├── frozen_error.rb ├── tests │ ├── test_star_ruby.rb │ ├── test_numeric.rb │ ├── test_input.rb │ ├── test_color.rb │ ├── test_audio.rb │ ├── test_game.rb │ ├── test_font.rb │ └── test_texture_palette.rb ├── runner.rb ├── benchmark3.rb ├── benchmark2.rb ├── benchmark.rb └── test_gc_stress.rb ├── .gitignore ├── win32 ├── dll │ ├── SDL.dll │ ├── SDL_ttf.dll │ ├── smpeg.dll │ ├── zlib1.dll │ ├── SDL_mixer.dll │ ├── libogg-0.dll │ ├── libpng12-0.dll │ ├── libvorbis-0.dll │ ├── libfreetype-6.dll │ └── libvorbisfile-3.dll ├── install.rb └── readmes │ └── win32.txt ├── samples ├── images │ ├── ruby.png │ ├── star.png │ ├── music.png │ ├── sound.png │ ├── ruby-logo-R.png │ ├── ruby_palette.png │ ├── falling_blocks │ │ ├── blocks.png │ │ └── background.png │ └── about.txt ├── fonts │ ├── ORANGEKI.TTF │ ├── falling_blocks │ │ └── flappy_for_famicom.TTF │ └── about.txt ├── sounds │ ├── hello.ogg │ ├── music.ogg │ └── about.txt ├── keyboard.rb ├── raster_scroll.rb ├── window.rb ├── rectangles.rb ├── timer.rb ├── gamepad.rb ├── README_falling_blocks ├── fullscreen.rb ├── falling_blocks.rb ├── helloworld.rb ├── palette.rb ├── mouse.rb ├── sprites.rb ├── falling_blocks │ ├── field.rb │ ├── piece.rb │ ├── controller.rb │ ├── model.rb │ └── view.rb ├── audio.rb ├── lines.rb ├── geometry.rb ├── tone.rb └── airship.rb ├── src ├── starruby.h ├── starruby.c ├── starruby_private.h ├── color.c ├── audio.c ├── font.c ├── game.c └── input.c ├── README ├── COPYING ├── create-win32-package.rb ├── extconf.rb └── LGPL /test/fonts/maybefont2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fonts/maybefont.ttc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fonts/maybefont.ttf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fonts/maybefont2.ttc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fonts/maybefont2.ttf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/images/not_image.txt: -------------------------------------------------------------------------------- 1 | Not Image! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.bundle 3 | *.log 4 | *.o 5 | *.so 6 | conftest.dSYM 7 | Makefile 8 | -------------------------------------------------------------------------------- /test/fonts/about.txt: -------------------------------------------------------------------------------- 1 | ORANGEKI.TTF: 2 | Orange Kid 3 | http://www.larabiefonts.com/ 4 | -------------------------------------------------------------------------------- /win32/dll/SDL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/SDL.dll -------------------------------------------------------------------------------- /test/images/_ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/_ruby.png -------------------------------------------------------------------------------- /test/images/ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby.png -------------------------------------------------------------------------------- /test/images/ruby8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby8.png -------------------------------------------------------------------------------- /test/images/ruby_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby_.png -------------------------------------------------------------------------------- /test/images/rubypng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/rubypng -------------------------------------------------------------------------------- /test/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/star.png -------------------------------------------------------------------------------- /test/sounds/music.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/sounds/music.ogg -------------------------------------------------------------------------------- /win32/dll/SDL_ttf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/SDL_ttf.dll -------------------------------------------------------------------------------- /win32/dll/smpeg.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/smpeg.dll -------------------------------------------------------------------------------- /win32/dll/zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/zlib1.dll -------------------------------------------------------------------------------- /samples/images/ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/ruby.png -------------------------------------------------------------------------------- /samples/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/star.png -------------------------------------------------------------------------------- /test/fonts/ORANGEKI.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/fonts/ORANGEKI.TTF -------------------------------------------------------------------------------- /test/images/foo.bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/foo.bar.png -------------------------------------------------------------------------------- /test/images/ruby16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby16.png -------------------------------------------------------------------------------- /test/images/ruby32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby32.png -------------------------------------------------------------------------------- /test/images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/sample.png -------------------------------------------------------------------------------- /test/images/sample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/sample2.png -------------------------------------------------------------------------------- /test/sounds/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/sounds/sample.wav -------------------------------------------------------------------------------- /test/sounds/sample2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/sounds/sample2.wav -------------------------------------------------------------------------------- /win32/dll/SDL_mixer.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/SDL_mixer.dll -------------------------------------------------------------------------------- /win32/dll/libogg-0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/libogg-0.dll -------------------------------------------------------------------------------- /samples/fonts/ORANGEKI.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/fonts/ORANGEKI.TTF -------------------------------------------------------------------------------- /samples/images/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/music.png -------------------------------------------------------------------------------- /samples/images/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/sound.png -------------------------------------------------------------------------------- /samples/sounds/hello.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/sounds/hello.ogg -------------------------------------------------------------------------------- /samples/sounds/music.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/sounds/music.ogg -------------------------------------------------------------------------------- /test/images/ambiguous.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ambiguous.gif -------------------------------------------------------------------------------- /test/images/ambiguous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ambiguous.png -------------------------------------------------------------------------------- /win32/dll/libpng12-0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/libpng12-0.dll -------------------------------------------------------------------------------- /win32/dll/libvorbis-0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/libvorbis-0.dll -------------------------------------------------------------------------------- /src/starruby.h: -------------------------------------------------------------------------------- 1 | #ifndef STARRUBY_H 2 | #define STARRUBY_H 3 | 4 | void Init_starruby(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /test/images/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/hello_world.png -------------------------------------------------------------------------------- /win32/dll/libfreetype-6.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/libfreetype-6.dll -------------------------------------------------------------------------------- /samples/images/ruby-logo-R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/ruby-logo-R.png -------------------------------------------------------------------------------- /samples/images/ruby_palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/ruby_palette.png -------------------------------------------------------------------------------- /test/images/ruby8_16colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby8_16colors.png -------------------------------------------------------------------------------- /win32/dll/libvorbisfile-3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/win32/dll/libvorbisfile-3.dll -------------------------------------------------------------------------------- /test/images/about.txt: -------------------------------------------------------------------------------- 1 | Ruby.png 2 | Creative Commons Attribution-ShareAlike 2.5 License 3 | http://rubyidentity.org/ 4 | -------------------------------------------------------------------------------- /test/images/ruby32_interlace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby32_interlace.png -------------------------------------------------------------------------------- /test/images/ruby8_without_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/test/images/ruby8_without_alpha.png -------------------------------------------------------------------------------- /samples/images/falling_blocks/blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/falling_blocks/blocks.png -------------------------------------------------------------------------------- /samples/images/falling_blocks/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/images/falling_blocks/background.png -------------------------------------------------------------------------------- /samples/fonts/falling_blocks/flappy_for_famicom.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hajimehoshi/starruby/HEAD/samples/fonts/falling_blocks/flappy_for_famicom.TTF -------------------------------------------------------------------------------- /samples/fonts/about.txt: -------------------------------------------------------------------------------- 1 | ORANGEKI.TTF: 2 | Orange Kid 3 | http://www.larabiefonts.com/ 4 | flappy_for_famicom.TTF: 5 | http://www.jttk.zaq.ne.jp/babwp701/hpfont/font.html 6 | -------------------------------------------------------------------------------- /test/frozen_error.rb: -------------------------------------------------------------------------------- 1 | unless defined? FrozenError 2 | if "1.9.0" <= RUBY_VERSION 3 | FrozenError = RuntimeError 4 | else 5 | FrozenError = TypeError 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/sounds/about.txt: -------------------------------------------------------------------------------- 1 | sample.wav, sample2.wav: 2 | http://osabisi.sakura.ne.jp/m2/ 3 | 4 | music.ogg: 5 | http://commons.wikimedia.org/wiki/Image:Mozart_-_Concerto_in_D_for_Flute_K.314.ladybyron.ogg 6 | (GNU Free Documentation License) 7 | -------------------------------------------------------------------------------- /test/tests/test_star_ruby.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "test/unit" 4 | require "starruby" 5 | 6 | class TestStarRuby < Test::Unit::TestCase 7 | 8 | def test_version 9 | assert_equal "0.4.0", StarRuby::VERSION 10 | assert StarRuby::VERSION.frozen? 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /test/tests/test_numeric.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "test/unit" 4 | require "starruby" 5 | 6 | class TestNumeric < Test::Unit::TestCase 7 | 8 | def test_degree 9 | -10.step(370, 0.25) do |i| 10 | assert_equal i * 2 * Math::PI / 360, i.degree 11 | assert_equal i * 2 * Math::PI / 360, i.degrees 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /samples/sounds/about.txt: -------------------------------------------------------------------------------- 1 | music.ogg: 2 | Mozart_-_Concerto_in_D_for_Flute_K.314.ladybyron.ogg 3 | GNU Free Documentation License 4 | http://commons.wikimedia.org/wiki/Image:Mozart_-_Concerto_in_D_for_Flute_K.314.ladybyron.ogg 5 | 6 | hello.ogg: 7 | En-us-hello-1.ogg 8 | GNU Free Documentation License 9 | http://commons.wikimedia.org/wiki/Image:En-us-hello-1.ogg 10 | -------------------------------------------------------------------------------- /test/runner.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if RUBY_VERSION < "1.9.0" 4 | require "test/unit" 5 | Test::Unit::AutoRunner.run(true, "./tests") 6 | else 7 | require "minitest/unit" 8 | u = MiniTest::Unit 9 | def u.autorun 10 | # do nothing 11 | end 12 | require "test/unit" 13 | Dir.glob("./tests/*.rb") do |path| 14 | require(path) 15 | end 16 | MiniTest::Unit.new.run 17 | end 18 | -------------------------------------------------------------------------------- /samples/images/about.txt: -------------------------------------------------------------------------------- 1 | music.png: 2 | music.png (Silk icon set) 3 | http://famfamfam.com/ 4 | 5 | star.png: 6 | icon_favorites.gif (Mini Icons) 7 | http://famfamfam.com/ 8 | 9 | soundc.png: 10 | sound.png (Silk icon set) 11 | http://famfamfam.com/ 12 | 13 | ruby.png: 14 | Rubin,_biotyt,_Ihosy,_Madagaskar.JPG 15 | GNU Free Documentation License 16 | http://commons.wikimedia.org/wiki/Image:Rubin%2C_biotyt%2C_Ihosy%2C_Madagaskar.JPG 17 | -------------------------------------------------------------------------------- /test/benchmark3.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | require "benchmark" 5 | 6 | include StarRuby 7 | 8 | Benchmark.bm do |bm| 9 | random_rgbas = Array.new(100000) do 10 | [rand(256), rand(256), rand(256), rand(256)] 11 | end 12 | bm.report("miss") do 13 | random_rgbas.each do |r, g, b, a| 14 | Color.new(r, g, b, a) 15 | end 16 | end 17 | bm.report("hit") do 18 | random_rgbas.each do |r, g, b, a| 19 | Color.new(0, 0, 0, 0) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /samples/keyboard.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | font = Font.new("fonts/ORANGEKI", 12) 7 | white = Color.new(255, 255, 255) 8 | 9 | Game.run(320, 240, :title => "Keyboard") do |game| 10 | s = game.screen 11 | s.clear 12 | # Get keys of the keyboard as an Array object 13 | keys = Input.keys(:keyboard) 14 | s.render_text("Pressed Keys:", 8, 8, font, white) 15 | # Render keys with the format "foo,bar,baz" 16 | s.render_text(keys.map{|k| k.to_s}.join(","), 24, 24, font, white) 17 | end 18 | -------------------------------------------------------------------------------- /win32/install.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "rbconfig" 4 | require "fileutils" 5 | 6 | option = {:noop => false, :verbose => true} 7 | 8 | dlldir = Config::CONFIG["bindir"] 9 | sitearchdir = Config::CONFIG["sitearchdir"] 10 | 11 | FileUtils.mkdir_p(dlldir) 12 | FileUtils.mkdir_p(sitearchdir) 13 | 14 | Dir.glob("dll/*.dll") do |path| 15 | next if path =~ /zlib/ and File.exist?(File.join(dlldir, File.basename(path))) 16 | FileUtils.install(path, dlldir, option) 17 | end 18 | Dir.glob("ext/*.so") do |path| 19 | FileUtils.install(path, sitearchdir, option) 20 | end 21 | 22 | puts "Installation Star Ruby completed!" 23 | -------------------------------------------------------------------------------- /samples/raster_scroll.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | texture = Texture.load("images/ruby") 7 | ox, oy = (320 - texture.width) / 2, (240 - texture.height) / 2 8 | counter = 0 9 | 10 | Game.run(320, 240, :title => "Raster scroll") do |game| 11 | break if Input.keys(:keyboard).include?(:escape) 12 | counter = (counter + 1) % 60 13 | s = game.screen 14 | s.clear 15 | texture.height.times do |j| 16 | i = (10 * Math.sin((counter + j) * 2 * Math::PI / 60)).round 17 | s.render_texture(texture, ox + i, oy + j, { 18 | :src_x => 0, :src_y => j, :src_height => 1 19 | }) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /samples/window.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | font = Font.new("fonts/ORANGEKI", 12) 7 | white = Color.new(255, 255, 255) 8 | 9 | Game.run(320, 240, :title => "Window") do |game| 10 | break if Input.keys(:keyboard).include?(:escape) 11 | 12 | keys = Input.keys(:keyboard) 13 | if keys.include?(:f1) 14 | game.window_scale = 3 - game.window_scale 15 | elsif keys.include?(:f2) 16 | game.fullscreen = !game.fullscreen? 17 | end 18 | 19 | game.screen.clear 20 | game.screen.render_text("F1: toggle window scale 1 and 2", 8, 8, font, white) 21 | game.screen.render_text("F2: toggle full screen mode on and off", 8, 24, font, white) 22 | end 23 | -------------------------------------------------------------------------------- /samples/rectangles.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | Game.run(320, 240, :title => "Rectangles (Click to speed up!)") do |game| 7 | break if Input.keys(:keyboard).include?(:escape) 8 | if Input.keys(:mouse).include?(:left) 9 | game.fps = 100000 10 | else 11 | game.fps = 30 12 | end 13 | begin 14 | x1 = rand(320) 15 | x2 = rand(320) 16 | y1 = rand(240) 17 | y2 = rand(240) 18 | end while x1 == x2 or y1 == y2 19 | width = (x1 - x2).abs 20 | height = (y1 - y2).abs 21 | x = [x1, x2].min 22 | y = [y1, y2].min 23 | color = Color.new(rand(256), rand(256), rand(256), rand(256)) 24 | game.screen.render_rect(x, y, width, height, color) 25 | end 26 | -------------------------------------------------------------------------------- /samples/timer.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | font = Font.new("fonts/ORANGEKI", 24) 7 | white = Color.new(255, 255, 255) 8 | 9 | counter = 0 10 | Game.run(320, 240, :timer => "Timer") do |game| 11 | break if Input.keys(:keyboard).include?(:escape) 12 | 13 | counter += 1 14 | 15 | s = game.screen 16 | s.clear 17 | i = -1 18 | s.render_text("FPS: #{game.fps}", 8, 8 + 32 * (i += 1), font, white) 19 | s.render_text("Real FPS: %0.6f" % game.real_fps, 8, 8 + 32 * (i += 1), font, white) 20 | s.render_text("Frames: #{counter}", 8, 8 + 32 * (i += 1), font, white) 21 | s.render_text("Ticks: #{Game.ticks}", 8, 8 + 32 * (i += 1), font, white) 22 | end 23 | -------------------------------------------------------------------------------- /test/benchmark2.rb: -------------------------------------------------------------------------------- 1 | # ref: http://dgames.jp/dan/?permalink&date=20080417 2 | 3 | require "starruby" 4 | require "benchmark.rb" 5 | include StarRuby 6 | 7 | texture = Texture.load("images/star") 8 | 9 | opts = {} 10 | case ARGV.shift 11 | when 'alpha' 12 | opts = { :alpha => 100 } 13 | when 'angle' 14 | opts = { :angle => 0.314 } 15 | when 'scale' 16 | opts = { :scale_x => 2.0, :scale_y => 2.0 } 17 | end 18 | 19 | x, y = 50, 50 20 | render = lambda do 21 | Game.screen.render_texture(texture, x, y, opts) 22 | end 23 | 24 | Game.fps = 1000 25 | counter = 0 26 | Benchmark.bm do |b| 27 | b.report do 28 | Game.run(256, 256) do 29 | Game.screen.clear 30 | 5000.times(&render) 31 | break if (counter += 1) >= 60 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /samples/gamepad.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | font = Font.new("fonts/ORANGEKI", 12) 7 | white = Color.new(255, 255, 255) 8 | 9 | Game.run(320, 240, :title => "Gamepad") do |game| 10 | break if Input.keys(:keyboard).include?(:escape) 11 | s = game.screen 12 | s.clear 13 | # Get pressed keys of the gamepad 14 | keys = Input.keys(:gamepad) 15 | # Render the arrows of the keys 16 | s.render_text("Directions:", 8, 8, font, white) 17 | directions = keys.select{|b| b.kind_of?(Symbol)}.map{|b|b.to_s} 18 | s.render_text(directions.join(","), 24, 24, font, white) 19 | # Render the buttons of the keys 20 | s.render_text("Buttons:", 8, 48, font, white) 21 | buttons = keys.select{|b| b.kind_of?(Integer)}.map{|b|b.to_s} 22 | s.render_text(buttons.join(","), 24, 64, font, white) 23 | end 24 | -------------------------------------------------------------------------------- /samples/README_falling_blocks: -------------------------------------------------------------------------------- 1 | Falling Blocks Game 2 | 3 | 4 | * Usage 5 | 6 | ruby falling_blocks.rb [init-level] 7 | 8 | 9 | * How to Play 10 | 11 | Move left: 12 | Left (Keyboard) 13 | Left (Gamepad) 14 | 15 | Move right: 16 | Right (Keyboard) 17 | Right (Gamepad) 18 | 19 | Rotate left: 20 | Z (Keyboard) 21 | Button 2 (Gamepad) 22 | 23 | Rotate right: 24 | X (Keyboard) 25 | Button 1 (Gamepad) 26 | 27 | Force to fall: 28 | Down (Keyboard) 29 | Down (Gamepad) 30 | 31 | Pause: 32 | C (Keyboard) 33 | Button 3 (GamePad) 34 | 35 | Unpause: 36 | Any key (Keyboard) 37 | Any key (Gamepad) 38 | 39 | 40 | * Score 41 | 42 | (base score) * (level) 43 | 44 | base score: 45 | 1 line: 100 46 | 2 lines: 300 47 | 3 lines: 900 48 | 4 lines: 2700 49 | -------------------------------------------------------------------------------- /samples/fullscreen.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | texture = Texture.load("images/star") 7 | x, y = 0, 0 8 | vx, vy = 2, 2 9 | 10 | Game.run(240, 700, :fullscreen => true, :cursor => true) do |game| 11 | break if Input.keys(:keyboard).include?(:escape) 12 | s = game.screen 13 | max_x = s.width - texture.width 14 | max_y = s.height - texture.height 15 | s.clear 16 | s.fill(Color.new(64, 64, 64)) 17 | x += vx 18 | y += vy 19 | if x < 0 20 | x *= -1 21 | vx = vx.abs 22 | end 23 | if y < 0 24 | y *= -1 25 | vy = vy.abs 26 | end 27 | if max_x <= x 28 | x = -(x - max_x) + max_x 29 | vx = -(vx.abs) 30 | end 31 | if max_y <= y 32 | y = -(y - max_y) + max_y 33 | vy = -(vy.abs) 34 | end 35 | s.render_texture(texture, x, y) 36 | mx, my = *Input.mouse_location 37 | s.render_texture(texture, mx, my) 38 | end 39 | -------------------------------------------------------------------------------- /win32/readmes/win32.txt: -------------------------------------------------------------------------------- 1 | %title% 2 | (Binary for 32bit Windows / Ruby %ruby_version%) 3 | 4 | 5 | * About Star Ruby 6 | 7 | Star Ruby is a ruby extensional library for creating games. 8 | You can develop SNES-like 2D games with Star Ruby. 9 | 10 | 11 | * How to Install 12 | 13 | Install Ruby %ruby_version% if you need. 14 | 15 | Active Script Ruby (Windows Installer) 16 | http://arton.no-ip.info/data/asr/ 17 | 18 | Execute "install.rb" to install Star Ruby. 19 | 20 | > ruby install.rb 21 | 22 | 23 | * Licenses 24 | 25 | Star Ruby: 26 | MIT License 27 | 28 | Ruby: 29 | Ruby License 30 | http://www.ruby-lang.org/ 31 | 32 | SDL, SDL_mixer, SDL_ttf: 33 | GNU Lesser General Public License 34 | http://www.libsdl.org/ 35 | 36 | 37 | * Web Site 38 | 39 | http://www.starruby.info/ 40 | 41 | 42 | * The Author 43 | 44 | Hajime Hoshi (hajimehoshi@gmail.com) 45 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Star Ruby 0.4.0 (2010-06-29) 2 | 3 | 4 | * About Star Ruby 5 | 6 | Star Ruby is a ruby extensional library for creating games. 7 | You can develop SNES-like 2D games with Star Ruby. 8 | 9 | 10 | * Dependent Libraries 11 | 12 | - Fontconfig (Optional) 13 | - libfreetype 14 | - libGL / libopengl32 15 | - libpng 16 | - libogg 17 | - libruby18 / libruby19 18 | - libvorbis 19 | - libvorbisfile 20 | - SDL 21 | - SDL_mixer 22 | - SDL_ttf 23 | - SMPEG 24 | - zlib 25 | 26 | 27 | * How to Install 28 | 29 | $ ruby extconf.rb [--with-opt-dir=DIRECTORY] 30 | $ make 31 | $ make install 32 | 33 | 34 | * How to Do Tests 35 | 36 | $ ruby -Ctest -I.. -I. runner.rb 37 | 38 | 39 | * Licenses 40 | 41 | MIT License 42 | 43 | 44 | * Web Site 45 | 46 | http://www.starruby.info/ 47 | 48 | 49 | * The Authors 50 | 51 | Hajime Hoshi (hajimehoshi@gmail.com) 52 | Daigo Sato (daigo_gamemaker@hotmail.com) 53 | -------------------------------------------------------------------------------- /samples/falling_blocks.rb: -------------------------------------------------------------------------------- 1 | require "starruby" 2 | require "falling_blocks/model" 3 | require "falling_blocks/controller" 4 | require "falling_blocks/view" 5 | 6 | def main 7 | # Model holds the state of this game 8 | model = FallingBlocks::Model.new(ARGV[0].to_i) 9 | # Controller changes the state of the model 10 | controller = FallingBlocks::Controller.new(model) 11 | # View renders the screen 12 | view = FallingBlocks::View.new(model) 13 | StarRuby::Game.run(320, 240, 14 | :title => "Falling Blocks Game", 15 | :window_scale => 2) do |game| 16 | # If ESC key is pressed, quit this game 17 | break if Input.keys(:keyboard).include?(:escape) 18 | # Update the controller 19 | controller.update 20 | # Update the view 21 | view.update(game.screen) 22 | # Start the gabage collection at each frame 23 | GC.start 24 | end 25 | end 26 | 27 | main 28 | -------------------------------------------------------------------------------- /samples/helloworld.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | # Require Star Ruby library 5 | require "starruby" 6 | # Include StarRuby module 7 | include StarRuby 8 | 9 | # Create Font object with a TTF file and 12 px size 10 | font = Font.new("fonts/ORANGEKI", 12) 11 | 12 | # Create Color object of white color (R, G, and B are all 255) 13 | white = Color.new(255, 255, 255) 14 | 15 | # Run game with 320 x 240 size of the screen. 16 | # Given block are called at every frame (by default, it is called at 1/30 second intervals) 17 | Game.run(320, 240, :title => "Hello, World!") do |game| 18 | # Quit this game if ESC key is pressed 19 | break if Input.keys(:keyboard).include?(:escape) 20 | # Initialize the screen. 21 | # The screen remains at the previous frame 22 | game.screen.clear 23 | # Render the text "Hello, World!" at (8, 8) with the created font and white color 24 | game.screen.render_text("Hello, World!", 8, 8, font, white) 25 | end 26 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2008 Hajime Hoshi, Daigo Sato 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /samples/palette.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | # Create Texture object with a palette 7 | main_texture = Texture.load("images/ruby_palette", :palette => true) 8 | # Create the palette image 9 | palette_texture = Texture.new(16 * 4, 16 * 4) 10 | 11 | counter = 0 12 | Game.run(320, 240, :title => "Palette") do |game| 13 | break if Input.keys(:keyboard).include?(:escape) 14 | # Change the palette randomly at 2 second interval 15 | counter += 1 16 | counter %= game.fps * 2 17 | if counter == 0 18 | new_palette = Array.new(main_texture.palette.size) do |i| 19 | Color.new(rand(256), rand(256), rand(256), main_texture.palette[i].alpha) 20 | end 21 | main_texture.change_palette!(new_palette) 22 | end 23 | 24 | # Update the palette image 25 | 4.times do |j| 26 | 4.times do |i| 27 | color = main_texture.palette[i * 4 + j] 28 | palette_texture.fill_rect(i * 16, j * 16, 16, 16, color) 29 | end 30 | end 31 | 32 | s = game.screen 33 | s.clear 34 | s.render_texture(main_texture, 16, 16, :scale_x => 2, :scale_y => 2) 35 | s.render_texture(palette_texture, 16 + main_texture.width * 2 + 16, 16) 36 | end 37 | -------------------------------------------------------------------------------- /test/tests/test_input.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "test/unit" 4 | require "starruby" 5 | include StarRuby 6 | 7 | class TestInput < Test::Unit::TestCase 8 | 9 | def test_keys_type 10 | assert_raise TypeError do 11 | Input.keys(nil) 12 | end 13 | begin 14 | Input.keys(:foo) 15 | flunk 16 | rescue ArgumentError => e 17 | assert_equal "invalid device: :foo", e.message 18 | end 19 | assert_raise TypeError do 20 | Input.keys(:gamepad, false) 21 | end 22 | [:device_number, :duration, :delay, :interval].each do |key| 23 | assert_raise TypeError do 24 | Input.keys(:gamepad, key => false) 25 | end 26 | end 27 | end 28 | 29 | def test_mouse_location 30 | assert_kind_of Array, Input.mouse_location 31 | assert_equal 2, Input.mouse_location.size 32 | assert Input.mouse_location.frozen? 33 | end 34 | 35 | def test_mouse_location_eq 36 | Input.mouse_location = 1, 2 37 | assert_raise ArgumentError do 38 | Input.mouse_location = 1, 2, 3 39 | end 40 | assert_raise TypeError do 41 | Input.mouse_location = false 42 | end 43 | end 44 | 45 | def test_gamepad_device_number 46 | assert_equal [], Input.keys(:gamepad, :device_number => -1) 47 | assert_equal [], Input.keys(:gamepad, :device_number => 100) 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /samples/mouse.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | cursor = Texture.load("images/star") 7 | 8 | # The angle of the cursor image (degree) 9 | angle = 0 10 | 11 | Game.run(320, 240, :title => "Mouse (Click buttons!)") do |game| 12 | break if Input.keys(:keyboard).include?(:escape) 13 | # If 'c' key is pressed, set the mouse location at the center 14 | Input.mouse_location = 160, 120 if Input.keys(:keyboard).include?(:c) 15 | game.screen.clear 16 | # Get the location of the mouse cursor 17 | x, y = Input.mouse_location 18 | # Initialize properties of rendering the cursor 19 | alpha = 128 20 | scale_x = scale_y = 1 21 | # Get keys of the mouse 22 | keys = Input.keys(:mouse) 23 | # If the left key is pressed: 24 | if keys.include?(:left) 25 | alpha = 255 26 | end 27 | # If the right key is pressed: 28 | if keys.include?(:right) 29 | scale_x = scale_y = 2 30 | end 31 | # If the middle key is pressed: 32 | if keys.include?(:middle) 33 | angle += 6 34 | angle %= 360 35 | end 36 | game.screen.render_texture(cursor, x, y, { 37 | :alpha => alpha, 38 | :center_x => cursor.width / 2, 39 | :center_y => cursor.height / 2, 40 | :angle => angle.degrees, # Numeric#degree(s) converts degrees into radians 41 | :scale_x => scale_x, 42 | :scale_y => scale_y, 43 | }) 44 | end 45 | -------------------------------------------------------------------------------- /samples/sprites.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | class Sprite 7 | @@texture = Texture.load("images/star") 8 | MAX_X = 320 - @@texture.width 9 | MAX_Y = 240 - @@texture.height 10 | 11 | attr_reader :x 12 | attr_reader :y 13 | 14 | def initialize 15 | @x = rand(MAX_X) 16 | @y = rand(MAX_Y) 17 | @vx = rand(2) * 2 - 1 18 | @vy = rand(2) * 2 - 1 19 | end 20 | 21 | def texture 22 | @@texture 23 | end 24 | 25 | def update 26 | @x += @vx 27 | @y += @vy 28 | if @x < 0 29 | @x = -@x 30 | @vx = 1 31 | end 32 | if @y < 0 33 | @y = -@y 34 | @vy = 1 35 | end 36 | if MAX_X <= @x 37 | @x = -(@x - MAX_X) + MAX_X 38 | @vx = -1 39 | end 40 | if MAX_Y <= @y 41 | @y = -(@y - MAX_Y) + MAX_Y 42 | @vy = -1 43 | end 44 | end 45 | end 46 | 47 | sprites = Array.new(200) {Sprite.new} 48 | 49 | Game.run(320, 240, :title => "Sprites (Click to speed up!)") do |game| 50 | break if Input.keys(:keyboard).include?(:escape) 51 | if Input.keys(:mouse).include?(:left) 52 | game.fps = 100000 53 | else 54 | game.fps = 30 55 | end 56 | sprites.each do |sprite| 57 | sprite.update 58 | end 59 | game.screen.clear 60 | sprites.each do |sprite| 61 | game.screen.render_texture(sprite.texture, sprite.x, sprite.y) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /create-win32-package.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "fileutils" 4 | include FileUtils 5 | 6 | def main 7 | show_usage if ARGV.length != 0 8 | title = "" 9 | open("README") do |fp| 10 | title = fp.gets.chomp 11 | end 12 | version = title[/\d+\.\d+\.\d+/] 13 | ruby_version = RUBY_VERSION[0, 1] + RUBY_VERSION[2, 1] 14 | main_dir = "starruby-#{version}-win32-ruby#{ruby_version}" 15 | mkdir_p(main_dir, :verbose => true) 16 | # readme 17 | open("win32/readmes/win32.txt", "r") do |fp| 18 | open(File.join(main_dir, "readme.txt"), "w") do |fp2| 19 | ruby_version = RUBY_VERSION[0, 4] + "*" 20 | str = fp.read.gsub("%title%", title).gsub("%ruby_version%", ruby_version) 21 | fp2.write(str) 22 | end 23 | end 24 | # dlls 25 | Dir.glob("win32/*\0win32/dll/*.dll") do |path| 26 | next unless FileTest.file?(path) 27 | dir = File.join(main_dir, File.dirname(path.split("/")[1..-1].join("/"))) 28 | mkdir_p(dir, :verbose => true) unless FileTest.directory?(dir) 29 | cp(path, dir, :verbose => true) 30 | end 31 | # samples 32 | Dir.glob("samples/**/*") do |path| 33 | next unless FileTest.file?(path) 34 | dir = File.join(main_dir, File.dirname(path)) 35 | mkdir_p(dir, :verbose => true) unless FileTest.directory?(dir) 36 | cp(path, dir, :verbose => true) 37 | end 38 | # starruby so 39 | mkdir(File.join(main_dir, "ext"), :verbose => true) 40 | cp("starruby.so", File.join(main_dir, "ext"), :verbose => true) 41 | end 42 | 43 | def show_usage 44 | $stderr.puts("Usage: #{$0}") 45 | exit(1) 46 | end 47 | 48 | main 49 | -------------------------------------------------------------------------------- /samples/falling_blocks/field.rb: -------------------------------------------------------------------------------- 1 | module FallingBlocks 2 | 3 | class Field 4 | 5 | def initialize 6 | @blocks = Array.new(width * height){nil} 7 | end 8 | 9 | def width 10 | 10 11 | end 12 | 13 | def height 14 | 20 15 | end 16 | 17 | def [](x, y) 18 | return true unless (0...width).include?(x) and (0...height).include?(y) 19 | @blocks[x + y * width] 20 | end 21 | 22 | def conflict?(piece, x, y, angle) 23 | piece.height.times do |j| 24 | piece.width.times do |i| 25 | return true if piece[i, j, angle] and self[x + i, y + j] 26 | end 27 | end 28 | false 29 | end 30 | 31 | def add_piece(piece, x, y, angle) 32 | result = true 33 | piece.height.times do |j| 34 | piece.width.times do |i| 35 | if piece[i, j, angle] 36 | result = false if result and self[x + i, y + j] 37 | @blocks[(x + i) + (y + j) * width] = piece.id 38 | end 39 | end 40 | end 41 | result 42 | end 43 | 44 | def flashing_lines 45 | (0...height).select do |j| 46 | @blocks[j * width, width].all? 47 | end 48 | end 49 | 50 | def flash 51 | j = height - 1 52 | while 0 <= j 53 | if @blocks[j * width, width].all? 54 | j.downto(1) do |j2| 55 | width.times do |i| 56 | @blocks[i + j2 * width] = @blocks[i + (j2 - 1) * width] 57 | end 58 | end 59 | else 60 | j -= 1 61 | end 62 | end 63 | end 64 | 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /samples/falling_blocks/piece.rb: -------------------------------------------------------------------------------- 1 | module FallingBlocks 2 | 3 | class Piece 4 | 5 | attr_reader :id 6 | 7 | def initialize(id, blocks) 8 | @id = id 9 | @blocks = blocks 10 | end 11 | 12 | t = true 13 | _ = false 14 | @@pieces = [] 15 | @@pieces << Piece.new(0, [_,_,_,_, 16 | t,t,t,t, 17 | _,_,_,_, 18 | _,_,_,_]) 19 | @@pieces << Piece.new(1, [t,_,_, 20 | t,t,t, 21 | _,_,_]) 22 | @@pieces << Piece.new(2, [_,t,_, 23 | t,t,t, 24 | _,_,_]) 25 | @@pieces << Piece.new(3, [_,_,t, 26 | t,t,t, 27 | _,_,_]) 28 | @@pieces << Piece.new(4, [t,t,_, 29 | _,t,t, 30 | _,_,_]) 31 | @@pieces << Piece.new(5, [_,t,t, 32 | t,t,_, 33 | _,_,_]) 34 | @@pieces << Piece.new(6, [t,t, 35 | t,t]) 36 | 37 | def self.[](id) 38 | @@pieces[id] 39 | end 40 | 41 | def self.count 42 | @@pieces.size 43 | end 44 | 45 | def width 46 | @width ||= Math.sqrt(@blocks.size).to_i 47 | end 48 | 49 | alias height width 50 | 51 | def [](x, y, angle = 0) 52 | case angle 53 | when 0 54 | @blocks[x + y * width] 55 | when 1 56 | @blocks[y + (width - x - 1) * width] 57 | when 2 58 | @blocks[(width - x - 1) + (width - y - 1) * width] 59 | when 3 60 | @blocks[(width - y - 1) + x * width] 61 | end 62 | end 63 | 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /samples/audio.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | font = Font.new("fonts/ORANGEKI", 12) 7 | white = Color.new(255, 255, 255) 8 | 9 | music_texture = Texture.load("images/music") 10 | sound_texture = Texture.load("images/sound") 11 | 12 | bgm_position = 0 13 | Game.run(320, 240, :title => "Audio") do |game| 14 | music_alpha = Audio.playing_bgm? ? 255 : 128 15 | sound_alpha = 128 16 | # Get pressed keys of a keyboard. 17 | # ':duration => 1' means that keys are pressed just now. 18 | keys = Input.keys(:keyboard, :duration => 1) 19 | # If ESC key is pressed, exit this game. 20 | break if keys.include?(:escape) 21 | # Get the current position (Fixnum) of BGM. 22 | # If you use this value when playing BGM, BGM can be played at middle of time. 23 | bgm_position = Audio.bgm_position 24 | # If 'm' key is pressed, play or stop BGM 25 | if keys.include?(:m) 26 | if Audio.playing_bgm? 27 | Audio.stop_bgm 28 | else 29 | Audio.play_bgm("sounds/music", 30 | :position => bgm_position, 31 | :loop => true) 32 | end 33 | end 34 | # If 's' key is pressed, play SE 35 | if keys.include?(:s) 36 | Audio.play_se("sounds/hello") 37 | sound_alpha = 255 38 | end 39 | s = game.screen 40 | s.clear 41 | s.render_texture(music_texture, 16, 16, 42 | :alpha => music_alpha, 43 | :scale_x => 4, 44 | :scale_y => 4) 45 | text = "Press 'm' to #{Audio.playing_bgm? ? 'stop':'play'} music" 46 | s.render_text(text, 96, 32, font, white) 47 | s.render_text("Position: #{bgm_position}", 96, 48, font, white) 48 | s.render_texture(sound_texture, 16, 80, 49 | :alpha => sound_alpha, 50 | :scale_x => 4, 51 | :scale_y => 4) 52 | s.render_text("Press 's' to play sound", 96, 96, font, white) 53 | if 0 < Audio.playing_se_count 54 | s.render_text("Now #{Audio.playing_se_count} SEs are playing", 96, 112, font, white) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/tests/test_color.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "test/unit" 4 | require "starruby" 5 | include StarRuby 6 | 7 | class TestColor < Test::Unit::TestCase 8 | 9 | def test_color 10 | c1 = Color.new(1, 2, 3, 4) 11 | c2 = Color.new(5, 6, 7) 12 | 13 | assert_equal 1, c1.red 14 | assert_equal 2, c1.green 15 | assert_equal 3, c1.blue 16 | assert_equal 4, c1.alpha 17 | assert_equal 255, c2.alpha 18 | 19 | assert c1 == Color.new(1, 2, 3, 4) 20 | assert c2 == Color.new(5, 6, 7) 21 | assert c2 == Color.new(5, 6, 7, 255) 22 | assert c1 != Color.new(1, 2, 3, 5) 23 | assert c1 != Object.new 24 | assert c1.eql?(Color.new(1, 2, 3, 4)) 25 | assert_equal c1.hash, Color.new(1, 2, 3, 4).hash 26 | assert_equal c2.hash, Color.new(5, 6, 7).hash 27 | end 28 | 29 | def test_color_type 30 | assert_raise TypeError do 31 | Color.new(nil, 2, 3, 4) 32 | end 33 | assert_raise TypeError do 34 | Color.new(1, nil, 3, 4) 35 | end 36 | assert_raise TypeError do 37 | Color.new(1, 2, nil, 4) 38 | end 39 | assert_raise TypeError do 40 | Color.new(1, 2, 3, false) 41 | end 42 | end 43 | 44 | def test_color_overflow 45 | assert_raise ArgumentError do 46 | Color.new(-1, 0, 0, 0) 47 | end 48 | assert_raise ArgumentError do 49 | Color.new(256, 0, 0, 0) 50 | end 51 | assert_raise ArgumentError do 52 | Color.new(0, -1, 0, 0) 53 | end 54 | assert_raise ArgumentError do 55 | Color.new(0, 256, 0, 0) 56 | end 57 | assert_raise ArgumentError do 58 | Color.new(0, 0, -1, 0) 59 | end 60 | assert_raise ArgumentError do 61 | Color.new(0, 0, 256, 0) 62 | end 63 | assert_raise ArgumentError do 64 | Color.new(0, 0, 0, -1) 65 | end 66 | assert_raise ArgumentError do 67 | Color.new(0, 0, 0, 256) 68 | end 69 | end 70 | 71 | def test_to_s 72 | c = Color.new(1, 2, 3, 4) 73 | assert_equal "#", c.to_s 74 | c = Color.new(255, 255, 255, 255) 75 | assert_equal "#", c.to_s 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /extconf.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "mkmf" 3 | 4 | case CONFIG["arch"] 5 | when /mingw32/ 6 | $CFLAGS += " -DWIN32 -DUNICODE -D_UNICODE" 7 | when /mswin32|cygwin|bccwin32|interix|djgpp/ 8 | raise "not supported arch: #{CONFIG["arch"]}" 9 | end 10 | 11 | $CFLAGS += " " + `env libpng-config --cflags`.chomp 12 | $CFLAGS += " " + `env sdl-config --cflags`.chomp 13 | $LDFLAGS += " " + `env libpng-config --ldflags`.chomp 14 | $LOCAL_LIBS += " " + `env libpng-config --libs`.chomp 15 | $LOCAL_LIBS += " " + `env sdl-config --libs`.chomp 16 | 17 | if CONFIG["arch"] !~ /mingw32/ 18 | # TODO: Is pkg-config needed? 19 | $CFLAGS += " " + `env pkg-config fontconfig --cflags`.chomp 20 | $LOCAL_LIBS += " " + `env pkg-config fontconfig --libs`.chomp 21 | end 22 | if CONFIG["arch"] =~ /darwin/ 23 | $LDFLAGS += " -framework OpenGL" 24 | end 25 | 26 | have_header("png.h") or exit(false) 27 | have_header("zlib.h") or exit(false) 28 | have_library("SDL_mixer", "Mix_OpenAudio") or exit(false) 29 | have_library("SDL_ttf", "TTF_Init") or exit(false) 30 | 31 | if have_header("fontconfig/fontconfig.h") 32 | have_library("fontconfig", "FcInit") or exit(false) 33 | end 34 | 35 | if CONFIG["arch"] =~ /mingw32/ 36 | have_library("opengl32") or exit(false) 37 | elsif CONFIG["arch"] =~ /darwin/ 38 | have_library("GL") 39 | else 40 | have_library("GL") or exit(false) 41 | end 42 | 43 | $CFLAGS += " -finline-functions -Wall -W -Wpointer-arith -Wunused-parameter" 44 | $CFLAGS += " -Wno-declaration-after-statement" 45 | $CFLAGS += " -pedantic -std=c99 -funit-at-a-time" 46 | # TODO: use gcc -dumpspecs 47 | if RUBY_PLATFORM !~ /^powerpc/ and CONFIG["arch"] !~ /darwin/ 48 | $CFLAGS += " -mfpmath=sse -msse2" 49 | end 50 | if CONFIG["arch"] !~ /darwin/ 51 | $LDFLAGS += " -Wl,--no-undefined" 52 | end 53 | 54 | if arg_config("--debug", false) 55 | $CFLAGS += " -DDEBUG -O0 -g -pg" 56 | end 57 | 58 | create_makefile("starruby", "./src") 59 | 60 | if CONFIG["arch"] =~ /mingw32/ 61 | str = open("./Makefile", "rb") do |fp| 62 | str = fp.read 63 | drive_name = Dir.pwd[/^(.+:)/].upcase 64 | str.gsub(" /msys/", " #{drive_name}/msys/").gsub(" -L/msys/", " -L#{drive_name}/msys/").gsub("$(DESTDIR)", "#{drive_name}") 65 | end 66 | str 67 | open("./Makefile", "wb") do |fp| 68 | fp.write(str) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /samples/lines.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | class Texture 7 | # Add a method of StarRuby::Texture class 8 | def render_line_16(x1, y1, x2, y2, color) 9 | render_line(x1 * 16, y1 * 16, x2 * 16, y2 * 16, color) 10 | end 11 | end 12 | 13 | color = Color.new(255, 255, 255, 255) 14 | 15 | Game.run(320, 240, :title => "Lines") do |game| 16 | break if Input.keys(:keyboard).include?(:escape) 17 | s = game.screen 18 | s.clear 19 | 20 | # S 21 | s.render_line_16(4, 1, 2, 1, color) 22 | s.render_line_16(2, 1, 1, 1.5, color) 23 | s.render_line_16(1, 1.5, 2, 2.5, color) 24 | s.render_line_16(2, 2.5, 3, 2.5, color) 25 | s.render_line_16(3, 2.5, 4, 3, color) 26 | s.render_line_16(4, 3, 3, 4, color) 27 | s.render_line_16(3, 4, 1, 4, color) 28 | 29 | # T 30 | s.render_line_16(5, 1, 8, 1, color) 31 | s.render_line_16(6.5, 1, 6.5, 4, color) 32 | 33 | # A 34 | s.render_line_16(10.5, 1, 9, 3, color) 35 | s.render_line_16(9, 3, 9, 4, color) 36 | s.render_line_16(10.5, 1, 12, 3, color) 37 | s.render_line_16(12, 3, 12, 4, color) 38 | s.render_line_16(9, 3, 12, 3, color) 39 | 40 | # R 41 | s.render_line_16(13, 1, 15, 1, color) 42 | s.render_line_16(15, 1, 16, 2, color) 43 | s.render_line_16(13, 3, 15, 3, color) 44 | s.render_line_16(15, 3, 16, 2, color) 45 | s.render_line_16(13, 1, 13, 4, color) 46 | s.render_line_16(15, 3, 16, 4, color) 47 | 48 | # R 49 | s.render_line_16(1, 5, 3, 5, color) 50 | s.render_line_16(3, 5, 4, 6, color) 51 | s.render_line_16(1, 7, 3, 7, color) 52 | s.render_line_16(3, 7, 4, 6, color) 53 | s.render_line_16(1, 5, 1, 8, color) 54 | s.render_line_16(3, 7, 4, 8, color) 55 | 56 | # U 57 | s.render_line_16(5, 5, 5, 7, color) 58 | s.render_line_16(5, 7, 6.5, 8, color) 59 | s.render_line_16(6.5, 8, 8, 7, color) 60 | s.render_line_16(8, 7, 8, 5, color) 61 | 62 | # B 63 | s.render_line_16(9, 5, 9, 8, color) 64 | s.render_line_16(9, 5, 11, 5, color) 65 | s.render_line_16(9, 6.5, 11, 6.5, color) 66 | s.render_line_16(9, 8, 11, 8, color) 67 | s.render_line_16(11, 5, 12, 5.5, color) 68 | s.render_line_16(12, 5.5, 11, 6.5, color) 69 | s.render_line_16(11, 6.5, 12, 7, color) 70 | s.render_line_16(12, 7, 11, 8, color) 71 | 72 | # Y 73 | s.render_line_16(13, 5, 14.5, 7, color) 74 | s.render_line_16(16, 5, 14.5, 7, color) 75 | s.render_line_16(14.5, 7, 14.5, 8, color) 76 | end 77 | -------------------------------------------------------------------------------- /samples/geometry.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | texture = Texture.load("images/ruby") 7 | cover = Texture.new(320, 240) 8 | cover.fill(Color.new(64, 64, 64)) 9 | 10 | font = Font.new("fonts/ORANGEKI", 12) 11 | white = Color.new(255, 255, 255) 12 | 13 | # angle (0 to 49) 14 | angle = 0 15 | # X-way scale (-10 to 10), the decuple power of 2 (then the real scale is 2^(-1.0) to 2^(1.0)) 16 | scale_x = 0 17 | # Y-way scale (-10 to 10), the decuple power of 2 (then the real scale is 2^(-1.0) to 2^(1.0)) 18 | scale_y = 0 19 | 20 | Game.run(320, 240, :title => "Geometry") do |game| 21 | # Quit this game if ESC key is pressed 22 | break if Input.keys(:keyboard).include?(:escape) 23 | s = game.screen 24 | s.clear 25 | # Get the keys of the keyboard with repeating 26 | keys = Input.keys(:keyboard, :duration => 1, :delay => 8, :interval => 2) 27 | if keys.include?(:r) 28 | angle = 0 29 | scale_x = 0 30 | scale_y = 0 31 | end 32 | if keys.include?(:z) 33 | angle += 1 34 | elsif keys.include?(:x) 35 | angle -= 1 36 | end 37 | if keys.include?(:right) 38 | scale_x += 1 39 | elsif keys.include?(:left) 40 | scale_x -= 1 41 | end 42 | if keys.include?(:up) 43 | scale_y += 1 44 | elsif keys.include?(:down) 45 | scale_y -= 1 46 | end 47 | angle %= 50 48 | scale_x = [[scale_x, -10].max, 10].min 49 | scale_y = [[scale_y, -10].max, 10].min 50 | # Render the image of Ruby with options 51 | s.render_texture(texture, (s.width - texture.width) / 2, 32, { 52 | :angle => angle * 2 * Math::PI / 50, 53 | :scale_x => 2 ** (scale_x / 10.0), 54 | :scale_y => 2 ** (scale_y / 10.0), 55 | :center_x => texture.width / 2, 56 | :center_y => texture.height / 2, 57 | }) 58 | # Show the help when 'h' key is pressed 59 | if Input.keys(:keyboard).include?(:h) 60 | s.render_texture(cover, 0, 0, :alpha => 128) 61 | s.render_text("'z': Rotate right", 8, 8, font, white) 62 | s.render_text("'x': Rotate left", 8, 8 + 16*1, font, white) 63 | s.render_text("'right': Stretch along the X-axis", 8, 8 + 16*3, font, white) 64 | s.render_text("'left': Shrink along the X-axis", 8, 8 + 16*4, font, white) 65 | s.render_text("'up': Stretch along the Y-axis", 8, 8 + 16*6, font, white) 66 | s.render_text("'down': Shrink along the Y-axis", 8, 8 + 16*7, font, white) 67 | s.render_text("'r': Reset", 8, 8 + 16*9, font, white) 68 | else 69 | s.render_text("Click 'h' to show help", 8, 8, font, white) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /samples/tone.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | texture = Texture.load("images/ruby") 7 | cover = Texture.new(320, 240) 8 | cover.fill(Color.new(64, 64, 64)) 9 | 10 | font = Font.new("fonts/ORANGEKI", 12) 11 | white = Color.new(255, 255, 255) 12 | 13 | tone_red = 0 14 | tone_green = 0 15 | tone_blue = 0 16 | saturation = 255 17 | 18 | Game.run(320, 240, :title => "Tone") do |game| 19 | break if Input.keys(:keyboard).include?(:escape) 20 | s = game.screen 21 | s.clear 22 | keys = Input.keys(:keyboard, :duration => 1, :delay => 8, :interval => 2) 23 | if keys.include?(:r) 24 | tone_red = 0 25 | tone_green = 0 26 | tone_blue = 0 27 | saturation = 255 28 | end 29 | if keys.include?(:d2) 30 | tone_red += 15 31 | elsif keys.include?(:d1) 32 | tone_red -= 15 33 | end 34 | if keys.include?(:w) 35 | tone_green += 15 36 | elsif keys.include?(:q) 37 | tone_green -= 15 38 | end 39 | if keys.include?(:s) 40 | tone_blue += 15 41 | elsif keys.include?(:a) 42 | tone_blue -= 15 43 | end 44 | if keys.include?(:x) 45 | saturation += 15 46 | elsif keys.include?(:z) 47 | saturation -= 15 48 | end 49 | tone_red = [[tone_red, -255].max, 255].min 50 | tone_green = [[tone_green, -255].max, 255].min 51 | tone_blue = [[tone_blue, -255].max, 255].min 52 | saturation = [[saturation, 0].max, 255].min 53 | s.render_texture(texture, (s.width - texture.width) / 2, 32, { 54 | :tone_red => tone_red, 55 | :tone_green => tone_green, 56 | :tone_blue => tone_blue, 57 | :saturation => saturation 58 | }) 59 | if Input.keys(:keyboard).include?(:h) 60 | s.render_texture(cover, 0, 0, :alpha => 128) 61 | s.render_text("'2': Turn up the red part", 8, 8, font, white) 62 | s.render_text("'1': Turn down the red part", 8, 8 + 16, font, white) 63 | s.render_text("'w': Turn up the green part", 8, 8 + 16*3, font, white) 64 | s.render_text("'q': Turn down the green part", 8, 8 + 16*4, font, white) 65 | s.render_text("'s': Turn up the blue part", 8, 8 + 16*6, font, white) 66 | s.render_text("'a': Turn down the blue part", 8, 8 + 16*7, font, white) 67 | s.render_text("'x': Turn up the saturation", 8, 8 + 16*9, font, white) 68 | s.render_text("'z': Turn down the saturation", 8, 8 + 16*10, font, white) 69 | s.render_text("'r': Reset", 8, 8 + 16*12, font, white) 70 | else 71 | s.render_text("Click 'h' to show help", 8, 8, font, white) 72 | s.render_text("(R, G, B, S) = (#{tone_red}, #{tone_green}, #{tone_blue}, #{saturation})", 8, 200, font, white) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /samples/falling_blocks/controller.rb: -------------------------------------------------------------------------------- 1 | module FallingBlocks 2 | 3 | StarRuby::Input.instance_eval do 4 | alias _orig_keys keys 5 | 6 | def keys(device, options = {}) 7 | if device.kind_of?(Enumerable) 8 | device.inject([]) do |result, d| 9 | result | _orig_keys(d, options) 10 | end 11 | else 12 | _orig_keys(device, options) 13 | end 14 | end 15 | 16 | def triggers(device) 17 | keys(device, :duration => 1) 18 | end 19 | 20 | def repeatings(device) 21 | keys(device, { 22 | :duration => 1, :delay => 2, :interval => 0 23 | }) 24 | end 25 | end 26 | 27 | class Controller 28 | 29 | attr_reader :model 30 | 31 | def initialize(model) 32 | @model = model 33 | end 34 | 35 | def update 36 | send("update_#{model.state}") 37 | end 38 | 39 | def update_start 40 | if 0 < Input.triggers([:keyboard, :gamepad]).size 41 | model.start_playing 42 | end 43 | end 44 | 45 | def update_playing 46 | if Input.triggers(:keyboard).include?(:c) or 47 | Input.triggers(:gamepad).include?(3) 48 | model.pause 49 | elsif model.flashing? 50 | @count ||= 20 51 | @count -= 1 52 | if @count <= 0 53 | model.finish_flashing 54 | @count = nil 55 | end 56 | else 57 | if Input.repeatings([:keyboard, :gamepad]).include?(:left) 58 | model.try_move(:left) 59 | elsif Input.repeatings([:keyboard, :gamepad]).include?(:right) 60 | model.try_move(:right) 61 | end 62 | if Input.triggers(:keyboard).include?(:z) or 63 | Input.triggers(:gamepad).include?(2) 64 | model.try_rotate(:left) 65 | elsif Input.triggers(:keyboard).include?(:x) or 66 | Input.triggers(:gamepad).include?(1) 67 | model.try_rotate(:right) 68 | end 69 | down_pressed = Input.keys([:keyboard, :gamepad]).include?(:down) 70 | if model.falling_piece_landing? 71 | @count ||= 20 72 | @count -= down_pressed ? 3 : 1 73 | if @count <= 0 74 | model.do_landing 75 | @count = nil 76 | end 77 | else 78 | model.add_score(1) if down_pressed 79 | model.try_fall(down_pressed) 80 | end 81 | end 82 | end 83 | 84 | def update_pause 85 | if 0 < Input.triggers([:keyboard, :gamepad]).size 86 | model.resume 87 | end 88 | end 89 | 90 | def update_gameover 91 | if 0 < Input.triggers([:keyboard, :gamepad]).size 92 | model.start 93 | end 94 | end 95 | 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /src/starruby.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | static volatile VALUE rb_eStarRubyError = Qundef; 4 | 5 | VALUE 6 | strb_GetCompletePath(VALUE rbPath, bool raiseNotFoundError) 7 | { 8 | const char* path = StringValueCStr(rbPath); 9 | if (!RTEST(rb_funcall(rb_mFileTest, rb_intern("file?"), 1, rbPath))) { 10 | volatile VALUE rbPathes = 11 | rb_funcall(rb_cDir, rb_intern("[]"), 1, 12 | rb_str_cat2(rb_str_dup(rbPath), ".*")); 13 | volatile VALUE rbFileName = 14 | rb_funcall(rb_cFile, rb_intern("basename"), 1, rbPath); 15 | for (int i = 0; i < RARRAY_LEN(rbPathes); i++) { 16 | volatile VALUE rbFileNameWithoutExt = 17 | rb_funcall(rb_cFile, rb_intern("basename"), 2, 18 | RARRAY_PTR(rbPathes)[i], rb_str_new2(".*")); 19 | if (rb_str_cmp(rbFileName, rbFileNameWithoutExt) != 0) { 20 | RARRAY_PTR(rbPathes)[i] = Qnil; 21 | } 22 | } 23 | rb_funcall(rbPathes, rb_intern("compact!"), 0); 24 | switch (RARRAY_LEN(rbPathes)) { 25 | case 0: 26 | if (raiseNotFoundError) { 27 | rb_raise(rb_path2class("Errno::ENOENT"), "%s", path); 28 | } 29 | break; 30 | case 1: 31 | return RARRAY_PTR(rbPathes)[0]; 32 | default: 33 | rb_raise(rb_eArgError, "ambiguous path: %s", path); 34 | break; 35 | } 36 | return Qnil; 37 | } else { 38 | return rbPath; 39 | } 40 | } 41 | 42 | static void 43 | FinalizeStarRuby(VALUE unused) 44 | { 45 | TTF_Quit(); 46 | strb_FinalizeAudio(); 47 | strb_FinalizeInput(); 48 | SDL_Quit(); 49 | } 50 | 51 | static VALUE 52 | Numeric_degree(VALUE self) 53 | { 54 | return rb_float_new(NUM2DBL(self) * PI / 180.0); 55 | } 56 | 57 | VALUE 58 | strb_GetStarRubyErrorClass(void) 59 | { 60 | return rb_eStarRubyError; 61 | } 62 | 63 | void 64 | Init_starruby(void) 65 | { 66 | volatile VALUE rb_mStarRuby = rb_define_module("StarRuby"); 67 | rb_eStarRubyError = 68 | rb_define_class_under(rb_mStarRuby, "StarRubyError", rb_eStandardError); 69 | 70 | if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)) { 71 | rb_raise_sdl_error(); 72 | } 73 | strb_InitializeSdlAudio(); 74 | strb_InitializeSdlFont(); 75 | strb_InitializeSdlInput(); 76 | 77 | volatile VALUE rbVersion = rb_str_new2("0.4.0"); 78 | OBJ_FREEZE(rbVersion); 79 | rb_define_const(rb_mStarRuby, "VERSION", rbVersion); 80 | strb_InitializeAudio(rb_mStarRuby); 81 | strb_InitializeColor(rb_mStarRuby); 82 | strb_InitializeFont(rb_mStarRuby); 83 | strb_InitializeGame(rb_mStarRuby); 84 | strb_InitializeInput(rb_mStarRuby); 85 | strb_InitializeTexture(rb_mStarRuby); 86 | 87 | rb_set_end_proc(FinalizeStarRuby, Qnil); 88 | 89 | rb_define_method(rb_cNumeric, "degree", Numeric_degree, 0); 90 | rb_define_method(rb_cNumeric, "degrees", Numeric_degree, 0); 91 | 92 | #ifdef DEBUG 93 | strb_TestInput(); 94 | #endif 95 | } 96 | -------------------------------------------------------------------------------- /test/tests/test_audio.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | =begin 4 | If you execute these tests singularly, it will fail 5 | because both Test::Unit and StarRuby use Kernel#at_exit. 6 | When these tests are executed, the audio module of Star Ruby 7 | is closed at the same time. 8 | =end 9 | 10 | require "test/unit" 11 | require "starruby" 12 | include StarRuby 13 | 14 | class TestAudio < Test::Unit::TestCase 15 | 16 | def test_audio_type 17 | assert_raise TypeError do 18 | Audio.bgm_volume = nil 19 | end 20 | assert_raise TypeError do 21 | Audio.play_bgm(nil) 22 | end 23 | Audio.play_bgm("sounds/music") 24 | assert_raise TypeError do 25 | Audio.play_bgm("sounds/music", false) 26 | end 27 | [:position, :volume, :time].each do |key| 28 | assert_raise TypeError do 29 | Audio.play_bgm("sounds/music", key => false) 30 | end 31 | end 32 | assert_raise TypeError do 33 | Audio.play_se(nil) 34 | end 35 | assert_raise TypeError do 36 | Audio.play_se("sounds/sample", false) 37 | end 38 | [:panning, :volume, :time].each do |key| 39 | assert_raise TypeError do 40 | Audio.play_se("sounds/sample", key => false) 41 | end 42 | end 43 | assert_raise TypeError do 44 | Audio.stop_all_ses(false) 45 | end 46 | assert_raise TypeError do 47 | Audio.stop_all_ses(:time => false) 48 | end 49 | assert_raise TypeError do 50 | Audio.stop_bgm(false) 51 | end 52 | assert_raise TypeError do 53 | Audio.stop_bgm(:time => false) 54 | end 55 | end 56 | 57 | def test_bgm_volume 58 | assert_equal 255, Audio.bgm_volume 59 | (0..255).each do |volume| 60 | Audio.bgm_volume = volume 61 | assert_equal volume, Audio.bgm_volume 62 | end 63 | assert_raise ArgumentError do 64 | Audio.bgm_volume = -1 65 | end 66 | assert_raise ArgumentError do 67 | Audio.bgm_volume = 256 68 | end 69 | end 70 | 71 | def test_play_bgm 72 | (0..255).each do |volume| 73 | Audio.play_bgm("sounds/music", :volume => volume) 74 | assert_equal volume, Audio.bgm_volume 75 | Audio.stop_bgm 76 | end 77 | assert_raise ArgumentError do 78 | Audio.play_bgm("sounds/music", :volume => -1) 79 | end 80 | assert_raise ArgumentError do 81 | Audio.play_bgm("sounds/music", :volume => 256) 82 | end 83 | end 84 | 85 | def test_play_se 86 | (0..255).each do |volume| 87 | Audio.play_se("sounds/sample", :volume => volume) 88 | Audio.stop_all_ses 89 | end 90 | assert_raise ArgumentError do 91 | Audio.play_se("sounds/sample", :volume => -1) 92 | end 93 | assert_raise ArgumentError do 94 | Audio.play_se("sounds/sample", :volume => 256) 95 | end 96 | (-255..255).each do |volume| 97 | Audio.play_se("sounds/sample", :panning => volume) 98 | Audio.stop_all_ses 99 | end 100 | assert_raise ArgumentError do 101 | Audio.play_se("sounds/sample", :panning => -256) 102 | end 103 | assert_raise ArgumentError do 104 | Audio.play_se("sounds/sample", :panning => 256) 105 | end 106 | end 107 | 108 | def test_max_se_count 109 | assert_equal 8, Audio::MAX_SE_COUNT 110 | end 111 | 112 | end 113 | -------------------------------------------------------------------------------- /test/benchmark.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | require "benchmark" 5 | 6 | include StarRuby 7 | 8 | src = Texture.new(100, 100) 9 | src.undump(Array.new(src.width * src.height * 3){rand(256).chr}.join, "rgb") 10 | src.undump(Array.new(src.width * src.height){0 < rand(2) ? "\xff" : "\x00"}.join, "a") 11 | dst = Texture.new(319, 239) 12 | dst.undump(Array.new(dst.width * dst.height * 3){rand(256).chr}.join, "rgb") 13 | dst.undump(Array.new(dst.width * dst.height){rand(256).chr}.join, "a") 14 | 15 | Benchmark.bm do |b| 16 | dst.clear 17 | b.report "loop " do 18 | 10000.times do |i| 19 | dst.clear if i & 3 == 0 20 | end 21 | end 22 | b.report "normal" do 23 | 10000.times do |i| 24 | dst.clear if i & 3 == 0 25 | x = i % dst.width 26 | y = i % dst.height 27 | dst.render_texture(src, x, y) 28 | end 29 | end 30 | dst.clear 31 | b.report "alpha " do 32 | 10000.times do |i| 33 | dst.clear if i & 3 == 0 34 | x = i % dst.width 35 | y = i % dst.height 36 | alpha = i % 256 37 | dst.render_texture(src, x, y, :alpha => i) 38 | end 39 | end 40 | dst.clear 41 | b.report "none " do 42 | 10000.times do |i| 43 | dst.clear if i & 3 == 0 44 | x = i % dst.width 45 | y = i % dst.height 46 | dst.render_texture(src, x, y, :blend_type => :none) 47 | end 48 | end 49 | dst.clear 50 | angle = Math::PI / 4 51 | b.report "geo " do 52 | 10000.times do |i| 53 | dst.clear if i & 3 == 0 54 | x = i % dst.width 55 | y = i % dst.height 56 | dst.render_texture(src, x, y, :scale_x => 2, :angle => angle) 57 | end 58 | end 59 | dst.clear 60 | b.report "tone " do 61 | 10000.times do |i| 62 | dst.clear if i & 3 == 0 63 | x = i % dst.width 64 | y = i % dst.height 65 | dst.render_texture(src, x, y, :tone_red => 100) 66 | end 67 | end 68 | dst.clear 69 | b.report "sub " do 70 | 10000.times do |i| 71 | dst.clear if i & 3 == 0 72 | x = i % dst.width 73 | y = i % dst.height 74 | dst.render_texture(src, x, y, :blend_type => :sub) 75 | end 76 | end 77 | dst.clear 78 | b.report "perse " do 79 | 1000.times do |i| 80 | dst.clear if i & 3 == 0 81 | dst.render_in_perspective(src, 82 | :camera_x => src.width / 2, 83 | :camera_y => src.height, 84 | :camera_height => 100, 85 | :intersection_x => dst.width / 2, 86 | :intersection_y => dst.height / 2, 87 | :loop => true) 88 | end 89 | end 90 | dst.clear 91 | b.report "perse2" do 92 | 1000.times do |i| 93 | dst.clear if i & 3 == 0 94 | dst.render_in_perspective(src, 95 | :camera_x => src.width / 2, 96 | :camera_y => src.height, 97 | :camera_height => 100, 98 | :intersection_x => dst.width / 2, 99 | :intersection_y => dst.height / 2, 100 | :loop => true, 101 | :blur => Color.new(0, 0, 0)) 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /src/starruby_private.h: -------------------------------------------------------------------------------- 1 | #ifndef STARRUBY_PRIVATE_H 2 | #define STARRUBY_PRIVATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #ifndef RHASH_IFNONE 16 | # define RHASH_IFNONE(h) (RHASH(h)->ifnone) 17 | #endif 18 | #ifndef RHASH_TBL 19 | # define RHASH_TBL(h) (RHASH(h)->tbl) 20 | #endif 21 | #ifdef HAVE_RUBY_ST_H 22 | # include "ruby/st.h" 23 | #else 24 | # include "st.h" 25 | #endif 26 | 27 | #ifdef WIN32 28 | # include 29 | # include 30 | # include 31 | # include 32 | # ifndef SHGFP_TYPE_CURRENT 33 | # define SHGFP_TYPE_CURRENT (0) 34 | # endif 35 | #endif 36 | #include "starruby.h" 37 | 38 | #ifndef PI 39 | # ifdef M_PI 40 | # define PI M_PI 41 | # else 42 | # define PI (3.1415926535897932384626433832795) 43 | # endif 44 | #endif 45 | 46 | typedef struct { 47 | #if SDL_BYTEORDER == SDL_LIL_ENDIAN 48 | uint8_t blue, green, red, alpha; 49 | #else 50 | uint8_t alpha, red, green, blue; 51 | #endif 52 | } Color; 53 | 54 | typedef union { 55 | Color color; 56 | uint32_t value; 57 | } Pixel; 58 | 59 | typedef struct { 60 | uint16_t width, height; 61 | Pixel* pixels; 62 | int paletteSize; 63 | Color* palette; 64 | uint8_t* indexes; 65 | } Texture; 66 | 67 | typedef struct { 68 | int size; 69 | TTF_Font* sdlFont; 70 | } Font; 71 | 72 | #define MAX(x, y) (((x) >= (y)) ? (x) : (y)) 73 | #define MIN(x, y) (((x) <= (y)) ? (x) : (y)) 74 | #define DIV255(x) ((x) / 255) 75 | 76 | #define rb_raise_sdl_error() \ 77 | rb_raise(strb_GetStarRubyErrorClass(), "%s", SDL_GetError()) 78 | #define rb_raise_sdl_mix_error() \ 79 | rb_raise(strb_GetStarRubyErrorClass(), "%s", Mix_GetError()) 80 | #define rb_raise_sdl_ttf_error() \ 81 | rb_raise(strb_GetStarRubyErrorClass(), "%s", TTF_GetError()) 82 | #define rb_raise_opengl_error() \ 83 | rb_raise(strb_GetStarRubyErrorClass(), "OpenGL Error: 0x%x", glGetError()); 84 | 85 | VALUE strb_GetColorClass(void); 86 | VALUE strb_GetStarRubyErrorClass(void); 87 | VALUE strb_GetTextureClass(void); 88 | 89 | VALUE strb_GetCompletePath(VALUE, bool); 90 | 91 | void strb_GetColorFromRubyValue(Color*, VALUE); 92 | 93 | void strb_CheckFont(VALUE); 94 | void strb_CheckTexture(VALUE); 95 | 96 | VALUE strb_InitializeAudio(VALUE rb_mStarRuby); 97 | VALUE strb_InitializeColor(VALUE rb_mStarRuby); 98 | VALUE strb_InitializeGame(VALUE rb_mStarRuby); 99 | VALUE strb_InitializeFont(VALUE rb_mStarRuby); 100 | VALUE strb_InitializeInput(VALUE rb_mStarRuby); 101 | VALUE strb_InitializeStarRubyError(VALUE rb_mStarRuby); 102 | VALUE strb_InitializeTexture(VALUE rb_mStarRuby); 103 | 104 | void strb_UpdateInput(void); 105 | 106 | void strb_FinalizeAudio(void); 107 | void strb_FinalizeInput(void); 108 | 109 | void strb_InitializeSdlAudio(void); 110 | void strb_InitializeSdlFont(void); 111 | void strb_InitializeSdlInput(void); 112 | 113 | void strb_GetRealScreenSize(int*, int*); 114 | void strb_GetScreenSize(int*, int*); 115 | int strb_GetWindowScale(void); 116 | 117 | void strb_CheckDisposedTexture(const Texture* const); 118 | bool strb_IsDisposedTexture(const Texture* const); 119 | 120 | #ifdef DEBUG 121 | #include 122 | void strb_TestInput(void); 123 | #endif 124 | 125 | #ifndef HAVE_RUBY_ENCODING_H 126 | #define rb_intern_str(str) ID2SYM(rb_intern(StringValueCStr(str))) 127 | #endif 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /test/test_gc_stress.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if GC.respond_to?(:stress=) 4 | GC.stress = true 5 | else 6 | $gc_stress = Thread.new do 7 | loop do 8 | Thread.critical = true 9 | GC.start 10 | Thread.critical = false 11 | end 12 | end 13 | end 14 | 15 | require "starruby" 16 | require "test/unit" 17 | 18 | include StarRuby 19 | 20 | class GCStressTest < Test::Unit::TestCase 21 | 22 | def test_color 23 | color = Color.new(1, 2, 3, 4) 24 | color.red 25 | color.green 26 | color.blue 27 | color.alpha 28 | color == Color.new(1, 2, 3, 4) 29 | color.hash 30 | color.eql?(Color.new(1, 2, 3, 4)) 31 | color.to_s 32 | end 33 | 34 | def test_font 35 | Font.exist?("Arial") 36 | Font.exist?("FreeSans") 37 | Font.exist?("fonts/ORANGEKI") 38 | font = Font.new("fonts/ORANGEKI", 12, { 39 | :bold => true, :italic => true, :ttc_index => 0 40 | }) 41 | font.name 42 | font.bold? 43 | font.italic? 44 | font.size 45 | font.get_size("foo") 46 | font.dispose 47 | font.disposed? 48 | end 49 | 50 | def test_game 51 | Game.fps 52 | Game.fps = 30 53 | Game.title 54 | Game.title = "foo" 55 | Game.screen 56 | Game.running? 57 | Game.terminate 58 | Game.run(320, 240) do 59 | Game.fps 60 | Game.fps = 30 61 | Game.title 62 | Game.title = "foo" 63 | Game.screen 64 | Game.running? 65 | Game.terminate 66 | end 67 | end 68 | 69 | def test_texture 70 | t = Texture.new(123, 456) 71 | t.size 72 | t.width 73 | t.height 74 | font = Font.new("fonts/ORANGEKI", 12) 75 | color = Color.new(255, 255, 255) 76 | t = Texture.new_text("foo", font, color, true) 77 | t = Texture.load("images/ruby") 78 | t = t.clone 79 | t = t.dup 80 | t.get_pixel(0, 1) 81 | t.set_pixel(2, 3, Color.new(4, 5, 6, 7)) 82 | t.fill(Color.new(0, 1, 2, 3)) 83 | t.fill_rect(0, 1, 2, 3, Color.new(4, 5, 6, 7)) 84 | t = t.change_hue(Math::PI) 85 | t.change_hue!(Math::PI) 86 | t.render_text("bar", 0, 1, font, color) 87 | t.render_texture(t, 0, 1, { 88 | :alpha => 2, 89 | :angle => Math::PI * 3 / 4, 90 | :blend_type => :add, 91 | :center_x => 3, 92 | :center_y => 4, 93 | :saturation => 5, 94 | :scale_x => 6, 95 | :scale_y => 7, 96 | :src_height => 9, 97 | :src_width => 8, 98 | :src_x => 10, 99 | :src_y => 11, 100 | :tone_red => 12, 101 | :tone_green => -13, 102 | :tone_blue => 14, 103 | }) 104 | t.clear 105 | t.save("images/saved_image.png") 106 | if FileTest.exist?("images/saved_image.png") 107 | File.delete("images/saved_image.png") 108 | end 109 | str = t.dump("rgb") 110 | t.undump(str, "gbr") 111 | t.dispose 112 | t.disposed? 113 | end 114 | 115 | def test_input 116 | Input.pressed_keys(:keyboard, :duration => 0, :delay => 1, :interval => 2) 117 | Input.pressed_keys(:gamepad, { 118 | :device_number => 3, :duration => 4, :delay => 5, :interval => 6 119 | }) 120 | Input.pressed_keys(:mouse, :duration => 7, :delay => 8, :interval => 9) 121 | Input.mouse_location 122 | end 123 | 124 | def test_audio 125 | Audio.bgm_volume 126 | Audio.bgm_volume = 0 127 | Audio.play_bgm("sounds/music") 128 | Audio.playing_bgm? 129 | Audio.stop_bgm 130 | Audio.playing_bgm? 131 | Audio.play_bgm("sounds/music", :position => 0, :volume => 1, :time => 2) 132 | Audio.playing_bgm? 133 | Audio.stop_bgm(:time => 3) 134 | Audio.play_se("sounds/sample") 135 | Audio.stop_all_ses 136 | Audio.play_se("sounds/sample", :panning => 4, :volume => 5, :time => 6) 137 | Audio.stop_all_ses(:time => 7) 138 | end 139 | 140 | end 141 | -------------------------------------------------------------------------------- /samples/falling_blocks/model.rb: -------------------------------------------------------------------------------- 1 | require "falling_blocks/piece" 2 | require "falling_blocks/field" 3 | 4 | module FallingBlocks 5 | 6 | class Model 7 | 8 | # Current state of this game. 9 | # :start, :playing, :pause, or :gameover 10 | attr_reader :state 11 | 12 | attr_reader :score 13 | def level 14 | [@lines / 5 + 1, @init_level].max 15 | end 16 | attr_reader :lines 17 | attr_reader :field 18 | 19 | attr_reader :falling_piece 20 | def falling_piece_x 21 | @falling_piece_x_100.quo(100) 22 | end 23 | def falling_piece_y 24 | @falling_piece_y_100.quo(100) 25 | end 26 | attr_reader :falling_piece_angle 27 | 28 | attr_reader :next_piece 29 | 30 | def initialize(init_level) 31 | @init_level = init_level 32 | start 33 | end 34 | 35 | def start 36 | @state = :start 37 | @score = 0 38 | @lines = 0 39 | @field = Field.new 40 | go_next_piece 41 | end 42 | 43 | def start_playing 44 | @state = :playing 45 | end 46 | 47 | def pause 48 | @state = :pause if @state == :playing 49 | end 50 | 51 | def resume 52 | @state = :playing if @state == :pause 53 | end 54 | 55 | def try_move(direction) 56 | x_100 = @falling_piece_x_100 57 | case direction 58 | when :left 59 | x_100 -= 100 60 | when :right 61 | x_100 += 100 62 | end 63 | x = x_100.quo(100).ceil 64 | y = @falling_piece_y_100.quo(100).ceil 65 | angle = @falling_piece_angle 66 | unless field.conflict?(@falling_piece, x, y, angle) 67 | @falling_piece_x_100 = x_100 68 | end 69 | end 70 | 71 | def try_rotate(direction) 72 | angle = @falling_piece_angle 73 | case direction 74 | when :left 75 | angle = (angle + 3) % 4 76 | when :right 77 | angle = (angle + 1) % 4 78 | end 79 | x = @falling_piece_x_100.quo(100).ceil 80 | y = @falling_piece_y_100.quo(100).ceil 81 | unless field.conflict?(@falling_piece, x, y, angle) 82 | @falling_piece_angle = angle 83 | end 84 | end 85 | 86 | def try_fall(down_key = false) 87 | dy_100 = level * 5 88 | dy_100 = [dy_100, 100].max if down_key 89 | y_100 = @falling_piece_y_100 90 | x = @falling_piece_x_100.quo(100).ceil 91 | angle = @falling_piece_angle 92 | loop do 93 | y_100 += [100, dy_100].min 94 | dy_100 -= [100, dy_100].min 95 | y = y_100.quo(100).ceil 96 | if field.conflict?(@falling_piece, x, y, angle) 97 | @falling_piece_y_100 = (y - 1) * 100 98 | break 99 | end 100 | if dy_100 <= 0 101 | @falling_piece_y_100 = y_100 102 | break 103 | end 104 | end 105 | end 106 | 107 | def add_score(d_score) 108 | @score += d_score 109 | end 110 | 111 | def falling_piece_landing? 112 | x = @falling_piece_x_100.quo(100).ceil 113 | y = @falling_piece_y_100.quo(100).floor 114 | angle = @falling_piece_angle 115 | field.conflict?(@falling_piece, x, y + 1, angle) 116 | end 117 | 118 | def do_landing 119 | x = @falling_piece_x_100 / 100 120 | y = @falling_piece_y_100 / 100 121 | angle = @falling_piece_angle 122 | if @field.add_piece(@falling_piece, x, y, angle) 123 | go_next_piece if @field.flashing_lines.empty? 124 | else 125 | @state = :gameover 126 | end 127 | end 128 | 129 | def flashing? 130 | not @field.flashing_lines.empty? 131 | end 132 | 133 | def finish_flashing 134 | lines_size = @field.flashing_lines.size 135 | @score += (case lines_size 136 | when 1; 100 137 | when 2; 300 138 | when 3; 900 139 | when 4; 2700 140 | end) * level 141 | @lines += lines_size 142 | @field.flash 143 | go_next_piece 144 | end 145 | 146 | private 147 | 148 | def go_next_piece 149 | @falling_piece = @next_piece || Piece[rand(Piece.count)] 150 | @next_piece = Piece[rand(Piece.count)] 151 | @falling_piece_x_100 = (field.width - @falling_piece.width) / 2 * 100 152 | @falling_piece_y_100 = 0 153 | @falling_piece_angle = 0 154 | end 155 | 156 | end 157 | 158 | end 159 | -------------------------------------------------------------------------------- /samples/airship.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "starruby" 4 | include StarRuby 5 | 6 | field_texture = Texture.load("images/ruby-logo-R") 7 | star_texture = Texture.load("images/star") 8 | 9 | Airship = Struct.new(:x, :y, :yaw, :pitch, :roll, :height) 10 | airship = Airship.new 11 | airship.x = field_texture.width / 2 12 | airship.y = field_texture.height 13 | airship.yaw = 0 14 | airship.pitch = 0 15 | airship.roll = 0 16 | airship.height = 25 17 | fearless = false 18 | 19 | font = Font.new("fonts/ORANGEKI", 12) 20 | yellow = Color.new(255, 255, 128) 21 | 22 | Game.run(320, 240, :title => "Airship") do |game| 23 | # Begin Inputing 24 | if Input.keys(:keyboard, :duration => 1).include?(:f) 25 | fearless = ! fearless 26 | end 27 | keys = Input.keys(:keyboard) 28 | # Terminate if the ESC key is pressed 29 | break if keys.include?(:escape) 30 | # Go left or right 31 | if keys.include?(:left) 32 | airship.yaw = (airship.yaw + 358) % 360 33 | airship.roll = [airship.roll - (fearless ? 8 : 3), fearless ? -180 : -20].max 34 | elsif keys.include?(:right) 35 | airship.yaw = (airship.yaw + 2) % 360 36 | airship.roll = [airship.roll + (fearless ? 8 : 3), fearless ? 180 : 20].min 37 | else 38 | # If not pressed the left or the right key, 39 | # the airship will be set its original position gradually. 40 | if 0 < airship.roll 41 | airship.roll = [airship.roll - (fearless ? 8 : 3), 0].max 42 | elsif airship.roll < 0 43 | airship.roll = [airship.roll + (fearless ? 8 : 3), 0].min 44 | end 45 | end 46 | # Go forward 47 | if keys.include?(:space) 48 | airship.x = (airship.x + 10 * Math.sin(airship.yaw.degree)).to_i 49 | airship.y = (airship.y - 10 * Math.cos(airship.yaw.degree)).to_i 50 | end 51 | # Go up or down 52 | if keys.include?(:down) 53 | airship.height = [airship.height + 2, 45].min 54 | airship.pitch = [airship.pitch + (fearless ? 8 : 3), fearless ? 180 : 20].min 55 | elsif keys.include?(:up) 56 | airship.height = [airship.height - 2, 5].max 57 | airship.pitch = [airship.pitch - (fearless ? 8 : 3), fearless ? -180 : -20].max 58 | else 59 | # If not pressed the up or the down key, 60 | # the airship will be set its original position gradually. 61 | if 0 < airship.pitch 62 | airship.pitch = [airship.pitch - (fearless ? 8 : 3), 0].max 63 | elsif airship.pitch < 0 64 | airship.pitch = [airship.pitch + (fearless ? 8 : 3), 0].min 65 | end 66 | end 67 | # End Inputing 68 | # Begin View 69 | s = game.screen 70 | s.fill(Color.new(128, 128, 128)) 71 | options = { 72 | :camera_x => airship.x, 73 | :camera_y => airship.y, 74 | :camera_height => airship.height, 75 | :camera_yaw => airship.yaw.degree, 76 | :camera_pitch => airship.pitch.degree, 77 | :camera_roll => airship.roll.degree, 78 | :loop => true, 79 | :blur => :background, 80 | } 81 | # Render the ground 82 | s.render_in_perspective(field_texture, options) 83 | # Render stars 84 | # The following array means stars' coords ([x, y, height]). 85 | [[-20, -20, 0], [-20, 20, 0], [20, -20, 0], [20, 20, 0], 86 | [0, 0, 20]].map do |x, y, height| 87 | x += field_texture.width / 2 88 | y += field_texture.height / 2 89 | # The following statement returns an array ([x, y, scale]) 90 | s.transform_in_perspective(x, y, height, options) 91 | # The following code was deprecated since the version 0.2 92 | # Texture.transform_in_perspective(x, y, height, options) 93 | end.select do |x, y, scale| 94 | # If either x, y or scale holds nil value, 95 | # the star will not be shown. 96 | x and y and scale and 0 < scale 97 | end.sort do |a, b| 98 | # Sort by each scale 99 | a[2] <=> b[2] 100 | end.each do |x, y, scale| 101 | # Adjust the rendering position 102 | x -= star_texture.width / 2 103 | y -= star_texture.height 104 | # If x or y value is not Fixnum, skip rendering. 105 | next unless x.kind_of?(Fixnum) and y.kind_of?(Fixnum) 106 | s.render_texture(star_texture, x, y, 107 | :scale_x => scale, 108 | :scale_y => scale, 109 | # A star's center is its lower center. 110 | :center_x => star_texture.width / 2, 111 | :center_y => star_texture.height, 112 | :angle => 2 * Math::PI - options[:camera_roll]) 113 | end 114 | # Render texts 115 | s.render_text("[Arrow] Rotate Camera", 8, 8, font, yellow) 116 | s.render_text("[Space] Go Forward", 8, 8 + 16, font, yellow) 117 | str = "[F] Fearless? #{fearless ? '(Yes)' : '(No)'}" 118 | s.render_text(str, 8, 8 + 32, font, yellow) 119 | # End Viewing 120 | end 121 | -------------------------------------------------------------------------------- /src/color.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | static volatile VALUE rb_cColor = Qundef; 4 | 5 | VALUE 6 | strb_GetColorClass(void) 7 | { 8 | return rb_cColor; 9 | } 10 | 11 | static void Color_free(Color*); 12 | inline void 13 | strb_GetColorFromRubyValue(Color* color, VALUE rbColor) 14 | { 15 | Check_Type(rbColor, T_DATA); 16 | if (RDATA(rbColor)->dfree != (RUBY_DATA_FUNC)Color_free) { 17 | rb_raise(rb_eTypeError, "wrong argument type %s (expected StarRuby::Color)", 18 | rb_obj_classname(rbColor)); 19 | } 20 | const Pixel p = (Pixel){ 21 | .value = (uint32_t)(VALUE)DATA_PTR(rbColor) 22 | }; 23 | *color = p.color; 24 | } 25 | 26 | static VALUE 27 | Color_s_new(int argc, VALUE* argv, VALUE self) 28 | { 29 | volatile VALUE rbRed, rbGreen, rbBlue, rbAlpha; 30 | rb_scan_args(argc, argv, "31", 31 | &rbRed, &rbGreen, &rbBlue, &rbAlpha); 32 | const int red = NUM2INT(rbRed); 33 | const int green = NUM2INT(rbGreen); 34 | const int blue = NUM2INT(rbBlue); 35 | const int alpha = NIL_P(rbAlpha) ? 255 : NUM2INT(rbAlpha); 36 | if (red < 0 || 256 <= red || green < 0 || 256 <= green || 37 | blue < 0 || 256 <= blue || alpha < 0 || 256 <= alpha) { 38 | rb_raise(rb_eArgError, "invalid color value: (r:%d, g:%d, b:%d, a:%d)", 39 | red, green, blue, alpha); 40 | } 41 | static VALUE rbColorCache = Qundef; 42 | if (rbColorCache == Qundef) { 43 | rb_gc_register_address(&rbColorCache); 44 | const int cacheSize = 64; 45 | rbColorCache = rb_ary_new2(cacheSize); 46 | rb_ary_store(rbColorCache, cacheSize - 1, Qnil); 47 | } 48 | VALUE* rbColorCacheValues = RARRAY_PTR(rbColorCache); 49 | const long index = ((red & 3) << 4) | ((green & 3) << 2) | (blue & 3); 50 | volatile VALUE rbColor = rbColorCacheValues[index]; 51 | if (!NIL_P(rbColor)) { 52 | Color color; 53 | strb_GetColorFromRubyValue(&color, rbColor); 54 | if (color.red == red && 55 | color.green == green && 56 | color.blue == blue && 57 | color.alpha == alpha) { 58 | return rbColor; 59 | } 60 | } 61 | VALUE args[] = { 62 | rbRed, rbGreen, rbBlue, NIL_P(rbAlpha) ? INT2FIX(255) : rbAlpha, 63 | }; 64 | rbColor = rb_class_new_instance(sizeof(args) / sizeof(VALUE), args, self); 65 | rbColorCacheValues[index] = rbColor; 66 | return rbColor; 67 | } 68 | 69 | static void 70 | Color_free(Color* color) 71 | { 72 | } 73 | 74 | static VALUE 75 | Color_alloc(VALUE klass) 76 | { 77 | return Data_Wrap_Struct(klass, 0, Color_free, (void*)0); 78 | } 79 | 80 | static VALUE 81 | Color_initialize(VALUE self, VALUE rbRed, VALUE rbGreen, VALUE rbBlue, VALUE rbAlpha) 82 | { 83 | const int red = NUM2INT(rbRed); 84 | const int green = NUM2INT(rbGreen); 85 | const int blue = NUM2INT(rbBlue); 86 | const int alpha = NUM2INT(rbAlpha); 87 | const Pixel pixel = { 88 | .color = { 89 | .red = red, 90 | .green = green, 91 | .blue = blue, 92 | .alpha = alpha, 93 | } 94 | }; 95 | DATA_PTR(self) = (void*)(VALUE)pixel.value; 96 | return Qnil; 97 | } 98 | 99 | static VALUE 100 | Color_alpha(VALUE self) 101 | { 102 | Color color; 103 | strb_GetColorFromRubyValue(&color, self); 104 | return INT2FIX(color.alpha); 105 | } 106 | 107 | static VALUE 108 | Color_blue(VALUE self) 109 | { 110 | Color color; 111 | strb_GetColorFromRubyValue(&color, self); 112 | return INT2FIX(color.blue); 113 | } 114 | 115 | static VALUE 116 | Color_equal(VALUE self, VALUE rbOther) 117 | { 118 | if (self == rbOther) { 119 | return Qtrue; 120 | } 121 | if (!rb_obj_is_kind_of(rbOther, rb_cColor)) { 122 | return Qfalse; 123 | } 124 | Color color1, color2; 125 | strb_GetColorFromRubyValue(&color1, self); 126 | strb_GetColorFromRubyValue(&color2, rbOther); 127 | return (color1.red == color2.red && 128 | color1.green == color2.green && 129 | color1.blue == color2.blue && 130 | color1.alpha == color2.alpha) ? Qtrue : Qfalse; 131 | } 132 | 133 | static VALUE 134 | Color_green(VALUE self) 135 | { 136 | Color color; 137 | strb_GetColorFromRubyValue(&color, self); 138 | return INT2FIX(color.green); 139 | } 140 | 141 | static VALUE 142 | Color_hash(VALUE self) 143 | { 144 | Color color; 145 | strb_GetColorFromRubyValue(&color, self); 146 | #if POSFIXABLE(0xffffffff) 147 | const uint32_t hash = (color.alpha << 24) | 148 | (color.red << 16) | 149 | (color.green << 8) | 150 | color.blue; 151 | #else 152 | const uint32_t hash = ((color.alpha >> 6) << 24) | 153 | ((color.red ^ ((color.alpha >> 4) & 0x3)) << 16) | 154 | ((color.green ^ ((color.alpha >> 2) & 0x3)) << 8) | 155 | (color.blue ^ (color.alpha & 0x3)); 156 | #endif 157 | return INT2FIX(hash); 158 | } 159 | 160 | static VALUE 161 | Color_red(VALUE self) 162 | { 163 | Color color; 164 | strb_GetColorFromRubyValue(&color, self); 165 | return INT2FIX(color.red); 166 | } 167 | 168 | static VALUE 169 | Color_to_s(VALUE self) 170 | { 171 | Color color; 172 | strb_GetColorFromRubyValue(&color, self); 173 | char str[256]; 174 | snprintf(str, sizeof(str), 175 | "#", 176 | color.alpha, color.red, color.green, color.blue); 177 | return rb_str_new2(str); 178 | } 179 | 180 | VALUE 181 | strb_InitializeColor(VALUE rb_mStarRuby) 182 | { 183 | rb_cColor = rb_define_class_under(rb_mStarRuby, "Color", rb_cObject); 184 | rb_define_singleton_method(rb_cColor, "new", Color_s_new, -1); 185 | rb_define_alloc_func(rb_cColor, Color_alloc); 186 | rb_define_private_method(rb_cColor, "initialize", Color_initialize, 4); 187 | rb_define_method(rb_cColor, "alpha", Color_alpha, 0); 188 | rb_define_method(rb_cColor, "blue", Color_blue, 0); 189 | rb_define_method(rb_cColor, "==", Color_equal, 1); 190 | rb_define_method(rb_cColor, "eql?", Color_equal, 1); 191 | rb_define_method(rb_cColor, "green", Color_green, 0); 192 | rb_define_method(rb_cColor, "hash", Color_hash, 0); 193 | rb_define_method(rb_cColor, "red", Color_red, 0); 194 | rb_define_method(rb_cColor, "to_s", Color_to_s, 0); 195 | 196 | return rb_cColor; 197 | } 198 | -------------------------------------------------------------------------------- /samples/falling_blocks/view.rb: -------------------------------------------------------------------------------- 1 | include StarRuby 2 | 3 | module FallingBlocks 4 | 5 | class View 6 | 7 | def render_text(screen, text, x, y, in_window = false) 8 | fore_color = in_window ? Color.new(255, 255, 255) : Color.new(51, 51, 153) 9 | screen.render_text(text, x + 1, y + 8 + 1, @font, Color.new(0, 0, 0, 64)) 10 | screen.render_text(text, x, y + 8, @font, fore_color) 11 | end 12 | 13 | private :render_text 14 | 15 | def render_piece(screen, piece, x, y, angle = 0, options = {}) 16 | blocks = @textures[:blocks] 17 | piece.height.times do |j| 18 | piece.width.times do |i| 19 | if piece[i, j, angle] 20 | screen.render_texture(blocks, x + i * 10, y + j * 10, { 21 | :src_x => piece.id * 10, :src_width => 10, :src_height => 10 22 | }.merge(options)) 23 | end 24 | end 25 | end 26 | end 27 | 28 | private :render_piece 29 | 30 | attr_reader :model 31 | 32 | def initialize(model) 33 | @model = model 34 | @textures = { 35 | :background => Texture.load("images/falling_blocks/background"), 36 | :blocks => Texture.load("images/falling_blocks/blocks"), 37 | :field_window => Texture.new(100, 200), 38 | :next_piece_window => Texture.new(50, 50), 39 | :score_window => Texture.new(140, 20), 40 | :level_window => Texture.new(140, 20), 41 | :lines_window => Texture.new(140, 20), 42 | :start_info => Texture.new(320, 240), 43 | :pause_info => Texture.new(320, 240), 44 | :gameover_info => Texture.new(320, 240), 45 | } 46 | @font = Font.new("fonts/falling_blocks/flappy_for_famicom", 8) 47 | 48 | texture = @textures[:start_info] 49 | texture.fill(Color.new(0, 0, 0, 128)) 50 | str = "PRESS ANY KEY TO PLAY" 51 | width, height = @font.get_size(str) 52 | render_text(texture, str, (texture.width - width) / 2, (texture.height - height) / 2, true) 53 | 54 | texture = @textures[:pause_info] 55 | texture.fill(Color.new(0, 0, 0, 128)) 56 | str = "PAUSE" 57 | width, height = @font.get_size(str) 58 | render_text(texture, str, (texture.width - width) / 2, (texture.height - height) / 2, true) 59 | 60 | texture = @textures[:gameover_info] 61 | texture.fill(Color.new(0, 0, 0, 128)) 62 | str = "GAME OVER" 63 | width, height = @font.get_size(str) 64 | render_text(texture, str, (texture.width - width) / 2, (texture.height - height) / 2, true) 65 | end 66 | 67 | def update(screen) 68 | # Clear windows 69 | @textures.keys.select{|k| k.to_s =~ /window$/}.each do |key| 70 | @textures[key].fill(Color.new(0, 0, 0, 192)) 71 | end 72 | 73 | # Render the field 74 | if [:playing, :gameover].include?(model.state) 75 | window = @textures[:field_window] 76 | blocks = @textures[:blocks] 77 | field = model.field 78 | field.height.times do |j| 79 | field.width.times do |i| 80 | if field[i, j] 81 | window.render_texture(blocks, i * 10, j * 10, { 82 | :src_x => field[i, j] * 10, :src_width => 10, :src_height => 10 83 | }) 84 | end 85 | end 86 | end 87 | end 88 | 89 | # Render the falling piece 90 | if [:playing, :gameover].include?(model.state) 91 | window = @textures[:field_window] 92 | x = model.falling_piece_x 93 | y = model.falling_piece_y 94 | angle = model.falling_piece_angle 95 | options = {} 96 | options.merge!({ 97 | :tone_red => 128, :tone_green => 128, :tone_blue => 128 98 | }) if model.falling_piece_landing? 99 | render_piece(window, model.falling_piece, x * 10, y * 10, angle, options) 100 | end 101 | 102 | # Render flashing 103 | if model.state == :playing and model.flashing? 104 | window = @textures[:field_window] 105 | lines = model.field.flashing_lines 106 | flashing_texture = Texture.new(model.field.width * 10, 10) 107 | flashing_texture.fill(Color.new(255, 0, 0, 128)) 108 | lines.each do |line| 109 | window.render_texture(flashing_texture, 0, line * 10) 110 | end 111 | end 112 | 113 | # Render the next piece 114 | if [:playing, :gameover].include?(model.state) and model.next_piece 115 | window = @textures[:next_piece_window] 116 | x = (window.width - model.next_piece.width * 10) / 2 117 | y = (window.height - model.next_piece.height * 10) / 2 118 | render_piece(window, model.next_piece, x, y) 119 | end 120 | 121 | # Render texts 122 | %w(score level lines).each do |key| 123 | value = model.send(key).to_s 124 | texture = @textures["#{key}_window".intern] 125 | x = texture.width - @font.get_size(value)[0] - 5 126 | render_text(texture, value, x, 0, true) 127 | end 128 | 129 | screen.clear 130 | 131 | screen.render_texture(@textures[:background], 0, 0) 132 | screen.render_texture(@textures[:field_window], 20, 20) 133 | 134 | render_text(screen, "NEXT", 140, 15) 135 | screen.render_texture(@textures[:next_piece_window], 140, 35) 136 | render_text(screen, "SCORE", 140, 100) 137 | screen.render_texture(@textures[:score_window], 140, 120) 138 | render_text(screen, "LEVEL", 140, 140) 139 | screen.render_texture(@textures[:level_window], 140, 160) 140 | render_text(screen, "LINES", 140, 180) 141 | screen.render_texture(@textures[:lines_window], 140, 200) 142 | 143 | case model.state 144 | when :start 145 | screen.render_texture(@textures[:start_info], 0, 0) 146 | when :pause 147 | screen.render_texture(@textures[:pause_info], 0, 0) 148 | when :gameover 149 | screen.render_texture(@textures[:gameover_info], 0, 0) 150 | end 151 | end 152 | 153 | end 154 | 155 | end 156 | -------------------------------------------------------------------------------- /test/tests/test_game.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | =begin 4 | If you execute these tests singularly, it will fail 5 | because both Test::Unit and StarRuby use Kernel#at_exit. 6 | When these tests are executed, the Game module of Star Ruby 7 | is closed at the same time. 8 | =end 9 | 10 | require "test/unit" 11 | require "starruby" 12 | include StarRuby 13 | 14 | class TestGame < Test::Unit::TestCase 15 | 16 | def test_new_defaults 17 | g = nil 18 | begin 19 | g = Game.new(320, 240) 20 | assert_equal "", g.title 21 | assert_equal 30, g.fps 22 | assert_equal 1, g.window_scale 23 | assert_equal false, g.disposed? 24 | ensure 25 | if g 26 | g.dispose 27 | assert_equal true, g.disposed? 28 | end 29 | end 30 | end 31 | 32 | def test_new_duplicate 33 | g = nil 34 | begin 35 | g = Game.new(320, 240) 36 | assert_raise StarRubyError do 37 | Game.new(320, 240) 38 | end 39 | ensure 40 | g.dispose if g 41 | end 42 | end 43 | 44 | def test_new_with_options 45 | assert_nil Game.current 46 | g = nil 47 | begin 48 | g = Game.new(320, 240, 49 | :title => "foo", :fps => 31, :window_scale => 2) 50 | assert_equal Game.current, g 51 | assert_equal false, g.window_closing? 52 | assert_not_nil g.screen 53 | assert_equal "foo", g.title 54 | title = g.title = "bar" 55 | assert_equal "bar", g.title 56 | title << "baz" 57 | assert_equal "bar", g.title 58 | assert_equal 31, g.fps 59 | g.fps = 32 60 | assert_equal 32, g.fps 61 | assert_equal 2, g.window_scale 62 | assert_equal false, g.fullscreen? 63 | assert_equal 0.0, g.real_fps 64 | assert_kind_of Float, g.real_fps 65 | g.update_state 66 | g.update_screen 67 | g.update_state 68 | g.update_screen 69 | ensure 70 | g.dispose if g 71 | end 72 | assert_nil Game.current 73 | end 74 | 75 | def test_new_type 76 | assert_raise TypeError do 77 | Game.new(nil, 240) 78 | end 79 | assert_raise TypeError do 80 | Game.new(320, nil) 81 | end 82 | assert_raise TypeError do 83 | Game.new(320, 240, false) 84 | end 85 | assert_raise TypeError do 86 | Game.new(320, 240, :title => false) 87 | end 88 | assert_raise TypeError do 89 | Game.new(320, 240, :fps => false) 90 | end 91 | assert_raise TypeError do 92 | Game.new(320, 240, :window_scale => false) 93 | end 94 | end 95 | 96 | def test_dispose 97 | g = Game.new(320, 240) 98 | assert_equal false, g.disposed? 99 | g.dispose 100 | assert_equal true, g.disposed? 101 | assert_raise RuntimeError do 102 | g.fps 103 | end 104 | assert_raise RuntimeError do 105 | g.fps = 30 106 | end 107 | assert_raise RuntimeError do 108 | g.fullscreen? 109 | end 110 | assert_raise RuntimeError do 111 | g.real_fps 112 | end 113 | assert_raise RuntimeError do 114 | g.screen 115 | end 116 | assert_raise RuntimeError do 117 | g.title 118 | end 119 | assert_raise RuntimeError do 120 | g.title = "foo" 121 | end 122 | assert_raise RuntimeError do 123 | g.update_screen 124 | end 125 | assert_raise RuntimeError do 126 | g.update_state 127 | end 128 | assert_raise RuntimeError do 129 | g.wait 130 | end 131 | assert_raise RuntimeError do 132 | g.window_closing? 133 | end 134 | assert_raise RuntimeError do 135 | g.window_scale 136 | end 137 | assert_raise RuntimeError do 138 | g.window_scale = 2 139 | end 140 | ensure 141 | g.dispose if g 142 | end 143 | 144 | def test_run 145 | assert_nil Game.current 146 | called = false 147 | Game.run(320, 240, :title => "foo") do |g| 148 | called = true 149 | assert_not_nil Game.current 150 | assert_equal "foo", g.title 151 | assert_equal 30, g.fps 152 | assert_raise StarRubyError do 153 | Game.run(320, 240) {} 154 | end 155 | break 156 | end 157 | assert called 158 | assert_nil Game.current 159 | end 160 | 161 | def test_run_error 162 | assert_nil Game.current 163 | begin 164 | Game.run(320, 240) do |game| 165 | assert_not_nil game.screen 166 | raise RuntimeError, "runtime error" 167 | end 168 | rescue RuntimeError 169 | end 170 | assert_nil Game.current 171 | end 172 | 173 | def test_run_window_scale 174 | Game.run(320, 240, :window_scale => 2) do |game| 175 | assert_equal [320, 240], game.screen.size 176 | break 177 | end 178 | end 179 | 180 | def test_run_type 181 | assert_raise TypeError do 182 | Game.run(nil, 240) {} 183 | end 184 | assert_raise TypeError do 185 | Game.run(320, nil) {} 186 | end 187 | assert_raise TypeError do 188 | Game.run(320, 240, false) {} 189 | end 190 | assert_raise TypeError do 191 | Game.run(320, 240, :title => false) {} 192 | end 193 | assert_raise TypeError do 194 | Game.run(320, 240, :fps => false) {} 195 | end 196 | assert_raise TypeError do 197 | Game.run(320, 240, :window_scale => false) {} 198 | end 199 | end 200 | 201 | def test_screen 202 | Game.run(320, 240) do |game| 203 | begin 204 | assert_kind_of Texture, game.screen 205 | assert_equal [320, 240], game.screen.size 206 | ensure 207 | break 208 | end 209 | end 210 | Game.run(123, 456) do |game| 211 | begin 212 | assert_kind_of Texture, game.screen 213 | assert_equal [123, 456], game.screen.size 214 | ensure 215 | break 216 | end 217 | end 218 | end 219 | 220 | def test_screen_dispose 221 | g = nil 222 | begin 223 | g = Game.new(320, 240, 224 | :title => "foo", :fps => 31, :window_scale => 2) 225 | g.screen.dispose 226 | g.update_state 227 | assert_raise RuntimeError do 228 | g.update_screen 229 | end 230 | ensure 231 | g.dispose if g 232 | end 233 | end 234 | 235 | def test_ticks 236 | ticks1 = Game.ticks 237 | ticks2 = 0 238 | Game.run(320, 240) do 239 | ticks2 = Game.ticks 240 | assert ticks1 <= ticks2 241 | break 242 | end 243 | ticks3 = Game.ticks 244 | assert ticks2 <= ticks3 245 | end 246 | 247 | def test_window_scale 248 | Game.run(320, 240, :window_scale => 1) do |game| 249 | assert_equal 1, game.window_scale 250 | game.window_scale = 2 251 | assert_equal 2, game.window_scale 252 | break 253 | end 254 | end 255 | 256 | end 257 | -------------------------------------------------------------------------------- /test/tests/test_font.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # coding: utf-8 3 | 4 | require "test/unit" 5 | require "starruby" 6 | include StarRuby 7 | 8 | class TestFont < Test::Unit::TestCase 9 | 10 | def test_exist 11 | case RUBY_PLATFORM 12 | when /mswin32|cygwin|mingw32|bccwin32|interix|djgpp/ 13 | # Windows 14 | assert_equal true, Font.exist?("Arial") 15 | assert_equal false, Font.exist?("arial.ttf") 16 | assert_equal false, Font.exist?("arial.ttc") 17 | assert_equal (File.exist?("./arial.ttf") or File.exist?("./arial.ttc")), Font.exist?("arial") 18 | assert_equal (File.exist?("./Arial.ttf") or File.exist?("./Arial.ttc")), Font.exist?("./Arial") 19 | assert_equal (File.exist?("./arial.ttf") or File.exist?("./arial.ttc")), Font.exist?("./arial") 20 | assert_equal false, Font.exist?("msgothic") 21 | assert_equal false, Font.exist?("msgothic.ttf") 22 | assert_equal false, Font.exist?("msgothic.ttc") 23 | assert_equal true, Font.exist?("Arial") 24 | assert_equal true, (Font.exist?("MS ゴシック") or Font.exist?("MS Gothic")) 25 | assert_equal false, Font.exist?("notfont") 26 | assert_equal false, Font.exist?("notfont.ttf") 27 | assert_equal false, Font.exist?("notfont.ttc") 28 | when /linux/ 29 | # Linux 30 | assert_equal true, Font.exist?("FreeSans") 31 | assert_equal true, Font.exist?("FreeSans") 32 | assert_equal false, Font.exist?("FreeSans:style=Bold") 33 | assert_equal true, Font.exist?("FreeSans, Bold") 34 | assert_equal true, Font.exist?("FreeSans, BoldOblique") 35 | assert_equal false, Font.exist?("FreeSans, NotStyle") 36 | assert_equal true, Font.exist?("FreeSans ,Bold") 37 | assert_equal true, Font.exist?("FreeSans ,") 38 | assert_equal false, Font.exist?("FreeSans.ttf") 39 | assert_equal false, Font.exist?("FreeSans.ttc") 40 | when /darwin/ 41 | # Mac OS X 42 | assert_equal true, Font.exist?("Helvetica") 43 | assert_equal true, Font.exist?("Helvetica, Regular") 44 | assert_equal true, Font.exist?("Helvetica, Oblique") 45 | assert_equal false, Font.exist?("Helvetica, NotStyle") 46 | end 47 | assert_raise ArgumentError do 48 | Font.exist?("fonts/maybefont") 49 | end 50 | assert_equal true, Font.exist?("fonts/ORANGEKI") 51 | assert_equal true, Font.exist?("fonts/maybefont2") 52 | end 53 | 54 | def test_exist_type 55 | assert_raise TypeError do 56 | Font.exist?(nil) 57 | end 58 | end 59 | 60 | def test_new 61 | if Font.exist?("Arial") 62 | font = Font.new("Arial", 16) 63 | assert_equal "Arial", font.name 64 | elsif Font.exist?("FreeSans") 65 | font = Font.new("FreeSans", 16) 66 | assert_equal "FreeSans", font.name 67 | elsif Font.exist?("Helvetica Neue") 68 | font = Font.new("Helvetica Neue", 16) 69 | assert_equal "Helvetica Neue", font.name 70 | else 71 | flunk 72 | end 73 | assert_equal 16, font.size 74 | assert_equal false, font.bold? 75 | assert_equal false, font.italic? 76 | if Font.exist?("MS UI Gothic") 77 | font = Font.new("MS UI Gothic", 12, { 78 | :ttc_index => 1, :bold => true, :italic => true 79 | }) # :ttc_index is ignored 80 | assert_equal 12, font.size 81 | assert_equal "MS UI Gothic", font.name 82 | assert_equal true, font.bold? 83 | assert_equal true, font.italic? 84 | end 85 | assert_raise Errno::ENOENT do 86 | Font.new("notfont", 12) 87 | end 88 | assert_raise ArgumentError do 89 | Font.new("fonts/maybefont", 12) 90 | end 91 | assert_raise StarRubyError do 92 | Font.new("fonts/maybefont2", 12) 93 | end 94 | end 95 | 96 | def test_equal 97 | case RUBY_PLATFORM 98 | when /mswin32|cygwin|mingw32|bccwin32|interix|djgpp/ 99 | names = ["MS UI Gothic", "Arial", "Arial Black"] 100 | when /linux/ 101 | names = ["Bitstream Charter, Bold", "Bitstream Charter, Bold Italic", "Courier 10 Pitch, Regular"] 102 | when /darwin/ 103 | names = ["Bitstream Charter, Bold", "Bitstream Charter, Bold Italic", "Courier, Regular"] 104 | end 105 | assert names.all?{|name| Font.exist?(name)} 106 | assert Font.new(names[0], 12).equal?(Font.new(names[0], 12)) 107 | assert ! Font.new(names[0], 12).equal?(Font.new(names[1], 12)) 108 | assert ! Font.new(names[0], 12).equal?(Font.new(names[2], 12)) 109 | assert Font.new(names[1], 12).equal?(Font.new(names[1], 12)) 110 | assert ! Font.new(names[1], 12).equal?(Font.new(names[2], 12)) 111 | assert Font.new(names[2], 12).equal?(Font.new(names[2], 12)) 112 | assert ! Font.new(names[0], 12).equal?(Font.new(names[0], 11)) 113 | assert ! Font.new(names[0], 12).equal?(Font.new(names[0], 13)) 114 | assert ! Font.new(names[0], 12).equal?(Font.new(names[0], 12, :bold => true)) 115 | assert ! Font.new(names[0], 12).equal?(Font.new(names[0], 12, :italic => true)) 116 | assert ! Font.new(names[0], 12).equal?(Font.new(names[0], 12, :bold => true, :italic => true)) 117 | assert Font.new(names[0], 12, :bold => true).equal?(Font.new(names[0], 12, :bold => true)) 118 | assert Font.new(names[0], 12, :italic => true).equal?(Font.new(names[0], 12, :italic => true)) 119 | assert Font.new(names[0], 12, :bold => true, :italic => true).equal?(Font.new(names[0], 12, :bold => true, :italic => true)) 120 | assert ! Font.new(names[0], 12).equal?(Font.new(names[1], 14, :bold => true)) 121 | assert Font.new(names[1], 14, :bold => true).equal?(Font.new(names[1], 14, :bold => true)) 122 | end 123 | 124 | def test_new_type 125 | assert_raise TypeError do 126 | Font.new(nil, 12) 127 | end 128 | assert_raise TypeError do 129 | Font.new("fonts/ORANGEKI", nil) 130 | end 131 | assert_raise TypeError do 132 | Font.new("fonts/ORANGEKI", 12, false) 133 | end 134 | end 135 | 136 | def test_get_size 137 | if Font.exist?("MS ゴシック") 138 | font = Font.new("MS ゴシック", 12) 139 | assert_equal "MS Gothic", font.name 140 | assert_equal [6, 13], font.get_size("A") 141 | assert font.get_size("A").frozen? 142 | h = font.get_size("A")[1] 143 | assert_equal [78, h], font.get_size("Hello, World!") 144 | assert_equal [60, h], font.get_size("こんにちは") 145 | assert_equal [30, h], font.get_size("aaa&a") 146 | end 147 | if Font.exist?("Arial") 148 | font = Font.new("Arial", 16) 149 | elsif Font.exist?("FreeSans") 150 | font = Font.new("FreeSans", 16) 151 | elsif Font.exist?("Helvetica Neue") 152 | font = Font.new("Helvetica Neue", 16) 153 | else 154 | flunk 155 | end 156 | size = font.get_size("AAAAAAAAAAAAAA"); 157 | size[0] # No Exception 158 | size[1] # No Exception 159 | end 160 | 161 | def test_get_size_type 162 | font = Font.new("fonts/ORANGEKI", 12) 163 | assert_raise TypeError do 164 | font.get_size(nil) 165 | end 166 | end 167 | 168 | end 169 | -------------------------------------------------------------------------------- /LGPL: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/audio.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | #define MAX_CHANNEL_COUNT (8) 4 | 5 | static bool isEnabled = false; 6 | static bool bgmLoop = false; 7 | static uint8_t bgmVolume = 255; 8 | static Mix_Music* sdlBgm = NULL; 9 | static Uint32 sdlBgmStartTicks = 0; 10 | static Uint32 sdlBgmLastPausedPosition = 0; 11 | 12 | static volatile VALUE rbChunkCache = Qundef; 13 | static volatile VALUE rbMusicCache = Qundef; 14 | 15 | static volatile VALUE symbol_loop = Qundef; 16 | static volatile VALUE symbol_panning = Qundef; 17 | static volatile VALUE symbol_position = Qundef; 18 | static volatile VALUE symbol_time = Qundef; 19 | static volatile VALUE symbol_volume = Qundef; 20 | 21 | static VALUE 22 | Audio_bgm_position(VALUE self) 23 | { 24 | if (isEnabled) { 25 | if (Mix_PlayingMusic()) { 26 | sdlBgmLastPausedPosition = SDL_GetTicks() - sdlBgmStartTicks; 27 | } 28 | return LONG2NUM(sdlBgmLastPausedPosition); 29 | } else { 30 | return Qnil; 31 | } 32 | } 33 | 34 | static VALUE 35 | Audio_bgm_volume(VALUE self) 36 | { 37 | return INT2FIX(bgmVolume); 38 | } 39 | 40 | static VALUE 41 | Audio_bgm_volume_eq(VALUE self, VALUE rbVolume) 42 | { 43 | int tmpBgmVolume = NUM2INT(rbVolume); 44 | if (tmpBgmVolume < 0 || 256 <= tmpBgmVolume) { 45 | rb_raise(rb_eArgError, "invalid bgm volume: %d", bgmVolume); 46 | } 47 | bgmVolume = tmpBgmVolume; 48 | if (isEnabled) { 49 | Mix_VolumeMusic(DIV255((int)(bgmVolume * MIX_MAX_VOLUME))); 50 | } 51 | return INT2FIX(bgmVolume); 52 | } 53 | 54 | static VALUE 55 | Audio_play_bgm(int argc, VALUE* argv, VALUE self) 56 | { 57 | volatile VALUE rbPath, rbOptions; 58 | rb_scan_args(argc, argv, "11", &rbPath, &rbOptions); 59 | if (NIL_P(rbOptions)) { 60 | rbOptions = rb_hash_new(); 61 | } 62 | Check_Type(rbOptions, T_HASH); 63 | 64 | volatile VALUE rbCompletePath = strb_GetCompletePath(rbPath, true); 65 | volatile VALUE val; 66 | if (!NIL_P(val = rb_hash_aref(rbMusicCache, rbCompletePath))) { 67 | sdlBgm = (Mix_Music*)NUM2LONG(val); 68 | } else { 69 | const char* path = StringValueCStr(rbCompletePath); 70 | if (!(sdlBgm = Mix_LoadMUS(path))) { 71 | rb_raise_sdl_mix_error(); 72 | } 73 | rb_hash_aset(rbMusicCache, rbCompletePath, ULONG2NUM((unsigned long)sdlBgm)); 74 | } 75 | 76 | int time = 0; 77 | int volume = 255; 78 | long bgmPosition = 0; 79 | bgmLoop = RTEST(rb_hash_aref(rbOptions, symbol_loop)); 80 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_position))) { 81 | bgmPosition = MAX(NUM2LONG(val), 0); 82 | } 83 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_time))) { 84 | time = NUM2INT(val); 85 | } 86 | if (bgmPosition) { 87 | time = MAX(time, 250); 88 | } else if (time < 250) { 89 | time = 0; 90 | } 91 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_volume))) { 92 | volume = NUM2INT(val); 93 | if (volume < 0 || 256 <= volume) { 94 | rb_raise(rb_eArgError, "invalid volume: %d", volume); 95 | } 96 | } 97 | Audio_bgm_volume_eq(self, INT2FIX(volume)); 98 | if (!isEnabled) { 99 | return Qnil; 100 | } 101 | //printf("time: %d, bgmPosition: %d\n", time, bgmPosition); 102 | if (Mix_FadeInMusicPos(sdlBgm, 0, time, bgmPosition)) { 103 | rb_raise_sdl_mix_error(); 104 | } 105 | sdlBgmStartTicks = SDL_GetTicks() - bgmPosition; 106 | 107 | return Qnil; 108 | } 109 | 110 | static VALUE 111 | Audio_play_se(int argc, VALUE* argv, VALUE self) 112 | { 113 | volatile VALUE rbPath, rbOptions; 114 | rb_scan_args(argc, argv, "11", &rbPath, &rbOptions); 115 | if (NIL_P(rbOptions)) { 116 | rbOptions = rb_hash_new(); 117 | } 118 | Check_Type(rbOptions, T_HASH); 119 | 120 | volatile VALUE rbCompletePath = strb_GetCompletePath(rbPath, true); 121 | Mix_Chunk* sdlSE = NULL; 122 | volatile VALUE val; 123 | if (isEnabled) { 124 | if (!NIL_P(val = rb_hash_aref(rbChunkCache, rbCompletePath))) { 125 | sdlSE = (Mix_Chunk*)NUM2ULONG(val); 126 | } else { 127 | const char* path = StringValueCStr(rbCompletePath); 128 | if (!(sdlSE = Mix_LoadWAV(path))) { 129 | rb_raise_sdl_mix_error(); 130 | } 131 | rb_hash_aset(rbChunkCache, rbCompletePath, ULONG2NUM((unsigned long)sdlSE)); 132 | } 133 | } 134 | 135 | int panning = 0; 136 | int time = 0; 137 | int volume = 255; 138 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_panning))) { 139 | panning = NUM2INT(val); 140 | if (panning <= -256 || 256 <= panning) { 141 | rb_raise(rb_eArgError, "invalid panning: %d", panning); 142 | } 143 | } 144 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_time))) { 145 | time = NUM2INT(val); 146 | } 147 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_volume))) { 148 | volume = NUM2INT(val); 149 | if (volume < 0 || 256 <= volume) { 150 | rb_raise(rb_eArgError, "invalid volume: %d", volume); 151 | } 152 | } 153 | if (!isEnabled) { 154 | return Qnil; 155 | } 156 | int sdlChannel; 157 | if (time < 250) { 158 | sdlChannel = Mix_PlayChannel(-1, sdlSE, 0); 159 | } else { 160 | sdlChannel = Mix_FadeInChannel(-1, sdlSE, 0, time); 161 | } 162 | if (sdlChannel == -1) { 163 | return Qnil; 164 | } 165 | Mix_Volume(sdlChannel, DIV255(volume * MIX_MAX_VOLUME)); 166 | int sdlLeftPanning = 255; 167 | int sdlRightPanning = 255; 168 | if (panning < 0) { 169 | sdlRightPanning = 255 - (-panning); 170 | } else if (0 < panning) { 171 | sdlLeftPanning = 255 - panning; 172 | } 173 | if (!Mix_SetPanning(sdlChannel, sdlLeftPanning, sdlRightPanning)) { 174 | rb_raise_sdl_mix_error(); 175 | } 176 | 177 | return Qnil; 178 | } 179 | 180 | static VALUE 181 | Audio_playing_bgm(VALUE self) 182 | { 183 | return (isEnabled && 184 | (Mix_PlayingMusic() || 185 | Mix_FadingMusic() != MIX_NO_FADING)) ? Qtrue : Qfalse; 186 | } 187 | 188 | static VALUE 189 | Audio_playing_se_count(VALUE self) 190 | { 191 | if (!isEnabled) { 192 | return INT2FIX(0); 193 | } 194 | return INT2FIX(Mix_Playing(-1)); 195 | } 196 | 197 | static VALUE 198 | Audio_stop_all_ses(int argc, VALUE* argv, VALUE self) 199 | { 200 | volatile VALUE rbOptions; 201 | rb_scan_args(argc, argv, "01", &rbOptions); 202 | if (NIL_P(rbOptions)) { 203 | rbOptions = rb_hash_new(); 204 | } 205 | Check_Type(rbOptions, T_HASH); 206 | 207 | int time = 0; 208 | volatile VALUE val; 209 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_time))) { 210 | time = NUM2INT(val); 211 | } 212 | if (!isEnabled) { 213 | return Qnil; 214 | } 215 | if (time <= 250) { 216 | Mix_HaltChannel(-1); 217 | } else { 218 | Mix_FadeOutChannel(-1, time); 219 | } 220 | return Qnil; 221 | } 222 | 223 | static VALUE 224 | Audio_stop_bgm(int argc, VALUE* argv, VALUE self) 225 | { 226 | volatile VALUE rbOptions; 227 | rb_scan_args(argc, argv, "01", &rbOptions); 228 | if (NIL_P(rbOptions)) { 229 | rbOptions = rb_hash_new(); 230 | } 231 | Check_Type(rbOptions, T_HASH); 232 | 233 | if (!isEnabled) { 234 | return Qnil; 235 | } 236 | int time = 0; 237 | volatile VALUE val; 238 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_time))) { 239 | time = NUM2INT(val); 240 | } 241 | time = MAX(time, 0); 242 | if (time < 250) { 243 | Mix_HaltMusic(); 244 | } else { 245 | Mix_FadeOutMusic(time); 246 | } 247 | sdlBgm = NULL; 248 | return Qnil; 249 | } 250 | 251 | static void 252 | SdlMusicFinished(void) 253 | { 254 | if (sdlBgm && bgmLoop) { 255 | sdlBgmStartTicks = SDL_GetTicks(); 256 | if (isEnabled && Mix_PlayMusic(sdlBgm, 0)) { 257 | rb_raise_sdl_mix_error(); 258 | } 259 | } 260 | } 261 | 262 | void 263 | strb_InitializeSdlAudio(void) 264 | { 265 | if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024)) { 266 | rb_io_puts(1, (VALUE[]) {rb_str_new2(Mix_GetError())}, rb_stderr); 267 | isEnabled = false; 268 | } else { 269 | Mix_AllocateChannels(MAX_CHANNEL_COUNT); 270 | Mix_HookMusicFinished(SdlMusicFinished); 271 | isEnabled = true; 272 | } 273 | } 274 | 275 | VALUE 276 | strb_InitializeAudio(VALUE rb_mStarRuby) 277 | { 278 | VALUE rb_mAudio = rb_define_module_under(rb_mStarRuby, "Audio"); 279 | rb_define_module_function(rb_mAudio, "bgm_position", 280 | Audio_bgm_position, 0); 281 | rb_define_module_function(rb_mAudio, "bgm_volume", 282 | Audio_bgm_volume, 0); 283 | rb_define_module_function(rb_mAudio, "bgm_volume=", 284 | Audio_bgm_volume_eq, 1); 285 | rb_define_module_function(rb_mAudio, "play_bgm", 286 | Audio_play_bgm, -1); 287 | rb_define_module_function(rb_mAudio, "play_se", 288 | Audio_play_se, -1); 289 | rb_define_module_function(rb_mAudio, "playing_bgm?", 290 | Audio_playing_bgm, 0); 291 | rb_define_module_function(rb_mAudio, "playing_se_count", 292 | Audio_playing_se_count, 0); 293 | rb_define_module_function(rb_mAudio, "stop_all_ses", 294 | Audio_stop_all_ses, -1); 295 | rb_define_module_function(rb_mAudio, "stop_bgm", 296 | Audio_stop_bgm, -1); 297 | 298 | rb_define_const(rb_mAudio, "MAX_SE_COUNT", INT2FIX(MAX_CHANNEL_COUNT)); 299 | 300 | symbol_loop = ID2SYM(rb_intern("loop")); 301 | symbol_panning = ID2SYM(rb_intern("panning")); 302 | symbol_position = ID2SYM(rb_intern("position")); 303 | symbol_time = ID2SYM(rb_intern("time")); 304 | symbol_volume = ID2SYM(rb_intern("volume")); 305 | 306 | Audio_bgm_volume_eq(rb_mAudio, INT2FIX(255)); 307 | 308 | rbMusicCache = rb_iv_set(rb_mAudio, "music_cache", rb_hash_new()); 309 | rbChunkCache = rb_iv_set(rb_mAudio, "chunk_cache", rb_hash_new()); 310 | 311 | return rb_mAudio; 312 | } 313 | 314 | static int 315 | FreeChunkCacheItem(VALUE rbKey, VALUE rbValue) 316 | { 317 | Mix_Chunk* chunk = (Mix_Chunk*)NUM2ULONG(rbValue); 318 | if (chunk) { 319 | Mix_FreeChunk(chunk); 320 | } 321 | return ST_CONTINUE; 322 | } 323 | 324 | static int 325 | FreeMusicCacheItem(VALUE rbKey, VALUE rbValue) 326 | { 327 | Mix_Music* music = (Mix_Music*)NUM2ULONG(rbValue); 328 | if (music) { 329 | Mix_FreeMusic(music); 330 | } 331 | return ST_CONTINUE; 332 | } 333 | 334 | void 335 | strb_FinalizeAudio(void) 336 | { 337 | rb_hash_foreach(rbChunkCache, FreeChunkCacheItem, 0); 338 | rb_hash_foreach(rbMusicCache, FreeMusicCacheItem, 0); 339 | Mix_CloseAudio(); 340 | } 341 | -------------------------------------------------------------------------------- /src/font.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | #ifdef HAVE_FONTCONFIG_FONTCONFIG_H 4 | #include 5 | #endif 6 | #ifdef WIN32 7 | static volatile VALUE rbWindowsFontDirPathSymbol = Qundef; 8 | #endif 9 | 10 | static VALUE rbFontCache = Qundef; 11 | 12 | static volatile VALUE symbol_bold = Qundef; 13 | static volatile VALUE symbol_italic = Qundef; 14 | static volatile VALUE symbol_ttc_index = Qundef; 15 | 16 | typedef struct FontFileInfo { 17 | VALUE rbFontNameSymbol; 18 | VALUE rbFileNameSymbol; 19 | int ttcIndex; 20 | struct FontFileInfo* next; 21 | } FontFileInfo; 22 | static FontFileInfo* fontFileInfos; 23 | 24 | static void Font_free(Font*); 25 | inline void 26 | strb_CheckFont(VALUE rbFont) 27 | { 28 | Check_Type(rbFont, T_DATA); 29 | if (RDATA(rbFont)->dfree != (RUBY_DATA_FUNC)Font_free) { 30 | rb_raise(rb_eTypeError, "wrong argument type %s (expected StarRuby::Font)", 31 | rb_obj_classname(rbFont)); 32 | } 33 | } 34 | 35 | static void 36 | SearchFont(VALUE rbFilePathOrName, 37 | VALUE* volatile rbRealFilePath, int* ttcIndex) 38 | { 39 | *rbRealFilePath = Qnil; 40 | if (ttcIndex != NULL) { 41 | *ttcIndex = -1; 42 | } 43 | *rbRealFilePath = strb_GetCompletePath(rbFilePathOrName, false); 44 | if (!NIL_P(*rbRealFilePath)) { 45 | return; 46 | } 47 | volatile VALUE rbFontNameSymbol = 48 | ID2SYM(rb_intern_str(rbFilePathOrName)); 49 | FontFileInfo* info = fontFileInfos; 50 | while (info) { 51 | if (info->rbFontNameSymbol == rbFontNameSymbol) { 52 | *rbRealFilePath = rb_str_new2(rb_id2name(SYM2ID(info->rbFileNameSymbol))); 53 | #ifdef WIN32 54 | volatile VALUE rbTemp = 55 | rb_str_new2(rb_id2name(SYM2ID(rbWindowsFontDirPathSymbol))); 56 | *rbRealFilePath = rb_str_concat(rb_str_cat2(rbTemp, "\\"), *rbRealFilePath); 57 | #endif 58 | if (ttcIndex != NULL) { 59 | *ttcIndex = info->ttcIndex; 60 | } 61 | return; 62 | } 63 | info = info->next; 64 | } 65 | #ifdef HAVE_FONTCONFIG_FONTCONFIG_H 66 | if (!FcInit()) { 67 | FcFini(); 68 | rb_raise(strb_GetStarRubyErrorClass(), "can't initialize fontconfig library"); 69 | return; 70 | } 71 | int nameLength = RSTRING_LEN(rbFilePathOrName) + 1; 72 | char name[nameLength]; 73 | strncpy(name, StringValueCStr(rbFilePathOrName), nameLength); 74 | char* delimiter = strchr(name, ','); 75 | char* style = NULL; 76 | if (delimiter) { 77 | *delimiter = '\0'; 78 | style = delimiter + 1; 79 | char* nameTail = delimiter - 1; 80 | while (*nameTail == ' ') { 81 | *nameTail = '\0'; 82 | nameTail--; 83 | } 84 | while (*style == ' ') { 85 | style++; 86 | } 87 | } 88 | FcPattern* pattern = FcPatternBuild(NULL, FC_FAMILY, FcTypeString, name, NULL); 89 | if (style && 0 < strlen(style)) { 90 | FcPatternAddString(pattern, FC_STYLE, (FcChar8*)style); 91 | } 92 | FcObjectSet* objectSet = FcObjectSetBuild(FC_FAMILY, FC_FILE, NULL); 93 | FcFontSet* fontSet = FcFontList(NULL, pattern, objectSet); 94 | if (objectSet) { 95 | FcObjectSetDestroy(objectSet); 96 | } 97 | if (pattern) { 98 | FcPatternDestroy(pattern); 99 | } 100 | if (fontSet) { 101 | for (int i = 0; i < fontSet->nfont; i++) { 102 | FcChar8* fileName = NULL; 103 | if (FcPatternGetString(fontSet->fonts[i], FC_FILE, 0, &fileName) == 104 | FcResultMatch) { 105 | FcChar8* fontName = FcNameUnparse(fontSet->fonts[i]); 106 | *rbRealFilePath = rb_str_new2((char*)fileName); 107 | volatile VALUE rbFontName = rb_str_new2((char*)fontName); 108 | free(fontName); 109 | fontName = NULL; 110 | if (ttcIndex != NULL && strchr(StringValueCStr(rbFontName), ',')) { 111 | *ttcIndex = 0; 112 | } 113 | } 114 | } 115 | FcFontSetDestroy(fontSet); 116 | } 117 | FcFini(); 118 | if (!NIL_P(*rbRealFilePath)) { 119 | return; 120 | } 121 | #endif 122 | return; 123 | } 124 | 125 | static VALUE 126 | Font_s_exist(VALUE self, VALUE rbFilePath) 127 | { 128 | volatile VALUE rbRealFilePath = Qnil; 129 | SearchFont(rbFilePath, (VALUE*)&rbRealFilePath, NULL); 130 | return !NIL_P(rbRealFilePath) ? Qtrue : Qfalse; 131 | } 132 | 133 | static void 134 | Font_free(Font* font) 135 | { 136 | if (TTF_WasInit()) { 137 | TTF_CloseFont(font->sdlFont); 138 | } 139 | font->sdlFont = NULL; 140 | free(font); 141 | } 142 | 143 | static VALUE 144 | Font_s_new(int argc, VALUE* argv, VALUE self) 145 | { 146 | volatile VALUE rbPath, rbSize, rbOptions; 147 | rb_scan_args(argc, argv, "21", &rbPath, &rbSize, &rbOptions); 148 | if (NIL_P(rbOptions)) { 149 | rbOptions = rb_hash_new(); 150 | } 151 | volatile VALUE rbRealFilePath; 152 | int preTtcIndex = -1; 153 | SearchFont(rbPath, (VALUE*)&rbRealFilePath, &preTtcIndex); 154 | if (NIL_P(rbRealFilePath)) { 155 | char* path = StringValueCStr(rbPath); 156 | rb_raise(rb_path2class("Errno::ENOENT"), "%s", path); 157 | return Qnil; 158 | } 159 | int size = NUM2INT(rbSize); 160 | bool bold = false; 161 | bool italic = false; 162 | int ttcIndex = 0; 163 | volatile VALUE val; 164 | Check_Type(rbOptions, T_HASH); 165 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_bold))) { 166 | bold = RTEST(val); 167 | } 168 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_italic))) { 169 | italic = RTEST(val); 170 | } 171 | if (preTtcIndex != -1) { 172 | ttcIndex = preTtcIndex; 173 | } else if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_ttc_index))) { 174 | ttcIndex = NUM2INT(val); 175 | } 176 | volatile VALUE rbHashKey = rb_str_dup(rbRealFilePath); 177 | char temp[256]; 178 | // TODO: change the delimiter or the way to name a hash key 179 | rb_str_cat2(rbHashKey, ";size="); 180 | snprintf(temp, sizeof(temp), "%d", size); 181 | rb_str_cat2(rbHashKey, temp); 182 | if (bold) { 183 | rb_str_cat2(rbHashKey, ";bold=true"); 184 | } else { 185 | rb_str_cat2(rbHashKey, ";bold=false"); 186 | } 187 | if (italic) { 188 | rb_str_cat2(rbHashKey, ";italic=true"); 189 | } else { 190 | rb_str_cat2(rbHashKey, ";italic=false"); 191 | } 192 | rb_str_cat2(rbHashKey, ";ttc_index="); 193 | snprintf(temp, sizeof(temp), "%d", ttcIndex); 194 | rb_str_cat2(rbHashKey, temp); 195 | 196 | if (!NIL_P(val = rb_hash_aref(rbFontCache, rbHashKey))) { 197 | return val; 198 | } else { 199 | VALUE args[] = { 200 | rbRealFilePath, 201 | rbSize, 202 | bold ? Qtrue : Qfalse, 203 | italic ? Qtrue : Qfalse, 204 | INT2NUM(ttcIndex), 205 | }; 206 | volatile VALUE rbNewFont = 207 | rb_class_new_instance(sizeof(args) / sizeof(VALUE), args, self); 208 | rb_hash_aset(rbFontCache, rbHashKey, rbNewFont); 209 | return rbNewFont; 210 | } 211 | } 212 | 213 | static VALUE 214 | Font_alloc(VALUE klass) 215 | { 216 | Font* font = ALLOC(Font); 217 | font->sdlFont = NULL; 218 | return Data_Wrap_Struct(klass, 0, Font_free, font); 219 | } 220 | 221 | static VALUE 222 | Font_initialize(VALUE self, VALUE rbRealFilePath, VALUE rbSize, 223 | VALUE rbBold, VALUE rbItalic, VALUE rbTtcIndex) 224 | { 225 | const char* path = StringValueCStr(rbRealFilePath); 226 | const int size = NUM2INT(rbSize); 227 | const bool bold = RTEST(rbBold); 228 | const bool italic = RTEST(rbItalic); 229 | const int ttcIndex = NUM2INT(rbTtcIndex); 230 | 231 | Font* font; 232 | Data_Get_Struct(self, Font, font); 233 | font->size = size; 234 | font->sdlFont = TTF_OpenFontIndex(path, size, ttcIndex); 235 | if (!font->sdlFont) { 236 | rb_raise(strb_GetStarRubyErrorClass(), "%s (%s)", TTF_GetError(), path); 237 | } 238 | const int style = TTF_STYLE_NORMAL | 239 | (bold ? TTF_STYLE_BOLD : 0) | (italic ? TTF_STYLE_ITALIC : 0); 240 | TTF_SetFontStyle(font->sdlFont, style); 241 | 242 | return Qnil; 243 | } 244 | 245 | static VALUE 246 | Font_bold(VALUE self) 247 | { 248 | const Font* font; 249 | Data_Get_Struct(self, Font, font); 250 | return (TTF_GetFontStyle(font->sdlFont) & TTF_STYLE_BOLD) ? Qtrue : Qfalse; 251 | } 252 | 253 | static VALUE 254 | Font_italic(VALUE self) 255 | { 256 | const Font* font; 257 | Data_Get_Struct(self, Font, font); 258 | return (TTF_GetFontStyle(font->sdlFont) & TTF_STYLE_ITALIC) ? Qtrue : Qfalse; 259 | } 260 | 261 | static VALUE 262 | Font_get_size(VALUE self, VALUE rbText) 263 | { 264 | const Font* font; 265 | Data_Get_Struct(self, Font, font); 266 | const char* text = StringValueCStr(rbText); 267 | int width, height; 268 | if (TTF_SizeUTF8(font->sdlFont, text, &width, &height)) { 269 | rb_raise_sdl_ttf_error(); 270 | } 271 | volatile VALUE rbSize = rb_assoc_new(INT2NUM(width), INT2NUM(height)); 272 | OBJ_FREEZE(rbSize); 273 | return rbSize; 274 | } 275 | 276 | static VALUE 277 | Font_name(VALUE self) 278 | { 279 | const Font* font; 280 | Data_Get_Struct(self, Font, font); 281 | return rb_str_new2(TTF_FontFaceFamilyName(font->sdlFont)); 282 | } 283 | 284 | static VALUE 285 | Font_size(VALUE self) 286 | { 287 | const Font* font; 288 | Data_Get_Struct(self, Font, font); 289 | return INT2NUM(font->size); 290 | } 291 | 292 | #define ADD_INFO(currentInfo, _rbFontNameSymbol, \ 293 | _rbFileNameSymbol, _ttcIndex) \ 294 | do { \ 295 | FontFileInfo* info = ALLOC(FontFileInfo); \ 296 | info->rbFontNameSymbol = _rbFontNameSymbol; \ 297 | info->rbFileNameSymbol = _rbFileNameSymbol; \ 298 | info->ttcIndex = _ttcIndex; \ 299 | info->next = NULL; \ 300 | currentInfo->next = info; \ 301 | currentInfo = info; \ 302 | } while (false) 303 | 304 | void 305 | strb_InitializeSdlFont(void) 306 | { 307 | if (TTF_Init()) { 308 | rb_raise_sdl_ttf_error(); 309 | } 310 | fontFileInfos = ALLOC(FontFileInfo); 311 | fontFileInfos->rbFontNameSymbol = Qundef; 312 | fontFileInfos->rbFileNameSymbol = Qundef; 313 | fontFileInfos->ttcIndex = -1; 314 | fontFileInfos->next = NULL; 315 | FontFileInfo* currentInfo = fontFileInfos; 316 | (void)currentInfo; 317 | 318 | #ifdef WIN32 319 | HKEY hKey; 320 | TCHAR* regPath = 321 | _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"); 322 | if (SUCCEEDED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regPath, 0, 323 | KEY_READ, &hKey))) { 324 | DWORD fontNameBuffMaxLength; 325 | DWORD fileNameBuffMaxByteLength; 326 | RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 327 | &fontNameBuffMaxLength, &fileNameBuffMaxByteLength, 328 | NULL, NULL); 329 | TCHAR fontNameBuff[fontNameBuffMaxLength + 1]; 330 | BYTE fileNameByteBuff[fileNameBuffMaxByteLength]; 331 | for (DWORD dwIndex = 0; ;dwIndex++) { 332 | ZeroMemory(fontNameBuff, sizeof(fontNameBuff)); 333 | ZeroMemory(fileNameByteBuff, sizeof(fileNameByteBuff)); 334 | DWORD fontNameBuffLength = sizeof(fontNameBuff) / sizeof(TCHAR); 335 | DWORD fileNameBuffByteLength = fileNameBuffMaxByteLength; 336 | LONG result = RegEnumValue(hKey, dwIndex, 337 | fontNameBuff, &fontNameBuffLength, 338 | NULL, NULL, 339 | fileNameByteBuff, &fileNameBuffByteLength); 340 | TCHAR* fileNameBuff = (TCHAR*)fileNameByteBuff; 341 | DWORD fileNameBuffLength = _tcslen(fileNameBuff); 342 | if (result == ERROR_SUCCESS) { 343 | const TCHAR* ext = &(fileNameBuff[fileNameBuffLength - 3]); 344 | if (tolower(ext[0]) == _T('t') && 345 | tolower(ext[1]) == _T('t') && 346 | (tolower(ext[2]) == _T('f') || 347 | tolower(ext[2]) == _T('c'))) { 348 | TCHAR* fontName = fontNameBuff; 349 | const TCHAR* fileName = fileNameBuff; 350 | // A TTF font name must end with ' (TrueType)'. 351 | fontName[fontNameBuffLength - 11] = _T('\0'); 352 | for (int i = fileNameBuffLength - 1; 0 <= i; i--) { 353 | if (fileName[i] == _T('\\')) { 354 | fileName += i + 1; 355 | break; 356 | } 357 | } 358 | int length = 359 | WideCharToMultiByte(CP_UTF8, 0, 360 | fontName, -1, 361 | NULL, 0, 362 | NULL, NULL); 363 | char fontNameUTF8[length]; 364 | WideCharToMultiByte(CP_UTF8, 0, 365 | fontName, -1, 366 | fontNameUTF8, length, 367 | NULL, NULL); 368 | volatile VALUE rbFontName = rb_str_new2(fontNameUTF8); 369 | length = 370 | WideCharToMultiByte(CP_ACP, 0, 371 | fileName, -1, 372 | NULL, 0, 373 | NULL, NULL); 374 | char fileNameANSI[length]; 375 | WideCharToMultiByte(CP_ACP, 0, 376 | fileName, -1, 377 | fileNameANSI, length, 378 | NULL, NULL); 379 | volatile VALUE rbFileName = rb_str_new2(fileNameANSI); 380 | if (strchr(StringValueCStr(rbFontName), '&')) { 381 | volatile VALUE rbArr = rb_str_split(rbFontName, "&"); 382 | const int arrLength = RARRAY_LEN(rbArr); 383 | int ttcIndex = 0; 384 | for (int i = 0; i < arrLength; i++) { 385 | volatile VALUE rbFontName = rb_ary_entry(rbArr, i); 386 | rb_funcall(rbFontName, rb_intern("strip!"), 0); 387 | if (0 < RSTRING_LEN(rbFontName)) { 388 | volatile VALUE rbFontNameSymbol = rb_str_intern(rbFontName); 389 | volatile VALUE rbFileNameSymbol = rb_str_intern(rbFileName); 390 | ADD_INFO(currentInfo, rbFontNameSymbol, rbFileNameSymbol, 391 | ttcIndex); 392 | ttcIndex++; 393 | } 394 | } 395 | } else { 396 | volatile VALUE rbFontNameSymbol = rb_str_intern(rbFontName); 397 | volatile VALUE rbFileNameSymbol = rb_str_intern(rbFileName); 398 | ADD_INFO(currentInfo, rbFontNameSymbol, rbFileNameSymbol, -1); 399 | } 400 | } 401 | } else { 402 | break; 403 | } 404 | } 405 | RegCloseKey(hKey); 406 | } else { 407 | rb_raise(strb_GetStarRubyErrorClass(), 408 | "Win32API error: %d", (int)GetLastError()); 409 | } 410 | TCHAR szWindowsFontDirPath[MAX_PATH + 1]; 411 | if (FAILED(SHGetFolderPath(NULL, CSIDL_FONTS, NULL, 412 | SHGFP_TYPE_CURRENT, 413 | szWindowsFontDirPath))) { 414 | rb_raise(strb_GetStarRubyErrorClass(), 415 | "Win32API error: %d", (int)GetLastError()); 416 | } 417 | int length = 418 | WideCharToMultiByte(CP_UTF8, 0, 419 | szWindowsFontDirPath, -1, 420 | NULL, 0, 421 | NULL, NULL); 422 | char szWindowsFontDirPathUTF8[length]; 423 | WideCharToMultiByte(CP_UTF8, 0, 424 | szWindowsFontDirPath, -1, 425 | szWindowsFontDirPathUTF8, length, 426 | NULL, NULL); 427 | volatile VALUE rbWindowsFontDirPath = rb_str_new2(szWindowsFontDirPathUTF8); 428 | rbWindowsFontDirPathSymbol = rb_str_intern(rbWindowsFontDirPath); 429 | #endif 430 | } 431 | 432 | VALUE 433 | strb_InitializeFont(VALUE rb_mStarRuby) 434 | { 435 | VALUE rb_cFont = rb_define_class_under(rb_mStarRuby, "Font", rb_cObject); 436 | rb_define_singleton_method(rb_cFont, "exist?", Font_s_exist, 1); 437 | rb_define_singleton_method(rb_cFont, "new", Font_s_new, -1); 438 | rb_define_alloc_func(rb_cFont, Font_alloc); 439 | rb_define_private_method(rb_cFont, "initialize", Font_initialize, 5); 440 | rb_define_method(rb_cFont, "bold?", Font_bold, 0); 441 | rb_define_method(rb_cFont, "get_size", Font_get_size, 1); 442 | rb_define_method(rb_cFont, "italic?", Font_italic, 0); 443 | rb_define_method(rb_cFont, "name", Font_name, 0); 444 | rb_define_method(rb_cFont, "size", Font_size, 0); 445 | 446 | symbol_bold = ID2SYM(rb_intern("bold")); 447 | symbol_italic = ID2SYM(rb_intern("italic")); 448 | symbol_ttc_index = ID2SYM(rb_intern("ttc_index")); 449 | 450 | rbFontCache = rb_hash_new(); 451 | rb_gc_register_address(&rbFontCache); 452 | 453 | return rb_cFont; 454 | } 455 | -------------------------------------------------------------------------------- /test/tests/test_texture_palette.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "test/unit" 4 | require "starruby" 5 | include StarRuby 6 | 7 | require "frozen_error" 8 | 9 | class TestTexturePalette < Test::Unit::TestCase 10 | 11 | def test_palette 12 | texture = Texture.load("images/ruby") 13 | assert_nil texture.palette 14 | texture = Texture.load("images/ruby8") 15 | assert_nil texture.palette 16 | texture = Texture.load("images/ruby8", :palette => false) 17 | assert_nil texture.palette 18 | texture = Texture.load("images/ruby8", :palette => true) 19 | assert_kind_of Array, texture.palette 20 | assert texture.palette.frozen? 21 | assert_equal 255, texture.palette.size 22 | assert_equal 0, texture.palette[0].alpha 23 | assert_equal Color.new(0x79, 0x03, 0x00, 0xff), texture.palette[1] 24 | assert_equal Color.new(0x82, 0x00, 0x00, 0xff), texture.palette[2] 25 | assert_equal Color.new(0xff, 0xfc, 0xfb, 0xff), texture.palette[253] 26 | assert_equal Color.new(0xfd, 0xff, 0xfc, 0xff), texture.palette[254] 27 | texture = Texture.load("images/ruby8_without_alpha", :palette => true) 28 | assert_equal 253, texture.palette.size 29 | assert_equal Color.new(0x82, 0x00, 0x00, 0xff), texture.palette[0] 30 | assert_equal Color.new(0x73, 0x09, 0x03, 0xff), texture.palette[1] 31 | assert_equal Color.new(0xff, 0xfc, 0xfb, 0xff), texture.palette[251] 32 | assert_equal Color.new(0xfd, 0xff, 0xfc, 0xff), texture.palette[252] 33 | texture = Texture.load("images/ruby8_16colors", :palette => true) 34 | assert_equal 16, texture.palette.size 35 | assert_equal 0, texture.palette[0].alpha 36 | assert_equal Color.new(0x8c, 0x0c, 0x02, 0xff), texture.palette[1] 37 | assert_equal Color.new(0x96, 0x0f, 0x00, 0xff), texture.palette[2] 38 | assert_equal Color.new(0xfb, 0xf6, 0xf3, 0xff), texture.palette[14] 39 | assert_equal Color.new(0x7d, 0x0b, 0x06, 0xff), texture.palette[15] 40 | end 41 | 42 | def test_palette_frozen 43 | texture = Texture.load("images/ruby8", :palette => true) 44 | texture.freeze 45 | assert_kind_of Array, texture.palette 46 | end 47 | 48 | def test_palette_disposed 49 | texture = Texture.load("images/ruby8", :palette => true) 50 | texture.dispose 51 | assert_raise RuntimeError do 52 | texture.palette 53 | end 54 | end 55 | 56 | def test_palette_dup 57 | texture = Texture.load("images/ruby8", :palette => true) 58 | assert_equal texture.palette, texture.dup.palette 59 | assert_equal texture.palette, texture.clone.palette 60 | end 61 | 62 | def test_palette2 63 | orig_normal_texture = normal_texture = Texture.load("images/ruby8") 64 | orig_palette_texture = palette_texture = Texture.load("images/ruby8", :palette => true) 65 | target_texture = Texture.load("images/ruby") 66 | if Font.exist?("Arial") 67 | font = Font.new("Arial", 16) 68 | elsif Font.exist?("FreeSans") 69 | font = Font.new("FreeSans", 16) 70 | elsif Font.exist?("Helvetica Neue") 71 | font = Font.new("Helvetica Neue", 16) 72 | else 73 | flunk 74 | end 75 | # []= 76 | normal_texture[0, 0] = Color.new(0, 0, 0, 0) 77 | assert_raise StarRubyError do 78 | palette_texture[0, 0] = Color.new(0, 0, 0, 0) 79 | end 80 | # change_hue 81 | normal_texture.change_hue(0) 82 | palette_texture.change_hue(0) 83 | # change_hue! 84 | normal_texture.change_hue!(0) 85 | palette_texture.change_hue!(0) 86 | # clear 87 | normal_texture = orig_normal_texture.dup 88 | palette_texture = orig_palette_texture.dup 89 | normal_texture.clear 90 | assert_raise StarRubyError do 91 | palette_texture.clear 92 | end 93 | # fill 94 | normal_texture = orig_normal_texture.dup 95 | palette_texture = orig_palette_texture.dup 96 | normal_texture.fill(Color.new(1, 2, 3, 4)) 97 | assert_raise StarRubyError do 98 | palette_texture.fill(Color.new(1, 2, 3, 4)) 99 | end 100 | # fill_rect 101 | normal_texture = orig_normal_texture.dup 102 | palette_texture = orig_palette_texture.dup 103 | normal_texture.fill_rect(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 104 | assert_raise StarRubyError do 105 | palette_texture.fill_rect(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 106 | end 107 | # render_in_perspective 108 | normal_texture = orig_normal_texture.dup 109 | palette_texture = orig_palette_texture.dup 110 | normal_texture.render_in_perspective(target_texture) 111 | assert_raise StarRubyError do 112 | palette_texture.render_in_perspective(target_texture) 113 | end 114 | # render_line 115 | normal_texture = orig_normal_texture.dup 116 | palette_texture = orig_palette_texture.dup 117 | normal_texture.render_line(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 118 | assert_raise StarRubyError do 119 | palette_texture.render_line(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 120 | end 121 | # render_pixel 122 | normal_texture = orig_normal_texture.dup 123 | palette_texture = orig_palette_texture.dup 124 | normal_texture.render_pixel(1, 2, Color.new(1, 2, 3, 4)) 125 | assert_raise StarRubyError do 126 | palette_texture.render_pixel(1, 2, Color.new(1, 2, 3, 4)) 127 | end 128 | # render_rect 129 | normal_texture = orig_normal_texture.dup 130 | palette_texture = orig_palette_texture.dup 131 | normal_texture.render_rect(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 132 | assert_raise StarRubyError do 133 | palette_texture.render_rect(1, 2, 3, 4, Color.new(1, 2, 3, 4)) 134 | end 135 | # render_text 136 | normal_texture = orig_normal_texture.dup 137 | palette_texture = orig_palette_texture.dup 138 | normal_texture.render_text("text", 0, 0, font, Color.new(1, 2, 3, 4)) 139 | assert_raise StarRubyError do 140 | palette_texture.render_text("text", 0, 0, font, Color.new(1, 2, 3, 4)) 141 | end 142 | # render_texture 143 | normal_texture = orig_normal_texture.dup 144 | palette_texture = orig_palette_texture.dup 145 | normal_texture.render_texture(target_texture, 0, 0) 146 | assert_raise StarRubyError do 147 | palette_texture.render_texture(target_texture, 0, 0) 148 | end 149 | # undump 150 | normal_texture = orig_normal_texture.dup 151 | palette_texture = orig_palette_texture.dup 152 | size = normal_texture.width * normal_texture.height 153 | normal_texture.undump("\0" * size, "r") 154 | assert_raise StarRubyError do 155 | palette_texture.undump("\0" * size, "r") 156 | end 157 | end 158 | 159 | def test_change_palette_original_palette 160 | texture = Texture.load("images/ruby8", :palette => true) 161 | orig_texture = texture.dup 162 | texture.change_palette!(texture.palette) 163 | texture.height.times do |j| 164 | texture.width.times do |i| 165 | assert_equal orig_texture[i, j], texture[i, j], [i, j].inspect 166 | end 167 | end 168 | texture = Texture.load("images/ruby8", :palette => true) 169 | texture2 = texture.change_palette(texture.palette) 170 | texture.height.times do |j| 171 | texture.width.times do |i| 172 | assert_equal texture[i, j], texture2[i, j], [i, j].inspect 173 | end 174 | end 175 | end 176 | 177 | def test_change_palette_null_palette 178 | texture = Texture.load("images/ruby8", :palette => true) 179 | size = texture.palette.size 180 | texture.change_palette!([]) 181 | assert_equal ([Color.new(0, 0, 0, 0)] * size), texture.palette 182 | texture.height.times do |j| 183 | texture.width.times do |i| 184 | assert_equal Color.new(0, 0, 0, 0), texture[i, j] 185 | end 186 | end 187 | texture = Texture.load("images/ruby8", :palette => true) 188 | orig_texture = texture.dup 189 | texture2 = texture.change_palette([]) 190 | assert_equal orig_texture.palette, texture.palette 191 | assert_equal ([Color.new(0, 0, 0, 0)] * size), texture2.palette 192 | texture.height.times do |j| 193 | texture.width.times do |i| 194 | assert_equal orig_texture[i, j], texture[i, j] 195 | assert_equal Color.new(0, 0, 0, 0), texture2[i, j] 196 | end 197 | end 198 | end 199 | 200 | def test_change_palette_small_palette 201 | texture = Texture.load("images/ruby8", :palette => true) 202 | size = texture.palette.size 203 | assert_equal Color.new(0x79, 0x03, 0x00, 0xff), texture.palette[1] 204 | assert_equal Color.new(0x79, 0x03, 0x00, 0xff), texture[93, 8] 205 | assert_equal Color.new(0x82, 0x00, 0x00, 0xff), texture.palette[2] 206 | assert_equal Color.new(0x82, 0x00, 0x00, 0xff), texture[81, 55] 207 | texture.change_palette!([Color.new(1, 2, 3, 4), 208 | Color.new(5, 6, 7, 8), 209 | Color.new(9, 10, 11, 12)]) 210 | assert_equal size, texture.palette.size 211 | assert_equal Color.new(1, 2, 3, 4), texture.palette[0] 212 | assert_equal Color.new(5, 6, 7, 8), texture.palette[1] 213 | assert_equal Color.new(9, 10, 11, 12), texture.palette[2] 214 | assert_equal [Color.new(0, 0, 0, 0)] * (size - 3), texture.palette[3, size - 3] 215 | assert_equal Color.new(1, 2, 3, 4), texture[0, 0] 216 | assert_equal Color.new(5, 6, 7, 8), texture[93, 8] 217 | assert_equal Color.new(9, 10, 11, 12), texture[81, 55] 218 | texture = Texture.load("images/ruby8", :palette => true) 219 | orig_texture = texture.dup 220 | texture2 = texture.change_palette([Color.new(1, 2, 3, 4), 221 | Color.new(5, 6, 7, 8), 222 | Color.new(9, 10, 11, 12)]) 223 | assert_equal orig_texture.palette, texture.palette 224 | texture.height.times do |j| 225 | texture.width.times do |i| 226 | assert_equal orig_texture[i, j], texture[i, j] 227 | end 228 | end 229 | assert_equal Color.new(1, 2, 3, 4), texture2.palette[0] 230 | assert_equal Color.new(5, 6, 7, 8), texture2.palette[1] 231 | assert_equal Color.new(9, 10, 11, 12), texture2.palette[2] 232 | assert_equal [Color.new(0, 0, 0, 0)] * (size - 3), texture2.palette[3, size - 3] 233 | assert_equal Color.new(1, 2, 3, 4), texture2[0, 0] 234 | assert_equal Color.new(5, 6, 7, 8), texture2[93, 8] 235 | assert_equal Color.new(9, 10, 11, 12), texture2[81, 55] 236 | end 237 | 238 | def test_change_palette_big_palette 239 | texture = Texture.load("images/ruby8", :palette => true) 240 | size = texture.palette.size 241 | texture.change_palette!([Color.new(0x24, 0x3f, 0x6a, 0x88)] * 512) 242 | assert_equal size, texture.palette.size 243 | texture.height.times do |j| 244 | texture.width.times do |i| 245 | assert_equal Color.new(0x24, 0x3f, 0x6a, 0x88), texture[i, j] 246 | end 247 | end 248 | texture = Texture.load("images/ruby8", :palette => true) 249 | orig_texture = texture.dup 250 | texture2 = texture.change_palette([Color.new(0x24, 0x3f, 0x6a, 0x88)] * 512) 251 | assert_equal orig_texture.palette, texture.palette 252 | texture.height.times do |j| 253 | texture.width.times do |i| 254 | assert_equal orig_texture[i, j], texture[i, j] 255 | assert_equal Color.new(0x24, 0x3f, 0x6a, 0x88), texture2[i, j] 256 | end 257 | end 258 | end 259 | 260 | def test_change_palette_without_palette 261 | texture = Texture.load("images/ruby", :palette => true) 262 | assert_raise StarRubyError do 263 | texture.change_palette!([]) 264 | end 265 | assert_raise StarRubyError do 266 | texture.change_palette([]) 267 | end 268 | end 269 | 270 | def test_change_palette_frozen 271 | texture = Texture.load("images/ruby8", :palette => true) 272 | texture.freeze 273 | assert_raise FrozenError do 274 | texture.change_palette!([]) 275 | end 276 | texture.change_palette([]) 277 | end 278 | 279 | def test_change_palette_disposed 280 | texture = Texture.load("images/ruby8", :palette => true) 281 | texture.dispose 282 | assert_raise RuntimeError do 283 | texture.change_palette!([]) 284 | end 285 | assert_raise RuntimeError do 286 | texture.change_palette([]) 287 | end 288 | end 289 | 290 | def test_change_palette_type 291 | texture = Texture.load("images/ruby8", :palette => true) 292 | assert_raise TypeError do 293 | texture.change_palette!(false) 294 | end 295 | assert_raise TypeError do 296 | texture.change_palette(false) 297 | end 298 | end 299 | 300 | def test_change_hue_palette 301 | texture = Texture.load("images/ruby8", :palette => true) 302 | orig_texture = texture.clone 303 | # 0 304 | texture = orig_texture.change_hue(0) 305 | texture.height.times do |j| 306 | texture.width.times do |i| 307 | assert_equal orig_texture[i, j], texture[i, j] 308 | end 309 | end 310 | assert_equal orig_texture.palette, texture.palette 311 | texture = orig_texture.clone 312 | texture.change_hue!(0) 313 | texture.height.times do |j| 314 | texture.width.times do |i| 315 | assert_equal orig_texture[i, j], texture[i, j] 316 | end 317 | end 318 | assert_equal orig_texture.palette, texture.palette 319 | # (2/3) * pi 320 | texture = orig_texture.change_hue(Math::PI * 2 / 3) 321 | texture.height.times do |j| 322 | texture.width.times do |i| 323 | p1 = orig_texture[i, j] 324 | p2 = texture[i, j] 325 | assert_in_delta p1.blue, p2.red, 1 326 | assert_in_delta p1.red, p2.green, 1 327 | assert_in_delta p1.green, p2.blue, 1 328 | assert_equal p1.alpha, p2.alpha 329 | end 330 | end 331 | orig_texture.palette.zip(texture.palette).each_with_index do |ps, i| 332 | p1, p2 = ps 333 | assert_in_delta p1.blue, p2.red, 1 334 | assert_in_delta p1.red, p2.green, 1 335 | assert_in_delta p1.green, p2.blue, 1 336 | assert_equal p1.alpha, p2.alpha 337 | end 338 | texture = orig_texture.clone 339 | texture.change_hue!(Math::PI * 2 / 3) 340 | texture.height.times do |j| 341 | texture.width.times do |i| 342 | p1 = orig_texture[i, j] 343 | p2 = texture[i, j] 344 | assert_in_delta p1.blue, p2.red, 1 345 | assert_in_delta p1.red, p2.green, 1 346 | assert_in_delta p1.green, p2.blue, 1 347 | assert_equal p1.alpha, p2.alpha 348 | end 349 | end 350 | orig_texture.palette.zip(texture.palette).each_with_index do |ps, i| 351 | p1, p2 = ps 352 | assert_in_delta p1.blue, p2.red, 1 353 | assert_in_delta p1.red, p2.green, 1 354 | assert_in_delta p1.green, p2.blue, 1 355 | assert_equal p1.alpha, p2.alpha 356 | end 357 | # (4/3) * pi 358 | texture = orig_texture.change_hue(Math::PI * 4 / 3) 359 | texture.height.times do |j| 360 | texture.width.times do |i| 361 | p1 = orig_texture[i, j] 362 | p2 = texture[i, j] 363 | assert_in_delta p1.green, p2.red, 1 364 | assert_in_delta p1.blue, p2.green, 1 365 | assert_in_delta p1.red, p2.blue, 1 366 | assert_equal p1.alpha, p2.alpha 367 | end 368 | end 369 | orig_texture.palette.zip(texture.palette).each_with_index do |ps, i| 370 | p1, p2 = ps 371 | assert_in_delta p1.green, p2.red, 1 372 | assert_in_delta p1.blue, p2.green, 1 373 | assert_in_delta p1.red, p2.blue, 1 374 | assert_equal p1.alpha, p2.alpha 375 | end 376 | texture = orig_texture.clone 377 | texture.change_hue!(Math::PI * 4 / 3) 378 | texture.height.times do |j| 379 | texture.width.times do |i| 380 | p1 = orig_texture[i, j] 381 | p2 = texture[i, j] 382 | assert_in_delta p1.green, p2.red, 1 383 | assert_in_delta p1.blue, p2.green, 1 384 | assert_in_delta p1.red, p2.blue, 1 385 | assert_equal p1.alpha, p2.alpha 386 | end 387 | end 388 | orig_texture.palette.zip(texture.palette).each_with_index do |ps, i| 389 | p1, p2 = ps 390 | assert_in_delta p1.green, p2.red, 1 391 | assert_in_delta p1.blue, p2.green, 1 392 | assert_in_delta p1.red, p2.blue, 1 393 | assert_equal p1.alpha, p2.alpha 394 | end 395 | end 396 | 397 | def test_change_palette_overrided_dup 398 | texture = Texture.load("images/ruby8", :palette => true) 399 | palette = texture.palette.map do 400 | Color.new(rand(0xff), rand(0xff), rand(0xff), rand(0xff)) 401 | end 402 | texture2 = texture.change_palette(palette) 403 | def texture.dup 404 | Color.new(0, 0, 0, 0) 405 | end 406 | texture3 = texture.change_palette(palette) 407 | assert_equal texture2.width, texture3.width 408 | assert_equal texture2.height, texture3.height 409 | texture2.height.times do |j| 410 | texture2.width.times do |i| 411 | assert_equal texture2[i, j], texture3[i, j] 412 | end 413 | end 414 | end 415 | 416 | def test_change_palette_overrided_clone 417 | texture = Texture.load("images/ruby8", :palette => true) 418 | palette = texture.palette.map do 419 | Color.new(rand(0xff), rand(0xff), rand(0xff), rand(0xff)) 420 | end 421 | texture2 = texture.change_palette(palette) 422 | def texture.clone 423 | Color.new(0, 0, 0, 0) 424 | end 425 | texture3 = texture.change_palette(palette) 426 | assert_equal texture2.width, texture3.width 427 | assert_equal texture2.height, texture3.height 428 | texture2.height.times do |j| 429 | texture2.width.times do |i| 430 | assert_equal texture2[i, j], texture3[i, j] 431 | end 432 | end 433 | end 434 | 435 | end 436 | -------------------------------------------------------------------------------- /src/game.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | static volatile VALUE rb_cGame = Qundef; 4 | static volatile VALUE rb_mStarRuby = Qundef; 5 | 6 | static volatile VALUE symbol_cursor = Qundef; 7 | static volatile VALUE symbol_fps = Qundef; 8 | static volatile VALUE symbol_fullscreen = Qundef; 9 | static volatile VALUE symbol_title = Qundef; 10 | static volatile VALUE symbol_vsync = Qundef; 11 | static volatile VALUE symbol_window_scale = Qundef; 12 | 13 | typedef struct { 14 | Uint32 error; 15 | Uint32 before; 16 | Uint32 before2; 17 | int counter; 18 | } GameTimer; 19 | 20 | typedef struct { 21 | int windowScale; 22 | bool isFullscreen; 23 | VALUE screen; 24 | SDL_Surface* sdlScreen; 25 | SDL_Surface* sdlScreenBuffer; 26 | GLuint glScreen; 27 | int fps; 28 | double realFps; 29 | GameTimer timer; 30 | bool isWindowClosing; 31 | bool isVsync; 32 | } Game; 33 | 34 | inline static void 35 | CheckDisposed(const Game* const game) 36 | { 37 | if (!game) { 38 | rb_raise(rb_eRuntimeError, "can't modify disposed StarRuby::Game"); 39 | } 40 | } 41 | 42 | inline static int 43 | Power2(int x) 44 | { 45 | int result = 1; 46 | while (result < x) { 47 | result <<= 1; 48 | } 49 | return result; 50 | } 51 | 52 | static VALUE Game_s_current(VALUE); 53 | 54 | void 55 | strb_GetRealScreenSize(int* width, int* height) 56 | { 57 | volatile VALUE rbCurrent = Game_s_current(rb_cGame); 58 | if (!NIL_P(rbCurrent)) { 59 | const Game* game; 60 | Data_Get_Struct(rbCurrent, Game, game); 61 | *width = game->sdlScreen->w; 62 | *height = game->sdlScreen->h; 63 | } else { 64 | *width = 0; 65 | *height = 0; 66 | } 67 | } 68 | 69 | void 70 | strb_GetScreenSize(int* width, int* height) 71 | { 72 | volatile VALUE rbCurrent = Game_s_current(rb_cGame); 73 | if (!NIL_P(rbCurrent)) { 74 | const Game* game; 75 | Data_Get_Struct(rbCurrent, Game, game); 76 | volatile VALUE rbScreen = game->screen; 77 | const Texture* screen; 78 | Data_Get_Struct(rbScreen, Texture, screen); 79 | if (!strb_IsDisposedTexture(screen)) { 80 | *width = screen->width; 81 | *height = screen->height; 82 | } else { 83 | *width = 0; 84 | *height = 0; 85 | } 86 | } else { 87 | *width = 0; 88 | *height = 0; 89 | } 90 | } 91 | 92 | int 93 | strb_GetWindowScale(void) 94 | { 95 | volatile VALUE rbCurrent = Game_s_current(rb_cGame); 96 | if (!NIL_P(rbCurrent)) { 97 | const Game* game; 98 | Data_Get_Struct(rbCurrent, Game, game); 99 | return game->windowScale; 100 | } else { 101 | return 1; 102 | } 103 | } 104 | 105 | static VALUE Game_dispose(VALUE); 106 | static VALUE Game_fps(VALUE); 107 | static VALUE Game_fps_eq(VALUE, VALUE); 108 | static VALUE Game_screen(VALUE); 109 | static VALUE Game_title(VALUE); 110 | static VALUE Game_title_eq(VALUE, VALUE); 111 | static VALUE Game_real_fps(VALUE); 112 | static VALUE Game_update_screen(VALUE); 113 | static VALUE Game_update_state(VALUE); 114 | static VALUE Game_wait(VALUE); 115 | static VALUE Game_window_closing(VALUE); 116 | 117 | static VALUE 118 | Game_s_current(VALUE self) 119 | { 120 | return rb_iv_get(self, "current"); 121 | } 122 | 123 | static VALUE 124 | RunGame(VALUE rbGame) 125 | { 126 | const Game* game; 127 | Data_Get_Struct(rbGame, Game, game); 128 | while (true) { 129 | Game_update_state(rbGame); 130 | if (RTEST(Game_window_closing(rbGame))) { 131 | break; 132 | } 133 | rb_yield(rbGame); 134 | Game_update_screen(rbGame); 135 | Game_wait(rbGame); 136 | } 137 | return Qnil; 138 | } 139 | 140 | static VALUE 141 | RunGameEnsure(VALUE rbGame) 142 | { 143 | Game_dispose(rbGame); 144 | return Qnil; 145 | } 146 | 147 | static VALUE 148 | Game_s_run(int argc, VALUE* argv, VALUE self) 149 | { 150 | volatile VALUE rbGame = rb_funcall2(self, rb_intern("new"), argc, argv); 151 | rb_ensure(RunGame, rbGame, RunGameEnsure, rbGame); 152 | return Qnil; 153 | } 154 | 155 | static VALUE 156 | Game_s_ticks(VALUE self) 157 | { 158 | return INT2NUM(SDL_GetTicks()); 159 | } 160 | 161 | static void 162 | Game_mark(Game* game) 163 | { 164 | if (game && !NIL_P(game->screen)) { 165 | rb_gc_mark(game->screen); 166 | } 167 | } 168 | 169 | static void 170 | Game_free(Game* game) 171 | { 172 | // should NOT to call SDL_FreeSurface 173 | if (game) { 174 | game->sdlScreen = NULL; 175 | if (game->sdlScreenBuffer) { 176 | SDL_FreeSurface(game->sdlScreenBuffer); 177 | game->sdlScreenBuffer = NULL; 178 | } 179 | } 180 | free(game); 181 | } 182 | 183 | static VALUE 184 | Game_alloc(VALUE klass) 185 | { 186 | // do not call rb_raise in this function 187 | Game* game = ALLOC(Game); 188 | game->windowScale = 1; 189 | game->isFullscreen = false; 190 | game->screen = Qnil; 191 | game->sdlScreen = NULL; 192 | game->sdlScreenBuffer = NULL; 193 | game->glScreen = 0; 194 | game->realFps = 0; 195 | game->timer.error = 0; 196 | game->timer.before = SDL_GetTicks(); 197 | game->timer.before2 = game->timer.before2; 198 | game->timer.counter = 0; 199 | game->isWindowClosing = false; 200 | game->isVsync = false; 201 | return Data_Wrap_Struct(klass, Game_mark, Game_free, game);; 202 | } 203 | 204 | static void 205 | InitializeScreen(Game* game) 206 | { 207 | const int bpp = 32; 208 | 209 | VALUE rbScreen = game->screen; 210 | const Texture* screen; 211 | Data_Get_Struct(rbScreen, Texture, screen); 212 | strb_CheckDisposedTexture(screen); 213 | const int width = screen->width; 214 | const int height = screen->height; 215 | int screenWidth = 0; 216 | int screenHeight = 0; 217 | 218 | Uint32 options = 0; 219 | options |= SDL_OPENGL; 220 | if (game->isFullscreen) { 221 | options |= SDL_HWSURFACE | SDL_FULLSCREEN; 222 | game->windowScale = 1; 223 | SDL_Rect** modes = SDL_ListModes(NULL, options); 224 | if (!modes) { 225 | rb_raise(rb_eRuntimeError, "not supported fullscreen resolution"); 226 | } 227 | if (modes != (SDL_Rect**)-1) { 228 | for (int i = 0; modes[i]; i++) { 229 | int realBpp = SDL_VideoModeOK(modes[i]->w, modes[i]->h, bpp, options); 230 | if (width <= modes[i]->w && height <= modes[i]->h && realBpp == bpp) { 231 | screenWidth = modes[i]->w; 232 | screenHeight = modes[i]->h; 233 | } else { 234 | break; 235 | } 236 | } 237 | if (screenWidth == 0 || screenHeight == 0) { 238 | rb_raise(rb_eRuntimeError, "not supported fullscreen resolution"); 239 | } 240 | } else { 241 | // any resolution are available 242 | screenWidth = width; 243 | screenHeight = height; 244 | } 245 | } else { 246 | screenWidth = width * game->windowScale; 247 | screenHeight = height * game->windowScale; 248 | options |= SDL_SWSURFACE; 249 | } 250 | 251 | SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); 252 | SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); 253 | SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 254 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 255 | SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, game->isVsync ? 1 : 0); 256 | 257 | game->sdlScreen = SDL_SetVideoMode(screenWidth, screenHeight, 258 | bpp, options); 259 | if (!game->sdlScreen) { 260 | rb_raise_sdl_error(); 261 | } 262 | SDL_PixelFormat* format = game->sdlScreen->format; 263 | game->sdlScreenBuffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 264 | Power2(width), 265 | Power2(height), 266 | bpp, 267 | format->Bmask, format->Gmask, format->Bmask, format->Amask); 268 | if (!game->sdlScreenBuffer) { 269 | rb_raise_sdl_error(); 270 | } 271 | 272 | glClearColor(0.0, 0.0, 0.0, 0.0); 273 | glOrtho(0.0, screenWidth, screenHeight, 0.0, -1.0, 1.0); 274 | glEnable(GL_TEXTURE_2D); 275 | glGenTextures(1, &game->glScreen); 276 | glBindTexture(GL_TEXTURE_2D, game->glScreen); 277 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 278 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 279 | } 280 | 281 | static VALUE 282 | Game_initialize(int argc, VALUE* argv, VALUE self) 283 | { 284 | if (!NIL_P(Game_s_current(rb_cGame))) { 285 | rb_raise(strb_GetStarRubyErrorClass(), "already run"); 286 | } 287 | 288 | volatile VALUE rbWidth, rbHeight, rbOptions; 289 | rb_scan_args(argc, argv, "21", &rbWidth, &rbHeight, &rbOptions); 290 | if (NIL_P(rbOptions)) { 291 | rbOptions = rb_hash_new(); 292 | } else { 293 | Check_Type(rbOptions, T_HASH); 294 | } 295 | Game* game; 296 | Data_Get_Struct(self, Game, game); 297 | 298 | if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER)) { 299 | rb_raise_sdl_error(); 300 | } 301 | 302 | const int width = NUM2INT(rbWidth); 303 | const int height = NUM2INT(rbHeight); 304 | 305 | volatile VALUE rbFps = rb_hash_aref(rbOptions, symbol_fps); 306 | Game_fps_eq(self, !NIL_P(rbFps) ? rbFps : INT2FIX(30)); 307 | 308 | volatile VALUE rbTitle = rb_hash_aref(rbOptions, symbol_title); 309 | Game_title_eq(self, !NIL_P(rbTitle) ? rbTitle : rb_str_new2("")); 310 | 311 | bool cursor = false; 312 | 313 | volatile VALUE val; 314 | Check_Type(rbOptions, T_HASH); 315 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_cursor))) { 316 | cursor = RTEST(val); 317 | } 318 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_fullscreen))) { 319 | game->isFullscreen = RTEST(val); 320 | } 321 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_window_scale))) { 322 | game->windowScale = NUM2INT(val); 323 | if (game->windowScale < 1) { 324 | rb_raise(rb_eArgError, "invalid window scale: %d", 325 | game->windowScale); 326 | } 327 | } 328 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_vsync))) { 329 | game->isVsync = RTEST(val); 330 | } 331 | 332 | SDL_ShowCursor(cursor ? SDL_ENABLE : SDL_DISABLE); 333 | 334 | volatile VALUE rbScreen = 335 | rb_class_new_instance(2, (VALUE[]){INT2NUM(width), INT2NUM(height)}, 336 | strb_GetTextureClass()); 337 | game->screen = rbScreen; 338 | 339 | InitializeScreen(game); 340 | 341 | rb_iv_set(rb_cGame, "current", self); 342 | 343 | return Qnil; 344 | } 345 | 346 | static VALUE 347 | Game_dispose(VALUE self) 348 | { 349 | Game* game; 350 | Data_Get_Struct(self, Game, game); 351 | DATA_PTR(self) = NULL; 352 | if (game) { 353 | volatile VALUE rbScreen = game->screen; 354 | if (!NIL_P(rbScreen)) { 355 | rb_funcall(rbScreen, rb_intern("dispose"), 0); 356 | game->screen = Qnil; 357 | } 358 | if (game->glScreen) { 359 | glDeleteTextures(1, &game->glScreen); 360 | game->glScreen = 0; 361 | } 362 | } 363 | SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER); 364 | rb_iv_set(rb_cGame, "current", Qnil); 365 | return Qnil; 366 | } 367 | 368 | static VALUE 369 | Game_disposed(VALUE self) 370 | { 371 | const Game* game; 372 | Data_Get_Struct(self, Game, game); 373 | return !game ? Qtrue : Qfalse; 374 | } 375 | 376 | static VALUE 377 | Game_fps(VALUE self) 378 | { 379 | const Game* game; 380 | Data_Get_Struct(self, Game, game); 381 | CheckDisposed(game); 382 | return INT2NUM(game->fps); 383 | } 384 | 385 | static VALUE 386 | Game_fps_eq(VALUE self, VALUE rbFps) 387 | { 388 | Game* game; 389 | Data_Get_Struct(self, Game, game); 390 | CheckDisposed(game); 391 | game->fps = NUM2INT(rbFps); 392 | return rbFps; 393 | } 394 | 395 | static VALUE 396 | Game_fullscreen(VALUE self) 397 | { 398 | Game* game; 399 | Data_Get_Struct(self, Game, game); 400 | CheckDisposed(game); 401 | return game->isFullscreen ? Qtrue : Qfalse; 402 | } 403 | 404 | static VALUE 405 | Game_fullscreen_eq(VALUE self, VALUE rbFullscreen) 406 | { 407 | Game* game; 408 | Data_Get_Struct(self, Game, game); 409 | CheckDisposed(game); 410 | game->isFullscreen = RTEST(rbFullscreen); 411 | InitializeScreen(game); 412 | return Qnil; 413 | } 414 | 415 | static VALUE 416 | Game_real_fps(VALUE self) 417 | { 418 | const Game* game; 419 | Data_Get_Struct(self, Game, game); 420 | CheckDisposed(game); 421 | return rb_float_new(game->realFps); 422 | } 423 | 424 | static VALUE 425 | Game_screen(VALUE self) 426 | { 427 | const Game* game; 428 | Data_Get_Struct(self, Game, game); 429 | CheckDisposed(game); 430 | return game->screen; 431 | } 432 | 433 | static VALUE 434 | Game_title(VALUE self) 435 | { 436 | const Game* game; 437 | Data_Get_Struct(self, Game, game); 438 | CheckDisposed(game); 439 | return rb_iv_get(self, "title"); 440 | } 441 | 442 | static VALUE 443 | Game_title_eq(VALUE self, VALUE rbTitle) 444 | { 445 | Game* game; 446 | Data_Get_Struct(self, Game, game); 447 | CheckDisposed(game); 448 | Check_Type(rbTitle, T_STRING); 449 | if (SDL_WasInit(SDL_INIT_VIDEO)) { 450 | SDL_WM_SetCaption(StringValueCStr(rbTitle), NULL); 451 | } 452 | return rb_iv_set(self, "title", rb_str_dup(rbTitle)); 453 | } 454 | 455 | static VALUE 456 | Game_update_screen(VALUE self) 457 | { 458 | const Game* game; 459 | Data_Get_Struct(self, Game, game); 460 | CheckDisposed(game); 461 | 462 | volatile VALUE rbScreen = game->screen; 463 | const Texture* texture; 464 | Data_Get_Struct(rbScreen, Texture, texture); 465 | strb_CheckDisposedTexture(texture); 466 | const Pixel* src = texture->pixels; 467 | SDL_Surface* sdlScreenBuffer = game->sdlScreenBuffer; 468 | SDL_LockSurface(sdlScreenBuffer); 469 | Pixel* dst = (Pixel*)sdlScreenBuffer->pixels; 470 | const int screenPadding = 471 | sdlScreenBuffer->pitch / sdlScreenBuffer->format->BytesPerPixel - sdlScreenBuffer->w; 472 | const int textureWidth = texture->width; 473 | const int textureHeight = texture->height; 474 | const int heightPadding = sdlScreenBuffer->w - texture->width + screenPadding; 475 | for (int j = 0; j < textureHeight; j++, dst += heightPadding) { 476 | for (int i = 0; i < textureWidth; i++, src++, dst++) { 477 | const uint8_t alpha = src->color.alpha; 478 | if (alpha == 255) { 479 | *dst = *src; 480 | } else if (alpha) { 481 | dst->color.red = DIV255(src->color.red * alpha); 482 | dst->color.green = DIV255(src->color.green * alpha); 483 | dst->color.blue = DIV255(src->color.blue * alpha); 484 | } else { 485 | dst->color.red = 0; 486 | dst->color.green = 0; 487 | dst->color.blue = 0; 488 | } 489 | } 490 | } 491 | 492 | SDL_UnlockSurface(sdlScreenBuffer); 493 | 494 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 495 | sdlScreenBuffer->w, sdlScreenBuffer->h, 496 | 0, GL_BGRA, GL_UNSIGNED_BYTE, sdlScreenBuffer->pixels); 497 | glClear(GL_COLOR_BUFFER_BIT); 498 | glColor3f(1.0, 1.0, 1.0); 499 | glBegin(GL_QUADS); 500 | { 501 | int x1, y1, x2, y2; 502 | if (!game->isFullscreen) { 503 | x1 = 0; 504 | y1 = 0; 505 | x2 = game->sdlScreen->w; 506 | y2 = game->sdlScreen->h; 507 | } else { 508 | x1 = (game->sdlScreen->w - textureWidth) / 2; 509 | y1 = (game->sdlScreen->h - textureHeight) / 2; 510 | x2 = x1 + textureWidth; 511 | y2 = y1 + textureHeight; 512 | } 513 | const double tu = (double)textureWidth / sdlScreenBuffer->w; 514 | const double tv = (double)textureHeight / sdlScreenBuffer->h; 515 | glTexCoord2f(0.0, 0.0); 516 | glVertex3i(x1, y1, 0); 517 | glTexCoord2f(tu, 0.0); 518 | glVertex3i(x2, y1, 0); 519 | glTexCoord2f(tu, tv); 520 | glVertex3i(x2, y2, 0); 521 | glTexCoord2f(0.0, tv); 522 | glVertex3i(x1, y2, 0); 523 | } 524 | glEnd(); 525 | 526 | SDL_GL_SwapBuffers(); 527 | return Qnil; 528 | } 529 | 530 | static VALUE 531 | Game_update_state(VALUE self) 532 | { 533 | Game* game; 534 | Data_Get_Struct(self, Game, game); 535 | CheckDisposed(game); 536 | SDL_Event event; 537 | game->isWindowClosing = (SDL_PollEvent(&event) && event.type == SDL_QUIT); 538 | strb_UpdateInput(); 539 | return Qnil; 540 | } 541 | 542 | static VALUE 543 | Game_wait(VALUE self) 544 | { 545 | Game* game; 546 | Data_Get_Struct(self, Game, game); 547 | CheckDisposed(game); 548 | GameTimer* gameTimer = &(game->timer); 549 | const unsigned int fps = game->fps; 550 | Uint32 now; 551 | while (true) { 552 | now = SDL_GetTicks(); 553 | Uint32 diff = (now - gameTimer->before) * fps + gameTimer->error; 554 | if (1000 <= diff) { 555 | gameTimer->error = MIN(diff - 1000, 1000); 556 | gameTimer->before = now; 557 | break; 558 | } 559 | SDL_Delay(1); 560 | } 561 | gameTimer->counter++; 562 | if (1000 <= now - gameTimer->before2) { 563 | game->realFps = gameTimer->counter * 1000.0 / 564 | (now - gameTimer->before2); 565 | gameTimer->counter = 0; 566 | gameTimer->before2 = SDL_GetTicks(); 567 | } 568 | return Qnil; 569 | } 570 | 571 | static VALUE 572 | Game_window_closing(VALUE self) 573 | { 574 | const Game* game; 575 | Data_Get_Struct(self, Game, game); 576 | CheckDisposed(game); 577 | return game->isWindowClosing ? Qtrue : Qfalse; 578 | } 579 | 580 | static VALUE 581 | Game_window_scale(VALUE self) 582 | { 583 | const Game* game; 584 | Data_Get_Struct(self, Game, game); 585 | CheckDisposed(game); 586 | return INT2FIX(game->windowScale); 587 | } 588 | 589 | static VALUE 590 | Game_window_scale_eq(VALUE self, VALUE rbWindowScale) 591 | { 592 | Game* game; 593 | Data_Get_Struct(self, Game, game); 594 | CheckDisposed(game); 595 | game->windowScale = NUM2INT(rbWindowScale); 596 | InitializeScreen(game); 597 | return Qnil; 598 | } 599 | 600 | VALUE 601 | strb_InitializeGame(VALUE _rb_mStarRuby) 602 | { 603 | rb_mStarRuby = _rb_mStarRuby; 604 | 605 | rb_cGame = rb_define_class_under(rb_mStarRuby, "Game", rb_cObject); 606 | rb_define_singleton_method(rb_cGame, "current", Game_s_current, 0); 607 | rb_define_singleton_method(rb_cGame, "run", Game_s_run, -1); 608 | rb_define_singleton_method(rb_cGame, "ticks", Game_s_ticks, 0); 609 | rb_define_alloc_func(rb_cGame, Game_alloc); 610 | rb_define_private_method(rb_cGame, "initialize", Game_initialize, -1); 611 | rb_define_method(rb_cGame, "dispose", Game_dispose, 0); 612 | rb_define_method(rb_cGame, "disposed?", Game_disposed, 0); 613 | rb_define_method(rb_cGame, "fps", Game_fps, 0); 614 | rb_define_method(rb_cGame, "fps=", Game_fps_eq, 1); 615 | rb_define_method(rb_cGame, "fullscreen?", Game_fullscreen, 0); 616 | rb_define_method(rb_cGame, "fullscreen=", Game_fullscreen_eq, 1); 617 | rb_define_method(rb_cGame, "real_fps", Game_real_fps, 0); 618 | rb_define_method(rb_cGame, "screen", Game_screen, 0); 619 | rb_define_method(rb_cGame, "title", Game_title, 0); 620 | rb_define_method(rb_cGame, "title=", Game_title_eq, 1); 621 | rb_define_method(rb_cGame, "update_screen", Game_update_screen, 0); 622 | rb_define_method(rb_cGame, "update_state", Game_update_state, 0); 623 | rb_define_method(rb_cGame, "wait", Game_wait, 0); 624 | rb_define_method(rb_cGame, "window_closing?", Game_window_closing, 0); 625 | rb_define_method(rb_cGame, "window_scale", Game_window_scale, 0); 626 | rb_define_method(rb_cGame, "window_scale=", Game_window_scale_eq, 1); 627 | 628 | symbol_cursor = ID2SYM(rb_intern("cursor")); 629 | symbol_fps = ID2SYM(rb_intern("fps")); 630 | symbol_fullscreen = ID2SYM(rb_intern("fullscreen")); 631 | symbol_title = ID2SYM(rb_intern("title")); 632 | symbol_vsync = ID2SYM(rb_intern("vsync")); 633 | symbol_window_scale = ID2SYM(rb_intern("window_scale")); 634 | 635 | return rb_cGame; 636 | } 637 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include "starruby_private.h" 2 | 3 | typedef struct KeyboardKey { 4 | VALUE rbSymbol; 5 | SDLKey sdlKey; 6 | int state; 7 | struct KeyboardKey* next; 8 | } KeyboardKey; 9 | static KeyboardKey* keyboardKeys; 10 | 11 | static int gamepadCount; 12 | 13 | typedef struct { 14 | SDL_Joystick* sdlJoystick; 15 | int downState; 16 | int leftState; 17 | int rightState; 18 | int upState; 19 | int buttonCount; 20 | int* buttonStates; 21 | } Gamepad; 22 | static Gamepad* gamepads; 23 | 24 | typedef struct { 25 | int leftState; 26 | int middleState; 27 | int rightState; 28 | } Mouse; 29 | static Mouse* mouse; 30 | 31 | static volatile VALUE rb_mInput = Qundef; 32 | 33 | static volatile VALUE symbol_delay = Qundef; 34 | static volatile VALUE symbol_device_number = Qundef; 35 | static volatile VALUE symbol_down = Qundef; 36 | static volatile VALUE symbol_duration = Qundef; 37 | static volatile VALUE symbol_gamepad = Qundef; 38 | static volatile VALUE symbol_interval = Qundef; 39 | static volatile VALUE symbol_keyboard = Qundef; 40 | static volatile VALUE symbol_left = Qundef; 41 | static volatile VALUE symbol_middle = Qundef; 42 | static volatile VALUE symbol_mouse = Qundef; 43 | static volatile VALUE symbol_right = Qundef; 44 | static volatile VALUE symbol_up = Qundef; 45 | 46 | static VALUE 47 | Input_gamepad_count(VALUE self) 48 | { 49 | return INT2FIX(gamepadCount); 50 | } 51 | 52 | static VALUE 53 | Input_mouse_location(VALUE self) 54 | { 55 | return rb_iv_get(self, "mouse_location"); 56 | } 57 | 58 | static VALUE 59 | Input_mouse_location_eq(VALUE self, VALUE rbValue) 60 | { 61 | Check_Type(rbValue, T_ARRAY); 62 | if (RARRAY_LEN(rbValue) != 2) { 63 | rb_raise(rb_eArgError, "array size should be 2, %ld given", RARRAY_LEN(rbValue)); 64 | } 65 | const int windowScale = strb_GetWindowScale(); 66 | // TODO: Fix it for fullscreen 67 | SDL_WarpMouse(NUM2INT(RARRAY_PTR(rbValue)[0]) * windowScale, 68 | NUM2INT(RARRAY_PTR(rbValue)[1]) * windowScale); 69 | int mouseLocationX, mouseLocationY; 70 | SDL_GetMouseState(&mouseLocationX, &mouseLocationY); 71 | int screenWidth = 0, screenHeight = 0; 72 | strb_GetScreenSize(&screenWidth, &screenHeight); 73 | int realScreenWidth = 0, realScreenHeight = 0; 74 | strb_GetRealScreenSize(&realScreenWidth, &realScreenHeight); 75 | mouseLocationX -= (realScreenWidth - screenWidth * windowScale) / 2; 76 | mouseLocationY -= (realScreenHeight - screenHeight * windowScale) / 2; 77 | volatile VALUE rbMouseLocation = 78 | rb_assoc_new(INT2NUM(mouseLocationX / windowScale), 79 | INT2NUM(mouseLocationY / windowScale)); 80 | OBJ_FREEZE(rbMouseLocation); 81 | rb_iv_set(self, "mouse_location", rbMouseLocation); 82 | return rbValue; 83 | } 84 | 85 | static bool 86 | IsPressed(const int status, const int duration, const int delay, const int interval) 87 | { 88 | /* 89 | * on: ------------ - - ... 90 | * off: --------- ------------ ------------ ... 91 | * <-duration-><-delay-> <-interval-> <-interval-> ... 92 | */ 93 | if (status <= 0 || duration == 0) { 94 | return false; 95 | } 96 | if (duration < 0) { 97 | return true; 98 | } 99 | if (status <= duration) { 100 | return true; 101 | } 102 | if (delay < 0) { 103 | return false; 104 | } 105 | if (status <= duration + delay) { 106 | return false; 107 | } 108 | if (0 <= interval) { 109 | return (status - (duration + delay + 1)) % (interval + 1) == 0; 110 | } 111 | return false; 112 | } 113 | 114 | static VALUE 115 | Input_keys(int argc, VALUE* argv, VALUE self) 116 | { 117 | volatile VALUE rbDevice, rbOptions; 118 | rb_scan_args(argc, argv, "11", &rbDevice, &rbOptions); 119 | Check_Type(rbDevice, T_SYMBOL); 120 | if (NIL_P(rbOptions)) { 121 | rbOptions = rb_hash_new(); 122 | } 123 | volatile VALUE rbResult = rb_ary_new(); 124 | 125 | int deviceNumber = 0; 126 | int duration = -1; 127 | int delay = -1; 128 | int interval = 0; 129 | 130 | volatile VALUE val; 131 | Check_Type(rbOptions, T_HASH); 132 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_device_number))) { 133 | deviceNumber = NUM2INT(val); 134 | } 135 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_duration))) { 136 | duration = NUM2INT(val); 137 | } 138 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_delay))) { 139 | delay = NUM2INT(val); 140 | } 141 | if (!NIL_P(val = rb_hash_aref(rbOptions, symbol_interval))) { 142 | interval = NUM2INT(val); 143 | } 144 | if (rbDevice == symbol_keyboard) { 145 | KeyboardKey* key = keyboardKeys; 146 | while (key) { 147 | if (IsPressed(key->state, duration, delay, interval)) { 148 | rb_ary_push(rbResult, key->rbSymbol); 149 | } 150 | key = key->next; 151 | } 152 | } else if (rbDevice == symbol_gamepad) { 153 | if (0 <= deviceNumber && deviceNumber < gamepadCount) { 154 | Gamepad* gamepad = &(gamepads[deviceNumber]); 155 | if (IsPressed(gamepad->downState, duration, delay, interval)) { 156 | rb_ary_push(rbResult, symbol_down); 157 | } 158 | if (IsPressed(gamepad->leftState, duration, delay, interval)) { 159 | rb_ary_push(rbResult, symbol_left); 160 | } 161 | if (IsPressed(gamepad->rightState, duration, delay, interval)) { 162 | rb_ary_push(rbResult, symbol_right); 163 | } 164 | if (IsPressed(gamepad->upState, duration, delay, interval)) { 165 | rb_ary_push(rbResult, symbol_up); 166 | } 167 | for (int i = 0; i < gamepad->buttonCount; i++) { 168 | if (IsPressed(gamepad->buttonStates[i], duration, delay, interval)) { 169 | rb_ary_push(rbResult, INT2FIX(i + 1)); 170 | } 171 | } 172 | } 173 | } else if (rbDevice == symbol_mouse) { 174 | if (IsPressed(mouse->leftState, duration, delay, interval)) { 175 | rb_ary_push(rbResult, symbol_left); 176 | } 177 | if (IsPressed(mouse->middleState, duration, delay, interval)) { 178 | rb_ary_push(rbResult, symbol_middle); 179 | } 180 | if (IsPressed(mouse->rightState, duration, delay, interval)) { 181 | rb_ary_push(rbResult, symbol_right); 182 | } 183 | } else { 184 | volatile VALUE rbDeviceInspect = 185 | rb_funcall(rbDevice, rb_intern("inspect"), 0); 186 | rb_raise(rb_eArgError, "invalid device: %s", StringValueCStr(rbDeviceInspect)); 187 | } 188 | 189 | OBJ_FREEZE(rbResult); 190 | return rbResult; 191 | } 192 | 193 | #define ADD_KEY(currentKey, _name, _sdlKey) \ 194 | do { \ 195 | KeyboardKey* key = ALLOC(KeyboardKey); \ 196 | key->rbSymbol = ID2SYM(rb_intern(_name)); \ 197 | key->sdlKey = _sdlKey; \ 198 | key->state = 0; \ 199 | key->next = NULL; \ 200 | currentKey->next = key; \ 201 | currentKey = key; \ 202 | } while (false) 203 | 204 | void 205 | strb_UpdateInput(void) 206 | { 207 | SDL_PumpEvents(); 208 | SDL_JoystickUpdate(); 209 | 210 | const Uint8* sdlKeyState = SDL_GetKeyState(NULL); 211 | KeyboardKey* key = keyboardKeys; 212 | while (key) { 213 | if (sdlKeyState[key->sdlKey]) { 214 | key->state++; 215 | } else { 216 | key->state = 0; 217 | } 218 | key = key->next; 219 | } 220 | 221 | for (int i = 0; i < gamepadCount; i++) { 222 | Gamepad* gamepad = &(gamepads[i]); 223 | if (SDL_JoystickGetAxis(gamepad->sdlJoystick, 1) > 3200) { 224 | gamepad->downState++; 225 | } else { 226 | gamepad->downState = 0; 227 | } 228 | if (SDL_JoystickGetAxis(gamepad->sdlJoystick, 0) < -3200) { 229 | gamepad->leftState++; 230 | } else { 231 | gamepad->leftState = 0; 232 | } 233 | if (SDL_JoystickGetAxis(gamepad->sdlJoystick, 0) > 3200) { 234 | gamepad->rightState++; 235 | } else { 236 | gamepad->rightState = 0; 237 | } 238 | if (SDL_JoystickGetAxis(gamepad->sdlJoystick, 1) < -3200) { 239 | gamepad->upState++; 240 | } else { 241 | gamepad->upState = 0; 242 | } 243 | for (int j = 0; j < gamepad->buttonCount; j++) { 244 | if (SDL_JoystickGetButton(gamepad->sdlJoystick, j) == SDL_PRESSED) { 245 | gamepad->buttonStates[j]++; 246 | } else { 247 | gamepad->buttonStates[j] = 0; 248 | } 249 | } 250 | } 251 | 252 | const int windowScale = strb_GetWindowScale(); 253 | int mouseLocationX, mouseLocationY; 254 | const Uint8 sdlMouseButtons = 255 | SDL_GetMouseState(&mouseLocationX, &mouseLocationY); 256 | int screenWidth = 0, screenHeight = 0; 257 | strb_GetScreenSize(&screenWidth, &screenHeight); 258 | int realScreenWidth = 0, realScreenHeight = 0; 259 | strb_GetRealScreenSize(&realScreenWidth, &realScreenHeight); 260 | mouseLocationX -= (realScreenWidth - screenWidth * windowScale) / 2; 261 | mouseLocationY -= (realScreenHeight - screenHeight * windowScale) / 2; 262 | volatile VALUE rbMouseLocation = 263 | rb_assoc_new(INT2NUM(mouseLocationX / windowScale), 264 | INT2NUM(mouseLocationY / windowScale)); 265 | OBJ_FREEZE(rbMouseLocation); 266 | rb_iv_set(rb_mInput, "mouse_location", rbMouseLocation); 267 | 268 | if (sdlMouseButtons & SDL_BUTTON(SDL_BUTTON_LEFT)) { 269 | mouse->leftState++; 270 | } else { 271 | mouse->leftState = 0; 272 | } 273 | if (sdlMouseButtons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) { 274 | mouse->middleState++; 275 | } else { 276 | mouse->middleState = 0; 277 | } 278 | if (sdlMouseButtons & SDL_BUTTON(SDL_BUTTON_RIGHT)) { 279 | mouse->rightState++; 280 | } else { 281 | mouse->rightState = 0; 282 | } 283 | } 284 | 285 | void 286 | strb_InitializeSdlInput() 287 | { 288 | keyboardKeys = ALLOC(KeyboardKey); 289 | keyboardKeys->rbSymbol = Qundef; 290 | keyboardKeys->sdlKey = 0; 291 | keyboardKeys->state = 0; 292 | keyboardKeys->next = NULL; 293 | 294 | KeyboardKey* currentKey = keyboardKeys; 295 | for (int i = 0; i < SDLK_z - SDLK_a + 1; i++) { 296 | ADD_KEY(currentKey, ((char[]){'a' + i, '\0'}), SDLK_a + i); 297 | } 298 | for (int i = 0; i <= 9; i++) { 299 | ADD_KEY(currentKey, ((char[]){'d', '0' + i, '\0'}), SDLK_0 + i); 300 | } 301 | for (int i = 0; i < 15; i++) { 302 | char name[4]; 303 | snprintf(name, sizeof(name), "f%d", i + 1); 304 | ADD_KEY(currentKey, name, SDLK_F1 + i); 305 | } 306 | for (int i = 0; i <= 9; i++) { 307 | ADD_KEY(currentKey, ((char[]){'n','u','m','p','a','d', '0' + i, '\0'}), 308 | SDLK_KP0 + i); 309 | } 310 | char* names[] = { 311 | "add", 312 | "back", "backslash", "backquotes", 313 | "capslock", "clear", "closebrackets", "comma", 314 | "decimal", "delete", "divide", "down", 315 | "end", "enter", "escape", "equals", 316 | "help", "home", 317 | "insert", 318 | "lcontrolkey", "left", "lmenu", "lshiftkey", 319 | "minus", "multiply", 320 | "numlock", 321 | "openbrackets", 322 | "pagedown", "pageup", "period", 323 | "quotes", 324 | "rcontrolkey", "right", "rmenu", "rshiftkey", 325 | "scroll", "semicolon", "separator", "slash", "space", "subtract", 326 | "tab", 327 | "up", 328 | NULL, 329 | }; 330 | SDLKey sdlKeys[] = { 331 | SDLK_KP_PLUS, 332 | SDLK_BACKSPACE, SDLK_BACKSLASH, SDLK_BACKQUOTE, 333 | SDLK_CAPSLOCK, SDLK_CLEAR, SDLK_RIGHTBRACKET, SDLK_COMMA, 334 | SDLK_KP_PERIOD, SDLK_DELETE, SDLK_KP_DIVIDE, SDLK_DOWN, 335 | SDLK_END, SDLK_RETURN, SDLK_ESCAPE, SDLK_EQUALS, 336 | SDLK_HELP, SDLK_HOME, 337 | SDLK_INSERT, 338 | SDLK_LCTRL, SDLK_LEFT, SDLK_LALT, SDLK_LSHIFT, 339 | SDLK_MINUS, SDLK_KP_MULTIPLY, 340 | SDLK_NUMLOCK, 341 | SDLK_LEFTBRACKET, 342 | SDLK_PAGEDOWN, SDLK_PAGEUP, SDLK_PERIOD, 343 | SDLK_QUOTE, 344 | SDLK_RCTRL, SDLK_RIGHT, SDLK_RALT, SDLK_RSHIFT, 345 | SDLK_SCROLLOCK, SDLK_SEMICOLON, SDLK_KP_ENTER, SDLK_SLASH, SDLK_SPACE, SDLK_KP_MINUS, 346 | SDLK_TAB, 347 | SDLK_UP, 348 | -1, 349 | }; 350 | SDLKey* sdlKey; 351 | char** name; 352 | for (sdlKey = sdlKeys, name = names; 353 | *name; 354 | name++, sdlKey++) { 355 | ADD_KEY(currentKey, *name, *sdlKey); 356 | } 357 | SDL_JoystickEventState(SDL_ENABLE); 358 | gamepadCount = SDL_NumJoysticks(); 359 | gamepads = ALLOC_N(Gamepad, gamepadCount); 360 | MEMZERO(gamepads, Gamepad, gamepadCount); 361 | for (int i = 0; i < gamepadCount; i++) { 362 | SDL_Joystick* ptr = gamepads[i].sdlJoystick = SDL_JoystickOpen(i); 363 | if (ptr == NULL) { 364 | rb_raise_sdl_error(); 365 | } 366 | const int buttonCount = gamepads[i].buttonCount = SDL_JoystickNumButtons(ptr); 367 | gamepads[i].buttonStates = ALLOC_N(int, buttonCount); 368 | MEMZERO(gamepads[i].buttonStates, int, buttonCount); 369 | } 370 | 371 | mouse = ALLOC(Mouse); 372 | MEMZERO(mouse, Mouse, 1); 373 | } 374 | 375 | VALUE 376 | strb_InitializeInput(VALUE rb_mStarRuby) 377 | { 378 | rb_mInput = rb_define_module_under(rb_mStarRuby, "Input"); 379 | rb_define_module_function(rb_mInput, "gamepad_count", 380 | Input_gamepad_count, 0); 381 | rb_define_module_function(rb_mInput, "mouse_location", 382 | Input_mouse_location, 0); 383 | rb_define_module_function(rb_mInput, "mouse_location=", 384 | Input_mouse_location_eq, 1); 385 | rb_define_module_function(rb_mInput, "keys", Input_keys, -1); 386 | 387 | symbol_delay = ID2SYM(rb_intern("delay")); 388 | symbol_device_number = ID2SYM(rb_intern("device_number")); 389 | symbol_down = ID2SYM(rb_intern("down")); 390 | symbol_duration = ID2SYM(rb_intern("duration")); 391 | symbol_gamepad = ID2SYM(rb_intern("gamepad")); 392 | symbol_interval = ID2SYM(rb_intern("interval")); 393 | symbol_keyboard = ID2SYM(rb_intern("keyboard")); 394 | symbol_left = ID2SYM(rb_intern("left")); 395 | symbol_middle = ID2SYM(rb_intern("middle")); 396 | symbol_mouse = ID2SYM(rb_intern("mouse")); 397 | symbol_right = ID2SYM(rb_intern("right")); 398 | symbol_up = ID2SYM(rb_intern("up")); 399 | 400 | volatile VALUE rbMouseLocation = rb_assoc_new(INT2FIX(0), INT2FIX(0)); 401 | OBJ_FREEZE(rbMouseLocation); 402 | rb_iv_set(rb_mInput, "mouse_location", rbMouseLocation); 403 | 404 | return rb_mInput; 405 | } 406 | 407 | void 408 | strb_FinalizeInput(void) 409 | { 410 | free(mouse); 411 | mouse = NULL; 412 | 413 | for (int i = 0; i < gamepadCount; i++) { 414 | Gamepad* gamepad = &(gamepads[i]); 415 | if (SDL_JoystickOpened(i)) { 416 | SDL_JoystickClose(gamepad->sdlJoystick); 417 | } 418 | gamepad->sdlJoystick = NULL; 419 | free(gamepad->buttonStates); 420 | gamepad->buttonStates = NULL; 421 | } 422 | free(gamepads); 423 | gamepads = NULL; 424 | 425 | KeyboardKey* key = keyboardKeys; 426 | while (key) { 427 | KeyboardKey* nextKey = key->next; 428 | free(key); 429 | key = nextKey; 430 | } 431 | keyboardKeys = NULL; 432 | } 433 | 434 | #ifdef DEBUG 435 | static KeyboardKey* 436 | searchKey(const char* name) 437 | { 438 | volatile VALUE rbNameSymbol = ID2SYM(rb_intern(name)); 439 | KeyboardKey* key = keyboardKeys; 440 | while (key) { 441 | if (key->rbSymbol == rbNameSymbol) { 442 | return key; 443 | } 444 | key = key->next; 445 | } 446 | return NULL; 447 | } 448 | 449 | void 450 | strb_TestInput(void) 451 | { 452 | printf("Begin Test: Input\n"); 453 | 454 | assert(SDLK_a == searchKey("a")->sdlKey); 455 | assert(SDLK_b == searchKey("b")->sdlKey); 456 | assert(SDLK_c == searchKey("c")->sdlKey); 457 | assert(SDLK_e == searchKey("e")->sdlKey); 458 | assert(SDLK_i == searchKey("i")->sdlKey); 459 | assert(SDLK_q == searchKey("q")->sdlKey); 460 | assert(SDLK_z == searchKey("z")->sdlKey); 461 | 462 | assert(SDLK_0 == searchKey("d0")->sdlKey); 463 | assert(SDLK_1 == searchKey("d1")->sdlKey); 464 | assert(SDLK_2 == searchKey("d2")->sdlKey); 465 | assert(SDLK_4 == searchKey("d4")->sdlKey); 466 | assert(SDLK_8 == searchKey("d8")->sdlKey); 467 | assert(SDLK_9 == searchKey("d9")->sdlKey); 468 | 469 | assert(SDLK_F1 == searchKey("f1")->sdlKey); 470 | assert(SDLK_F2 == searchKey("f2")->sdlKey); 471 | assert(SDLK_F4 == searchKey("f4")->sdlKey); 472 | assert(SDLK_F8 == searchKey("f8")->sdlKey); 473 | assert(SDLK_F15 == searchKey("f15")->sdlKey); 474 | 475 | assert(SDLK_KP0 == searchKey("numpad0")->sdlKey); 476 | assert(SDLK_KP1 == searchKey("numpad1")->sdlKey); 477 | assert(SDLK_KP2 == searchKey("numpad2")->sdlKey); 478 | assert(SDLK_KP4 == searchKey("numpad4")->sdlKey); 479 | assert(SDLK_KP8 == searchKey("numpad8")->sdlKey); 480 | assert(SDLK_KP9 == searchKey("numpad9")->sdlKey); 481 | 482 | assert(SDLK_DOWN == searchKey("down")->sdlKey); 483 | assert(SDLK_LEFT == searchKey("left")->sdlKey); 484 | assert(SDLK_RIGHT == searchKey("right")->sdlKey); 485 | assert(SDLK_UP == searchKey("up")->sdlKey); 486 | 487 | for (KeyboardKey* key = keyboardKeys; key; key = key->next) { 488 | assert(0 == key->state); 489 | } 490 | 491 | assert(false == IsPressed(0, -1, -1, 0)); 492 | assert(true == IsPressed(1, -1, -1, 0)); 493 | assert(true == IsPressed(2, -1, -1, 0)); 494 | assert(true == IsPressed(3, -1, -1, 0)); 495 | assert(true == IsPressed(4, -1, -1, 0)); 496 | assert(true == IsPressed(5, -1, -1, 0)); 497 | assert(true == IsPressed(100, -1, -1, 0)); 498 | 499 | assert(false == IsPressed(0, 0, -1, 0)); 500 | assert(false == IsPressed(1, 0, -1, 0)); 501 | assert(false == IsPressed(2, 0, -1, 0)); 502 | assert(false == IsPressed(3, 0, -1, 0)); 503 | assert(false == IsPressed(4, 0, -1, 0)); 504 | assert(false == IsPressed(5, 0, -1, 0)); 505 | assert(false == IsPressed(100, 0, -1, 0)); 506 | 507 | assert(false == IsPressed(0, 1, -1, 0)); 508 | assert(true == IsPressed(1, 1, -1, 0)); 509 | assert(false == IsPressed(2, 1, -1, 0)); 510 | assert(false == IsPressed(3, 1, -1, 0)); 511 | assert(false == IsPressed(4, 1, -1, 0)); 512 | assert(false == IsPressed(5, 1, -1, 0)); 513 | assert(false == IsPressed(100, 1, -1, 0)); 514 | 515 | assert(false == IsPressed(0, 3, -1, 0)); 516 | assert(true == IsPressed(1, 3, -1, 0)); 517 | assert(true == IsPressed(2, 3, -1, 0)); 518 | assert(true == IsPressed(3, 3, -1, 0)); 519 | assert(false == IsPressed(4, 3, -1, 0)); 520 | assert(false == IsPressed(5, 3, -1, 0)); 521 | assert(false == IsPressed(100, 3, -1, 0)); 522 | 523 | // with delay 524 | assert(false == IsPressed(0, -1, 3, 0)); 525 | assert(true == IsPressed(1, -1, 3, 0)); 526 | assert(true == IsPressed(2, -1, 3, 0)); 527 | assert(true == IsPressed(3, -1, 3, 0)); 528 | assert(true == IsPressed(4, -1, 3, 0)); 529 | assert(true == IsPressed(5, -1, 3, 0)); 530 | assert(true == IsPressed(100, -1, 3, 0)); 531 | 532 | assert(false == IsPressed(0, 0, 3, 0)); 533 | assert(false == IsPressed(1, 0, 3, 0)); 534 | assert(false == IsPressed(2, 0, 3, 0)); 535 | assert(false == IsPressed(3, 0, 3, 0)); 536 | assert(false == IsPressed(4, 0, 3, 0)); 537 | assert(false == IsPressed(5, 0, 3, 0)); 538 | assert(false == IsPressed(100, 0, 3, 0)); 539 | 540 | assert(false == IsPressed(0, 1, 3, 0)); 541 | assert(true == IsPressed(1, 1, 3, 0)); 542 | assert(false == IsPressed(2, 1, 3, 0)); 543 | assert(false == IsPressed(3, 1, 3, 0)); 544 | assert(false == IsPressed(4, 1, 3, 0)); 545 | assert(true == IsPressed(5, 1, 3, 0)); 546 | assert(true == IsPressed(6, 1, 3, 0)); 547 | assert(true == IsPressed(7, 1, 3, 0)); 548 | assert(true == IsPressed(8, 1, 3, 0)); 549 | assert(true == IsPressed(100, 1, 3, 0)); 550 | 551 | assert(false == IsPressed(0, 3, 3, 0)); 552 | assert(true == IsPressed(1, 3, 3, 0)); 553 | assert(true == IsPressed(2, 3, 3, 0)); 554 | assert(true == IsPressed(3, 3, 3, 0)); 555 | assert(false == IsPressed(4, 3, 3, 0)); 556 | assert(false == IsPressed(5, 3, 3, 0)); 557 | assert(false == IsPressed(6, 3, 3, 0)); 558 | assert(true == IsPressed(7, 3, 3, 0)); 559 | assert(true == IsPressed(8, 3, 3, 0)); 560 | assert(true == IsPressed(100, 3, 3, 0)); 561 | 562 | // with delay and interval 563 | assert(false == IsPressed(0, -1, 3, 1)); 564 | assert(true == IsPressed(1, -1, 3, 1)); 565 | assert(true == IsPressed(2, -1, 3, 1)); 566 | assert(true == IsPressed(3, -1, 3, 1)); 567 | assert(true == IsPressed(4, -1, 3, 1)); 568 | assert(true == IsPressed(5, -1, 3, 1)); 569 | assert(true == IsPressed(100, -1, 3, 1)); 570 | 571 | assert(false == IsPressed(0, 0, 3, 1)); 572 | assert(false == IsPressed(1, 0, 3, 1)); 573 | assert(false == IsPressed(2, 0, 3, 1)); 574 | assert(false == IsPressed(3, 0, 3, 1)); 575 | assert(false == IsPressed(4, 0, 3, 1)); 576 | assert(false == IsPressed(5, 0, 3, 1)); 577 | assert(false == IsPressed(100, 0, 3, 1)); 578 | 579 | assert(false == IsPressed(0, 1, 3, 1)); 580 | assert(true == IsPressed(1, 1, 3, 1)); 581 | assert(false == IsPressed(2, 1, 3, 1)); 582 | assert(false == IsPressed(3, 1, 3, 1)); 583 | assert(false == IsPressed(4, 1, 3, 1)); 584 | assert(true == IsPressed(5, 1, 3, 1)); 585 | assert(false == IsPressed(6, 1, 3, 1)); 586 | assert(true == IsPressed(7, 1, 3, 1)); 587 | assert(false == IsPressed(8, 1, 3, 1)); 588 | assert(true == IsPressed(9, 1, 3, 1)); 589 | assert(false == IsPressed(10, 1, 3, 1)); 590 | assert(true == IsPressed(11, 1, 3, 1)); 591 | assert(false == IsPressed(12, 1, 3, 1)); 592 | assert(false == IsPressed(100, 1, 3, 1)); 593 | 594 | assert(false == IsPressed(0, 3, 3, 1)); 595 | assert(true == IsPressed(1, 3, 3, 1)); 596 | assert(true == IsPressed(2, 3, 3, 1)); 597 | assert(true == IsPressed(3, 3, 3, 1)); 598 | assert(false == IsPressed(4, 3, 3, 1)); 599 | assert(false == IsPressed(5, 3, 3, 1)); 600 | assert(false == IsPressed(6, 3, 3, 1)); 601 | assert(true == IsPressed(7, 3, 3, 1)); 602 | assert(false == IsPressed(8, 3, 3, 1)); 603 | assert(true == IsPressed(9, 3, 3, 1)); 604 | assert(false == IsPressed(10, 3, 3, 1)); 605 | assert(true == IsPressed(11, 3, 3, 1)); 606 | assert(false == IsPressed(12, 3, 3, 1)); 607 | assert(false == IsPressed(100, 3, 3, 1)); 608 | 609 | // with delay and interval 2 610 | assert(false == IsPressed(0, -1, 3, 2)); 611 | assert(true == IsPressed(1, -1, 3, 2)); 612 | assert(true == IsPressed(2, -1, 3, 2)); 613 | assert(true == IsPressed(3, -1, 3, 2)); 614 | assert(true == IsPressed(4, -1, 3, 2)); 615 | assert(true == IsPressed(5, -1, 3, 2)); 616 | assert(true == IsPressed(100, -1, 3, 2)); 617 | 618 | assert(false == IsPressed(0, 0, 3, 2)); 619 | assert(false == IsPressed(1, 0, 3, 2)); 620 | assert(false == IsPressed(2, 0, 3, 2)); 621 | assert(false == IsPressed(3, 0, 3, 2)); 622 | assert(false == IsPressed(4, 0, 3, 2)); 623 | assert(false == IsPressed(5, 0, 3, 2)); 624 | assert(false == IsPressed(100, 0, 3, 2)); 625 | 626 | assert(false == IsPressed(0, 1, 3, 2)); 627 | assert(true == IsPressed(1, 1, 3, 2)); 628 | assert(false == IsPressed(2, 1, 3, 2)); 629 | assert(false == IsPressed(3, 1, 3, 2)); 630 | assert(false == IsPressed(4, 1, 3, 2)); 631 | assert(true == IsPressed(5, 1, 3, 2)); 632 | assert(false == IsPressed(6, 1, 3, 2)); 633 | assert(false == IsPressed(7, 1, 3, 2)); 634 | assert(true == IsPressed(8, 1, 3, 2)); 635 | assert(false == IsPressed(9, 1, 3, 2)); 636 | assert(false == IsPressed(10, 1, 3, 2)); 637 | assert(true == IsPressed(11, 1, 3, 2)); 638 | assert(false == IsPressed(12, 1, 3, 2)); 639 | assert(false == IsPressed(100, 1, 3, 2)); 640 | 641 | assert(false == IsPressed(0, 3, 3, 2)); 642 | assert(true == IsPressed(1, 3, 3, 2)); 643 | assert(true == IsPressed(2, 3, 3, 2)); 644 | assert(true == IsPressed(3, 3, 3, 2)); 645 | assert(false == IsPressed(4, 3, 3, 2)); 646 | assert(false == IsPressed(5, 3, 3, 2)); 647 | assert(false == IsPressed(6, 3, 3, 2)); 648 | assert(true == IsPressed(7, 3, 3, 2)); 649 | assert(false == IsPressed(8, 3, 3, 2)); 650 | assert(false == IsPressed(9, 3, 3, 2)); 651 | assert(true == IsPressed(10, 3, 3, 2)); 652 | assert(false == IsPressed(11, 3, 3, 2)); 653 | assert(false == IsPressed(12, 3, 3, 2)); 654 | assert(true == IsPressed(100, 3, 3, 2)); 655 | 656 | printf("End Test: Input\n"); 657 | } 658 | #endif 659 | --------------------------------------------------------------------------------