File: cairo-pong.rb

package info (click to toggle)
ruby-gnome2 0.15.0-1.1
  • links: PTS
  • area: main
  • in suites: etch-m68k
  • size: 7,692 kB
  • ctags: 8,558
  • sloc: ansic: 69,912; ruby: 19,511; makefile: 97; xml: 35; sql: 13
file content (215 lines) | stat: -rwxr-xr-x 4,553 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env ruby
=begin
  cairo-pong.rb - Ruby/GTK with cairo sample script.

  Original: gtkcairo sample by Evan Martins.

  Copyright (c) 2005,2006  Ruby-GNOME2 Project Team 
  This program is licenced under the same licence as Ruby-GNOME2.

  $Id: cairo-pong.rb,v 1.3 2006/06/17 13:18:12 mutoh Exp $
=end

require "gtk2"

unless Gdk::Drawable.instance_methods.include?("create_cairo_context")
  raise "GTK+ 2.8.0 or later and cairo support are requered."
end

module Pong
  class CenteredItem
    attr_accessor :x, :y
    attr_reader :width, :height
    
    def initialize(x, y, width, height)
      @x = x
      @y = y
      @width = width
      @height = height
    end

    def min_x
      @x - @width / 2
    end
  
    def max_x
      @x + @width / 2
    end
  
    def min_y
      @y - @height / 2
    end
  
    def max_y
      @y + @height / 2
    end
  end

  class CenteredCircle < CenteredItem
    def draw(cr)
      cr.translate(min_x, min_y)
      cr.scale(@width, @height)
      cr.arc(0.5, 0.5, 0.5, 0, 2 * Math::PI)
      cr.fill
    end
  end
  
  class CenteredRect < CenteredItem
    def draw(cr)
      cr.translate(min_x, min_y)
      cr.scale(@width, @height)
      cr.rectangle(0, 0, 1, 1)
      cr.fill
    end
  end
  
  class Ball < CenteredCircle
    attr_accessor :dx, :dy
    def initialize(dx=0.02, dy=0.02)
      super(0.8, 0.5, 0.04, 0.04)
      @dx = dx
      @dy = dy
    end
    
    def update
      @x += @dx
      @y += @dy

      # ball bouncing
      if max_y > 1
        @y = 1 - (max_y - 1)
        @dy *= -1
      elsif min_y < 0
        @y -= min_y
        @dy *= -1
      end
      
      if max_x > 1
        @x = 1 - (max_x - 1)
        @dx *= -1
      elsif min_x < 0
        @x -= min_x
        @dx *= -1
      end
    end
  end
  
  class Paddle < CenteredRect
    def initialize(field, x, y)
      super(x, y, 0.05, 0.3)
      @field = field
    end
    
    def update(ball)
      # is the ball coming towards us?
      if (ball.x < @x and ball.dx > 0) or
          (ball.x > @x and ball.dx < 0)
        # move to intercept it
        @y = ball.y
      end
    end

    def ball_hit?(ball)
      ball.y > min_y and ball.y < max_y
    end
    
    def update_ball(ball)
      if ball_hit?(ball)
        if ball.min_x < @x and ball.max_x > min_x # hit our left side
          ball.x -= (ball.max_x - min_x)
          ball.dx = -ball.dx
        elsif ball.max_x > @x and ball.min_x < max_x # hit our right side
          ball.x += (max_x - ball.min_x)
          ball.dx = -ball.dx
        end
      end
    end
  end

  class Field
    attr_accessor :width, :height
    
    def initialize(margin=0.05)
      @margin = margin
      
      @left_paddle = Paddle.new(self, @margin, 0.5)
      @right_paddle = Paddle.new(self, 1 - @margin, 0.7)
      @paddles = [@left_paddle, @right_paddle]
      @ball = Ball.new
    end
    
    def update
      @paddles.each do |paddle|
        paddle.update(@ball)
      end
      @ball.update
      @paddles.each do |paddle|
        paddle.update_ball(@ball)
      end
    end
    
    def draw(cr)
      cr.set_source_rgba(1, 1, 1)
      cr.rectangle(0, 0, 1, 1)
      cr.fill

      cr.save do
        cr.set_source_rgba(0.8, 0.8, 0.8, 0.8)
        cr.set_line_join(Cairo::LINE_JOIN_ROUND)
        @paddles.each do |paddle|
          cr.save {paddle.draw(cr)}
        end
      end
      
      cr.set_source_rgba(0, 0, 0)
      cr.save {@ball.draw(cr)}
    end
  end

  class Window < Gtk::Window
    def initialize(speed=30)
      super()
      @speed = speed
      
      self.title = 'Pong Demonstration'
      signal_connect('destroy') { Gtk.main_quit }
      signal_connect("key_press_event") do |widget, event|
        if event.state.control_mask? and event.keyval == Gdk::Keyval::GDK_q
          destroy
          true
        else
          false
        end
      end

      set_default_size(260, 200)

      @field = Field.new

      @drawing_area = Gtk::DrawingArea.new
      set_expose_event

      vb = Gtk::VBox.new(false, 5)
      vb.border_width = 10
      vb.pack_start(@drawing_area, true, true, 0)
      vb.show_all
      add(vb)

      Gtk.timeout_add(@speed) do
        @field.update
        @drawing_area.queue_draw unless @drawing_area.destroyed?
      end
    end

    def set_expose_event
      @drawing_area.signal_connect('expose_event') do |widget, event|
        cr = widget.window.create_cairo_context
        cr.scale(*widget.window.size)
        @field.draw(cr)
      end
    end
  end
end

Pong::Window.new.show_all
Gtk.main