bangkok 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +25 -0
- data/README +18 -0
- data/Rakefile +2 -0
- data/TODO +3 -10
- data/lib/bangkok/board.rb +9 -9
- data/lib/bangkok/gamelistener.rb +105 -31
- data/lib/bangkok/info.rb +1 -1
- data/lib/bangkok/piece.rb +3 -3
- data/lib/bangkok/square.rb +19 -0
- data/test/mock_game_listener.rb +16 -1
- data/test/test_board.rb +62 -0
- data/test/test_gamelistener.rb +32 -0
- data/test/test_piece.rb +15 -0
- data/test/test_square.rb +20 -5
- metadata +4 -2
data/ChangeLog
CHANGED
@@ -1,5 +1,30 @@
|
|
1
|
+
2005-03-26 Jim Menard <jimm@io.com>
|
2
|
+
|
3
|
+
* lib/bangkok/gamelistener.rb (GameListener::end_game): call
|
4
|
+
Track#sort, not Track#recalc_delta_from_times, because we now
|
5
|
+
generate events out of order.
|
6
|
+
(GameListener::move): generate data differently. Call new
|
7
|
+
generate_* methods. Generate multiple volume and pan values based
|
8
|
+
on distance traveled.
|
9
|
+
(GameListener::interpolate): created.
|
10
|
+
(GameListener::generate_notes): created.
|
11
|
+
(GameListener::file_to_pan): created.
|
12
|
+
(GameListener::rank_to_volume): created.
|
13
|
+
(GameListener::generate_volume): created.
|
14
|
+
(GameListener::generate_pan): created.
|
15
|
+
(GameListener::generate_portamento): created.
|
16
|
+
|
17
|
+
* lib/bangkok/square.rb (Square::distance_to): created.
|
18
|
+
(Square::at): created.
|
19
|
+
|
20
|
+
* lib/bangkok/piece.rb: use new Square.at method where appropriate.
|
21
|
+
|
22
|
+
* lib/bangkok/board.rb: use new Square.at method where appropriate.
|
23
|
+
|
1
24
|
2005-03-25 Jim Menard <jimm@io.com>
|
2
25
|
|
26
|
+
* Version 0.1.1 released.
|
27
|
+
|
3
28
|
* lib/bangkok/chessgame.rb: removed shebang line.
|
4
29
|
|
5
30
|
* lib/bangkok/gamelistener.rb (GameListener::initialize): added
|
data/README
CHANGED
@@ -15,6 +15,24 @@ version of bangkok may be downloaded. Bangkok is also available as a RubyGem.
|
|
15
15
|
|
16
16
|
=== Recent Changes
|
17
17
|
|
18
|
+
==== 0.1.2
|
19
|
+
|
20
|
+
* GameListener#move outputs two notes for a move and sets CC_PORTAMENTO_TIME
|
21
|
+
so there is a glide from the first note to the second. The total length of
|
22
|
+
the two ntoes is proportional to the distance the piece travels. The first
|
23
|
+
note is short (a 32nd note), and the second takes up the rest of the time,
|
24
|
+
thus allowing the portamento to have its effect.
|
25
|
+
|
26
|
+
* GameListener#move also outputs multiple volume and pan values, moving
|
27
|
+
smoothly from the original to the new value for the duration of the move's
|
28
|
+
notes.
|
29
|
+
|
30
|
+
* New Square.at method returns a ready-made Square. Since they're immutable,
|
31
|
+
this reduces the number of objects that need to be created.
|
32
|
+
|
33
|
+
* More tests.
|
34
|
+
|
35
|
+
|
18
36
|
==== 0.1.1
|
19
37
|
|
20
38
|
* Fixed bugs in the bin/bangkok script.
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require_gem 'rake'
|
3
|
+
require 'rake/clean'
|
3
4
|
require 'rake/rdoctask'
|
4
5
|
require 'rake/gempackagetask'
|
5
6
|
require 'rake/contrib/rubyforgepublisher'
|
@@ -15,6 +16,7 @@ PKG_FILES = FileList[ 'ChangeLog', 'Credits', 'README', 'Rakefile', 'TODO',
|
|
15
16
|
'install.rb',
|
16
17
|
'lib/**/*.rb',
|
17
18
|
'test/**/*.rb']
|
19
|
+
CLEAN.include('game.mid', 'game.txt')
|
18
20
|
|
19
21
|
task :default => [:package]
|
20
22
|
|
data/TODO
CHANGED
@@ -1,20 +1,13 @@
|
|
1
1
|
== Bugs
|
2
2
|
|
3
|
-
- Pawn#could_perform_move does not check for en passant.
|
3
|
+
- Pawn#could_perform_move does not check for en passant. Might need a new ivar
|
4
|
+
@moved_two_spaces_on_first_move.
|
4
5
|
|
5
6
|
- Move#initialize does not handle "e.p." or "ep" in move text.
|
6
7
|
|
7
8
|
== To Do
|
8
9
|
|
9
|
-
-
|
10
|
-
directory as source code
|
11
|
-
|
12
|
-
- write "-c" code in bin/bangkok and examples
|
13
|
-
|
14
|
-
- test bankgok -c examples/program_changes.rb examples/game.pgn
|
15
|
-
|
16
|
-
- implement check for en passant in Pawn#could_perform_move. Might need a new
|
17
|
-
ivar @moved_two_spaces_on_first_move.
|
10
|
+
- Use Square.at(file, rank) where appropriate
|
18
11
|
|
19
12
|
- glide between squares
|
20
13
|
|
data/lib/bangkok/board.rb
CHANGED
@@ -7,12 +7,12 @@ class Board
|
|
7
7
|
@listener = listener
|
8
8
|
@pieces = []
|
9
9
|
[:R, :N, :B, :Q, :K, :B, :N, :R].each_with_index { | sym, file |
|
10
|
-
@pieces << Piece.create(self, listener, :white, sym, Square.
|
11
|
-
@pieces << Piece.create(self, listener, :black, sym, Square.
|
10
|
+
@pieces << Piece.create(self, listener, :white, sym, Square.at(file, 0))
|
11
|
+
@pieces << Piece.create(self, listener, :black, sym, Square.at(file, 7))
|
12
12
|
}
|
13
13
|
8.times { | file |
|
14
|
-
@pieces << Piece.create(self, listener, :white, :P, Square.
|
15
|
-
@pieces << Piece.create(self, listener, :black, :P, Square.
|
14
|
+
@pieces << Piece.create(self, listener, :white, :P, Square.at(file, 1))
|
15
|
+
@pieces << Piece.create(self, listener, :black, :P, Square.at(file, 6))
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
@@ -46,25 +46,25 @@ class Board
|
|
46
46
|
def apply_castle(move)
|
47
47
|
new_king_file = nil
|
48
48
|
new_rook_file = nil
|
49
|
-
king = @pieces.detect{ | p | p.piece == :K && p.color == move.color }
|
49
|
+
king = @pieces.detect { | p | p.piece == :K && p.color == move.color }
|
50
50
|
rook = nil
|
51
51
|
|
52
52
|
if move.queenside_castle?
|
53
53
|
new_king_file = king.square.square.file - 2
|
54
|
-
rook = @pieces.detect{ | p |
|
54
|
+
rook = @pieces.detect { | p |
|
55
55
|
p.piece == :R && p.color == move.color && p.square.file == 0
|
56
56
|
}
|
57
57
|
new_rook_file = rook.square.file + 3
|
58
58
|
else # kingside castle
|
59
59
|
new_king_file = king.square.file + 2
|
60
|
-
rook = @pieces.detect{ | p |
|
60
|
+
rook = @pieces.detect { | p |
|
61
61
|
p.piece == :R && p.color == move.color && p.square.file == 7
|
62
62
|
}
|
63
63
|
new_rook_file = rook.square.file - 2
|
64
64
|
end
|
65
65
|
|
66
|
-
king.move_to(Square.
|
67
|
-
rook.move_to(Square.
|
66
|
+
king.move_to(Square.at(new_king_file, king.square.rank))
|
67
|
+
rook.move_to(Square.at(new_rook_file, rook.square.rank))
|
68
68
|
end
|
69
69
|
|
70
70
|
def remove_from_board(piece)
|
data/lib/bangkok/gamelistener.rb
CHANGED
@@ -24,15 +24,6 @@ class GameListener
|
|
24
24
|
RANK_TO_NOTE[:white] = [64, 66, 68, 69, 71, 73, 75, 76]
|
25
25
|
RANK_TO_NOTE[:black] = RANK_TO_NOTE[:white].reverse
|
26
26
|
|
27
|
-
# Build array that maps file number to pan value
|
28
|
-
FILE_TO_PAN = []
|
29
|
-
8.times { | i | FILE_TO_PAN << ((127.0/7.0) * i).to_i }
|
30
|
-
|
31
|
-
# Build array that maps rank number to volume value
|
32
|
-
RANK_TO_VOL = []
|
33
|
-
4.times { | i | RANK_TO_VOL << 10 + ((117.0 / 3.0) * i).to_i }
|
34
|
-
4.times { | i | RANK_TO_VOL << 127 - (((117.0 / 3.0) * i).to_i) }
|
35
|
-
|
36
27
|
PIECE_MIDI_INFO = {}
|
37
28
|
PIECE_MIDI_INFO[:white] = {}
|
38
29
|
PIECE_MIDI_INFO[:black] = {}
|
@@ -99,7 +90,10 @@ class GameListener
|
|
99
90
|
track.name = "Tempo track"
|
100
91
|
@seq.tracks << track
|
101
92
|
|
102
|
-
@
|
93
|
+
@first_note_delta = @seq.note_to_delta('32nd')
|
94
|
+
@portamento_start = @seq.note_to_delta('64th')
|
95
|
+
@max_delta =
|
96
|
+
@seq.length_to_delta(Square.at(0, 0).distance_to(Square.at(7, 7)))
|
103
97
|
|
104
98
|
create_tracks()
|
105
99
|
@time_from_start = 0
|
@@ -107,20 +101,47 @@ class GameListener
|
|
107
101
|
|
108
102
|
def end_game
|
109
103
|
# When we created events, we set their start times, not their delta times.
|
110
|
-
# Now is the time to fix that.
|
111
|
-
|
104
|
+
# Now is the time to fix that. Sort sorts by start times then calls
|
105
|
+
# recalc_delta_from_times.
|
106
|
+
@seq.tracks.each { | t | t.sort }
|
112
107
|
@seq.write(@io)
|
113
108
|
end
|
114
109
|
|
110
|
+
# Move +piece+ +from+ one space +to+ another. Generate two notes and sets
|
111
|
+
# CC_PORTAMENTO_TIME so there is a glide from the first note to the second.
|
112
|
+
# Also output multiple volume and pan values, moving smoothly from the
|
113
|
+
# original to the new value.
|
115
114
|
def move(piece, from, to)
|
116
|
-
|
117
|
-
|
118
|
-
midi_for_position(piece, Square.new((from.file.to_f + to.file.to_f) / 2,
|
119
|
-
(from.rank.to_f + to.rank.to_f) / 2))
|
120
|
-
midi_for_position(piece, to)
|
121
|
-
end
|
115
|
+
raise "from #{from} may not be off the board" unless from.on_board?
|
116
|
+
|
122
117
|
# Do nothing if the piece moves off the board, because either capture()
|
123
118
|
# or pawn_to_queen() will be called.
|
119
|
+
return unless to.on_board?
|
120
|
+
|
121
|
+
track = track_of(piece)
|
122
|
+
channel = channel_of(piece)
|
123
|
+
|
124
|
+
dist = from.distance_to(to)
|
125
|
+
total_delta = @seq.length_to_delta(dist) # quarter note per space
|
126
|
+
|
127
|
+
generate_notes(track, channel, total_delta, piece, from, to)
|
128
|
+
generate_portamento(track, channel, total_delta)
|
129
|
+
|
130
|
+
steps = interpolate(16, 64, 0, @max_delta, total_delta).to_i
|
131
|
+
delta = (total_delta / steps).to_i
|
132
|
+
start = @time_from_start
|
133
|
+
|
134
|
+
steps.times { | step |
|
135
|
+
val = rank_to_volume(interpolate(from.rank, to.rank, 0, steps-1, step))
|
136
|
+
generate_volume(track, channel, start, val)
|
137
|
+
|
138
|
+
val = file_to_pan(interpolate(from.file, to.file, 0, steps-1, step))
|
139
|
+
generate_pan(track, channel, start, val)
|
140
|
+
|
141
|
+
start += delta
|
142
|
+
}
|
143
|
+
|
144
|
+
@time_from_start += total_delta
|
124
145
|
end
|
125
146
|
|
126
147
|
def capture(attacker, loser)
|
@@ -157,31 +178,84 @@ class GameListener
|
|
157
178
|
}
|
158
179
|
end
|
159
180
|
|
160
|
-
|
161
|
-
|
162
|
-
|
181
|
+
# Returns a value between range_min and range_max inclusive that is
|
182
|
+
# proportional to +value+'s place between value_min and value_max. The
|
183
|
+
# returned value may be floating point. If range_min and range_max or
|
184
|
+
# value_min and value_max are out of order, they are swapped.
|
185
|
+
def interpolate(range_min, range_max, value_min, value_max, value)
|
186
|
+
range_min, range_max = range_max, range_min if range_min > range_max
|
187
|
+
value_min, value_max = value_max, value_min if value_min > value_max
|
188
|
+
return range_min if value == value_min
|
189
|
+
frac = (value_max.to_f - value_min.to_f) / (value.to_f - value_min.to_f)
|
190
|
+
return range_min + (range_max.to_f - range_min.to_f) / frac
|
191
|
+
end
|
163
192
|
|
164
|
-
|
165
|
-
|
193
|
+
# Generate two notes, one at the current start time that is only a 32nd note
|
194
|
+
# long. The next follows immediately, and is for the +to+ square. Its length
|
195
|
+
# is the remaining time.
|
196
|
+
def generate_notes(track, channel, total_delta, piece, from, to)
|
197
|
+
# First note is quickly followed by the second note
|
198
|
+
note = RANK_TO_NOTE[piece.color][from.rank]
|
199
|
+
e = NoteOnEvent.new(channel, note, 127)
|
166
200
|
e.time_from_start = @time_from_start
|
167
201
|
track.events << e
|
168
202
|
|
169
|
-
|
170
|
-
e =
|
171
|
-
e.time_from_start = @time_from_start
|
203
|
+
e = NoteOffEvent.new(channel, note, 127)
|
204
|
+
e.time_from_start = @time_from_start + @first_note_delta - 1
|
172
205
|
track.events << e
|
173
206
|
|
174
|
-
# note
|
175
|
-
note = RANK_TO_NOTE[piece.color][
|
207
|
+
# Second note
|
208
|
+
note = RANK_TO_NOTE[piece.color][to.rank]
|
176
209
|
e = NoteOnEvent.new(channel, note, 127)
|
177
|
-
e.time_from_start = @time_from_start
|
210
|
+
e.time_from_start = @time_from_start + @first_note_delta
|
178
211
|
track.events << e
|
179
212
|
|
180
213
|
e = NoteOffEvent.new(channel, note, 127)
|
181
|
-
e.time_from_start = @time_from_start +
|
214
|
+
e.time_from_start = @time_from_start + total_delta - 1
|
182
215
|
track.events << e
|
216
|
+
end
|
217
|
+
|
218
|
+
# Translates a (possibly fractional) +file+ into an integer CC_PAN value.
|
219
|
+
def file_to_pan(file)
|
220
|
+
return interpolate(0, 127, 0, 7, file).to_i
|
221
|
+
end
|
183
222
|
|
184
|
-
|
223
|
+
# Translates a (possibly fractional) +rank+ into an integer CC_VOLUME value.
|
224
|
+
def rank_to_volume(rank)
|
225
|
+
rank = 3.5 - (3.5 - rank).abs
|
226
|
+
return interpolate(10, 127, 0, 3.5, rank).to_i
|
227
|
+
end
|
228
|
+
|
229
|
+
# Generates a single volume event. #move_to calls this multiple times,
|
230
|
+
# passing in new +delta+ and +value+ values.
|
231
|
+
def generate_volume(track, channel, delta, value)
|
232
|
+
raise "volume: bogus value #{value}" unless value >= 0 && value <= 127
|
233
|
+
e = Controller.new(channel, CC_VOLUME, value)
|
234
|
+
e.time_from_start = @time_from_start + delta
|
235
|
+
track.events << e
|
236
|
+
end
|
237
|
+
|
238
|
+
# Generates a single pan event. #move_to calls this multiple times, passing
|
239
|
+
# in new +delta+ and +value+ values.
|
240
|
+
def generate_pan(track, channel, delta, value)
|
241
|
+
raise "pan: bogus value #{value}" unless value >= 0 && value <= 127
|
242
|
+
e = Controller.new(channel, CC_PAN, value)
|
243
|
+
e.time_from_start = @time_from_start + delta
|
244
|
+
track.events << e
|
245
|
+
end
|
246
|
+
|
247
|
+
# Generates a single portamento event.
|
248
|
+
def generate_portamento(track, channel, total_delta)
|
249
|
+
e = Controller.new(channel, CC_PORTAMENTO_TIME, 0)
|
250
|
+
e.time_from_start = @time_from_start
|
251
|
+
track.events << e
|
252
|
+
|
253
|
+
value = interpolate(32, 100, 0, @max_delta, total_delta).to_i
|
254
|
+
raise "portamento: bogus value #{value}" unless value >= 0 && value <= 127
|
255
|
+
e = Controller.new(channel, CC_PORTAMENTO_TIME, value)
|
256
|
+
|
257
|
+
e.time_from_start = @time_from_start + @portamento_start
|
258
|
+
track.events << e
|
185
259
|
end
|
186
260
|
|
187
261
|
end
|
data/lib/bangkok/info.rb
CHANGED
data/lib/bangkok/piece.rb
CHANGED
@@ -65,7 +65,7 @@ class Piece
|
|
65
65
|
end
|
66
66
|
|
67
67
|
while curr_file != end_file || curr_rank != end_rank
|
68
|
-
return false unless @board.empty_at?(Square.
|
68
|
+
return false unless @board.empty_at?(Square.at(curr_file, curr_rank))
|
69
69
|
curr_file += file_delta
|
70
70
|
curr_rank += rank_delta
|
71
71
|
end
|
@@ -194,7 +194,7 @@ class Pawn < Piece
|
|
194
194
|
elsif !@moved && square.file == @square.file &&
|
195
195
|
square.rank == @square.rank + 2
|
196
196
|
# first move: 2 squares forward
|
197
|
-
return @board.empty_at?(Square.
|
197
|
+
return @board.empty_at?(Square.at(@square.file, @square.rank + 1)) &&
|
198
198
|
@board.empty_at?(square)
|
199
199
|
|
200
200
|
# TODO Implement en passant checking. The following code was wrong.
|
@@ -223,7 +223,7 @@ class Pawn < Piece
|
|
223
223
|
elsif !@moved && square.file == @square.file &&
|
224
224
|
square.rank == @square.rank - 2
|
225
225
|
# first move: 2 spaces forward
|
226
|
-
return @board.empty_at?(Square.
|
226
|
+
return @board.empty_at?(Square.at(@square.file, @square.rank - 1)) &&
|
227
227
|
@board.empty_at?(square)
|
228
228
|
|
229
229
|
# TODO Implement en passant checking. The following code was wrong.
|
data/lib/bangkok/square.rb
CHANGED
@@ -3,6 +3,10 @@ class Square
|
|
3
3
|
attr_reader :file, :rank # Always 0-7
|
4
4
|
attr_reader :color # :white or :black
|
5
5
|
|
6
|
+
def Square.at(file, rank)
|
7
|
+
SQUARES[file][rank]
|
8
|
+
end
|
9
|
+
|
6
10
|
def initialize(*args)
|
7
11
|
@file = @rank = @color = nil
|
8
12
|
case args[0]
|
@@ -38,10 +42,25 @@ class Square
|
|
38
42
|
return @file && @rank
|
39
43
|
end
|
40
44
|
|
45
|
+
# Return the distance between this square and the other. Returns nil of
|
46
|
+
# eithr square is off the board.
|
47
|
+
def distance_to(square)
|
48
|
+
return nil unless on_board? && square.on_board?
|
49
|
+
d_file = square.file - @file
|
50
|
+
d_rank = square.rank - @rank
|
51
|
+
return Math.sqrt(d_file * d_file + d_rank * d_rank)
|
52
|
+
end
|
53
|
+
|
41
54
|
def to_s
|
42
55
|
return "<off-board>" if @file.nil? || @rank.nil?
|
43
56
|
return "#{@file ? (?a + file).chr : ''}#{@rank + 1}"
|
44
57
|
end
|
45
58
|
|
59
|
+
SQUARES = []
|
60
|
+
8.times { | file |
|
61
|
+
SQUARES[file] = []
|
62
|
+
8.times { | rank | SQUARES[file][rank] = Square.new(file, rank) }
|
63
|
+
}
|
64
|
+
|
46
65
|
OFF_BOARD = Square.new
|
47
66
|
end
|
data/test/mock_game_listener.rb
CHANGED
@@ -1,22 +1,37 @@
|
|
1
1
|
class MockGameListener
|
2
|
-
def
|
2
|
+
def initialize
|
3
|
+
@called = []
|
4
|
+
end
|
5
|
+
|
6
|
+
def start_game(io)
|
7
|
+
@called << :start_game
|
3
8
|
end
|
4
9
|
|
5
10
|
def end_game
|
11
|
+
@called << :end_game
|
6
12
|
end
|
7
13
|
|
8
14
|
def move(piece, from, to)
|
15
|
+
@called << :move
|
9
16
|
end
|
10
17
|
|
11
18
|
def capture(attacker, loser)
|
19
|
+
@called << :capture
|
12
20
|
end
|
13
21
|
|
14
22
|
def check
|
23
|
+
@called << :check
|
15
24
|
end
|
16
25
|
|
17
26
|
def checkmate
|
27
|
+
@called << :checkmate
|
18
28
|
end
|
19
29
|
|
20
30
|
def pawn_to_queen(pawn)
|
31
|
+
@called << :pawn_to_queen
|
32
|
+
end
|
33
|
+
|
34
|
+
def called(sym)
|
35
|
+
@called.include?(sym)
|
21
36
|
end
|
22
37
|
end
|
data/test/test_board.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'stringio'
|
3
|
+
$LOAD_PATH[0, 0] = File.dirname(__FILE__)
|
4
|
+
$LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
5
|
+
require 'bangkok/piece'
|
6
|
+
require 'bangkok/square'
|
7
|
+
require 'bangkok/move'
|
8
|
+
require 'bangkok/board'
|
9
|
+
require 'bangkok/chessgame'
|
10
|
+
require 'mock_game_listener'
|
11
|
+
|
12
|
+
class BoardTest < Test::Unit::TestCase
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@listener = MockGameListener.new
|
16
|
+
@board = Board.new(@listener)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_setup
|
20
|
+
8.times { | i |
|
21
|
+
assert_instance_of(Pawn, @board.at(Square.at(i, 1)))
|
22
|
+
assert_instance_of(Pawn, @board.at(Square.at(i, 6)))
|
23
|
+
}
|
24
|
+
[:R, :N, :B, :Q, :K, :B, :N, :R].each_with_index { | sym, file |
|
25
|
+
assert_equal(sym, @board.at(Square.at(file, 0)).piece)
|
26
|
+
assert_equal(sym, @board.at(Square.at(file, 7)).piece)
|
27
|
+
}
|
28
|
+
8.times { | file |
|
29
|
+
4.times { | rank |
|
30
|
+
assert_nil(@board.at(Square.at(file, rank + 2)))
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_listener_calls
|
36
|
+
game = ChessGame.new(@listener)
|
37
|
+
game_text = <<EOS
|
38
|
+
[Event "?"]
|
39
|
+
1. f4 Nf6 2. Nf3 c5 3. e3 d5 4. d4 Bf5 5. c3 e6 6. Bd3 Bxd3 7. Qxd3 Nc6
|
40
|
+
EOS
|
41
|
+
game.read_moves(StringIO.new(game_text))
|
42
|
+
|
43
|
+
out = StringIO.new
|
44
|
+
game.play(out)
|
45
|
+
assert(@listener.called(:start_game))
|
46
|
+
assert(@listener.called(:end_game))
|
47
|
+
assert(@listener.called(:move))
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_midi_output
|
51
|
+
game = ChessGame.new # default GameListener created
|
52
|
+
game_text = <<EOS
|
53
|
+
[Event "?"]
|
54
|
+
1. f4 Nf6 2. Nf3 c5 3. e3 d5 4. d4 Bf5 5. c3 e6 6. Bd3 Bxd3 7. Qxd3 Nc6
|
55
|
+
EOS
|
56
|
+
game.read_moves(StringIO.new(game_text))
|
57
|
+
|
58
|
+
out = StringIO.new
|
59
|
+
game.play(out)
|
60
|
+
assert(out.string.length > 0)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
$LOAD_PATH[0, 0] = File.dirname(__FILE__)
|
3
|
+
$LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
require 'bangkok/gamelistener'
|
5
|
+
|
6
|
+
class GameListenerTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@listener = GameListener.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_interp
|
13
|
+
assert_equal(0, @listener.interpolate(0, 100, 0, 100, 0))
|
14
|
+
assert_equal(100, @listener.interpolate(0, 100, 0, 100, 100))
|
15
|
+
assert_equal(50, @listener.interpolate(0, 100, 0, 100, 50))
|
16
|
+
|
17
|
+
assert_equal(50, @listener.interpolate(0, 100, 0, 500, 250))
|
18
|
+
assert_equal(127, @listener.interpolate(0, 127, 0, 100, 100))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_file_to_pan
|
22
|
+
assert_equal(0, @listener.file_to_pan(0))
|
23
|
+
assert_equal(127, @listener.file_to_pan(7))
|
24
|
+
assert_equal(63, @listener.file_to_pan(3.5))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_rank_to_pan
|
28
|
+
assert_equal(10, @listener.rank_to_volume(0)) # 10 is minimum volume
|
29
|
+
assert_equal(10, @listener.rank_to_volume(7))
|
30
|
+
assert_equal(127, @listener.rank_to_volume(3.5))
|
31
|
+
end
|
32
|
+
end
|
data/test/test_piece.rb
CHANGED
@@ -73,6 +73,7 @@ class PieceTest < Test::Unit::TestCase
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def test_pawn_en_passant
|
76
|
+
# TODO
|
76
77
|
# Warning: en passant is not handled yet by the code
|
77
78
|
end
|
78
79
|
|
@@ -92,9 +93,23 @@ class PieceTest < Test::Unit::TestCase
|
|
92
93
|
end
|
93
94
|
|
94
95
|
def test_knight_could_move_to
|
96
|
+
knight = @board.at(Square.new('b1')) # white knight
|
97
|
+
assert_equal(:N, knight.piece)
|
98
|
+
assert(knight.could_perform_move(Move.new(:white, 'Nc3')))
|
99
|
+
assert(knight.could_perform_move(Move.new(:white, 'Na3')))
|
100
|
+
assert(!knight.could_perform_move(Move.new(:white, 'Nb3')))
|
101
|
+
|
102
|
+
knight = @board.at(Square.new('b8')) # black knight
|
103
|
+
knight.move_to(Square.new('d4'))
|
104
|
+
assert_not_nil(@board.at(Square.new('c2')))
|
105
|
+
assert_not_nil(@board.at(Square.new('e2')))
|
106
|
+
%w(c2 e2 c6 e6 b3 b5 f3 f5).each { | sq |
|
107
|
+
assert(knight.could_perform_move(Move.new(:black, 'N' + sq)))
|
108
|
+
}
|
95
109
|
end
|
96
110
|
|
97
111
|
def test_bishop_could_move_to
|
112
|
+
bishop = @board.at(Square.new('c1'))
|
98
113
|
end
|
99
114
|
|
100
115
|
def test_queen_could_move_to
|
data/test/test_square.rb
CHANGED
@@ -9,12 +9,12 @@ class SquareTest < Test::Unit::TestCase
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_equals
|
12
|
-
s1 = Square.
|
12
|
+
s1 = Square.at(3, 4)
|
13
13
|
s2 = Square.new('d5')
|
14
14
|
assert_equal(s1, s2)
|
15
15
|
assert(s1 == s2)
|
16
16
|
|
17
|
-
s3 = Square.
|
17
|
+
s3 = Square.at(0, 0)
|
18
18
|
assert(s1 != s3)
|
19
19
|
end
|
20
20
|
|
@@ -29,7 +29,7 @@ class SquareTest < Test::Unit::TestCase
|
|
29
29
|
assert_nil(s.rank)
|
30
30
|
assert_nil(s.color)
|
31
31
|
|
32
|
-
s = Square.
|
32
|
+
s = Square.at(1, 3)
|
33
33
|
assert_equal(1, s.file)
|
34
34
|
assert_equal(3, s.rank)
|
35
35
|
assert_equal('b4', s.to_s) # Just making sure I know what color should be
|
@@ -66,11 +66,15 @@ class SquareTest < Test::Unit::TestCase
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
def test_at
|
70
|
+
assert_equal(Square.at(3, 3), Square.new('d4'))
|
71
|
+
end
|
72
|
+
|
69
73
|
def test_on_board
|
70
74
|
s = Square.new
|
71
75
|
assert(!s.on_board?)
|
72
76
|
|
73
|
-
s = Square.
|
77
|
+
s = Square.at(1, 3)
|
74
78
|
assert(s.on_board?)
|
75
79
|
|
76
80
|
s1 = Square.new(s)
|
@@ -90,7 +94,7 @@ class SquareTest < Test::Unit::TestCase
|
|
90
94
|
s = Square.new
|
91
95
|
assert_equal("<off-board>", s.to_s)
|
92
96
|
|
93
|
-
s = Square.
|
97
|
+
s = Square.at(1, 3)
|
94
98
|
assert_equal("b4", s.to_s)
|
95
99
|
|
96
100
|
s1 = Square.new(s)
|
@@ -106,4 +110,15 @@ class SquareTest < Test::Unit::TestCase
|
|
106
110
|
assert_equal("<off-board>", s.to_s)
|
107
111
|
end
|
108
112
|
|
113
|
+
def test_distance_to
|
114
|
+
assert_nil(Square.new.distance_to(Square.new))
|
115
|
+
assert_nil(Square.new.distance_to(Square.new('a4')))
|
116
|
+
assert_nil(Square.new('a4').distance_to(Square.new))
|
117
|
+
s = Square.new('b3')
|
118
|
+
assert_equal(1, s.distance_to(Square.new('b4')))
|
119
|
+
assert_equal(Math.sqrt(2), s.distance_to(Square.new('c4')))
|
120
|
+
assert_equal(4, s.distance_to(Square.new('f3')))
|
121
|
+
assert_equal(4, s.distance_to(Square.new('b7')))
|
122
|
+
end
|
123
|
+
|
109
124
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.8
|
|
3
3
|
specification_version: 1
|
4
4
|
name: bangkok
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2005-03-
|
6
|
+
version: 0.1.2
|
7
|
+
date: 2005-03-26
|
8
8
|
summary: Chess game file reader and player; can turn games into MIDI files
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -48,6 +48,8 @@ files:
|
|
48
48
|
- lib/bangkok/piece.rb
|
49
49
|
- lib/bangkok/square.rb
|
50
50
|
- test/mock_game_listener.rb
|
51
|
+
- test/test_board.rb
|
52
|
+
- test/test_gamelistener.rb
|
51
53
|
- test/test_piece.rb
|
52
54
|
- test/test_square.rb
|
53
55
|
test_files: []
|