bangkok 0.1.1 → 0.1.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.
- 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: []
|