Tetris 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7ce32b8877ef2c2a93a80eda998759003fb2c117
4
+ data.tar.gz: cddfe6415bbd8217f23d7442f35c4e76b76fc51f
5
+ SHA512:
6
+ metadata.gz: 398beb852d5c477e900c663d9dc598598baf2afab355f40e3769bce2a541042496c46879cb2f65899bfb661da4ec6245d3d827b7369d48008739eabee3b46383
7
+ data.tar.gz: 771a5cca7efea5491c3a7dcc1d74a4972d44ad4a049b14d5c0e08ffad3fbf6fb31f5d04f4e35c6695d99327871c5c5bea36595366cfff27382fc2361108a8db0
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tetris'
4
+ game = Game.new
5
+ game.show
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'gosu'
3
+
4
+ # One block - each shape is formed by multiple blocks
5
+ class Block
6
+ attr_accessor :x, :y, :color
7
+
8
+ @@image = nil
9
+ @@width = 32
10
+ @@height = 32
11
+
12
+ def self.width
13
+ @@width
14
+ end
15
+
16
+ def self.height
17
+ @@height
18
+ end
19
+
20
+ def initialize(game, x = 0, y = 0)
21
+ @@image = Gosu::Image.new(game, '../media/block.png', false) if @@image.nil?
22
+ @@width = @@image.width unless @@image.nil?
23
+ @@height = @@image.height unless @@image.nil?
24
+
25
+ @x = x
26
+ @y = y
27
+ @game = game
28
+
29
+ # AA RR GG BB
30
+ @color = 0xFFFF0000
31
+ end
32
+
33
+ def draw
34
+ # x, y, z, factor_z, factor_y, color, mode (= :default)
35
+ @@image.draw(@x, @y, 0, 1, 1, @color)
36
+ end
37
+
38
+ def collides_with_blocks?
39
+ @game.blocks.each do |block|
40
+ return true if block.x == @x && block.y == @y
41
+ end
42
+
43
+ false
44
+ end
45
+ end
@@ -0,0 +1,154 @@
1
+ require 'gosu'
2
+
3
+ # General Shape class
4
+ class Shape
5
+ attr_accessor :rotation, :x, :y
6
+ attr_reader :blocks
7
+
8
+ def initialize(game)
9
+ @game = game
10
+ @x = @y = 0
11
+ @blocks = []
12
+
13
+ # is the shape currently falling?
14
+ @falling = true
15
+
16
+ # by which block to rotate
17
+ @rotation_block = @blocks[1]
18
+
19
+ # how many rotation states are there
20
+ @rotations = 1
21
+
22
+ # rotation state
23
+ @rotation = 0
24
+ end
25
+
26
+ def update
27
+ return unless @falling
28
+
29
+ old_x = @x
30
+ old_y = @y
31
+
32
+ # first handle x change (handle keyboard - movement -
33
+ # and check for collision)
34
+ if update_move_x
35
+ if @game.button_down?(Gosu::KbLeft)
36
+ @x -= 32
37
+ elsif @game.button_down?(Gosu::KbRight)
38
+ @x += 32
39
+ end
40
+
41
+ @x = old_x if collides?
42
+ end
43
+
44
+ # then handle y change, so we can set @falling to false
45
+ # game speed check
46
+
47
+ if update_move_y
48
+ @y += 32
49
+
50
+ if collides?
51
+ @y = old_y
52
+ @falling = false
53
+ end
54
+ end
55
+ end
56
+
57
+ # Rotates the shape
58
+ def rotate
59
+ return if @rotation_block.nil?
60
+
61
+ (1..@rotation % @rotations).each do |_i|
62
+ @blocks.each do |block|
63
+ old_x = block.x
64
+ old_y = block.y
65
+ block.x = @rotation_block.x + (@rotation_block.y - old_y)
66
+ block.y = @rotation_block.y - (@rotation_block.x - old_x)
67
+ end
68
+ end
69
+ end
70
+
71
+ def shape_to_array
72
+ blocks_array = []
73
+ get_blocks.each do |block|
74
+ blocks_array << [block.x, block.y]
75
+ end
76
+ blocks_array
77
+ end
78
+
79
+ # updates move counter
80
+ def update_move_x
81
+ if @game.elapsed_seconds > @game.last_move_x + 0.05
82
+ @game.last_move_x = @game.elapsed_seconds
83
+ end
84
+ end
85
+
86
+ def update_move_y
87
+ if @game.button_down?(Gosu::KbDown)
88
+ @game.game_speed *= 0.2
89
+ else
90
+ @game.game_speed = 1
91
+ end
92
+
93
+ if @game.elapsed_seconds > @game.last_move_y + @game.game_speed
94
+ @game.last_move_y = @game.elapsed_seconds
95
+ end
96
+ end
97
+
98
+ def collides?
99
+ if collides_with_walls? || collides_with_blocks?
100
+ return true
101
+ end
102
+
103
+ false
104
+ end
105
+
106
+ def collides_with_walls?
107
+ max_y = maximum_y_block
108
+ min_x = minimum_x_block
109
+ max_x = maximum_x_block
110
+
111
+ min_x.x < 0 || max_x.x >= @game.screen_width || max_y.y + Block.height > @game.screen_height
112
+ end
113
+
114
+ def collides_with_blocks?
115
+ get_blocks.each do |block|
116
+ return true if block.collides_with_blocks?
117
+ end
118
+
119
+ false
120
+ end
121
+
122
+ # note that maximum y means the lowest block!
123
+ def maximum_y_block
124
+ get_blocks.max_by(&:y)
125
+ end
126
+
127
+ def minimum_x_block
128
+ get_blocks.min_by(&:x)
129
+ end
130
+
131
+ def maximum_x_block
132
+ get_blocks.max_by(&:x)
133
+ end
134
+
135
+ # flips the shape by y axis
136
+ def translate_by_y
137
+ min = @blocks.min_by(&:x)
138
+ max = @blocks.max_by(&:x)
139
+ center = (min.x + max.x) / 2.0
140
+ @blocks.each do |block|
141
+ block.x = 2 * center - block.x - Block.width
142
+ end
143
+ end
144
+
145
+ def draw
146
+ get_blocks.each(&:draw)
147
+ end
148
+
149
+ def falling?
150
+ @falling
151
+ end
152
+
153
+ attr_writer :falling
154
+ end
@@ -0,0 +1,38 @@
1
+ require 'shape.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ]
5
+ # [ ]
6
+ # [ ]
7
+ # [ ]
8
+ class ShapeI < Shape
9
+ def initialize(game)
10
+ super(game)
11
+
12
+ @blocks[0] = Block.new(game)
13
+ @blocks[1] = Block.new(game)
14
+ @blocks[2] = Block.new(game)
15
+ @blocks[3] = Block.new(game)
16
+
17
+ @rotation_block = @blocks[1]
18
+ @rotations = 2
19
+
20
+ @blocks.each do |block|
21
+ block.color = 0xFFFF0000
22
+ end
23
+ end
24
+
25
+ def get_blocks
26
+ @blocks[0].x = @x
27
+ @blocks[1].x = @x
28
+ @blocks[2].x = @x
29
+ @blocks[3].x = @x
30
+
31
+ @blocks[0].y = @y
32
+ @blocks[1].y = @blocks[0].y + Block.height
33
+ @blocks[2].y = @blocks[1].y + Block.height
34
+ @blocks[3].y = @blocks[2].y + Block.height
35
+ rotate
36
+ @blocks
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ require 'shapeL.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ]
5
+ # [ ]
6
+ # [ ][ ]
7
+ class ShapeJ < ShapeL
8
+ def get_blocks
9
+ old_rotation = @rotation
10
+ @rotation = 0
11
+
12
+ super
13
+ translate_by_y
14
+
15
+ @rotation = old_rotation
16
+ rotate
17
+
18
+ @blocks
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ require 'shape.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ]
5
+ # [ ]
6
+ # [ ][ ]
7
+ class ShapeL < Shape
8
+ def initialize(game)
9
+ super(game)
10
+
11
+ @blocks[0] = Block.new(game)
12
+ @blocks[1] = Block.new(game)
13
+ @blocks[2] = Block.new(game)
14
+ @blocks[3] = Block.new(game)
15
+
16
+ @rotation_block = @blocks[1]
17
+ @rotations = 4
18
+
19
+ @blocks.each do |block|
20
+ block.color = 0xFF0000FF
21
+ end
22
+ end
23
+
24
+ def get_blocks
25
+ @blocks[0].x = @x
26
+ @blocks[1].x = @x
27
+ @blocks[2].x = @x
28
+ @blocks[3].x = @x + Block.width
29
+
30
+ @blocks[0].y = @y
31
+ @blocks[1].y = @blocks[0].y + Block.height
32
+ @blocks[2].y = @blocks[1].y + Block.height
33
+ @blocks[3].y = @blocks[2].y
34
+ rotate
35
+ @blocks
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ require 'shape.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ][ ]
5
+ # [ ][ ]
6
+ class ShapeO < Shape
7
+ def initialize(game)
8
+ super(game)
9
+
10
+ @blocks[0] = Block.new(game)
11
+ @blocks[1] = Block.new(game)
12
+ @blocks[2] = Block.new(game)
13
+ @blocks[3] = Block.new(game)
14
+
15
+ @rotations = 1
16
+
17
+ @blocks.each do |block|
18
+ block.color = 0xFF00FF00
19
+ end
20
+ end
21
+
22
+ def get_blocks
23
+ @blocks[0].x = @x
24
+ @blocks[1].x = @x + Block.width
25
+ @blocks[2].x = @x
26
+ @blocks[3].x = @x + Block.width
27
+
28
+ @blocks[0].y = @y
29
+ @blocks[1].y = @y
30
+ @blocks[2].y = @blocks[0].y + Block.height
31
+ @blocks[3].y = @blocks[1].y + Block.height
32
+
33
+ @blocks
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ require 'shape.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ]
5
+ # [ ][ ]
6
+ # [ ]
7
+ class ShapeS < Shape
8
+ def initialize(game)
9
+ super(game)
10
+
11
+ @blocks[0] = Block.new(game)
12
+ @blocks[1] = Block.new(game)
13
+ @blocks[2] = Block.new(game)
14
+ @blocks[3] = Block.new(game)
15
+
16
+ @rotation_block = @blocks[1]
17
+ @rotations = 2
18
+
19
+ @blocks.each do |block|
20
+ block.color = 0xFFFFFF00
21
+ end
22
+ end
23
+
24
+ def get_blocks
25
+ @blocks[0].x = @x
26
+ @blocks[1].x = @x
27
+ @blocks[2].x = @x + Block.width
28
+ @blocks[3].x = @x + Block.width
29
+
30
+ @blocks[0].y = @y
31
+ @blocks[1].y = @blocks[0].y + Block.height
32
+ @blocks[2].y = @blocks[0].y + Block.height
33
+ @blocks[3].y = @blocks[2].y + Block.height
34
+ rotate
35
+ @blocks
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'shape.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ][ ][ ]
5
+ # [ ]
6
+ # [ ]
7
+ class ShapeT < Shape
8
+ def initialize(game)
9
+ super(game)
10
+
11
+ @blocks[0] = Block.new(game)
12
+ @blocks[1] = Block.new(game)
13
+ @blocks[2] = Block.new(game)
14
+ @blocks[3] = Block.new(game)
15
+
16
+ @rotation_block = @blocks[1]
17
+ @rotations = 4
18
+
19
+ @blocks.each do |block|
20
+ block.color = 0xFF00FFFF
21
+ end
22
+ end
23
+
24
+ def get_blocks
25
+ @blocks[0].x = @x
26
+ @blocks[1].x = @x + Block.width
27
+ @blocks[2].x = @blocks[1].x + Block.width
28
+ @blocks[3].x = @x + Block.width
29
+
30
+ @blocks[0].y = @y
31
+ @blocks[1].y = @y
32
+ @blocks[2].y = @y
33
+ @blocks[3].y = @y + Block.height
34
+ rotate
35
+ @blocks
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ require 'shapeS.rb'
2
+ require 'block.rb'
3
+
4
+ # [ ]
5
+ # [ ][ ]
6
+ # [ ]
7
+ class ShapeZ < ShapeS
8
+ def get_blocks
9
+ super
10
+ translate_by_y
11
+
12
+ @blocks
13
+ end
14
+ end
@@ -0,0 +1,162 @@
1
+ require 'rubygems'
2
+ require 'gosu'
3
+ require 'block.rb'
4
+ require 'shapeL.rb'
5
+ require 'shapeJ.rb'
6
+ require 'shapeI.rb'
7
+ require 'shapeO.rb'
8
+ require 'shapeT.rb'
9
+ require 'shapeZ.rb'
10
+ require 'shapeS.rb'
11
+
12
+ # Main class for Tetris.
13
+ class Game < Gosu::Window
14
+ attr_accessor :blocks, :current_shape, :score, :fall_speed, :game_state
15
+ attr_accessor :game_speed, :screen_width, :screen_height, :elapsed_seconds
16
+ attr_accessor :last_move_x, :last_move_y
17
+
18
+ attr_reader :shape
19
+
20
+ STATE_PLAYING = 1
21
+ STATE_END = 2
22
+
23
+ def initialize
24
+ @screen_width = 320
25
+ @screen_height = 640
26
+
27
+ super(@screen_width, @screen_height, false)
28
+ @score = 0
29
+ @blocks = []
30
+ @game_state = STATE_END
31
+ @game_speed = 1 # the lower, the faster
32
+
33
+ @game_over_text = Gosu::Image.from_text(self, 'Game Over', 'Arial', 40)
34
+ @press_space_text = Gosu::Image.from_text(self, 'Press SPACE to start a new game', 'Arial', 15)
35
+ @music = Gosu::Song.new('../media/Tetris.ogg')
36
+
37
+ self.caption = "Tetris! (#{@score} Points)"
38
+ @shape = nil
39
+ @elapsed_seconds = 0
40
+ @last_move_y = 0
41
+ @last_move_x = 0
42
+ end
43
+
44
+ def draw
45
+ @blocks.each(&:draw)
46
+ @shape.draw unless @shape.nil?
47
+ return if @game_state != STATE_END
48
+
49
+ @game_over_text.draw(@screen_width / 2 - 75, @screen_height / 2 - 40, 0)
50
+ @press_space_text.draw(@screen_width / 2 - 85, @screen_height / 2, 0)
51
+ end
52
+
53
+ # this is a callback for key up events or equivalent (there are
54
+ # constants for gamepad buttons and mouse clicks)
55
+ def button_up(key)
56
+ close if key == Gosu::KbEscape
57
+ end
58
+
59
+ # better responsiveness than button_up (rotates the shape immediately)
60
+ def button_down(key)
61
+ if @game_state == STATE_END
62
+
63
+ # (re)start the game
64
+ if key == Gosu::KbSpace
65
+ @blocks = []
66
+ @score = 0
67
+ @shape = nil
68
+ self.caption = "Tetris! (#{@score} Points)"
69
+ @game_state = STATE_PLAYING
70
+ @music.stop
71
+
72
+ # true indicates whether it should loop
73
+ @music.play(true)
74
+ end
75
+ elsif @game_state == STATE_PLAYING
76
+ if key == Gosu::KbSpace && !@shape.nil?
77
+ @shape.rotation += 1
78
+ @shape.rotation -= 1 if @shape.collides?
79
+ end
80
+ end
81
+ end
82
+
83
+ # deletes complete lines
84
+ # returns the number of deleted lines
85
+ def delete_lines
86
+ lines_to_delete = []
87
+
88
+ @shape.get_blocks.each do |block|
89
+ sum = @blocks.count { |x| x.y == block.y }
90
+ if sum == @screen_width / Block.width
91
+ lines_to_delete << block.y
92
+ @blocks.delete_if { |x| x.y == block.y }
93
+ end
94
+ end
95
+
96
+ update_score(lines_to_delete)
97
+ move_blocks_down(lines_to_delete)
98
+
99
+ lines_to_delete
100
+ end
101
+
102
+ # updates score
103
+ def update_score(lines)
104
+ return if lines.empty?
105
+
106
+ @score += 2**(lines.size - 1)
107
+ self.caption = "Tetris! (#{@score} Points)"
108
+ end
109
+
110
+ # moves appropriate blocks down (typically after lines removal)
111
+ def move_blocks_down(lines)
112
+ return if lines.empty?
113
+
114
+ @blocks.each do |block|
115
+ block.y += lines.size * Block.height if block.y < lines.min
116
+ end
117
+ end
118
+
119
+ def update
120
+ update_delta
121
+
122
+ return if @game_state != STATE_PLAYING
123
+
124
+ # do we have a falling shape?
125
+ if !@shape.nil? && @shape.falling?
126
+ @shape.update
127
+ else
128
+ spawn_shape
129
+ @game_state = STATE_END if @shape.collides?
130
+ end
131
+ # with a delta we need to express the speed of our entities in
132
+ # terms of pixels/second
133
+ end
134
+
135
+ def update_delta
136
+ # Gosu::millisecodns returns the time since the window was created
137
+ # Divide by 1000 since we want to work in seconds
138
+ @current_time = Gosu.milliseconds / 1000.0
139
+ # clamping here is important to avoid strange behaviors
140
+ @delta = [@current_time - @elapsed_seconds, 0.25].min
141
+ @elapsed_seconds = @current_time
142
+ end
143
+
144
+ # Spawns a new shape
145
+ def spawn_shape
146
+ # Add the previous shape to the block list
147
+ unless @shape.nil?
148
+ @shape.get_blocks.each do |block|
149
+ @blocks << block
150
+ end
151
+
152
+ # erase completed lines
153
+ delete_lines
154
+ end
155
+
156
+ shapes = [ShapeJ.new(self), ShapeL.new(self), ShapeI.new(self), ShapeO.new(self), ShapeT.new(self), ShapeZ.new(self), ShapeS.new(self)]
157
+ @shape = shapes.sample
158
+
159
+ @shape.x = Block.width * 4
160
+ @shape.get_blocks
161
+ end
162
+ end
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RSpec.configure do |c|
4
+ c.color = true
5
+ c.formatter = :documentation
6
+ c.tty = true
7
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/tetris'
3
+ require 'gosu'
4
+
5
+ describe Game do
6
+ before(:each) do
7
+ @game = Game.new
8
+ @game.button_down(Gosu::KbSpace) # start the game
9
+ @game.update
10
+ end
11
+
12
+ context 'Initial score is 0' do
13
+ it { expect(@game.score).to eq 0 }
14
+ end
15
+
16
+ context 'There are no blocks at the beginning' do
17
+ it { expect(@game.blocks).to be_empty }
18
+ end
19
+
20
+ context 'A shape has spawned after the game has been started' do
21
+ it 'spawns a shape' do
22
+ expect(@game.shape).to_not be_nil
23
+ end
24
+ end
25
+
26
+ context 'No lines should be deleted at the beginning of the game' do
27
+ it 'Doesn\'t delete any lines' do
28
+ expect(@game.delete_lines.size).to eq 0
29
+ end
30
+ end
31
+
32
+ context 'The game should delete complete lines' do
33
+ before(:each) do
34
+ @game.shape.y = @game.screen_height
35
+
36
+ (0..(@game.screen_width - Block.width)).step(Block.width) do |i|
37
+ @game.blocks << Block.new(@game, i, @game.screen_height)
38
+ end
39
+ end
40
+
41
+ it 'Correctly deletes lines' do
42
+ expect(@game.delete_lines.size).to eq 1
43
+ end
44
+
45
+ it 'Correctly increases score' do
46
+ expect{@game.delete_lines}.to change{@game.score}.from(0).to(1)
47
+ end
48
+ end
49
+
50
+ context 'Player should be able to lose' do
51
+ it 'Player loses' do
52
+ (0..(@game.screen_width - Block.width)).step(Block.width) do |i|
53
+ @game.blocks << Block.new(@game, Block.width * 4, i)
54
+ end
55
+
56
+ @game.shape.falling = false
57
+ expect{@game.update}.to change{@game.game_state}.from(1).to(2)
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Tetris
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Filip Vondrasek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple Tetris game
14
+ email: filip@vondrasek.net
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - bin/tetris
20
+ - lib/block.rb
21
+ - lib/shape.rb
22
+ - lib/shapeI.rb
23
+ - lib/shapeJ.rb
24
+ - lib/shapeL.rb
25
+ - lib/shapeO.rb
26
+ - lib/shapeS.rb
27
+ - lib/shapeT.rb
28
+ - lib/shapeZ.rb
29
+ - lib/tetris.rb
30
+ - media/Tetris.ogg
31
+ - media/block.png
32
+ - test/spec_helper.rb
33
+ - test/test_tetris.rb
34
+ homepage: ''
35
+ licenses:
36
+ - WTFPL
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.4.5
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: A simple Tetris game
58
+ test_files: []