twitch_plays 1.0.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59c3c777cdfd0a165cb02b9989c99e11fc393235
4
- data.tar.gz: 0fe695162142a9206d2c704d2ddef4b1fdcc7ca9
3
+ metadata.gz: 7f5eff287aae3d3f6221c28b55737e048175a81c
4
+ data.tar.gz: 599ed21f42926111ac564d491bb9ebbab2a781f5
5
5
  SHA512:
6
- metadata.gz: 6d23154385b836e69d92fa9a912226fc3733e69488c402ed113b3c55e7909fb852afc728396d4e7ffb08973a467cd18be53ea26842b430e67554479d412ad305
7
- data.tar.gz: c4ed7029795388bb91df1422b137d7694093327695213b4036264895500136625893dd93d02723d8e2bd7259002acd4306e1a89850370e46ba791e4a1ab416e3
6
+ metadata.gz: 8db97deb045cbe09dc0e1eecea0b0747034d1c0eec1f2d86b505d86a91649454b25a12a24e2ddd4c1875bbffc9cbe168b35c01faf510b1ab24cce1ae6c24b0f8
7
+ data.tar.gz: 80e19829f0c751bb97d2094cbc1fae45c8df52fbbaf4cfff29f2f9a1976dc69792f96ce01fc6129a36a98c21b89f6a5670afbffa1fd13aa108f7fa5e1aaa2cd2
data/README.md CHANGED
@@ -3,6 +3,12 @@ twitch_plays
3
3
 
4
4
  Yet another Twitch Plays Pokemon clone. MMO-ify any game over IRC.
5
5
 
6
+ Prerequisites
7
+ -------------
8
+
9
+ Requires xdotool on Linux.
10
+
11
+
6
12
  Configuration
7
13
  -------------
8
14
 
data/config.yml CHANGED
@@ -1,11 +1,13 @@
1
1
  # CONNECTION INFO
2
- :nick: TwitchPlaysSomething # same as Twitch username
3
- :password: # get one at http://twitchapps.com/tmi/
2
+ :nick: twitchplaysbot # Twitch username in all lowercase
3
+ :password: # Get one at http://twitchapps.com/tmi/
4
4
  :server: irc.twitch.tv
5
5
  :port: 6667
6
- :channel: '#TwitchPlaysSomething' # Twitch channel to listen on
6
+ :channel: '#twitchplaysbot' # Twitch channel to listen on, all lowercase
7
7
 
8
8
  :log_line_length: 35 # Enough to fit a twitch username, a space, and "democracy"
9
+ :use_touch: true
10
+ :touch_move_amount: 50 # How many pixels to move the mouse when a touchscreen move is performed
9
11
 
10
12
  # KEYMAP
11
13
  # key names are as in X's keysymdef.h, without the leading XK_
@@ -15,14 +17,14 @@
15
17
  :down: Down
16
18
  :left: Left
17
19
  :right: Right
18
- :a: c
19
- :b: x
20
+ :a: d
21
+ :b: c
20
22
  :x: s
21
23
  :y: x
22
24
  :l: z
23
25
  :r: a
24
26
  :start: Return
25
- :select: space
27
+ :select: Tab
26
28
  :save_state: F1
27
29
 
28
30
  # DEMOCRACY
@@ -0,0 +1,151 @@
1
+ require 'ffi'
2
+
3
+ module TwitchPlays
4
+ module OS
5
+ module Win
6
+ module User32
7
+ extend FFI::Library
8
+
9
+ INPUT_MOUSE = 0x00
10
+ INPUT_KEYBOARD = 0x01
11
+
12
+ MOUSEEVENTF_MOVE = 0x01
13
+ MOUSEEVENTF_LEFTDOWN = 0x02
14
+ MOUSEEVENTF_LEFTUP = 0x04
15
+
16
+ VK_BACK = 0x08
17
+ VK_TAB = 0x09
18
+ VK_RETURN = 0x0d
19
+ VK_SHIFT = 0x10
20
+ VK_CONTROL = 0x11
21
+ VK_SPACE = 0x20
22
+ VK_LEFT = 0x25
23
+ VK_UP = 0x26
24
+ VK_RIGHT = 0x27
25
+ VK_DOWN = 0x28
26
+ VK_F1 = 0x70
27
+ VK_F2 = 0x71
28
+ VK_F3 = 0x72
29
+ VK_F4 = 0x73
30
+ VK_F5 = 0x74
31
+ VK_F6 = 0x75
32
+ VK_F7 = 0x76
33
+ VK_F8 = 0x77
34
+ VK_F9 = 0x78
35
+ VK_F10 = 0x79
36
+ VK_F11 = 0x7a
37
+ VK_F12 = 0x7b
38
+
39
+ class MouseInput < FFI::Struct
40
+ layout(:dx, :long,
41
+ :dy, :long,
42
+ :mouse_data, :uint,
43
+ :flags, :uint,
44
+ :time, :uint,
45
+ :extra_info, :pointer)
46
+ end
47
+
48
+ class KeyboardInput < FFI::Struct
49
+ layout(:vk, :ushort,
50
+ :scan, :ushort,
51
+ :flags, :uint,
52
+ :time, :uint,
53
+ :extra_info, :pointer)
54
+ end
55
+
56
+ class InputEvent < FFI::Union
57
+ layout(:mi, MouseInput,
58
+ :ki, KeyboardInput)
59
+ end
60
+
61
+ class Input < FFI::Struct
62
+ layout(:type, :uint,
63
+ :evt, InputEvent)
64
+ end
65
+
66
+ ffi_lib 'user32'
67
+ ffi_convention :stdcall
68
+
69
+ attach_function :SendInput, [:uint, :pointer, :int], :uint
70
+ end
71
+
72
+ TRANSLATE_KEYS = {
73
+ 'BackSpace' => User32::VK_BACK,
74
+ 'Tab' => User32::VK_TAB,
75
+ 'Return' => User32::VK_RETURN,
76
+ 'Shift_L' => User32::VK_SHIFT,
77
+ 'Shift_R' => User32::VK_SHIFT,
78
+ 'Control_L' => User32::VK_CONTROL,
79
+ 'Control_R' => User32::VK_CONTROL,
80
+ 'space' => User32::VK_SPACE,
81
+ 'Left' => User32::VK_LEFT,
82
+ 'Up' => User32::VK_UP,
83
+ 'Right' => User32::VK_RIGHT,
84
+ 'Down' => User32::VK_DOWN,
85
+ 'F1' => User32::VK_F1,
86
+ 'F2' => User32::VK_F2,
87
+ 'F3' => User32::VK_F3,
88
+ 'F4' => User32::VK_F4,
89
+ 'F5' => User32::VK_F5,
90
+ 'F6' => User32::VK_F6,
91
+ 'F7' => User32::VK_F7,
92
+ 'F8' => User32::VK_F8,
93
+ 'F9' => User32::VK_F9,
94
+ 'F10' => User32::VK_F10,
95
+ 'F11' => User32::VK_F11,
96
+ 'F12' => User32::VK_F12
97
+ }
98
+
99
+ def self.press(key)
100
+ input = User32::Input.new
101
+ input[:type] = User32::INPUT_KEYBOARD
102
+ evt = input[:evt][:ki]
103
+ evt[:vk] = TRANSLATE_KEYS[key] || key.upcase.ord
104
+ evt[:scan] = 0
105
+ evt[:flags] = 0
106
+ evt[:time] = 0
107
+ evt[:extra_info] = 0
108
+ User32.SendInput(1, [input], User32::Input.size)
109
+ end
110
+
111
+ def self.touch_move(dx, dy)
112
+ input = User32::Input.new
113
+ input[:type] = User32::INPUT_MOUSE
114
+ evt = input[:evt][:mi]
115
+ evt[:dx] = dx
116
+ evt[:dy] = dy
117
+ evt[:mouse_data] = 0
118
+ evt[:flags] = User32::MOUSEEVENTF_MOVE
119
+ evt[:time] = 0
120
+ evt[:extra_info] = 0
121
+ User32.SendInput(1, [input], User32::Input.size)
122
+ end
123
+
124
+ def self.touch_press
125
+ input = User32::Input.new
126
+ input[:type] = User32::INPUT_MOUSE
127
+ evt = input[:evt][:mi]
128
+ evt[:dx] = 0
129
+ evt[:dy] = 0
130
+ evt[:mouse_data] = 0
131
+ evt[:flags] = User32::MOUSEEVENTF_LEFTDOWN
132
+ evt[:time] = 0
133
+ evt[:extra_info] = 0
134
+ User32.SendInput(1, [input], User32::Input.size)
135
+ end
136
+
137
+ def self.touch_release
138
+ input = User32::Input.new
139
+ input[:type] = User32::INPUT_MOUSE
140
+ evt = input[:evt][:mi]
141
+ evt[:dx] = 0
142
+ evt[:dy] = 0
143
+ evt[:mouse_data] = 0
144
+ evt[:flags] = User32::MOUSEEVENTF_LEFTUP
145
+ evt[:time] = 0
146
+ evt[:extra_info] = 0
147
+ User32.SendInput(1, [input], User32::Input.size)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,21 @@
1
+ module TwitchPlays
2
+ module OS
3
+ module X11
4
+ def self.press(key)
5
+ `xdotool key #{key}`
6
+ end
7
+
8
+ def self.touch_move(dx, dy)
9
+ `xdotool mousemove_relative --sync -- #{dx} #{dy}`
10
+ end
11
+
12
+ def self.touch_press
13
+ `xdotool mousedown 1`
14
+ end
15
+
16
+ def self.touch_release
17
+ `xdotool mouseup 1`
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module TwitchPlays
2
+ case RbConfig::CONFIG['host_os']
3
+ when /win/
4
+ require_relative 'os/win'
5
+ Output = OS::Win
6
+ when /linux/
7
+ require_relative 'os/x11'
8
+ Output = OS::X11
9
+ end
10
+ end
@@ -1,100 +1,30 @@
1
+ require_relative 'os'
1
2
  require 'yaml'
2
- require 'ffi' if RbConfig::CONFIG['host_os'] =~ /win/
3
3
  require 'cinch'
4
4
 
5
5
  module TwitchPlays
6
- if RbConfig::CONFIG['host_os'] =~ /win/
7
- module Win
8
- extend FFI::Library
9
-
10
- INPUT_KEYBOARD = 1
11
-
12
- VK_BACK = 0x08
13
- VK_TAB = 0x09
14
- VK_RETURN = 0x0d
15
- VK_SHIFT = 0x10
16
- VK_CONTROL = 0x11
17
- VK_SPACE = 0x20
18
- VK_LEFT = 0x25
19
- VK_UP = 0x26
20
- VK_RIGHT = 0x27
21
- VK_DOWN = 0x28
22
- VK_F1 = 0x70
23
- VK_F2 = 0x71
24
- VK_F3 = 0x72
25
- VK_F4 = 0x73
26
- VK_F5 = 0x74
27
- VK_F6 = 0x75
28
- VK_F7 = 0x76
29
- VK_F8 = 0x77
30
- VK_F9 = 0x78
31
- VK_F10 = 0x79
32
- VK_F11 = 0x7a
33
- VK_F12 = 0x7b
34
-
35
- class KeyboardInput < FFI::Struct
36
- layout(:vk, :ushort,
37
- :scan, :ushort,
38
- :flags, :uint,
39
- :time, :uint,
40
- :extra_info, :pointer)
41
- end
42
-
43
- class InputEvent < FFI::Union
44
- layout :ki, KeyboardInput
45
-
46
- class Input < FFI::Struct
47
- layout(:type, :uint,
48
- :evt, InputEvent)
49
- end
50
-
51
- ffi_lib 'user32'
52
- ffi_convention :stdcall
53
-
54
- attach_function :SendInput, [:uint, :pointer, :int], :uint
55
- end
56
- end
57
-
58
- TRANSLATE_KEYS = {
59
- 'BackSpace' => Win::VK_BACK,
60
- 'Tab' => Win::VK_TAB,
61
- 'Return' => Win::VK_RETURN,
62
- 'Shift_L' => Win::VK_SHIFT,
63
- 'Shift_R' => Win::VK_SHIFT,
64
- 'Control_L' => Win::VK_CONTROL,
65
- 'Control_R' => Win::VK_CONTROL,
66
- 'space' => Win::VK_SPACE,
67
- 'Left' => Win::VK_LEFT,
68
- 'Up' => Win::VK_UP,
69
- 'Right' => Win::VK_RIGHT,
70
- 'Down' => Win::VK_DOWN,
71
- 'F1' => Win::VK_F1,
72
- 'F2' => Win::VK_F2,
73
- 'F3' => Win::VK_F3,
74
- 'F4' => Win::VK_F4,
75
- 'F5' => Win::VK_F5,
76
- 'F6' => Win::VK_F6,
77
- 'F7' => Win::VK_F7,
78
- 'F8' => Win::VK_F8,
79
- 'F9' => Win::VK_F9,
80
- 'F10' => Win::VK_F10,
81
- 'F11' => Win::VK_F11,
82
- 'F12' => Win::VK_F12
83
- }
84
- end
85
-
86
6
  class Plugin
87
7
  include Cinch::Plugin
88
8
 
9
+ plugin_name = 'TwitchPlaysPlugin'
89
10
  listen_to :connect, method: :on_connect
90
11
  listen_to :message, method: :on_message
91
12
 
92
13
  def initialize(*args)
93
14
  super
94
15
  @keys = config[:keys]
16
+ @use_touch = config[:use_touch]
17
+ touch_move_amount = config[:touch_move_amount]
18
+ @touch_directions = {
19
+ touch_up: [0, -touch_move_amount],
20
+ touch_down: [0, touch_move_amount],
21
+ touch_left: [-touch_move_amount, 0],
22
+ touch_right: [touch_move_amount, 0]
23
+ }
95
24
  @democracy_votes = 0
96
25
  @anarchy_votes = 0
97
- @democracy_mode = config[:start_in_democracy]
26
+ @use_democracy = config[:use_democracy]
27
+ @democracy_mode = @use_democracy && config[:start_in_democracy]
98
28
  @democracy_ratio_needed = config[:democracy_ratio_needed]
99
29
  @anarchy_ratio_needed = config[:anarchy_ratio_needed]
100
30
  @button_votes = Hash.new(0)
@@ -113,66 +43,60 @@ module TwitchPlays
113
43
  end
114
44
 
115
45
  def start_democracy
46
+ return if @democracy_mode
116
47
  @democracy_mode = true
117
48
  puts '---STARTING DEMOCRACY MODE---'
118
49
  @voting_thread = Thread.new do
119
50
  while @democracy_mode
120
- @button_votes.clear
121
- @total_votes = 0
51
+ synchronize(:votes_mutex) do
52
+ @button_votes.clear
53
+ @total_votes = 0
54
+ end
122
55
  sleep @voting_time
123
- next if @total_votes == 0
124
- out = 'VOTES:'
125
- percentages = @button_votes.sort {|(_, count1), (_, count2)|
126
- count1 <=> count2
127
- }.first(5).map {|(btn, count)|
128
- [btn, count.to_f / @total_votes * 100]
129
- }.each {|(btn, percent)|
130
- out << format_log_line(btn, "#{percent}%")
131
- }
132
- puts out
133
- winner = @button_votes.max_by {|(_, count)| count}
134
- next if winner.nil?
135
- press(@keys[winner[0]])
56
+ next if @total_votes.zero?
57
+ winner = synchronize(:votes_mutex) do
58
+ puts "VOTES:\n" + @button_votes.sort {|(_, count1), (_, count2)|
59
+ count1 <=> count2
60
+ }.first(5).map {|(btn, count)|
61
+ [btn, count.to_f / @total_votes * 100]
62
+ }.map {|(btn, percent)|
63
+ format_log_line(btn, "#{percent}%")
64
+ }.join("\n")
65
+ @button_votes.max_by {|(_, count)| count}[0]
66
+ end
67
+ case winner
68
+ when :touch_up, :touch_down, :touch_left, :touch_right
69
+ Output.touch_move(*@touch_directions[winner])
70
+ when :touch_press
71
+ Output.touch_press
72
+ when :touch_release
73
+ Output.touch_release
74
+ else
75
+ Output.press(@keys[winner])
76
+ end
136
77
  end
137
78
  end
138
79
  end
139
80
 
140
81
  def start_anarchy
82
+ return unless @democracy_mode
141
83
  @democracy_mode = false
142
84
  @voting_thread.join
85
+ @voting_thread = nil
143
86
  puts '---STARTING ANARCHY MODE---'
144
87
  end
145
88
 
146
- case RbConfig::CONFIG['host_os']
147
- when /linux/
148
- def press(key)
149
- `xdotool key #{key}`
150
- end
151
- when /win/
152
- def press(key)
153
- input = Win::Input.new
154
- input[:type] = Win::INPUT_KEYBOARD
155
- evt = input[:evt][:ki]
156
- evt.vk = TRANSLATE_KEYS[key] || key.upcase.ord
157
- evt.scan = 0
158
- evt.flags = 0
159
- evt.time = 0
160
- evt.extra_info = 0
161
- Win.SendInput(1, [input], Win::Input.size)
162
- end
163
- end
164
-
165
89
  def format_log_line(name, command)
166
90
  "#{name}#{' ' * (@log_line_length - name.length - command.length)}#{command}"
167
91
  end
168
92
 
169
93
  def on_connect(msg)
170
- if @use_savestates
171
- @savestate_thread = Thread.new do
172
- loop do
173
- sleep @savestate_interval
174
- press(@keys[:save_state])
175
- end
94
+ return unless @use_savestates
95
+ @savestate_thread = Thread.new do
96
+ key = @keys[:save_state]
97
+ loop do
98
+ sleep @savestate_interval
99
+ Output.press(key)
176
100
  end
177
101
  end
178
102
  end
@@ -181,26 +105,69 @@ module TwitchPlays
181
105
  message = msg.message
182
106
  case message
183
107
  when /^anarchy$/i
108
+ return unless @use_democracy
184
109
  puts format_log_line(msg.user.nick, message.downcase)
185
- @anarchy_votes += 1
186
- if @democracy_mode && calculate_ratio <= @anarchy_ratio_needed
187
- start_anarchy
110
+ synchronize(:votes_mutex) do
111
+ @anarchy_votes += 1
112
+ if @democracy_mode && calculate_ratio <= @anarchy_ratio_needed
113
+ start_anarchy
114
+ end
188
115
  end
189
116
  when /^democracy$/i
117
+ return unless @use_democracy
190
118
  puts format_log_line(msg.user.nick, message.downcase)
191
- @democracy_votes += 1
192
- if !@democracy_mode && calculate_ratio >= @democracy_ratio_needed
193
- start_democracy
119
+ synchronize(:votes_mutex) do
120
+ @democracy_votes += 1
121
+ if !@democracy_mode && calculate_ratio >= @democracy_ratio_needed
122
+ start_democracy
123
+ end
194
124
  end
195
- when /^up|down|left|right|a|b|x|y|l|r|start|select$/i
125
+ when /^(?:up|down|left|right|a|b|x|y|l|r|start|select)$/i
196
126
  btn = $&.to_sym
127
+ return unless @keys.has_key?(btn)
128
+ if @democracy_mode
129
+ synchronize(:votes_mutex) do
130
+ @total_votes += 1
131
+ @button_votes[btn] += 1
132
+ end
133
+ return
134
+ end
135
+ puts format_log_line(msg.user.nick, message.downcase)
136
+ Output.press(@keys[btn])
137
+ when /^(?:touch_up|touch_down|touch_left|touch_right)$/i
138
+ return unless @use_touch
139
+ btn = $&.to_sym
140
+ if @democracy_mode
141
+ synchronize(:votes_mutex) do
142
+ @total_votes += 1
143
+ @button_votes[btn] += 1
144
+ end
145
+ return
146
+ end
147
+ puts format_log_line(msg.user.nick, message.downcase)
148
+ Output.touch_move(*@touch_directions[btn])
149
+ when /^touch_press$/i
150
+ return unless @use_touch
197
151
  if @democracy_mode
198
- @total_votes += 1
199
- @button_votes[btn] += 1
152
+ synchronize(:votes_mutex) do
153
+ @total_votes += 1
154
+ @button_votes[$&.to_sym] += 1
155
+ end
200
156
  return
201
157
  end
202
- puts format_log_line(msg.user.nick, $&.downcase)
203
- press(@keys[btn])
158
+ puts format_log_line(msg.user.nick, message.downcase)
159
+ Output.touch_press
160
+ when /^touch_release$/i
161
+ return unless @use_touch
162
+ if @democracy_mode
163
+ synchronize(:votes_mutex) do
164
+ @total_votes += 1
165
+ @button_votes[$&.to_sym] += 1
166
+ end
167
+ return
168
+ end
169
+ puts format_log_line(msg.user.nick, message.downcase)
170
+ Output.touch_release
204
171
  end
205
172
  end
206
173
  end
@@ -218,6 +185,9 @@ module TwitchPlays
218
185
  c.plugins.options[Plugin] = config
219
186
  end
220
187
  end
188
+ trap('INT') do
189
+ bot.quit
190
+ end
221
191
  bot.start
222
192
  end
223
193
  end
@@ -1,3 +1,3 @@
1
1
  module TwitchPlays
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twitch_plays
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Steward
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-14 00:00:00.000000000 Z
11
+ date: 2014-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cinch
@@ -51,6 +51,9 @@ files:
51
51
  - bin/twitch_plays~
52
52
  - config.yml
53
53
  - lib/twitch_plays.rb
54
+ - lib/twitch_plays/os.rb
55
+ - lib/twitch_plays/os/win.rb
56
+ - lib/twitch_plays/os/x11.rb
54
57
  - lib/twitch_plays/twitch_plays.rb
55
58
  - lib/twitch_plays/version.rb
56
59
  homepage: https://github.com/filialpails/twitch_plays
@@ -73,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
76
  version: '0'
74
77
  requirements: []
75
78
  rubyforge_project:
76
- rubygems_version: 2.2.2
79
+ rubygems_version: 2.3.0
77
80
  signing_key:
78
81
  specification_version: 4
79
82
  summary: Yet another Twitch Plays Pokemon clone.