Tetris 1.0.0

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: 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: []