-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCell.lua
540 lines (463 loc) · 13.7 KB
/
Cell.lua
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
-- Cell class
local bit = require 'plugin.bit'
local bezier = require 'Bezier'
local physics = require 'physics'
local Util = require 'Util'
local PLACE_COIN_CHANCE = 0.3333 / 2
-- local SHOW_HEXAGON = false
local Cell = {
-- prototype object
grid = nil, -- grid we belong to
x = nil, -- column 1 .. width
y = nil, -- row 1 .. height
center = nil, -- point table, screen coords
ne, e, se, sw, w, nw = nil, nil, nil, nil, nil, nil,
coins = 0,
bitCount = 0, -- hammingWeight
color = nil, -- e.g. {0,1,0}
section = nil, -- number of section (0 if locked)
hexagon = nil, -- ShapeObject for outline
grp = nil, -- display group to put shapes in
touchCoords = nil,
touchPos = nil,
}
Cell.__index = Cell
function Cell.new(grid, x, y)
local o = {}
setmetatable(o, Cell)
o.grid = grid
o.x = x
o.y = y
-- calculate where the screen coords center point will be
-- odd-r horizontal layout shoves odd rows half a hexagon width to the right
-- no '&' operator in Lua 5.1, hence math.fmod(y,2) == 1
local dim = o.grid.dim
-- o.center = {x=(x*grid.w) + (grid.w/2), y=(y * grid.h75) + (grid.h/2) - grid.h + 120}
o.center = {x=(x*dim.W)-(dim.W), y=(y*dim.H75)}
if math.fmod(y,2) == 1 then
o.center.x = o.center.x + dim.W50
end
-- can't have the center being on the edge of the screen, it would clip left of first cell
o.center.x = o.center.x + dim.W50 + 5
-- "These coordinates will automatically be re-centered about the center of the polygon."
o.hexagon = display.newPolygon(o.grid.gridGroup, o.center.x, o.center.y, dim.cellHex)
o.hexagon:setFillColor(unpack(o.grid.backgroundColor))
-- if SHOW_HEXAGON then
if _G.gameState.level == 1 then
o.hexagon:setStrokeColor(0.1)
o.hexagon.strokeWidth = 2
else
o.hexagon:setStrokeColor(0)
o.hexagon.strokeWidth = 0
end
o.hexagon:addEventListener('tap', o) -- table listener
o.hexagon:addEventListener('touch', o) -- table listener
return o
end
function Cell:reset()
self.coins = 0
self.color = nil
if self.grp then
self.grp:removeSelf()
self.grp = nil
end
end
function Cell:calcHammingWeight()
local function hammingWeight(coin)
local w = 0
for dir = 1, 6 do
if bit.band(coin, 1) == 1 then
w = w + 1
end
coin = bit.rshift(coin, 1)
end
return w
end
self.bitCount = hammingWeight(self.coins)
end
function Cell:shiftBits(num)
local dim = self.grid.dim
num = num or 1
while num > 0 do
if bit.band(self.coins, 32) == 32 then
-- high bit is set
self.coins = bit.lshift(self.coins, 1)
self.coins = bit.band(self.coins, dim.MASK)
self.coins = bit.bor(self.coins, 1)
else
self.coins = bit.lshift(self.coins, 1)
end
num = num - 1
end
end
function Cell:unshiftBits(num)
local function unshift(n)
if bit.band(n, 1) == 1 then
n = bit.rshift(n, 1)
n = bit.bor(n , 32)
else
n = bit.rshift(n, 1)
end
return n
end
-- assert(unshift(1) == 32)
-- assert(unshift(2) == 1)
-- assert(unshift(4) == 2)
-- assert(unshift(32) == 16)
-- assert(unshift(63) == 63)
num = num or 1
while num > 0 do
self.coins = unshift(self.coins)
num = num - 1
end
end
function Cell:isComplete(section)
local dim = self.grid.dim
if section and self.section ~= section then
return false
end
for _, cd in ipairs(dim.cellData) do
if bit.band(self.coins, cd.bit) == cd.bit then
local cn = self[cd.link]
if not cn then
return false
end
if section and cn.section ~= section then
return false
end
if bit.band(cn.coins, cd.oppBit) == 0 then
return false
end
end
end
return true
end
function Cell:placeCoin(mirror)
local dim = self.grid.dim
for _,cd in ipairs(dim.cellData) do
if math.random() < PLACE_COIN_CHANCE then
if self[cd.link] then
self.coins = bit.bor(self.coins, cd.bit)
self[cd.link].coins = bit.bor(self[cd.link].coins, cd.oppBit)
if mirror then
local m_cd = dim.cellData[cd.vsym]
-- assert(m_cd)
-- assert(m_cd.bit)
if mirror[m_cd.link] then
mirror.coins = bit.bor(mirror.coins, m_cd.bit)
mirror[m_cd.link].coins = bit.bor(mirror[m_cd.link].coins, m_cd.oppBit)
end
end
end
end
end
end
function Cell:jumbleCoin()
local moves = 0
if system.getInfo('environment') == 'simulator' then
if self.bitCount == 1 then
moves = 1
end
else
moves = math.random(5)
end
self:unshiftBits(moves)
return moves
end
function Cell:colorConnected(color, section)
local dim = self.grid.dim
self.color = color
self.section = section
for _, cd in ipairs(dim.cellData) do
if bit.band(self.coins, cd.bit) == cd.bit then
local cn = self[cd.link]
if cn and cn.coins ~= 0 and cn.color == nil then
cn:colorConnected(color, section)
end
end
end
end
--[[
When you modify a group's properties, all of its children are affected.
For example, if you set the alpha property on a display group,
each child's alpha value is multiplied by the new alpha of the group.
Groups automatically detect when a child's properties have changed
(position, rotation, etc.). Thus, on the next render pass, the child will re-render.
]]
--[[
function Cell:colorComplete()
self.color = self.grid.completeColor
if self.grp then
-- local dim = self.grid.dim
for i = 1, self.grp.numChildren do
local o = self.grp[i]
if o.setStrokeColor then
-- lines
o:setStrokeColor(unpack(self.color))
end
if o.setFillColor then
-- end caps (radius dim.Q20/2) and circles (radius dim.Q33)
if o.myceliumObjectType == 'endcap' then
o:setFillColor(unpack(self.color))
end
end
end
end
end
]]
function Cell:rotate(dir)
local function _afterRotate()
self:createGraphics(1)
if self.grid:isSectionComplete(self.section) then
Util.sound('section')
self.grid:hideSection(self.section)
end
if self.grid:isComplete() then
Util.sound('complete')
-- self.grid:colorComplete()
_G.gameState:advanceLevel()
end
end
dir = dir or 'clockwise'
if self.section == 0 then
Util.sound('locked')
elseif self.grp then
Util.sound('tap')
-- shift bits now (rather than in _afterRotate) in case another tap happens while animating
local degrees
if dir == 'clockwise' then
self:shiftBits()
degrees = 45
elseif dir == 'anticlockwise' then
self:unshiftBits()
degrees = -45
end
transition.to(self.grp, {
time = 100,
rotation = degrees,
transition = easing.linear,
onComplete = _afterRotate,
})
end
end
function Cell:tap(event)
-- trace('tap', event.numTaps, self.x, self.y, self.coins, self.bitCount)
if self.grid:isComplete() then
-- print('completed', event.name, event.numTaps, self.x, self.y, self.coins, self.bitCount)
self.grid:reset()
self.grid:newLevel()
else
self:rotate('clockwise')
end
return true
end
function Cell:touch(event)
local dim = self.grid.dim
-- trace(event.phase, event.x, event.y)
local target = event.target
if event.phase == 'began' then
-- tell corona that following touches come to this display object
display.getCurrentStage():setFocus(target)
-- remember that this object has the focus
target.hasFocus = true
-- building these as needed, the touched cell has these coords
self.touchCoords = {}
for i = 1, 6 do
local src = dim.cellTriangles[i]
local dst = {}
dst[1] = self.center.x
dst[2] = self.center.y
dst[3] = src[3] + self.center.x
dst[4] = src[4] + self.center.y
dst[5] = src[5] + self.center.x
dst[6] = src[6] + self.center.y
table.insert(self.touchCoords, dst)
-- assert(#dst==6)
end
end
if self.touchCoords then
for i = 1, 6 do
if Util.isPointInTriangle(event.x, event.y, unpack(self.touchCoords[i])) then
-- trace(event.phase, 'in triangle', i)
-- local c = dim.cellTriangles[i]
-- local tri1 = display.newLine(self.grp, 0,0, c[3], c[4])
-- tri1.strokeWidth = 2
-- local tri2 = display.newLine(self.grp, 0,0, c[5], c[6])
-- tri2.strokeWidth = 2
if event.phase == 'began' then
self.touchPos = i
elseif event.phase == 'moved' and self.touchPos then
if (i == self.touchPos + 1) or (self.touchPos == 6 and i == 1) then
self:rotate('clockwise')
self.touchPos = i
elseif (i == self.touchPos - 1) or (self.touchPos == 1 and i == 6) then
self:rotate('anticlockwise')
self.touchPos = i
end
end
break
end
end
end
if event.phase == 'ended' then
-- stop being responsible for touches
display.getCurrentStage():setFocus(nil)
-- remember this object no longer has the focus
target.hasFocus = false
self.touchCoords = nil
self.touchPos = nil
end
return true -- we handled event
end
function Cell:createGraphics(scale)
local dim = self.grid.dim
scale = scale or 1.0
-- gotcha the 4th argument to set color function ~= the .alpha property
-- blue={0,0,1}
-- trace(table.unpack(blue), 3)
-- > 0 6
--[[
local colora = {}
for k,v in pairs(self.color) do colora[k] = v end
assert(#colora==3)
table.insert(colora, alpha)
assert(#colora==4)
]]
if self.grp then
self.grp:removeSelf()
self.grp = nil
end
if 0 == self.coins then
return
end
self.grp = display.newGroup()
-- center the group on the center of the hexagon, otherwise it's at 0,0
self.grp.x = self.center.x
self.grp.y = self.center.y
self.grid.shapesGroup:insert(self.grp)
local sWidth = dim.Q20
local capRadius = sWidth/2
if self.bitCount == 1 then
local cd = table.find(dim.cellData, function(b) return self.coins == b.bit end)
assert(cd)
local line = display.newLine(self.grp,
cd.c2eX / 2.5,
cd.c2eY / 2.5,
cd.c2eX,
cd.c2eY)
line.strokeWidth = sWidth
line.alpha = 1
line.xScale, line.yScale = scale, scale
line:setStrokeColor(unpack(self.color))
line.myceliumObjectType = 'line'
local endcap = display.newCircle(self.grp, cd.c2eX, cd.c2eY, capRadius)
endcap:setFillColor(unpack(self.color))
endcap.alpha = 1
endcap.xScale, endcap.yScale = scale, scale
endcap.myceliumObjectType = 'endcap'
local circle = display.newCircle(self.grp, 0, 0, dim.Q33)
circle.strokeWidth = sWidth
circle.alpha = 1
circle.xScale, circle.yScale = scale, scale
circle:setStrokeColor(unpack(self.color))
circle:setFillColor(unpack(self.grid.backgroundColor))
circle.myceliumObjectType = 'circle'
else
-- until Bezier curves, just draw a line from coin-bit-edge to center
--[[
for _,cd in ipairs(dim.cellData) do
if bit.band(cd.bit, self.coins) == cd.bit then
local line = display.newLine(self.grp,
0,
0,
cd.c2eX,
cd.c2eY)
line.strokeWidth = dim.Q10
end
end
]]
-- make a list of edge coords we need to visit
--[[
with self.bitCount > 3
three consective bits should produce same pattern (rotated) no matter where they occur in coins:
000111
001110
011100
111000
110001 - ugly
100011 - ugly
hence self.bitCount > 2
]]
local arr = {}
for _,cd in ipairs(dim.cellData) do
if bit.band(self.coins, cd.bit) == cd.bit then
table.insert(arr, {x=cd.c2eX, y=cd.c2eY})
end
end
-- close path for better aesthetics
if self.bitCount > 2 then
table.insert(arr, arr[1])
end
for n = 1, #arr-1 do
-- use (off-)center and (off-)center as control points
-- local av = 1.8 -- make the three-edges circles round
local av = 3 -- make the three-edges circles triangular
local cp1 = {x=(arr[n].x)/av, y=(arr[n].y)/av}
local cp2 = {x=(arr[n+1].x)/av, y=(arr[n+1].y)/av}
local curve = bezier.new(
arr[n].x, arr[n].y,
cp1.x, cp1.y,
cp2.x, cp2.y,
arr[n+1].x, arr[n+1].y)
local curveDisplayObject = curve:get()
curveDisplayObject.strokeWidth = sWidth
curveDisplayObject:setStrokeColor(unpack(self.color))
curveDisplayObject.alpha = 1
curveDisplayObject.xScale, curveDisplayObject.yScale = scale, scale
curveDisplayObject.myceliumObjectType = 'curve'
self.grp:insert(curveDisplayObject)
end
for n = 1, #arr do
local endcap = display.newCircle(self.grp, arr[n].x, arr[n].y, capRadius)
endcap:setFillColor(unpack(self.color))
endcap.alpha = 1
endcap.xScale, endcap.yScale = scale, scale
endcap.myceliumObjectType = 'endcap'
end
end
end
--[[
function Cell:dilateCircle()
if self.grp and self.bitCount == 1 then
local circle = self.grp[3]
circle.path.radius = circle.path.radius * 0.5
end
end
]]
function Cell:fadeIn()
-- used to use fadeIn/Out, but couldn't figure out which blendMode to use
-- to stop endcaps and shape overlaps becoming visible during fade
if self.grp then
for i=1, self.grp.numChildren do
local o = self.grp[i]
transition.scaleTo(o, {xScale=1, yScale=1, time=500})
end
end
end
function Cell:fadeOut()
if self.grp then
for i=1, self.grp.numChildren do
local o = self.grp[i]
transition.scaleTo(o, {xScale=0.1, yScale=0.1, time=500})
physics.addBody(o, 'dynamic', { density=0.1, radius=10, bounce=0.9 } )
-- move slowly so not so many go off screen
o:setLinearVelocity( math.random( -10,10 ), math.random( -10,10 ) )
o.angularVelocity = math.random(0, 100)
end
end
end
--[[
function Cell:destroy()
end
]]
return Cell