launchpad_mk2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66358055a34d22fc58af54828d732c74d489e88e
4
+ data.tar.gz: 6f4a31bcb5eca94c4959d00d4e962817dd022880
5
+ SHA512:
6
+ metadata.gz: cc52a3b22776aa47684998163e02c5661da310fc2d397fe475f17e40b376fb072930a7c8f1691f0a752280d9487a4232867519ac3b048b7edd5853500ec1e859
7
+ data.tar.gz: 0018de9688efec8d10ccdf5a9b96a2c32c4d7ea33e1faf34f3e4646d83dda698b4eaf903a69f8ef573d856ce3f5495bb94468fd1a294149d685754674afb05b6
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ - 2.0.0
5
+ before_install:
6
+ - sudo apt-get update
7
+ - sudo apt-get install libportmidi-dev
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in launchpad.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2017 Andy Marks
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ = launchpad
2
+
3
+ {<img src="https://travis-ci.org/andeemarks/launchpad.png?branch=master" alt="Build Status" />}[https://travis-ci.org/andeemarks/launchpad]
4
+
5
+ [https://github.com/andeemarks/launchpad/blob/master/launchpad-gem-overview.gif]
6
+
7
+ This gem provides a Ruby interface to access the {Novation Launchpad MK2}[https://global.novationmusic.com/launch/launchpad#] programmatically. The code started life as a clone of {Thomas Jachman's gem}[https://github.com/thomasjachmann/launchpad] and was subsequently updated to handle the MK2 version of the Launchpad. The mapping of buttons and specification of colours completely changed when the MK2 was released, so lots of the original code in these areas has been re-written, but the interaction code is still largely intact.
8
+
9
+ == More Info
10
+
11
+ * Novation's Launchpad MK2 MIDI programmer's reference {here}[https://global.novationmusic.com/sites/default/files/novation/downloads/10529/launchpad-mk2-programmers-reference-guide_0.pdf] was the sole source for helping me understand how to interact with the Launchpad.
12
+
13
+ * Due to limitations in the Portmidi gem, it's not possible to specify which channel is used to send MIDI messages. As such, all interaction with the Launchpad are done over the default channel 1. This means some workarounds have been needed to access functionality like flash and pulse, which usually require channel 2 messages. All of these workarounds have been implemented via sending {System Exclusive messages}[http://electronicmusic.wikia.com/wiki/System_exclusive] as per the above reference guide.
14
+
15
+ == Requirements
16
+
17
+ * Roger B. Dannenberg's {portmidi library}[http://sourceforge.net/projects/portmedia/]
18
+ * Jan Krutisch's {portmidi gem}[http://github.com/halfbyte/portmidi]
19
+
20
+ == Compatibility
21
+
22
+ The gem is known to be compatible with the following ruby versions:
23
+
24
+ * MRI 2.3.1
25
+
26
+ == Installation
27
+
28
+ The gem is hosted on RubyGems[https://rubygems.org/], so in order to use it, you're gonna gem install it:
29
+
30
+ gem install launchpad
31
+
32
+
33
+ == Usage
34
+
35
+ There are two main entry points:
36
+
37
+ * <code>require 'launchpad/device'</code>, providing <code>Launchpad::Device</code>, which handles all the basic input/output stuff
38
+ * <code>require 'launchpad/interaction'</code> or just 'launchpad', additionally providing <code>Launchpad::Interaction</code>, which lets you respond to actions (button presses/releases)
39
+
40
+ This is a simple example (only requiring the device for output) that resets the launchpad and then lights the grid button at position 4/4 (from bottom left of 0/0).
41
+
42
+ require 'launchpad/device'
43
+
44
+ device = Launchpad::Device.new
45
+ sleep 1
46
+ device.reset_all
47
+ sleep 1
48
+ device.change :grid, :x => 4, :y => 4, :color => 72
49
+
50
+ This is an interaction example lighting all grid buttons in red when pressed and keeping them lit.
51
+
52
+ require 'launchpad'
53
+
54
+ interaction = Launchpad::Interaction.new
55
+ interaction.response_to(:grid, :down) do |interaction, action|
56
+ interaction.device.change(:grid, action.merge(:color => 72))
57
+ end
58
+ interaction.response_to(:mixer, :down) do |interaction, action|
59
+ interaction.stop
60
+ end
61
+
62
+ interaction.start
63
+
64
+
65
+ For more details, see the examples. examples/color_picker.rb is the most complex example with interaction.
66
+
67
+ == To Do
68
+
69
+ * Rename gem to avoid name clashes with original version
70
+ * Ensure all examples are working
71
+ * Update examples in README
72
+
73
+ == Copyright
74
+
75
+ Copyright (c) 2017 Andy Marks. See LICENSE for details.
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,41 @@
1
+ require 'launchpad_mk2'
2
+
3
+ device = Launchpad::Device.new
4
+
5
+ on = { :color => 16 }
6
+ off = { :color => 72 }
7
+
8
+ digit_map = [
9
+ [off, off, off, off],
10
+ [on , off, off, off],
11
+ [off, on , off, off],
12
+ [on , on , off, off],
13
+ [off, off, on , off],
14
+ [on , off, on , off],
15
+ [off, on , on , off],
16
+ [on , on , on , off],
17
+ [off, off, off, on ],
18
+ [on , off, off, on ]
19
+ ]
20
+
21
+ def offset_x(x)
22
+ if (x == 0 or x == 1)
23
+ return x
24
+ end
25
+
26
+ if (x == 2 or x == 3)
27
+ return (x + 1)
28
+ end
29
+
30
+ return (x + 2)
31
+ end
32
+
33
+ while true do
34
+ Time.now.strftime('%H%M%S').split('').each_with_index do |digit, x|
35
+ digit_map[digit.to_i].each_with_index do |color, y|
36
+ device.change :grid, color.merge(:x => offset_x(x), :y => (y + 2))
37
+ end
38
+ end
39
+
40
+ sleep 0.25
41
+ end
@@ -0,0 +1,96 @@
1
+ require 'launchpad_mk2'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ # build color arrays for color display views
6
+ colors_single = [
7
+ [ 0, 1, 2, 3, 0, 0, 0, 0],
8
+ [16, 17, 18, 19, 0, 0, 0, 0],
9
+ [32, 33, 34, 35, 0, 0, 0, 0],
10
+ [48, 49, 50, 51, 0, 0, 0, 0],
11
+ [0] * 8,
12
+ [0] * 8,
13
+ [0] * 8,
14
+ [0] * 8,
15
+ [0] * 8
16
+ ]
17
+ colors_double = [
18
+ [ 0, 0, 1, 1, 2, 2, 3, 3],
19
+ [ 0, 0, 1, 1, 2, 2, 3, 3],
20
+ [16, 16, 17, 17, 18, 18, 19, 19],
21
+ [16, 16, 17, 17, 18, 18, 19, 19],
22
+ [32, 32, 33, 33, 34, 34, 35, 35],
23
+ [32, 32, 33, 33, 34, 34, 35, 35],
24
+ [48, 48, 49, 49, 50, 50, 51, 51],
25
+ [48, 48, 49, 49, 50, 50, 51, 51],
26
+ [0] * 8
27
+ ]
28
+ colors_mirrored = [
29
+ [ 0, 1, 2, 3, 3, 2, 1, 0],
30
+ [16, 17, 18, 19, 19, 18, 17, 16],
31
+ [32, 33, 34, 35, 35, 34, 33, 32],
32
+ [48, 49, 50, 51, 51, 50, 49, 48],
33
+ [48, 49, 50, 51, 51, 50, 49, 48],
34
+ [32, 33, 34, 35, 35, 34, 33, 32],
35
+ [16, 17, 18, 19, 19, 18, 17, 16],
36
+ [ 0, 1, 2, 3, 3, 2, 1, 0],
37
+ [0] * 8
38
+ ]
39
+
40
+ # setup color display views
41
+ def display_color_view(colors)
42
+ lambda do |interaction, action|
43
+ # set color
44
+ interaction.device.change_all(colors)
45
+ # register mute interactor on scene buttons
46
+ interaction.response_to(%w(scene1 scene2 scene3 scene4 scene5 scene6 scene7 scene8), :down, :exclusive => true, &@mute)
47
+ end
48
+ end
49
+ interaction.response_to(:up, :down, &display_color_view(colors_single + [48, 16, 16, 16]))
50
+ interaction.response_to(:down, :down, &display_color_view(colors_double + [16, 48, 16, 16]))
51
+ interaction.response_to(:left, :down, &display_color_view(colors_mirrored + [16, 16, 48, 16]))
52
+
53
+ # setup color picker view
54
+ def display_color(opts)
55
+ lambda do |interaction, action|
56
+ @red = opts[:red] if opts[:red]
57
+ @green = opts[:green] if opts[:green]
58
+ colors = [(@green * 16 + @red)] * 64
59
+ scenes = [@red == 3 ? 51 : 3, @red == 2 ? 51 : 2, @red == 1 ? 51 : 1, @red == 0 ? 51 : 0, @green == 3 ? 51 : 48, @green == 2 ? 51 : 32, @green == 1 ? 51 : 16, @green == 0 ? 51 : 0]
60
+ interaction.device.change_all(colors + scenes + [16, 16, 16, 48])
61
+ end
62
+ end
63
+ interaction.response_to(:right, :down) do |interaction, action|
64
+ @red = 0
65
+ @green = 0
66
+ # register color picker interactors on scene buttons
67
+ interaction.response_to(:scene1, :down, :exclusive => true, &display_color(:red => 3))
68
+ interaction.response_to(:scene2, :down, :exclusive => true, &display_color(:red => 2))
69
+ interaction.response_to(:scene3, :down, :exclusive => true, &display_color(:red => 1))
70
+ interaction.response_to(:scene4, :down, :exclusive => true, &display_color(:red => 0))
71
+ interaction.response_to(:scene5, :down, :exclusive => true, &display_color(:green => 3))
72
+ interaction.response_to(:scene6, :down, :exclusive => true, &display_color(:green => 2))
73
+ interaction.response_to(:scene7, :down, :exclusive => true, &display_color(:green => 1))
74
+ interaction.response_to(:scene8, :down, :exclusive => true, &display_color(:green => 0))
75
+ # display color
76
+ interaction.respond_to(:scene8, :down)
77
+ end
78
+
79
+ # mixer button terminates interaction on button up
80
+ interaction.response_to(:mixer) do |interaction, action|
81
+ interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
82
+ interaction.stop if action[:state] == :up
83
+ end
84
+
85
+ # setup mute display interactors on all unused buttons
86
+ @mute = display_color_view([0] * 72 + [16, 16, 16, 16])
87
+ interaction.response_to(%w(session user1 user2 grid), :down, &@mute)
88
+
89
+ # display mute view
90
+ interaction.respond_to(:session, :down)
91
+
92
+ # start interacting
93
+ interaction.start
94
+
95
+ # sleep so that the messages can be sent before the program terminates
96
+ sleep 0.1
@@ -0,0 +1,27 @@
1
+ require 'launchpad_mk2'
2
+
3
+ device = Launchpad::Device.new(:input => false, :output => true)
4
+
5
+ color = 0
6
+ # first page of colors
7
+ (0..7).each do |row|
8
+ (0..7).each do |column|
9
+ device.change :grid, :x => column, :y => row, :color => color
10
+ color = color + 1
11
+ end
12
+ end
13
+
14
+ sleep 2
15
+
16
+ # second page of colors
17
+ (0..7).each do |row|
18
+ (0..7).each do |column|
19
+ device.change :grid, :x => column, :y => row, :color => color
20
+ color = color + 1
21
+ end
22
+ end
23
+
24
+ # sleep so that the messages can be sent before the program terminates
25
+ sleep 2
26
+
27
+ device.reset_all()
@@ -0,0 +1,11 @@
1
+ require 'launchpad_mk2'
2
+
3
+ device = Launchpad::Device.new(:input => false, :output => true)
4
+
5
+ device.change :grid, :x => 0, :y => 0, :color => 56 # fuschia
6
+ device.change :grid, :x => 7, :y => 7, :color => 63 # olive
7
+ device.change :grid, :x => 0, :y => 7, :color => 72 # red
8
+ device.change :grid, :x => 7, :y => 0, :color => 8 # orange
9
+
10
+ # sleep so that the messages can be sent before the program terminates
11
+ sleep 0.1
@@ -0,0 +1,65 @@
1
+ require 'launchpad_mk2'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ current_color = {
6
+ :red => :hi,
7
+ :green => :hi,
8
+ :mode => :normal
9
+ }
10
+
11
+ def update_scene_buttons(d, color)
12
+ on = {:red => :hi, :green => :hi}
13
+ d.change(:scene1, color[:red] == :hi ? on : {:red => :hi})
14
+ d.change(:scene2, color[:red] == :med ? on : {:red => :med})
15
+ d.change(:scene3, color[:red] == :lo ? on : {:red => :lo})
16
+ d.change(:scene4, color[:red] == :off ? on : {:red => :off})
17
+ d.change(:scene5, color[:green] == :hi ? on : {:green => :hi})
18
+ d.change(:scene6, color[:green] == :med ? on : {:green => :med})
19
+ d.change(:scene7, color[:green] == :lo ? on : {:green => :lo})
20
+ d.change(:scene8, color[:green] == :off ? on : {:green => :off})
21
+ d.change(:user1, :green => color[:mode] == :normal ? :lo : :hi, :mode => :flashing)
22
+ d.change(:user2, :green => color[:mode] == :normal ? :hi : :lo)
23
+ end
24
+
25
+ def choose_color(color, opts)
26
+ lambda do |interaction, action|
27
+ color.update(opts)
28
+ update_scene_buttons(interaction.device, color)
29
+ end
30
+ end
31
+
32
+ # register color picker interactors on scene buttons
33
+ interaction.response_to(:scene1, :down, &choose_color(current_color, :red => :hi))
34
+ interaction.response_to(:scene2, :down, &choose_color(current_color, :red => :med))
35
+ interaction.response_to(:scene3, :down, &choose_color(current_color, :red => :lo))
36
+ interaction.response_to(:scene4, :down, &choose_color(current_color, :red => :off))
37
+ interaction.response_to(:scene5, :down, &choose_color(current_color, :green => :hi))
38
+ interaction.response_to(:scene6, :down, &choose_color(current_color, :green => :med))
39
+ interaction.response_to(:scene7, :down, &choose_color(current_color, :green => :lo))
40
+ interaction.response_to(:scene8, :down, &choose_color(current_color, :green => :off))
41
+
42
+ # register mode picker interactors on user buttons
43
+ interaction.response_to(:user1, :down, &choose_color(current_color, :mode => :flashing))
44
+ interaction.response_to(:user2, :down, &choose_color(current_color, :mode => :normal))
45
+
46
+ # update scene buttons and start flashing
47
+ update_scene_buttons(interaction.device, current_color)
48
+
49
+ # feedback for grid buttons
50
+ interaction.response_to(:grid, :down) do |interaction, action|
51
+ interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => current_color)
52
+ end
53
+
54
+ # mixer button terminates interaction on button up
55
+ interaction.response_to(:mixer) do |interaction, action|
56
+ interaction.device.change(:mixer, :color => action[:state] == :down ? 5 : 61)
57
+ interaction.stop if action[:state] == :up
58
+ interaction.device.reset_all()
59
+ end
60
+
61
+ # start interacting
62
+ interaction.start
63
+
64
+ # sleep so that the messages can be sent before the program terminates
65
+ sleep 0.1
@@ -0,0 +1,20 @@
1
+ require 'launchpad_mk2'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ # yellow feedback for grid buttons
6
+ interaction.response_to(:grid, :down) do |interaction, action|
7
+ interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => 13)
8
+ end
9
+
10
+ # mixer button terminates interaction on button up
11
+ interaction.response_to(:mixer) do |interaction, action|
12
+ interaction.device.change(:mixer, :color => action[:state] == :down ? 5 : 61)
13
+ interaction.stop if action[:state] == :up
14
+ end
15
+
16
+ # start interacting
17
+ interaction.start
18
+
19
+ # sleep so that the messages can be sent before the program terminates
20
+ sleep 0.1
@@ -0,0 +1,30 @@
1
+ require 'launchpad_mk2'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ # yellow feedback for grid buttons
6
+ interaction.response_to(:grid) do |interaction, action|
7
+ interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => 72)
8
+ end
9
+
10
+ # red feedback for top control buttons
11
+ interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
12
+ interaction.device.change(action[:type], :color => 13)
13
+ end
14
+
15
+ # green feedback for scene buttons
16
+ interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
17
+ interaction.device.change(action[:type], :color => 16)
18
+ end
19
+
20
+ # mixer button terminates interaction on button up
21
+ interaction.response_to(:mixer, :up) do |interaction, action|
22
+ interaction.device.reset_all()
23
+ interaction.stop
24
+ end
25
+
26
+ # start interacting
27
+ interaction.start
28
+
29
+ # sleep so that the messages can be sent before the program terminates
30
+ sleep 0.1
@@ -0,0 +1,193 @@
1
+ require 'launchpad_mk2'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ UP = -1
6
+ DOWN = 1
7
+
8
+ @left_bottom_edge = @right_bottom_edge = 3
9
+ @left_score = @right_score = 0
10
+
11
+ def move_flipper(flipper, d, delta, column)
12
+ flipper = flipper + delta
13
+ if (flipper < 0 || flipper > 5)
14
+ return flipper - delta
15
+ end
16
+ show_flipper(d, column, flipper)
17
+
18
+ return flipper
19
+ end
20
+
21
+ def move_left_flipper(d, delta)
22
+ @left_bottom_edge = move_flipper(@left_bottom_edge, d, delta, 0)
23
+ end
24
+
25
+ def move_right_flipper(d, delta)
26
+ @right_bottom_edge = move_flipper(@right_bottom_edge, d, delta, 7)
27
+ end
28
+
29
+ def show_flipper(d, x, bottom_edge)
30
+ d.light1_column(x, 0)
31
+ d.change(:grid, :x => x, :y => bottom_edge, :color => 16)
32
+ d.change(:grid, :x => x, :y => bottom_edge + 1, :color => 16)
33
+ d.change(:grid, :x => x, :y => bottom_edge + 2, :color => 16)
34
+ end
35
+
36
+ # yellow feedback for grid buttons
37
+ interaction.response_to(:grid, :down) do |interaction, action|
38
+ x = action[:x]
39
+ y = action[:y]
40
+ if (x == 0 && y == 0)
41
+ move_left_flipper(interaction.device, UP)
42
+ elsif (x == 0 && y == 7)
43
+ move_left_flipper(interaction.device, DOWN)
44
+ elsif (x == 7 && y == 0)
45
+ move_right_flipper(interaction.device, UP)
46
+ elsif (x == 7 && y == 7)
47
+ move_right_flipper(interaction.device, DOWN)
48
+ end
49
+ end
50
+
51
+ # red feedback for top control buttons
52
+ interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
53
+ interaction.device.change(action[:type], :color => 13)
54
+ end
55
+
56
+ # green feedback for scene buttons
57
+ interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
58
+ interaction.device.change(action[:type], :color => 16)
59
+ end
60
+
61
+ # mixer button terminates interaction on button up
62
+ interaction.response_to(:mixer, :up) do |interaction, action|
63
+ interaction.device.reset_all()
64
+ interaction.stop
65
+ end
66
+
67
+ def hits_left_flipper?(new_ball_y)
68
+ new_ball_y >= @left_bottom_edge && new_ball_y <= @left_bottom_edge + 2
69
+ end
70
+
71
+ def hits_right_flipper?(new_ball_y)
72
+ new_ball_y >= @right_bottom_edge && new_ball_y <= @right_bottom_edge + 2
73
+ end
74
+
75
+ def update_scores(d)
76
+ puts "Left score: " + @left_score.to_s
77
+ puts "Right score: " + @right_score.to_s
78
+ if (@left_score > 0)
79
+ scene_button = (":scene" + @left_score.to_s).to_sym
80
+ puts scene_button
81
+ d.change(scene_button, :color => 72)
82
+ end
83
+ if (@right_score > 0)
84
+ scene_button = (":scene" + (9 - @right_score).to_s).to_sym
85
+ puts scene_button
86
+ d.change(scene_button, :color => 72)
87
+ end
88
+ if (@left_score == 4 || @right_score == 4)
89
+ @player_wins = true
90
+ end
91
+ end
92
+
93
+ def approaching_left_gutter?(new_ball_x)
94
+ new_ball_x < 1
95
+ end
96
+
97
+ def approaching_right_gutter?(new_ball_x)
98
+ new_ball_x > 6
99
+ end
100
+
101
+ def record_score(d)
102
+ update_scores(d)
103
+ hide_ball(d, @ball_x, @ball_y)
104
+ end
105
+
106
+ def record_right_score(d)
107
+ puts "Right score with ball at " + @ball_x.to_s + ", " + @ball_y.to_s + ", and left flipper at " + @left_bottom_edge.to_s
108
+ @right_score = @right_score + 1
109
+ record_score(d)
110
+ end
111
+
112
+ def record_left_score(d)
113
+ puts "Left score with ball at " + @ball_x.to_s + ", " + @ball_y.to_s + ", and right flipper at " + @right_bottom_edge.to_s
114
+ @left_score = @left_score + 1
115
+ record_score(d)
116
+ end
117
+
118
+ def bounce?(new_ball_y)
119
+ new_ball_y < 0 || new_ball_y > 7
120
+ end
121
+
122
+ def run_ball_movement(d)
123
+ Thread.new {
124
+ @player_wins = false
125
+ until @player_wins do
126
+ new_score = false
127
+ start_ball(d)
128
+ until new_score do
129
+ sleep 0.5
130
+ if (approaching_left_gutter?(@ball_x + @lat_delta))
131
+ if (hits_left_flipper?(@ball_y + @lon_delta))
132
+ @lat_delta = -@lat_delta
133
+ move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
134
+ else
135
+ record_right_score(d)
136
+ new_score = true
137
+ end
138
+ elsif (approaching_right_gutter?(@ball_x + @lat_delta))
139
+ if (hits_right_flipper?(@ball_y + @lon_delta))
140
+ @lat_delta = -@lat_delta
141
+ move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
142
+ else
143
+ record_left_score(d)
144
+ new_score = true
145
+ end
146
+ end
147
+ if (!new_score)
148
+ if (bounce?(@ball_y + @lon_delta))
149
+ @lon_delta = -@lon_delta
150
+ end
151
+ move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
152
+ end
153
+ end
154
+ end
155
+ }
156
+ end
157
+
158
+ def move_ball(d, new_ball_x, new_ball_y)
159
+ hide_ball(d, @ball_x, @ball_y)
160
+ @ball_x = new_ball_x
161
+ @ball_y = new_ball_y
162
+ show_ball(d, @ball_x, @ball_y)
163
+ end
164
+
165
+ def show_ball(d, x, y)
166
+ d.change(:grid, :x => x, :y => y, :color => 72)
167
+ end
168
+
169
+ def hilite_collision(d, x, y)
170
+ d.change(:grid, :x => x, :y => y, :color => 34)
171
+ end
172
+
173
+ def hide_ball(d, x, y)
174
+ d.change(:grid, :x => x, :y => y, :color => 0)
175
+ end
176
+
177
+ def start_ball(d)
178
+ @lat_delta = rand(2) == 0 ? -1 : 1
179
+ @lon_delta = rand(2) == 0 ? -1 : 1
180
+ @ball_x = @lat_delta == -1 ? 4 : 3
181
+ @ball_y = @lon_delta == -1 ? 4 : 3
182
+ puts "Ball moving using " + @lat_delta.to_s + ", " + @lon_delta.to_s + " from " + @ball_x.to_s + ", " + @ball_y.to_s
183
+ show_ball(d, @ball_x, @ball_y)
184
+ end
185
+
186
+ show_flipper(interaction.device, 0, @left_bottom_edge)
187
+ show_flipper(interaction.device, 7, @right_bottom_edge)
188
+ run_ball_movement(interaction.device)
189
+ # start interacting
190
+ interaction.start
191
+
192
+ # sleep so that the messages can be sent before the program terminates
193
+ sleep 0.1