· 4 years ago · Mar 26, 2021, 02:24 PM
1-- manages the playing area
2
3--[[ Variables ]]--
4local module = {}
5
6local RS = game.ReplicatedStorage
7 local boardBox = RS.Box
8
9local scripts = script.Parent
10local screenGui = scripts.Parent
11 local background = screenGui.Background
12 local menuPanel = background.MenuPanel
13 local difficultyPanel = background.DifficultyPanel
14 local playingArea = background.PlayingArea
15 local board = playingArea.Board
16 local toolPanel = playingArea.Tools
17 local numberPanel = toolPanel.Numbers
18 local eraserButton = toolPanel.Eraser
19 local writeModeSwitch = toolPanel.WriteModeSwitch.Switch
20 local settingsButton = playingArea.Settings
21 local resetButton = playingArea.Reset
22 local quitButton = playingArea.Quit
23
24
25local pencil = 0
26local writeMode = "fill"
27
28local grid = {}
29local originalGrid = {}
30local dimensions = 9
31local isGenerating = false
32
33
34
35--[[ Core functions ]]--
36
37-- initialises playing area
38function initialisePlayingArea()
39 initialiseNumbersPanel()
40 initialiseEraserButton()
41 initialiseWriteModeSwitch()
42 initialiseSettingsButton()
43 initialiseResetButton()
44 initialiseQuitButton()
45end
46
47-- initialises NumbersPanel
48function initialiseNumbersPanel()
49 for _,component in pairs(numberPanel:GetChildren()) do
50
51 -- applies functions to buttons
52 if component:IsA("TextButton") then
53
54 -- darken background on mouse enter
55 component.MouseEnter:Connect(function()
56 if tonumber(component.Text) ~= pencil then
57 component.BackgroundColor3 = Color3.new(0.975,0.975,0.975)
58 end
59 end)
60
61 -- revert background on mouse leave
62 component.MouseLeave:Connect(function()
63 if tonumber(component.Text) ~= pencil then
64 component.BackgroundColor3 = Color3.new(1,1,1)
65 end
66 end)
67
68 -- darken background and set pencil on click
69 component.MouseButton1Click:Connect(function()
70 if pencil ~= 0 then
71 numberPanel[pencil].BackgroundColor3 = Color3.new(1,1,1)
72 else
73 eraserButton.BackgroundColor3 = Color3.new(1,0.9,0.9)
74 end
75
76 component.BackgroundColor3 = Color3.new(0.95,0.95,0.95)
77
78 pencil = tonumber(component.Text)
79 end)
80 end
81 end
82end
83
84-- initialises eraser button
85function initialiseEraserButton()
86
87 -- darken background on mouse enter
88 eraserButton.MouseEnter:Connect(function()
89 if pencil ~= 0 then
90 eraserButton.BackgroundColor3 = Color3.new(1,0.8,0.8)
91 end
92 end)
93
94 -- revert background on mouse leave
95 eraserButton.MouseLeave:Connect(function()
96 if pencil ~= 0 then
97 eraserButton.BackgroundColor3 = Color3.new(1,0.9,0.9)
98 end
99 end)
100
101 -- darken background and set pencil to 0
102 eraserButton.MouseButton1Click:Connect(function()
103 if pencil ~= 0 then
104 numberPanel[pencil].BackgroundColor3 = Color3.new(1,1,1)
105 end
106
107 eraserButton.BackgroundColor3 = Color3.new(1,0.7,0.7)
108
109 writeMode = "fill"
110 pencil = 0
111 end)
112end
113
114-- initialises write mode toggle switch
115function initialiseWriteModeSwitch()
116
117 -- darken background on mouse enter
118 writeModeSwitch.MouseEnter:Connect(function()
119 writeModeSwitch.BackgroundColor3 = Color3.new(0.975,0.975,0.975)
120 end)
121
122 -- revert background on mouse leave
123 writeModeSwitch.MouseLeave:Connect(function()
124 writeModeSwitch.BackgroundColor3 = Color3.new(1,1,1)
125 end)
126
127 -- moves switch and sets text and writeMode to other mode
128 writeModeSwitch.MouseButton1Click:Connect(function()
129 if writeModeSwitch.Text == "Fill" then
130 writeModeSwitch.Text = "Note"
131 writeModeSwitch.Position = UDim2.new(0.5,0,1,0)
132 else
133 writeModeSwitch.Text = "Fill"
134 writeModeSwitch.Position = UDim2.new(0,0,1,0)
135 end
136
137 -- sets to lowercase to remove ambiguity
138 writeMode = writeModeSwitch.Text:lower()
139 end)
140end
141
142-- initialise settings button (stub function)
143function initialiseSettingsButton()
144
145 -- adds indentation on mouse enter
146 settingsButton.MouseEnter:Connect(function()
147 settingsButton.UIPadding.PaddingLeft = UDim.new(0.2,0)
148 end)
149
150 -- removes indentation on mouse leave
151 settingsButton.MouseLeave:Connect(function()
152 settingsButton.UIPadding.PaddingLeft = UDim.new(0.1,0)
153 end)
154
155 -- alters text on mouse click
156 settingsButton.MouseButton1Click:Connect(function()
157 if settingsButton.Text == "Settings" then
158
159 -- informs user before reverting text
160 local inform = coroutine.wrap(function()
161 settingsButton.Text = "Work in progress"
162
163 wait(1)
164
165 settingsButton.Text = "Settings"
166 end)
167
168 inform()
169 end
170 end)
171end
172
173initialiseSettingsButton()
174
175-- initialises reset button
176function initialiseResetButton()
177
178 -- adds indentation on mouse enter
179 resetButton.MouseEnter:Connect(function()
180 resetButton.UIPadding.PaddingLeft = UDim.new(0.2,0)
181 end)
182
183 -- removes indentation on mouse leave
184 resetButton.MouseLeave:Connect(function()
185 resetButton.UIPadding.PaddingLeft = UDim.new(0.1,0)
186 end)
187
188 -- resets board on click
189 resetButton.MouseButton1Click:Connect(function()
190
191 -- resets background colours
192 for row = 1,dimensions do
193 for column = 1,dimensions do
194 local boxNumber = getBoxNumber(row, column)
195
196 board[boxNumber].BackgroundColor3 =
197 boardBox.BackgroundColor3
198 end
199 end
200
201 grid = getCopy(originalGrid)
202 mapGridToBoxes()
203 end)
204end
205
206-- initialises quit button's functions
207function initialiseQuitButton()
208
209 -- adds indentation on mouse enter
210 quitButton.MouseEnter:Connect(function()
211 quitButton.UIPadding.PaddingLeft = UDim.new(0.2,0)
212 end)
213
214 -- removes indentation on mouse leave
215 quitButton.MouseLeave:Connect(function()
216 quitButton.UIPadding.PaddingLeft = UDim.new(0.1,0)
217 end)
218
219 -- board is cleared and user is brought back to main menu
220 quitButton.MouseButton1Click:Connect(function()
221
222 -- UIARC is cloned, all contents of board are deleted,
223 -- the UIARC is readded
224 local UIARC = board.UIAspectRatioConstraint:Clone()
225 board:ClearAllChildren()
226 UIARC.Parent = board
227
228 playingArea.Visible = false
229 menuPanel.Visible = true
230 end)
231end
232
233-- starts game with selected difficulty, called from InterfaceScript
234function module.startGame(difficulty)
235
236 -- makes playing area the focus of screen
237 playingArea.Visible = true
238 difficultyPanel.Visible = false
239
240 isGenerating = true
241
242 -- generates board
243 generateGrid(difficulty)
244 generateBoxes()
245 mapGridToBoxes()
246
247 originalGrid = getCopy(grid)
248
249 isGenerating = false
250end
251
252-- returns box number using row and column
253-- e.g. row 3 col 2 = box 20
254function getBoxNumber(row, column)
255 return ((row - 1) * dimensions) + column
256end
257
258-- fills the board with boxes
259function generateBoxes()
260 local padding = 0.01 -- relative size
261 local spacing = 0.01 -- relative size
262 local gaps = dimensions - 1
263
264 local tileLength = (1 - (spacing * gaps)) / dimensions
265
266 -- creates padding
267 local paddingLayout = Instance.new("UIPadding")
268 paddingLayout.PaddingTop = UDim.new(padding,0)
269 paddingLayout.PaddingRight = UDim.new(padding,0)
270 paddingLayout.PaddingBottom = UDim.new(padding,0)
271 paddingLayout.PaddingLeft = UDim.new(padding,0)
272
273 -- formats boxes
274 local gridLayout = Instance.new("UIGridLayout")
275 gridLayout.CellPadding = UDim2.new(spacing,0,spacing,0)
276 gridLayout.CellSize = UDim2.new(tileLength,0,tileLength, 0)
277 gridLayout.FillDirectionMaxCells = dimensions
278 gridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
279 gridLayout.VerticalAlignment = Enum.VerticalAlignment.Center
280 gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
281
282
283 -- creates dimensions^2 tiles (e.g. 9^2 = 81 boxes)
284 for b = 1,dimensions^2 do
285
286 -- new code
287 local box = boardBox:Clone()
288 box.LayoutOrder = b
289 box.Name = b
290
291 -- modulus gets column number of box
292 local A = 4 <= b % 9 and b % 9 <= 6
293
294 -- division gets row number of box
295 local B = 3 < b / 9 and b / 9 <= 6
296
297 -- apply boolean algebra
298 if (A or B) and not (A and B) then
299 box.BackgroundColor3 = Color3.new(0.95,0.95,0.95)
300 else
301 box.BackgroundColor3 = Color3.new(1,1,1)
302 end
303
304 local oldText = box.Text
305
306 -- filters text whenever the text is changed
307 box.MouseButton1Click:Connect(function()
308 if not isGenerating then
309
310 -- fills box if in fill mode
311 if writeMode == "fill" then
312 local noteFrame = box:FindFirstChild("NoteFrame")
313 if noteFrame then noteFrame:Destroy() end
314
315 local row = math.ceil(b / dimensions)
316 local column = ((b - 1) % dimensions) + 1
317
318 -- only overwrites if editable box
319 if originalGrid[row][column] == 0 then
320
321 -- clears text if pencil is 0, otherwise sets to pencil
322 box.Text = (pencil == 0 and "") or pencil
323
324 -- only checks board if it has changed
325 if box.Text ~= oldText then
326 oldText = box.Text
327
328 -- set associated grid value to value of pencil
329 grid[row][column] = pencil
330
331 if isBoardSolved() and isBoardFull() then
332 endGame()
333 end
334 end
335 end
336
337 -- makes note if in note mode and box is blank
338 elseif writeMode == "note" and box.Text == "" then
339 modifyNote(box)
340 end
341 end
342 end)
343
344 box.Parent = board
345 end
346
347 paddingLayout.Parent = board
348 gridLayout.Parent = board
349end
350
351-- generates the numbers for the grid, complying with Sudoku rules
352function generateGrid(difficulty)
353 setUpGrid()
354
355 -- constants
356 local row = 1
357 local column = 1
358 local rowClearTally = 0 -- no. of times rows have been cleared
359
360 -- fills grid with random numbers
361 while row <= dimensions do
362 local tries = 0 -- no. of times a number has been replaced
363
364 while column <= dimensions do
365
366 -- generates random number
367 local number = math.random(1,dimensions)
368
369 -- while 'number' is invalid,
370 while not isValid(number, row, column) do
371
372 -- generates new number
373 number = math.random(1,dimensions)
374 tries = tries + 1
375
376 -- if 50 tries or more then clear entire row
377 if tries >= 50 then
378 table.clear(grid[row])
379
380 -- increment rows cleared, start from beginning of row
381 rowClearTally = rowClearTally + 1
382 column = 1
383
384 -- if 50 or more rows have been cleared then clear entire grid
385 if rowClearTally >= 50 then
386 setUpGrid()
387
388 -- start from top left of board
389 row = 1
390 column = 1
391 end
392 end
393 end
394
395 -- if number is valid, add it to the grid
396 grid[row][column] = number
397 column = column + 1
398
399 -- reset tries to 0
400 tries = 0
401 end
402
403 -- start from beginning of next row
404 row = row + 1
405 column = 1
406 end
407
408 -- retrieves proportion of numbers to keep
409 local proportion = getFilledProportion(difficulty)
410
411 -- calculates number of numbers to remove
412 local numbersToRemove = dimensions^2 - (dimensions^2 * proportion)
413
414 -- continue removing numbers until sufficient
415 while numbersToRemove > 0 do
416
417 -- generates random row and column
418 local row = math.random(1,dimensions)
419 local column = math.random(1,dimensions)
420
421 -- if box is not empty then make empty
422 if grid[row][column] ~= 0 then
423 grid[row][column] = 0
424
425 -- decrement 'numbersToRemove'
426 numbersToRemove = numbersToRemove - 1
427 end
428 end
429end
430
431-- creates empty 2D array, each row being an array
432function setUpGrid()
433 table.clear(grid)
434
435 for i = 1,dimensions do
436 table.insert(grid, {})
437
438 for j = 1,dimensions do
439 table.insert(grid[i], 0)
440 end
441 end
442end
443
444-- gets copy of grid using recursion
445-- could be done iteratively, but more concise this way
446function getCopy(original)
447 local copy = {}
448
449 for index,value in pairs(original) do
450
451 -- goes deeper if value is table
452 if type(value) == "table" then
453 value = getCopy(value)
454 end
455
456 table.insert(copy, value)
457 end
458
459 return copy
460end
461
462-- displays entire grid for debugging
463function displayGrid(gridToDisplay)
464 print("START OF GRID")
465
466 for i = 1,dimensions do
467 print(table.concat(gridToDisplay[i], ", "))
468 end
469
470 print("END OF GRID")
471end
472
473-- modifies notes of clicked box and boxes in section
474function modifyNote(box)
475
476 -- continues if not erasing
477 if pencil ~= 0 then
478 local noteFrame = box:FindFirstChild("NoteFrame")
479 local candidate
480
481 -- continues if note frame found
482 if noteFrame then
483
484 -- assumes candidate already exists in note frame
485 candidate = noteFrame:FindFirstChild(pencil)
486
487 -- removes existing candidate
488 if candidate then
489 candidate:Destroy()
490
491 -- deletes note frame if no candidates found
492 -- UIGridLayout counts as child but isn't
493 -- corrected by -1
494 if #noteFrame:GetChildren() - 1 == 0 then
495 noteFrame:Destroy()
496 end
497
498 -- adds candidate to note frame
499 else
500 candidate = game.ReplicatedStorage.Candidate:Clone()
501 candidate.Text = pencil
502 candidate.Name = pencil
503
504 candidate.Parent = noteFrame
505 end
506
507 -- if no noteFrame, no candidate either, so create both
508 else
509 noteFrame = game.ReplicatedStorage.NoteFrame:Clone()
510
511 candidate = game.ReplicatedStorage.Candidate:Clone()
512 candidate.Text = pencil
513 candidate.Name = pencil
514
515 candidate.Parent = noteFrame
516 noteFrame.Parent = box
517 end
518
519 -- removes all notes from box
520 else
521 local noteFrame = box:FindFirstChild("NoteFrame")
522 if noteFrame then noteFrame:Destroy() end
523 end
524end
525
526-- checks if number in box is valid
527function isValid(number, row, column)
528
529 -- checks row
530 for c = 1,dimensions do
531 if grid[row][c] == number and c ~= column then
532 return false
533 end
534 end
535
536 -- checks column
537 for r = 1, dimensions do
538 if grid[r][column] == number and r ~= row then
539 return false
540 end
541 end
542
543 local topRow = row - ((row - 1) % 3)
544 local leftColumn = column - ((column - 1) % 3)
545
546 -- check 3x3 grid box is within
547 for r = topRow, topRow + 2 do
548 for c = leftColumn, leftColumn + 2 do
549 if grid[r][c] == number and not (r == row and c == column) then
550 return false
551 end
552 end
553 end
554
555 return true
556end
557
558-- returns proportion (percentage) of boxes that should be FILLED
559function getFilledProportion(difficulty)
560
561 -- format: [difficulty] = {lower percentage, upper percentage}
562 local percentages = {
563 ["Easy"] = {70,80},
564 ["Medium"] = {55,65},
565 ["Hard"] = {40,50},
566 ["Extreme"] = {30,35},
567 ["Impossible"] = {20,25}
568 }
569
570 local entry = percentages[difficulty]
571
572 return math.random(entry[1],entry[2]) / 100
573end
574
575-- maps numbers to boxes
576function mapGridToBoxes()
577 for row = 1,dimensions do
578 for column = 1,dimensions do
579 local value = grid[row][column]
580 local boxNumber = getBoxNumber(row, column)
581 local box = board:FindFirstChild(boxNumber)
582
583 if value ~= 0 then
584 box.Text = value
585
586 -- text colour is different to tell between the two types of boxes
587 else
588 box.TextColor3 = Color3.new(0.5,0.75,1)
589 box.Text = ""
590
591 local noteFrame = box:FindFirstChild("NoteFrame")
592 if noteFrame then noteFrame:Destroy() end
593 end
594 end
595 end
596end
597
598-- returns true if board is full
599function isBoardFull()
600 for row = 1,dimensions do
601 for column = 1,dimensions do
602 if grid[row][column] == 0 then
603 return false
604 end
605 end
606 end
607
608 return true
609end
610
611-- returns true if board is solved
612function isBoardSolved()
613 local flag = true
614
615 for row = 1,dimensions do
616 for column = 1,dimensions do
617 if originalGrid[row][column] == 0 then
618 local boxNumber = getBoxNumber(row, column)
619 local box = board[boxNumber]
620
621 box.TextColor3 = Color3.new(0.5,0.75,1)
622
623 if not isValid(grid[row][column], row, column) then
624 box.TextColor3 = Color3.new(1,0,0)
625 flag = false
626 end
627 end
628 end
629 end
630
631 return flag
632end
633
634-- officially ends game
635function endGame()
636
637 -- applies visual rainbow effect
638 for row = 1,dimensions do
639 for column = 1,dimensions do wait()
640 local boxNumber = getBoxNumber(row, column)
641
642 board[boxNumber].BackgroundColor3 =
643 Color3.fromHSV(boxNumber / (dimensions^2),0.1,1)
644 end
645 end
646end
647
648
649
650--[[ Start-up functions ]]
651initialisePlayingArea()
652
653return module
654