adamsanderson-lexery 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +91 -0
  2. data/README.markdown +55 -0
  3. data/bin/lexery +5 -0
  4. data/fonts/MuseoSans_500.otf +0 -0
  5. data/fonts/license_agreement.rtf +46 -0
  6. data/images/cursor.png +0 -0
  7. data/lib/exuberant/abstract_screen.rb +37 -0
  8. data/lib/exuberant/button.rb +51 -0
  9. data/lib/exuberant/colored.rb +13 -0
  10. data/lib/exuberant/container.rb +31 -0
  11. data/lib/exuberant/exuberant_game.rb +28 -0
  12. data/lib/exuberant/fade.rb +39 -0
  13. data/lib/exuberant/game_window.rb +34 -0
  14. data/lib/exuberant/gosu_support.rb +7 -0
  15. data/lib/exuberant/label.rb +40 -0
  16. data/lib/exuberant/move.rb +37 -0
  17. data/lib/exuberant/multi_transition.rb +17 -0
  18. data/lib/exuberant/positioned.rb +30 -0
  19. data/lib/exuberant/publisher.rb +43 -0
  20. data/lib/exuberant/support.rb +42 -0
  21. data/lib/exuberant/timer.rb +30 -0
  22. data/lib/exuberant/transition.rb +71 -0
  23. data/lib/lexery.rb +11 -0
  24. data/lib/lexery/colors.rb +9 -0
  25. data/lib/lexery/dictionary.rb +14 -0
  26. data/lib/lexery/fading_message.rb +22 -0
  27. data/lib/lexery/fps_counter.rb +19 -0
  28. data/lib/lexery/game.rb +32 -0
  29. data/lib/lexery/game_over_screen.rb +39 -0
  30. data/lib/lexery/game_rules.rb +51 -0
  31. data/lib/lexery/game_screen.rb +128 -0
  32. data/lib/lexery/layers.rb +5 -0
  33. data/lib/lexery/round.rb +17 -0
  34. data/lib/lexery/score_board.rb +31 -0
  35. data/lib/lexery/stats_screen.rb +46 -0
  36. data/lib/lexery/title_screen.rb +83 -0
  37. data/lib/lexery/word_control.rb +72 -0
  38. data/options.yml +9 -0
  39. data/script/console +5 -0
  40. data/sounds/README +6 -0
  41. data/sounds/bell.wav +0 -0
  42. data/sounds/chick.wav +0 -0
  43. data/sounds/click.wav +0 -0
  44. data/wordlists/README +11 -0
  45. data/wordlists/words.set +3611 -0
  46. data/wordlists/words.txt +178691 -0
  47. metadata +107 -0
@@ -0,0 +1,7 @@
1
+ module Gosu
2
+ class Color
3
+ def argb_components
4
+ [self.alpha, self.red, self.green, self.blue]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ class Label
2
+ attr_accessor :x, :y, :width, :height, :text, :color
3
+ include Positioned
4
+
5
+ def initialize(x,y, text=nil, options={}, &block)
6
+ super()
7
+ if text.is_a?(Hash)
8
+ options = text
9
+ text = ''
10
+ end
11
+
12
+ @x = x
13
+ @y = y
14
+ @options = options
15
+
16
+ @height = options[:height] || 32
17
+ @font = options[:font] || Game.load_font(:default, @height - 4)
18
+ @width = options[:width] || @font.text_width(@text) + 4
19
+
20
+ self.text = text
21
+ @block = block if block_given?
22
+
23
+ @color = options[:color] || Gosu::Color.new(0x66666666)
24
+ end
25
+
26
+ def text= new_text
27
+ if new_text != @text
28
+ @text = new_text
29
+ @width = @font.text_width(@text) + 4 unless @options[:width]
30
+ end
31
+ end
32
+
33
+ def update
34
+ self.text = @block.call if @block
35
+ end
36
+
37
+ def draw
38
+ @font.draw(text, x , y , Layers::UI, 1, 1, @color, :default)
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ class Move < MultiTransition
2
+ class << self
3
+ # Shorthand to create and start a move effect
4
+ def [] object, options={}
5
+
6
+ if options[:to]
7
+ x,y = options[:to]
8
+ elsif options[:by]
9
+ x,y = options[:by]
10
+ x += object.x
11
+ y += object.y
12
+ else
13
+ raise ArgumentError,"must specify :to, or :by as an option"
14
+ end
15
+
16
+ duration = options[:in] || 1000
17
+
18
+ Game.state.add self.new(object, x,y, duration, options)
19
+ end
20
+
21
+ def insert object, options={}
22
+ options[:before] = lambda{ Game.state.add(object) }.compose(options[:before])
23
+ self[object, options]
24
+ end
25
+
26
+ def remove object, options={}
27
+ options[:after] = lambda{ Game.state.remove(object) }.compose(options[:after])
28
+ self[object, options]
29
+ end
30
+ end
31
+
32
+ def initialize(object, dest_x,dest_y, duration=1000, options={})
33
+ super(duration, [object.x, object.y], [dest_x,dest_y], options ){|x,y|
34
+ object.x, object.y = x,y
35
+ }
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # Operates on multiple attributes at once
2
+ class MultiTransition < Transition
3
+ # Initialize with multiple start and end values, store them as a vector
4
+ def initialize(duration, start_values, end_values, options={}, &action)
5
+ super(duration, Vector[*start_values], Vector[*end_values], options, &action)
6
+ end
7
+
8
+ # Compute the change for each value
9
+ def compute_change(start_values, end_values)
10
+ (end_values - start_values).map{|v| v.to_f}
11
+ end
12
+
13
+ # Same as the normal apply, but unpack the vector
14
+ def apply(t)
15
+ @action.call(* tweening_function[t, @start_value, @change, @duration])
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ # Expects x,y,width,height
2
+ module Positioned
3
+ def mouse_within?
4
+ within? Game.window.mouse_x, Game.window.mouse_y
5
+ end
6
+
7
+ def top
8
+ y
9
+ end
10
+
11
+ def bottom
12
+ y + height
13
+ end
14
+
15
+ def left
16
+ x
17
+ end
18
+
19
+ def right
20
+ x + width
21
+ end
22
+
23
+ def within?(x,y)
24
+ left <= x and
25
+ top <= y and
26
+ right > x and
27
+ bottom > y
28
+
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ module Publisher
2
+ Subscriber = Struct.new :subscriber, :block
3
+
4
+ def subscribers
5
+ @subscribers ||= Hash.new{|h,k| h[k] = [] }
6
+ end
7
+
8
+ def subscribe(event, subscriber=nil, &block)
9
+ validate_event! event
10
+ subscribers[event] << Subscriber.new(subscriber, block)
11
+ end
12
+
13
+ def unsubscribe(event, subscriber=nil)
14
+ validate_event! event
15
+ subscribers[event].delete_if{|s| s.subscriber == subscriber}
16
+ end
17
+
18
+ def fire_event(event, *args)
19
+ validate_event! event
20
+ subscribers[event].each{|sub| sub.block.call(*args)}
21
+ end
22
+
23
+ def validate_event!(event)
24
+ raise "No such event '#{event}', should be one of #{self.class.events.inspect}" unless self.class.events.include? event
25
+ end
26
+
27
+ def self.included(base)
28
+ base.extend(ClassMethods)
29
+ end
30
+
31
+ module ClassMethods
32
+ def events(*events)
33
+ @pub_sub_events ||= []
34
+ if events.empty?
35
+ inherited_events = superclass.respond_to?(:events) ? superclass.send(:events) : []
36
+ [@pub_sub_events, inherited_events].flatten
37
+ else
38
+ @pub_sub_events.insert(0,*events)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # Ensure that classes are loaded on demand.
2
+ class Module
3
+ def const_missing(sym)
4
+ require sym.to_s.underscore
5
+ c = const_get(sym)
6
+ end
7
+ end
8
+
9
+ # String Helper, from ActiveSupport
10
+ class String
11
+ def underscore
12
+ self.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
13
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
14
+ tr("-", "_").
15
+ downcase
16
+ end
17
+
18
+ def get(index)
19
+ chars[index]
20
+ end
21
+
22
+ def chars
23
+ self.split(//)
24
+ end
25
+ end
26
+
27
+ class Range
28
+ def [] index
29
+ raise ArgumentError.new("Index #{index} must be between 0 and 1") if (index > 1 || index < 0)
30
+ (self.end - self.begin) * index + self.begin
31
+ end
32
+ end
33
+
34
+ class Proc
35
+ def compose(g)
36
+ return self if g.nil?
37
+ lambda { |*args| self[g[*args]] }
38
+ end
39
+ def *(g)
40
+ compose(self, g)
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ class Timer
2
+ include Publisher
3
+ events :ticked
4
+ attr_reader :ticks
5
+
6
+ def initialize(period, &action)
7
+ @period = period
8
+ @ticks = nil
9
+ subscribe(:ticked, self, &action) if block_given?
10
+ end
11
+
12
+ def start
13
+ @started = Gosu::milliseconds
14
+ end
15
+
16
+ def stop
17
+ @started = nil
18
+ end
19
+
20
+ def update
21
+ if @started
22
+ current_ticks = (Gosu::milliseconds - @started) / @period
23
+ if !@ticks || current_ticks > @ticks
24
+ @ticks = current_ticks
25
+ fire_event :ticked, current_ticks
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,71 @@
1
+ class Transition
2
+ def initialize(duration, start_value, end_value, options={}, &action)
3
+ @duration = duration.to_f
4
+ @start_value = start_value
5
+ @change = compute_change(start_value, end_value)
6
+ @options = options
7
+
8
+ @action = action
9
+
10
+ delay = options[:delay] || 0
11
+
12
+ before
13
+ @starts = Gosu::milliseconds + delay
14
+ end
15
+
16
+ def before
17
+ @options[:before].call if @options[:before]
18
+ end
19
+
20
+ def after
21
+ @options[:after].call if @options[:after]
22
+ Game.state.remove self
23
+ end
24
+
25
+ def tweening_function
26
+ return @function if @function
27
+
28
+ if @options[:mode].is_a? Symbol
29
+ @function = TWEEN_FUNCTIONS[@options[:mode]]
30
+ else
31
+ @function = @options[:mode] || TWEEN_FUNCTIONS.default
32
+ end
33
+
34
+ @function
35
+ end
36
+
37
+ def compute_change(start_value, end_value)
38
+ start_value.to_f - end_value.to_f
39
+ end
40
+
41
+ def update
42
+ t = (Gosu::milliseconds - @starts)
43
+
44
+ if t >= @duration
45
+ after
46
+ elsif t >= 0
47
+ apply(t)
48
+ end
49
+ end
50
+
51
+ def apply(t)
52
+ @action.call(tweening_function[t, @start_value, @change, @duration])
53
+ end
54
+
55
+ # Tweening functions; as per Robert Penner
56
+ # http://www.robertpenner.com/easing/penner_chapter7_tweening.pdf
57
+ # t: time
58
+ # b: beginning
59
+ # c: change
60
+ # d: duration
61
+ TWEEN_FUNCTIONS={
62
+ :linear_tween => lambda{|t, b, c, d| c*(t/d) + b},
63
+ :ease_in_quad => lambda{|t, b, c, d| c*(t/=d)*t + b},
64
+ :ease_out_quad => lambda{|t, b, c, d| c * (t/=d)*(t-2)*-1 + b},
65
+ :ease_in_quart => lambda{|t, b, c, d| c * ((t/d) ** 4) + b},
66
+ :ease_out_quart =>lambda{|t, b, c, d| c * ((t/d-1) ** 4)*-1 -1 + b},
67
+ :ease_in_sine => lambda{|t, b, c, d| c * (1 - Math.cos(t/d * (Math::PI/2))) + b},
68
+ :ease_out_sine => lambda{|t, b, c, d| c * Math.sin(t/d * (Math::PI/2)) + b}
69
+ }
70
+ TWEEN_FUNCTIONS.default = TWEEN_FUNCTIONS[:linear_tween]
71
+ end
data/lib/lexery.rb ADDED
@@ -0,0 +1,11 @@
1
+ GAME_ROOT = File.join(File.dirname(__FILE__),'..')
2
+
3
+ require 'rubygems'
4
+ require 'gosu'
5
+ require 'matrix' # used in multi_transition
6
+ require 'set'
7
+
8
+ $LOAD_PATH << File.join(GAME_ROOT,'lib','lexery')
9
+ $LOAD_PATH << File.join(GAME_ROOT,'lib','exuberant')
10
+ require 'support'
11
+ require 'gosu_support'
@@ -0,0 +1,9 @@
1
+ module Colors
2
+ HEADER = Gosu::Color.new(0x666666FF)
3
+ WARNING = Gosu::Color.new(128, 255,0,0)
4
+ FADED = Gosu::Color.new(96, 128, 128, 128)
5
+ FADED_WARNING = Gosu::Color.new(96, 128, 0, 0)
6
+
7
+ BACKGROUND_TOP = Gosu::Color.new(255, 255, 255, 255)
8
+ BACKGROUND_BOTTOM = Gosu::Color.new(255, 200, 200, 255)
9
+ end
@@ -0,0 +1,14 @@
1
+ class Dictionary
2
+ def initialize(path)
3
+ @words = Marshal.load(File.read(path))
4
+ end
5
+
6
+ def valid_word?(word)
7
+ @words.include? word.downcase
8
+ end
9
+
10
+ def pick(length=5)
11
+ candidates = @words.select{|w| w.length == length }
12
+ candidates[ rand(candidates.length) ]
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ class FadingMessage < Label
2
+ def initialize(x,y, text, options={})
3
+ super(x,y, text, options)
4
+
5
+ @start_y = self.y
6
+ @target_y = 0 - self.height
7
+ @base_color = self.color
8
+
9
+ Game.state.add MultiTransition.new(3000, [@start_y, 255], [@target_y, 0],
10
+ :mode=>options[:mode],
11
+ :after=>lambda{Game.state.remove self}
12
+ ){|y, alpha|
13
+ self.color = Gosu::Color.new(
14
+ alpha.to_i,
15
+ @base_color.red,
16
+ @base_color.green,
17
+ @base_color.blue
18
+ )
19
+ self.y = y
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ class FpsCounter
2
+ attr_reader :fps
3
+
4
+ def initialize
5
+ @current_second = Gosu::milliseconds / 1000
6
+ @accum_fps = 0
7
+ @fps = 0
8
+ end
9
+
10
+ def register_tick
11
+ @accum_fps += 1
12
+ current_second = Gosu::milliseconds / 1000
13
+ if current_second != @current_second
14
+ @current_second = current_second
15
+ @fps = @accum_fps
16
+ @accum_fps = 0
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ class Game < ExuberantGame
2
+ class << self
3
+ def options_set
4
+ @options_set ||= 'short game'
5
+ end
6
+
7
+ def all_options
8
+ @options ||= YAML.load_file(File.join(GAME_ROOT, 'options.yml'))
9
+ end
10
+
11
+ def options
12
+ all_options[options_set]
13
+ end
14
+
15
+ def dictionary
16
+ @dictionary ||= begin
17
+ path = File.join(GAME_ROOT, 'wordlists', 'words.set')
18
+ Dictionary.new(path)
19
+ end
20
+ end
21
+
22
+ def score_board
23
+ @score_board ||= begin
24
+ locations = [File.join(GAME_ROOT, 'scores'), File.join(ENV['HOME'],'.lexery_scores')]
25
+ # find a place where we can write the scores to
26
+ path = locations.find{|p| File.writable?(File.dirname(p))}
27
+ ScoreBoard.new(path)
28
+ end
29
+ end
30
+
31
+ end
32
+ end