ruby-ai 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|