-
Notifications
You must be signed in to change notification settings - Fork 5
/
main.rb
196 lines (183 loc) · 5.91 KB
/
main.rb
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
unless File.exist? "PressStart2P-Regular.ttf"
require "open-uri"
require "zip"
tempfile = Tempfile.new "Press_Start_2P.zip"
File.binwrite tempfile, (
Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5") ? Kernel : URI
).open("https://fonts.google.com/download?family=Press%20Start%202P", &:read)
Zip::File.open(tempfile){ |zip| zip.extract "PressStart2P-Regular.ttf", "PressStart2P-Regular.ttf" }
end
require "ruby2d"
field = nil
block_size = 30 + 2 * margin = 1
reset_field = lambda do
text_highscore = Text.new("", x: 5, y: 5, z: 1, font: Font.path("PressStart2P-Regular.ttf"))
lambda do
field = Array.new(20){ Array.new 10 }
text_highscore.text = "Highscore: #{
File.exist?("#{Dir.home}/.rbtris") ?
File.read("#{Dir.home}/.rbtris").scan(/^1 .*?(\S+)$/).map(&:first).map(&:to_i).max : "---"
}"
end
end.call
render = lambda do
reset_field.call
w = block_size * (2 + field.first.size)
h = block_size * (3 + field.size)
set width: w, height: h, title: "rbTris"
Rectangle.new width: w, height: h, color: "gray"
Rectangle.new width: w - 2 * block_size, height: h - 3 * block_size, color: "black", x: block_size, y: block_size * 2
blocks = Array.new(field.size) do |y|
Array.new(field.first.size) do |x|
[ Square.new(x: margin + block_size * (1 + x),
y: margin + block_size * (2 + y),
size: block_size - 2 * margin) ]
end
end
lambda do
blocks.each_with_index do |row, i|
row.each_with_index do |(block, drawn), j|
if field[i][j]
unless drawn == true
block.color = %w{ aqua yellow green red blue orange purple }[(field[i][j] || 0) - 1]
block.add
row[j][1] = true
end
else
unless drawn == false
block.remove
row[j][1] = false
end
end
end
end
end
end.call
figure = x = y = nil
mix = lambda do |f| # add or subtract the figure from the field (call it before rendering)
figure.each_with_index do |row, dy|
row.each_index do |dx|
field[y + dy][x + dx] = (row[dx] if f) unless row[dx].zero?
end
end
end
collision = lambda do
figure.each_with_index.any? do |row, dy|
row.each_with_index.any? do |a, dx|
not a.zero? ||
((0...field.size ) === y + dy) &&
((0...field.first.size) === x + dx) &&
!field[y + dy][x + dx]
end
end or (
mix.call true
render.call
mix.call false
false
)
end
score = nil
text_score = Text.new score, x: 5, y: block_size + 5, z: 1, font: Font.path("PressStart2P-Regular.ttf")
text_level = Text.new score, x: 5, y: block_size + 5, z: 1, font: Font.path("PressStart2P-Regular.ttf")
paused = false
pause_rect = Rectangle.new(width: Window.width, height: Window.height, color: [0.5, 0.5, 0.5, 0.75]).tap &:remove
pause_text = Text.new("press 'Space'", z: 1, font: Font.path("PressStart2P-Regular.ttf")).tap &:remove
init_figure = lambda do
figure = %w{ 070 777 006 666 500 555 440 044 033 330 22 22 1111 }.each_slice(2).to_a.sample
rest = figure.first.size - figure.size
x, y, figure = 3, 0, (
[?0 * figure.first.size] * (rest / 2) + figure +
[?0 * figure.first.size] * (rest - rest / 2)
).map{ |st| st.chars.map &:to_i }
next unless collision.call
File.open("#{Dir.home}/.rbtris", "a") do |f|
f.puts "1 #{"#{text_level.text} #{text_score.text}".tap &method(:puts)}"
end
[pause_rect, pause_text].each &((paused ^= true) ? :add : :remove)
score = nil
end
reset = lambda do
score, figure = 0, nil
reset_field.call
init_figure.call
end
semaphore = Mutex.new
prev, row_time = nil, 0
tap do
reset.call
Window.update do
current = Time.now
unless paused
text_score.text = "Score: #{score}"
text_score.x = Window.width - 5 - text_score.width
end
semaphore.synchronize do
unless paused
level = (((score / 5 + 0.125) * 2) ** 0.5 - 0.5 + 1e-6).floor # outside of Mutex score is being accesses by render[]
text_level.text = "Level: #{level}"
row_time = (0.8 - (level - 1) * 0.007) ** (level - 1)
end
prev ||= current - row_time
next unless current >= prev + row_time
prev += row_time
next unless figure && !paused
y += 1
next unless collision.call
y -= 1
# puts "FPS: #{(Window.frames.round - 1) / (current - first_time)}" if Window.frames.round > 1
mix.call true
field.partition(&:all?).tap do |a, b|
field = a.map{ Array.new field.first.size } + b
score += [0, 1, 3, 5, 8].fetch a.size
end
render.call
init_figure.call
end
end
end
try_move = lambda do |dir|
x += dir
next unless collision.call
x -= dir
end
try_rotate = lambda do
figure = figure.reverse.transpose
next unless collision.call
figure = figure.transpose.reverse
end
holding = Hash.new
pause_text.x = (Window.width - pause_text.width) / 2
pause_text.y = (Window.height - pause_text.height) / 2
Window.on :key_down do |event|
holding[event.key] = Time.now
semaphore.synchronize do
case event.key
when "left" ; try_move.call -1 if figure && !paused
when "right" ; try_move.call +1 if figure && !paused
when "up" ; try_rotate.call if figure && !paused
when "r"
reset.call unless paused
when "p", "space"
[pause_rect, pause_text].each &((paused ^= true) ? :add : :remove)
reset.call unless score
end
end
end
Window.on :key_held do |event|
semaphore.synchronize do
case event.key
when "left" ; try_move.call -1 if figure && 0.5 < Time.now - holding[event.key]
when "right" ; try_move.call +1 if figure && 0.5 < Time.now - holding[event.key]
when "up" ; try_rotate.call if figure && 0.5 < Time.now - holding[event.key]
when "down"
y += 1
prev = if collision.call
y -= 1
Time.now - row_time
else
Time.now
end
end
end unless paused
end
show