ruby-ai 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +84 -0
- data/bin/ruby-ai +12 -0
- data/lib/ruby-ai.rb +194 -0
- data/lib/ruby-ai/ai.rb +31 -0
- data/lib/ruby-ai/empty.rb +11 -0
- data/lib/ruby-ai/goal.rb +15 -0
- data/lib/ruby-ai/levels/level1.png +0 -0
- data/lib/ruby-ai/levels/level2.png +0 -0
- data/lib/ruby-ai/levels/level3.png +0 -0
- data/lib/ruby-ai/levels/level4.png +0 -0
- data/lib/ruby-ai/levels/level5.png +0 -0
- data/lib/ruby-ai/levels/level6.png +0 -0
- data/lib/ruby-ai/levels/level7.png +0 -0
- data/lib/ruby-ai/media/tiny-black.png +0 -0
- data/lib/ruby-ai/media/tiny-blue.png +0 -0
- data/lib/ruby-ai/media/tiny-brown.png +0 -0
- data/lib/ruby-ai/media/tiny-green.png +0 -0
- data/lib/ruby-ai/media/tiny-orange.png +0 -0
- data/lib/ruby-ai/media/tiny-purple.png +0 -0
- data/lib/ruby-ai/media/tiny-red.png +0 -0
- data/lib/ruby-ai/media/tiny-yellow.png +0 -0
- data/lib/ruby-ai/player.rb +97 -0
- data/lib/ruby-ai/tile.rb +11 -0
- data/lib/ruby-ai/ui.rb +16 -0
- data/lib/ruby-ai/wall.rb +16 -0
- data/lib/ruby-ai/window.rb +195 -0
- data/ruby-ai-0.0.1.gem +0 -0
- data/ruby-ai.gemspec +15 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ba65807dbe2e5d60896d8445d7059265ac0c7970
|
4
|
+
data.tar.gz: 3f2e8186d0d27868d408757a33a753684a4222c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93080045c2fefae29bd424cd5705797f8a2fd52d4b4b591e29f98307fc5b4320c36d5007b4904c07633177fd82b7aa908593a088bffadf99c6d801abc99b9f1d
|
7
|
+
data.tar.gz: 5127599601608ae2dcb761108d442eb28a18f70e75220aef592fd508af72a7d4bc59e05d30f6feef38b82271ec9990a89ae00af5f2e54739f84e9eb31544e2a2
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
Ruby AI
|
2
|
+
=========================
|
3
|
+
This is my first gem. Feedback on best practices is very welcome.
|
4
|
+
|
5
|
+
In this game, you write some basic AI to move the player block to the goal.
|
6
|
+
|
7
|
+
Getting Started
|
8
|
+
---------------------
|
9
|
+
|
10
|
+
```bash
|
11
|
+
$ gem install ruby-ai
|
12
|
+
```
|
13
|
+
|
14
|
+
Move to an empty directory of your choice.
|
15
|
+
|
16
|
+
```bash
|
17
|
+
$ ruby-ai init
|
18
|
+
```
|
19
|
+
|
20
|
+
The above command creates an ai.rb file and a levels directory within your current directory.
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ ruby-ai X
|
24
|
+
```
|
25
|
+
|
26
|
+
Substitute a level number for "X" to load that level. Seven pre-made levels are included, but you can easily add more (see below).
|
27
|
+
|
28
|
+
Writing AI
|
29
|
+
------------------
|
30
|
+
|
31
|
+
Simply open the ai.rb file. On each update tick, the
|
32
|
+
```ruby
|
33
|
+
def level(num)
|
34
|
+
# your code here
|
35
|
+
end
|
36
|
+
```
|
37
|
+
method will be called.
|
38
|
+
|
39
|
+
You have two methods at your disposal:
|
40
|
+
```ruby
|
41
|
+
look(direction)
|
42
|
+
```
|
43
|
+
```ruby
|
44
|
+
move(direction)
|
45
|
+
```
|
46
|
+
The usable directions are:
|
47
|
+
```ruby
|
48
|
+
:up, :down, :left, :right
|
49
|
+
```
|
50
|
+
|
51
|
+
The 'look' method will return a tile in the specified direction, or the tile at your current location if no direction in specified.
|
52
|
+
The 'move' method will move you in the specified direction. Moving twice or hitting a wall will end the game.
|
53
|
+
|
54
|
+
You can only see one space in each of the 4 available directions as you go. Each Tile object responds to:
|
55
|
+
```ruby
|
56
|
+
tile[:x] #=> the x coordinate of that tile
|
57
|
+
tile[:y] #=> the y coordinate of that tile
|
58
|
+
```
|
59
|
+
The three Tile classes are Goal, Wall, Empty.
|
60
|
+
|
61
|
+
Hint:
|
62
|
+
```ruby
|
63
|
+
look(:up).kind_of?(Wall)
|
64
|
+
look(:down).kind_of?(Empty)
|
65
|
+
```
|
66
|
+
|
67
|
+
Ruby AI will flip/rotate the level randomly, so you can't just create a list of moves.
|
68
|
+
|
69
|
+
Running the AI
|
70
|
+
------------------
|
71
|
+
|
72
|
+
Load a level and press enter to begin. Press up to increase speed or down to decrease speed. Press enter again to pause. Press escape to exit.
|
73
|
+
|
74
|
+
Creating Levels
|
75
|
+
-----------------------
|
76
|
+
|
77
|
+
Each level is represented by a .png image. Each pixel represents one tile. I recommend the MacOS program Paintbrush, since Ruby AI will recognize the pre-made colors.
|
78
|
+
|
79
|
+
The Player is orange, a.k.a. 'Tangerine' in Paintbrush.
|
80
|
+
The Goal is green, a.k.a. 'Spring' in Paintbrush.
|
81
|
+
Walls are brown, a.k.a. 'Mocha' in Paintbrush.
|
82
|
+
Empty space is black, a.k.a. 'Licorice' in Paintbrush.
|
83
|
+
|
84
|
+
Ruby AI _should_ support levels up to 64x64, but they will load slowly (if at all).
|
data/bin/ruby-ai
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ARGV[0] =='init'
|
4
|
+
bin_path = File.expand_path(File.dirname(__FILE__))
|
5
|
+
`cp #{bin_path}/../lib/ruby-ai/ai.rb .`
|
6
|
+
`cp -r #{bin_path}/../lib/ruby-ai/levels .`
|
7
|
+
else
|
8
|
+
require "#{Dir.pwd}/ai"
|
9
|
+
require 'ruby-ai'
|
10
|
+
level = ARGV[0]
|
11
|
+
RubyAI.new(level).show
|
12
|
+
end
|
data/lib/ruby-ai.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
#displays game, passes commands
|
2
|
+
LIB = File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'gosu'
|
5
|
+
require 'texplay'
|
6
|
+
require 'ruby-ai/goal'
|
7
|
+
require 'ruby-ai/wall'
|
8
|
+
require 'ruby-ai/empty'
|
9
|
+
require 'ruby-ai/player'
|
10
|
+
require 'ruby-ai/ui'
|
11
|
+
|
12
|
+
class RubyAI < Gosu::Window
|
13
|
+
attr_reader :tiles
|
14
|
+
|
15
|
+
COLORS = {
|
16
|
+
:player => [1.0, 0.5764706134796143, 0.0, 1.0], #orange, paintbrush 'tangerine'
|
17
|
+
:goal => [0.0, 0.9764706492424011, 0.0, 1.0], #green, paintbrush 'spring'
|
18
|
+
:wall => [0.5803921818733215, 0.32156863808631897, 0.0, 1.0], #brown, paintbrush 'mocha'
|
19
|
+
:empty => [0.0, 0.0, 0.0, 1.0], #black, paintbrush 'licorice'
|
20
|
+
}
|
21
|
+
|
22
|
+
DIMS = {:x => 64, :y => 64}
|
23
|
+
|
24
|
+
def initialize(level)
|
25
|
+
|
26
|
+
super(DIMS[:x] * 10, DIMS[:y] * 10, false)
|
27
|
+
|
28
|
+
self.caption = 'Ruby AI'
|
29
|
+
@drawn = []
|
30
|
+
@ui = UI.new(self)
|
31
|
+
@drawn << @ui
|
32
|
+
@tiles = []
|
33
|
+
@level = level
|
34
|
+
|
35
|
+
load_level
|
36
|
+
|
37
|
+
@ai = AI.new(@player)
|
38
|
+
unless @ai.respond_to?('level', "#{@level}")
|
39
|
+
puts "Your AI class must define a method 'level(num)'"
|
40
|
+
@player.stop = true
|
41
|
+
end
|
42
|
+
@ai_tick = 0.5
|
43
|
+
@last_ai_update = Time.now
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_level
|
47
|
+
level_image = Gosu::Image.new(self, "#{Dir.pwd}/levels/level#{@level}.png", false)
|
48
|
+
puts "Loading #{level_image.width * level_image.height} tiles."
|
49
|
+
image_dims = {:x => level_image.width, :y => level_image.height}
|
50
|
+
|
51
|
+
reverse = {:x => [true, false].sample, :y => [true, false].sample}
|
52
|
+
# reverse = {:x => false, :y => false}
|
53
|
+
rotate_by = [0, 90, 180, 270].sample
|
54
|
+
# rotate_by = 270
|
55
|
+
|
56
|
+
count = 0
|
57
|
+
coords = {}
|
58
|
+
mods = centering_modifiers(image_dims)
|
59
|
+
|
60
|
+
image_dims[:x].times do |x|
|
61
|
+
image_dims[:y].times do |y|
|
62
|
+
count += 1
|
63
|
+
if count > 99
|
64
|
+
print '.'
|
65
|
+
count = 0
|
66
|
+
end
|
67
|
+
|
68
|
+
coords[:x] = x
|
69
|
+
coords[:y] = y
|
70
|
+
|
71
|
+
[:x, :y].each do |dim|
|
72
|
+
coords[dim] = image_dims[dim] - 1 - coords[dim] if reverse[dim]
|
73
|
+
end
|
74
|
+
|
75
|
+
(rotate_by / 90).times do
|
76
|
+
rotate90!(image_dims, coords)
|
77
|
+
end
|
78
|
+
|
79
|
+
pixel = level_image.get_pixel(x, y)
|
80
|
+
add_tile(pixel, (coords[:x] + mods[:x]) * 10, (coords[:y] + mods[:y]) * 10)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
puts 'Done.'
|
84
|
+
end
|
85
|
+
|
86
|
+
def centering_modifiers(image_dims)
|
87
|
+
mods = {:x => 0, :y => 0}
|
88
|
+
[:x, :y].each do |dim|
|
89
|
+
if image_dims[dim] < DIMS[dim]
|
90
|
+
diff = DIMS[dim]- image_dims[dim]
|
91
|
+
mods[dim] = diff / 2
|
92
|
+
end
|
93
|
+
end
|
94
|
+
mods
|
95
|
+
end
|
96
|
+
|
97
|
+
def rotate90!(dims, coords)
|
98
|
+
old = coords[:y]
|
99
|
+
coords[:y] = coords[:x]
|
100
|
+
coords[:x] = dims[:y] - 1 - old
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_tile(pixel, x, y)
|
104
|
+
if pixel_equal?(pixel, COLORS[:wall])
|
105
|
+
wall = Wall.new(self, {:x => x, :y => y})
|
106
|
+
@tiles << wall
|
107
|
+
@drawn << wall
|
108
|
+
elsif pixel_equal?(pixel, COLORS[:player])
|
109
|
+
@player = Player.new(self, {:x => x, :y => y})
|
110
|
+
@drawn << @player
|
111
|
+
|
112
|
+
empty = Empty.new(self, {:x => x, :y => y})
|
113
|
+
@tiles << empty
|
114
|
+
elsif pixel_equal?(pixel, COLORS[:goal])
|
115
|
+
@goal = Goal.new(self, {:x => x, :y => y})
|
116
|
+
@tiles << @goal
|
117
|
+
@drawn << @goal
|
118
|
+
elsif pixel_equal?(pixel, COLORS[:empty])
|
119
|
+
empty = Empty.new(self, {:x => x, :y => y})
|
120
|
+
@tiles << empty
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def pixel_equal?(pixel, values)
|
125
|
+
4.times do |i|
|
126
|
+
return false unless rough_equal?(pixel[i], values[i])
|
127
|
+
end
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
131
|
+
def rough_equal?(float1, float2)
|
132
|
+
one = (float1 * 100).to_i
|
133
|
+
two = (float2 * 100).to_i
|
134
|
+
one == two
|
135
|
+
end
|
136
|
+
|
137
|
+
def update
|
138
|
+
if won?
|
139
|
+
@ui.text = 'Success! Press <enter> to exit.'
|
140
|
+
return
|
141
|
+
end
|
142
|
+
|
143
|
+
if @player.stop
|
144
|
+
@ui.text = 'Game over. Press <enter> to exit.'
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
if @player.pause
|
149
|
+
@ui.text = 'Paused. Press <enter> to resume.'
|
150
|
+
return
|
151
|
+
end
|
152
|
+
|
153
|
+
@ui.text = ''
|
154
|
+
|
155
|
+
if Time.now - @last_ai_update > @ai_tick
|
156
|
+
@last_ai_update = Time.now
|
157
|
+
@player.send(:moved=, false)
|
158
|
+
@ai.level(@level.to_i)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def won?
|
163
|
+
return true if @won
|
164
|
+
if @player.x == @goal.x && @player.y == @goal.y
|
165
|
+
puts 'Success!'
|
166
|
+
@won = true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def draw
|
171
|
+
@drawn.each {|o| o.send(:draw)}
|
172
|
+
end
|
173
|
+
|
174
|
+
def button_down(id)
|
175
|
+
if id == Gosu::KbEscape
|
176
|
+
close
|
177
|
+
elsif id == Gosu::KbReturn
|
178
|
+
if @player.stop || @won
|
179
|
+
exit
|
180
|
+
end
|
181
|
+
if @player.pause
|
182
|
+
@player.pause = false
|
183
|
+
else
|
184
|
+
@player.pause = true
|
185
|
+
end
|
186
|
+
elsif id == Gosu::KbDown
|
187
|
+
@ai_tick = @ai_tick * 2 unless @ai_tick > 1
|
188
|
+
elsif id == Gosu::KbUp
|
189
|
+
@ai_tick = @ai_tick / 2 unless @ai_tick < 0.01
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
end
|
data/lib/ruby-ai/ai.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#Add your own AI code here.
|
2
|
+
#You have two methods at your disposal:
|
3
|
+
# look(direction)
|
4
|
+
# use :up, :down, :left, :right, or nothing as your direction
|
5
|
+
# returns the tile in the specified direction, or the tile the player is currently standing on
|
6
|
+
#
|
7
|
+
# move(direction)
|
8
|
+
# use :up, :down, :left, or :right as your direction
|
9
|
+
# moves the player in the specified direction
|
10
|
+
#
|
11
|
+
# You can cheat pretty easily if you like, but it's not as fun that way.
|
12
|
+
|
13
|
+
class AI
|
14
|
+
attr_reader :player
|
15
|
+
|
16
|
+
def initialize(player)
|
17
|
+
@player = player
|
18
|
+
end
|
19
|
+
|
20
|
+
def level(num)
|
21
|
+
puts 'Add your own code to ai.rb'
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
#this method is required to allow method calls directly to player object
|
27
|
+
#exa. move(:up) vs player.move(:up)
|
28
|
+
def method_missing(m, *args)
|
29
|
+
player.send(m, *args)
|
30
|
+
end
|
31
|
+
end
|
data/lib/ruby-ai/goal.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#represents the goal
|
2
|
+
|
3
|
+
require 'ruby-ai/tile'
|
4
|
+
class Goal < Tile
|
5
|
+
attr_reader :x, :y
|
6
|
+
def initialize(window, coords)
|
7
|
+
@image = Gosu::Image.new(window, "#{LIB}/ruby-ai/media/tiny-green.png", false)
|
8
|
+
@x = coords[:x]
|
9
|
+
@y = coords[:y]
|
10
|
+
end
|
11
|
+
|
12
|
+
def draw
|
13
|
+
@image.draw(@x, @y, 1)
|
14
|
+
end
|
15
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#holds player state, handles lose conditions
|
2
|
+
|
3
|
+
require 'ruby-ai/tile'
|
4
|
+
|
5
|
+
class Player < Tile
|
6
|
+
attr_accessor :pause, :stop
|
7
|
+
attr_reader :x, :y, :moved
|
8
|
+
|
9
|
+
def initialize(window, coords)
|
10
|
+
@image = Gosu::Image.new(window, "#{LIB}/ruby-ai/media/tiny-orange.png", false)
|
11
|
+
@tiles = window.tiles
|
12
|
+
@x = coords[:x]
|
13
|
+
@y = coords[:y]
|
14
|
+
@moved = false
|
15
|
+
@pause = true
|
16
|
+
@stop = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def look(dir=nil, num=1)
|
20
|
+
return @tiles.find {|t| t.x == x && t.y == y} unless dir
|
21
|
+
at = send(dir, num)
|
22
|
+
tile = @tiles.find do |tile|
|
23
|
+
tile.x == at[:x] && tile.y == at[:y]
|
24
|
+
end
|
25
|
+
tile
|
26
|
+
end
|
27
|
+
|
28
|
+
def move(dir)
|
29
|
+
if moved? || hit_wall?(dir)
|
30
|
+
@stop = true
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
to = send(dir)
|
35
|
+
@x = to[:x]
|
36
|
+
@y = to[:y]
|
37
|
+
myputs "Moved #{dir}."
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop=(val)
|
41
|
+
if val
|
42
|
+
puts 'Game Over.'
|
43
|
+
@stop = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def up(num=1)
|
50
|
+
{:x => @x, :y => @y - num * 10}
|
51
|
+
end
|
52
|
+
|
53
|
+
def down(num=1)
|
54
|
+
{:x => @x, :y => @y + num * 10}
|
55
|
+
end
|
56
|
+
|
57
|
+
def left(num=1)
|
58
|
+
{:x => @x - num * 10, :y => @y}
|
59
|
+
end
|
60
|
+
|
61
|
+
def right(num=1)
|
62
|
+
{:x => @x + num * 10, :y => @y}
|
63
|
+
end
|
64
|
+
|
65
|
+
def moved?
|
66
|
+
if @moved
|
67
|
+
puts 'Already moved!'
|
68
|
+
true
|
69
|
+
else
|
70
|
+
@moved = true
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def moved=(val)
|
76
|
+
@moved = val
|
77
|
+
end
|
78
|
+
|
79
|
+
def hit_wall?(dir)
|
80
|
+
if look(dir).kind_of?(Wall)
|
81
|
+
puts "You moved #{dir} and knocked yourself out against a wall."
|
82
|
+
true
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def draw
|
89
|
+
@image.draw(@x, @y, 1)
|
90
|
+
end
|
91
|
+
|
92
|
+
def myputs(string)
|
93
|
+
@count ||= 0
|
94
|
+
@count += 1
|
95
|
+
puts "#{@count}. " + string
|
96
|
+
end
|
97
|
+
end
|
data/lib/ruby-ai/tile.rb
ADDED
data/lib/ruby-ai/ui.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#prints text on screen
|
2
|
+
|
3
|
+
class UI
|
4
|
+
attr_accessor :text
|
5
|
+
|
6
|
+
def initialize(window)
|
7
|
+
@font = Gosu::Font.new(window, Gosu::default_font_name, 15)
|
8
|
+
@text = 'Press <enter> to begin.'
|
9
|
+
@width = window.width
|
10
|
+
@height = window.height
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw
|
14
|
+
@font.draw_rel(@text, @width / 2, @height - 20, 3, 0.5, 0.0, 1.0, 1.0, 0xffffff00)
|
15
|
+
end
|
16
|
+
end
|
data/lib/ruby-ai/wall.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#represents a wall
|
2
|
+
|
3
|
+
require 'ruby-ai/tile'
|
4
|
+
class Wall < Tile
|
5
|
+
attr_reader :x, :y
|
6
|
+
|
7
|
+
def initialize(window, coords)
|
8
|
+
@image = Gosu::Image.new(window, "#{LIB}/ruby-ai/media/tiny-brown.png", true)
|
9
|
+
@x = coords[:x]
|
10
|
+
@y = coords[:y]
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw
|
14
|
+
@image.draw(@x, @y, 1)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
#displays game, passes commands
|
2
|
+
|
3
|
+
require 'gosu'
|
4
|
+
require 'texplay'
|
5
|
+
require APP_DIR + 'ai'
|
6
|
+
require APP_DIR + 'lib/goal'
|
7
|
+
require APP_DIR + 'lib/wall'
|
8
|
+
require APP_DIR + 'lib/empty'
|
9
|
+
require APP_DIR + 'lib/player'
|
10
|
+
require APP_DIR + 'lib/ui'
|
11
|
+
require APP_DIR + 'lib/fake_window'
|
12
|
+
|
13
|
+
class Window < Gosu::Window
|
14
|
+
attr_reader :tiles
|
15
|
+
|
16
|
+
COLORS = {
|
17
|
+
:player => [1.0, 0.5764706134796143, 0.0, 1.0], #orange, paintbrush 'tangerine'
|
18
|
+
:goal => [0.0, 0.9764706492424011, 0.0, 1.0], #green, paintbrush 'spring'
|
19
|
+
:wall => [0.5803921818733215, 0.32156863808631897, 0.0, 1.0], #brown, paintbrush 'mocha'
|
20
|
+
:empty => [0.0, 0.0, 0.0, 1.0], #black, paintbrush 'licorice'
|
21
|
+
}
|
22
|
+
|
23
|
+
DIMS = {:x => 64, :y => 64}
|
24
|
+
|
25
|
+
def initialize(level)
|
26
|
+
|
27
|
+
super(DIMS[:x] * 10, DIMS[:y] * 10, false)
|
28
|
+
|
29
|
+
self.caption = 'Ruby AI'
|
30
|
+
@drawn = []
|
31
|
+
@ui = UI.new(self)
|
32
|
+
@drawn << @ui
|
33
|
+
@tiles = []
|
34
|
+
@level = level
|
35
|
+
|
36
|
+
load_level
|
37
|
+
|
38
|
+
@ai = AI.new(@player)
|
39
|
+
unless @ai.respond_to?('level', "#{@level}")
|
40
|
+
puts "Your AI class must define a method 'level(num)'"
|
41
|
+
@player.stop = true
|
42
|
+
end
|
43
|
+
@ai_tick = 0.5
|
44
|
+
@last_ai_update = Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_level
|
48
|
+
level_image = Gosu::Image.new(self, APP_DIR + "levels/level#{@level}.png", false)
|
49
|
+
puts "Loading #{level_image.width * level_image.height} tiles."
|
50
|
+
image_dims = {:x => level_image.width, :y => level_image.height}
|
51
|
+
|
52
|
+
reverse = {:x => [true, false].sample, :y => [true, false].sample}
|
53
|
+
# reverse = {:x => false, :y => false}
|
54
|
+
rotate_by = [0, 90, 180, 270].sample
|
55
|
+
# rotate_by = 270
|
56
|
+
|
57
|
+
count = 0
|
58
|
+
coords = {}
|
59
|
+
mods = centering_modifiers(image_dims)
|
60
|
+
|
61
|
+
image_dims[:x].times do |x|
|
62
|
+
image_dims[:y].times do |y|
|
63
|
+
count += 1
|
64
|
+
if count > 99
|
65
|
+
print '.'
|
66
|
+
count = 0
|
67
|
+
end
|
68
|
+
|
69
|
+
coords[:x] = x
|
70
|
+
coords[:y] = y
|
71
|
+
|
72
|
+
[:x, :y].each do |dim|
|
73
|
+
coords[dim] = image_dims[dim] - 1 - coords[dim] if reverse[dim]
|
74
|
+
end
|
75
|
+
|
76
|
+
(rotate_by / 90).times do
|
77
|
+
rotate90!(image_dims, coords)
|
78
|
+
end
|
79
|
+
|
80
|
+
pixel = level_image.get_pixel(x, y)
|
81
|
+
add_tile(pixel, (coords[:x] + mods[:x]) * 10, (coords[:y] + mods[:y]) * 10)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
puts 'Done.'
|
85
|
+
end
|
86
|
+
|
87
|
+
def centering_modifiers(image_dims)
|
88
|
+
mods = {:x => 0, :y => 0}
|
89
|
+
[:x, :y].each do |dim|
|
90
|
+
if image_dims[dim] < DIMS[dim]
|
91
|
+
diff = DIMS[dim]- image_dims[dim]
|
92
|
+
mods[dim] = diff / 2
|
93
|
+
end
|
94
|
+
end
|
95
|
+
mods
|
96
|
+
end
|
97
|
+
|
98
|
+
def rotate90!(dims, coords)
|
99
|
+
old = coords[:y]
|
100
|
+
coords[:y] = coords[:x]
|
101
|
+
coords[:x] = dims[:y] - 1 - old
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_tile(pixel, x, y)
|
105
|
+
if pixel_equal?(pixel, COLORS[:wall])
|
106
|
+
wall = Wall.new(self, {:x => x, :y => y})
|
107
|
+
@tiles << wall
|
108
|
+
@drawn << wall
|
109
|
+
elsif pixel_equal?(pixel, COLORS[:player])
|
110
|
+
@player = Player.new(self, {:x => x, :y => y})
|
111
|
+
@drawn << @player
|
112
|
+
|
113
|
+
empty = Empty.new(self, {:x => x, :y => y})
|
114
|
+
@tiles << empty
|
115
|
+
elsif pixel_equal?(pixel, COLORS[:goal])
|
116
|
+
@goal = Goal.new(self, {:x => x, :y => y})
|
117
|
+
@tiles << @goal
|
118
|
+
@drawn << @goal
|
119
|
+
elsif pixel_equal?(pixel, COLORS[:empty])
|
120
|
+
empty = Empty.new(self, {:x => x, :y => y})
|
121
|
+
@tiles << empty
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def pixel_equal?(pixel, values)
|
126
|
+
4.times do |i|
|
127
|
+
return false unless rough_equal?(pixel[i], values[i])
|
128
|
+
end
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def rough_equal?(float1, float2)
|
133
|
+
one = (float1 * 100).to_i
|
134
|
+
two = (float2 * 100).to_i
|
135
|
+
one == two
|
136
|
+
end
|
137
|
+
|
138
|
+
def update
|
139
|
+
if won?
|
140
|
+
@ui.text = 'Success! Press <enter> to exit.'
|
141
|
+
return
|
142
|
+
end
|
143
|
+
|
144
|
+
if @player.stop
|
145
|
+
@ui.text = 'Game over. Press <enter> to exit.'
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
if @player.pause
|
150
|
+
@ui.text = 'Paused. Press <enter> to resume.'
|
151
|
+
return
|
152
|
+
end
|
153
|
+
|
154
|
+
@ui.text = ''
|
155
|
+
|
156
|
+
if Time.now - @last_ai_update > @ai_tick
|
157
|
+
@last_ai_update = Time.now
|
158
|
+
@player.send(:moved=, false)
|
159
|
+
@ai.level(@level.to_i)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def won?
|
164
|
+
return true if @won
|
165
|
+
if @player.x == @goal.x && @player.y == @goal.y
|
166
|
+
puts 'Success!'
|
167
|
+
@won = true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def draw
|
172
|
+
@drawn.each {|o| o.send(:draw)}
|
173
|
+
end
|
174
|
+
|
175
|
+
def button_down(id)
|
176
|
+
if id == Gosu::KbEscape
|
177
|
+
close
|
178
|
+
elsif id == Gosu::KbReturn
|
179
|
+
if @player.stop || @won
|
180
|
+
exit
|
181
|
+
end
|
182
|
+
if @player.pause
|
183
|
+
@player.pause = false
|
184
|
+
else
|
185
|
+
@player.pause = true
|
186
|
+
end
|
187
|
+
elsif id == Gosu::KbDown
|
188
|
+
@ai_tick = @ai_tick * 2 unless @ai_tick > 1
|
189
|
+
elsif id == Gosu::KbUp
|
190
|
+
@ai_tick = @ai_tick / 2 unless @ai_tick < 0.01
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
end
|
data/ruby-ai-0.0.1.gem
ADDED
Binary file
|
data/ruby-ai.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'ruby-ai'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.executables << 'ruby-ai'
|
5
|
+
s.date = '2013-09-24'
|
6
|
+
s.summary = 'ruby-ai'
|
7
|
+
s.description = 'Write AI code for this simple game.'
|
8
|
+
s.authors = ['Tyler Hartland']
|
9
|
+
s.email = 'tylerhartland7@gmail.com'
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.homepage = 'http://rubygems.org/gems/ruby-ai'
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.add_runtime_dependency 'gosu', ['~> 0.7']
|
14
|
+
s.add_runtime_dependency 'texplay', ['~> 0.4']
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-ai
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Hartland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gosu
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: texplay
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.4'
|
41
|
+
description: Write AI code for this simple game.
|
42
|
+
email: tylerhartland7@gmail.com
|
43
|
+
executables:
|
44
|
+
- ruby-ai
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- bin/ruby-ai
|
50
|
+
- lib/ruby-ai.rb
|
51
|
+
- lib/ruby-ai/ai.rb
|
52
|
+
- lib/ruby-ai/empty.rb
|
53
|
+
- lib/ruby-ai/goal.rb
|
54
|
+
- lib/ruby-ai/levels/level1.png
|
55
|
+
- lib/ruby-ai/levels/level2.png
|
56
|
+
- lib/ruby-ai/levels/level3.png
|
57
|
+
- lib/ruby-ai/levels/level4.png
|
58
|
+
- lib/ruby-ai/levels/level5.png
|
59
|
+
- lib/ruby-ai/levels/level6.png
|
60
|
+
- lib/ruby-ai/levels/level7.png
|
61
|
+
- lib/ruby-ai/media/tiny-black.png
|
62
|
+
- lib/ruby-ai/media/tiny-blue.png
|
63
|
+
- lib/ruby-ai/media/tiny-brown.png
|
64
|
+
- lib/ruby-ai/media/tiny-green.png
|
65
|
+
- lib/ruby-ai/media/tiny-orange.png
|
66
|
+
- lib/ruby-ai/media/tiny-purple.png
|
67
|
+
- lib/ruby-ai/media/tiny-red.png
|
68
|
+
- lib/ruby-ai/media/tiny-yellow.png
|
69
|
+
- lib/ruby-ai/player.rb
|
70
|
+
- lib/ruby-ai/tile.rb
|
71
|
+
- lib/ruby-ai/ui.rb
|
72
|
+
- lib/ruby-ai/wall.rb
|
73
|
+
- lib/ruby-ai/window.rb
|
74
|
+
- ruby-ai-0.0.1.gem
|
75
|
+
- ruby-ai.gemspec
|
76
|
+
homepage: http://rubygems.org/gems/ruby-ai
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.0.3
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: ruby-ai
|
100
|
+
test_files: []
|