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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
include Test # temporary WiP name for raylib bindings
WHITE = Color.new(r: 255, g: 255, b: 255, a: 255)
BLACK = Color.new(r: 0, g: 0, b: 0, a: 255)
LIGHTGRAY = Color.new(r: 235, g: 235, b: 235, a: 255)
DARKGRAY = Color.new(r: 200, g: 200, b: 200, a: 255)
VERYDARKGRAY = Color.new(r: 150, g: 150, b: 150, a: 255)
CardWidth = 55
CardHeight = (CardWidth * (4.0/3.0)).to_i
# By how many steps to subdivide
# more can be smoother but needs more time to process
SmoothenSteps = 2
ScreenWidth = 900
ScreenHeight = 650
# How far the border is from the edge of the screen
ScreenBorder = 60
ScreenBorderThickness = 5
# Visual game border
BorderRec = Rectangle.new(
x: ScreenBorder - ScreenBorderThickness,
y: ScreenBorder - ScreenBorderThickness,
width: ScreenWidth - ((ScreenBorder - ScreenBorderThickness) * 2),
height: ScreenHeight - ((ScreenBorder - ScreenBorderThickness) * 2),
)
# Card arena limits
MaxCardHeight = ScreenBorder
MinCardHeight = ScreenHeight - ScreenBorder - CardHeight
MaxCardWidth = ScreenBorder
MinCardWidth = ScreenWidth - ScreenBorder - CardWidth
init_window(width: ScreenWidth, height: ScreenHeight, title: "Card Example Thingie")
Test.target_fps = 60
DefaultOrigin = Vector2.new(x: 0, y: 0)
ButtonHeight = 35
ButtonWidth = 120
ButtonFontSize = 20
Button = Struct.new(:rec, :text).new(Rectangle.new(x: (ScreenWidth / 2) - (ButtonWidth / 2), y: ScreenHeight - (ScreenBorder / 2) - (ButtonHeight / 2), width: ButtonWidth, height: ButtonHeight), "Add Card")
Button.instance_eval do
def draw
tmp_color = LIGHTGRAY
if check_collision_point_rec(rec: self.rec, point: mouse_position)
if mouse_button_down?(0)
tmp_color = VERYDARKGRAY
else
tmp_color = DARKGRAY
end
end
draw_rectangle_pro(rec: self.rec, origin: DefaultOrigin, rotation: 0, color: tmp_color)
draw_rectangle_lines_ex(rec: self.rec, line_thick: 2, color: BLACK)
draw_text(text: self.text, pos_x: self.rec.x + 12, pos_y: self.rec.y + 8, font_size: ButtonFontSize, color: BLACK)
end
def clicked?
if mouse_button_pressed?(0) && check_collision_point_rec(rec: self.rec, point: mouse_position)
return true
else
return false
end
end
end
class Card
Width = CardWidth
Height = CardHeight
# List of valid coordinates a card can spawn
ValidSpawnRange = [(ScreenBorder..(ScreenWidth-Card::Width-ScreenBorder)).to_a, (ScreenBorder..(ScreenHeight-Card::Height-ScreenBorder)).to_a]
# Stores any new cards that are created
Objects = []
# Stores groups of cards that are overlapping
Resolver = []
attr_accessor :rec, :text, :color, :dragged, :momentum
def initialize(rec: Rectangle.new(x: ValidSpawnRange[0].sample, y: ValidSpawnRange[1].sample, width: Card::Width, height: Card::Height),
text: "New Card",
color: WHITE)
self.rec = rec
self.text = text
self.color = color
self.dragged = false
self.momentum = [0.0, 0.0]
Card::Objects.push self
Resolver.push [self]
end
# class methods
class << self
attr_accessor :card_offset
attr_writer :card_dragged
def card_dragged
@card_dragged ||= false
end
def resolve_drag
# if click -> find if you are clicking one -> if so set it click
if mouse_button_pressed?(0)
Card::Objects.reverse_each do |card|
if check_collision_point_rec(point: mouse_position, rec: card.rec)
self.card_dragged = card
card.dragged = true
self.card_offset = [card.rec.x - mouse_x, card.rec.y - mouse_y]
# Place card at end of array(to draw on top)
Card::Objects.push Card::Objects.delete(card)
break
end
end
# if holding click -> check which one is being clicked -> drag it
elsif mouse_button_down?(0) && self.card_dragged
card_dragged.rec = Rectangle.new(
x: (self.card_offset[0] + mouse_x).clamp(MaxCardWidth, MinCardWidth),
y: (self.card_offset[1] + mouse_y).clamp(MaxCardHeight, MinCardHeight),
width: card_dragged.rec.width,
height: card_dragged.rec.height,
)
# if let go of click -> remove click state
elsif mouse_button_up?(0) && self.card_dragged
card_dragged.rec = Rectangle.new(
x: (self.card_offset[0] + mouse_x).clamp(MaxCardWidth, MinCardWidth),
y: (self.card_offset[1] + mouse_y).clamp(MaxCardHeight, MinCardHeight),
width: card_dragged.rec.width,
height: card_dragged.rec.height,
)
Resolver.push [card_dragged]
self.card_dragged.dragged = false
self.card_dragged = false
self.card_offset = false
end
end
def check_overlap
# check overlaps
if !Resolver.empty?
old_resolver = Resolver.flatten
Resolver.clear
old_resolver.each do |colliding_card|
next if colliding_card.dragged
Resolver.push []
Card::Objects.each do |card|
next if (card == colliding_card) || card.dragged
if check_collision_recs(rec1: card.rec, rec2: colliding_card.rec)
old_resolver.delete card
Resolver.last.push colliding_card unless Resolver.last.include? colliding_card
Resolver.last.push card
recurse_check(card, old_resolver)
end
end
Resolver.pop if Resolver.last.empty?
end
end
end
def recurse_check(colliding_card, old_resolver)
Card::Objects.each do |card|
next if (card == colliding_card) || card.dragged || (Resolver.last.include? card)
if check_collision_recs(rec1: card.rec, rec2: colliding_card.rec)
old_resolver.delete card
Resolver.last.push colliding_card unless Resolver.last.include? colliding_card
Resolver.last.push card
recurse_check(card, old_resolver)
end
end
end
def resolve_overlap
if !Resolver.empty?
Resolver.each do |segment|
center = [0.0, 0.0]
segment.each do |card|
# get center point
center[0] += card.rec.x
center[1] += card.rec.y
end
center[0] /= segment.length
center[1] /= segment.length
segment.each do |card|
# move from center
# direction
dir = [card.rec.x - center[0], card.rec.y - center[1]]
# Check to prevent NaN
unless dir[0] == 0.0 && dir[1] == 0.0
dir[0] = dir[0] / Math.sqrt((dir[0] ** 2) + (dir[1] ** 2))
dir[1] = dir[1] / Math.sqrt((dir[0] ** 2) + (dir[1] ** 2))
end
# Smoothens out the movement
card.momentum[0] += dir[0]
card.momentum[1] += dir[1]
card.momentum[0] /= SmoothenSteps.to_f
card.momentum[1] /= SmoothenSteps.to_f
dest = [
(card.rec.x + card.momentum[0]).clamp(MaxCardWidth, MinCardWidth),
(card.rec.y + card.momentum[1]).clamp(MaxCardHeight, MinCardHeight),
]
card.rec = Rectangle.new(
x: dest[0],
y: dest[1],
width: card.rec.width,
height: card.rec.height,
)
end
end
end
end
def draw
Card::Objects.each do |card|
draw_rectangle_pro(rec: card.rec, origin: DefaultOrigin, rotation: 0, color: card.color)
draw_rectangle_lines_ex(rec: card.rec, line_thick: 2, color: BLACK)
draw_text(text: card.text, pos_x: card.rec.x + 7, pos_y: card.rec.y + 7, font_size: 30, color: BLACK)
end
end
end
end
ColorRange = (0..255).to_a
CardNames = ('A'..'ZZ').to_a.reverse
10.times do
Card.new(text: CardNames.pop, color: Color.new(r: ColorRange.sample, g: ColorRange.sample, b: ColorRange.sample, a: 255))
end
while !window_should_close do
begin_drawing
clear_background(WHITE)
draw_rectangle_lines_ex(rec: BorderRec, line_thick: 5, color: BLACK)
SmoothenSteps.times do
Card.resolve_drag
Card.check_overlap
Card.resolve_overlap
end
Card.draw
Button.draw
unless CardNames.empty?
if Button.clicked?
Card.new(text: CardNames.pop, color: Color.new(r: ColorRange.sample, g: ColorRange.sample, b: ColorRange.sample, a: 255))
end
end
draw_text(text: "Cards: #{Card::Objects.count}", pos_x: ScreenWidth - 120, pos_y: 10, font_size: 21, color: BLACK)
draw_fps(pos_x: 10, pos_y: 10)
end_drawing
end
close_window
|