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 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: []