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 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
- - better way to specify configs/program numbers; file must not live in same
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
 
@@ -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.new(file, 0))
11
- @pieces << Piece.create(self, listener, :black, sym, Square.new(file, 7))
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.new(file, 1))
15
- @pieces << Piece.create(self, listener, :black, :P, Square.new(file, 6))
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.new(new_king_file, king.square.rank))
67
- rook.move_to(Square.new(new_rook_file, rook.square.rank))
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)
@@ -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
- @cached_quarter_delta = @seq.note_to_delta('quarter')
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
- @seq.tracks.each { | t | t.recalc_delta_from_times }
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
- if to.on_board?
117
- midi_for_position(piece, from)
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
- def midi_for_position(piece, square)
161
- track = track_of(piece)
162
- channel = channel_of(piece)
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
- # pan
165
- e = Controller.new(channel, CC_PAN, FILE_TO_PAN[square.file])
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
- # volume
170
- e = Controller.new(channel, CC_VOLUME, RANK_TO_VOL[square.rank])
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 on and off
175
- note = RANK_TO_NOTE[piece.color][square.rank]
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 + @cached_quarter_delta
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
- @time_from_start = e.time_from_start
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
@@ -1,6 +1,6 @@
1
1
  VERSION_MAJOR = 0
2
2
  VERSION_MINOR = 1
3
- VERSION_TWEAK = 1
3
+ VERSION_TWEAK = 2
4
4
 
5
5
  Version = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_TWEAK}"
6
6
  Copyright = 'Copyright (c) 2005 by Jim Menard <jimm@io.com>'
@@ -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.new(curr_file, curr_rank))
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.new(@square.file, @square.rank + 1)) &&
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.new(@square.file, @square.rank - 1)) &&
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.
@@ -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
@@ -1,22 +1,37 @@
1
1
  class MockGameListener
2
- def start_game
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
@@ -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
@@ -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
@@ -9,12 +9,12 @@ class SquareTest < Test::Unit::TestCase
9
9
  end
10
10
 
11
11
  def test_equals
12
- s1 = Square.new(3, 4)
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.new(0, 0)
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.new(1, 3)
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.new(1, 3)
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.new(1, 3)
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.1
7
- date: 2005-03-25
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: []