├── .gitignore
├── LICENSE
├── README.md
└── src
├── GameFrame.kt
└── SudokuGame.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | .idea/
3 | *.iml
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ana Capatina / anikiki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Workshop: Kotlin - Learn by building a simple Sudoku Game (Part 1 of 3)
2 |
3 | This is a skeleton to build a simple standalone Sudoku Game in Kotlin.
4 |
5 |
6 | ## Workshop resources
7 | * Tutorial:
8 | https://goo.gl/LbqBk2
9 |
10 | * Java SDK:
11 | http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
12 |
13 | * IntelliJ IDEA:
14 | https://www.jetbrains.com/idea/download
15 |
16 |
17 | ## Kotlin resources
18 |
19 | * Browse through samples and try them online:
20 | https://try.kotlinlang.org/#/Examples/Hello,%20world!/Simplest%20version/Simplest%20version.kt
21 |
22 | * Kotlin koans. Hands-on learning:
23 | https://try.kotlinlang.org/#/Kotlin%20Koans/Introduction/Hello,%20world!/Task.kt
24 |
25 | * Kotlin language reference:
26 | http://kotlinlang.org/docs/reference/
27 |
28 |
29 | ## Feedback
30 | Please help improve this workshop - https://goo.gl/XgAEir
31 |
32 | ## Next part of the workshop
33 | * Part 2:
34 | https://github.com/anikiki/sudoku-init-part2
35 |
36 |
--------------------------------------------------------------------------------
/src/GameFrame.kt:
--------------------------------------------------------------------------------
1 | import java.awt.Color
2 | import java.awt.Dimension
3 | import java.awt.Font
4 | import java.awt.GridLayout
5 | import java.awt.event.ActionEvent
6 | import java.awt.event.ActionListener
7 | import javax.swing.*
8 |
9 | /**
10 | * The Sudoku game UI built using java swing.
11 | */
12 | class GameFrame(sudokuGame: SudokuGame) : JFrame() {
13 |
14 | // 9x9 matrix of JTextFields, each containing String "1" to "9", or empty String
15 | val cells = Array>(GRID_SIZE) { arrayOfNulls(GRID_SIZE) }
16 |
17 | init {
18 | val cp = contentPane
19 | cp.layout = GridLayout(3, 3)
20 |
21 | val listener = InputListener(sudokuGame, this)
22 |
23 | // Construct 3x3 JPanels and add to the content-pane
24 | val panels = Array>(3) { arrayOfNulls(3) }
25 | for (row in 0 until 3) {
26 | for (col in 0 until 3) {
27 | val panel = JPanel()
28 | panel.border = BorderFactory.createLineBorder(Color.GRAY, 2)
29 | panels[row][col] = panel
30 | // Construct 9x9 JTextFields and add to the content-pane
31 | createBoard(panel, sudokuGame, listener, row * 3, row * 3 + 3, col * 3, col * 3 + 3)
32 | cp.add(panel)
33 | }
34 | }
35 |
36 | // Set the size of the content-pane and pack all the components under this container.
37 | cp.preferredSize = Dimension(CANVAS_WIDTH, CANVAS_HEIGHT)
38 | pack()
39 |
40 | defaultCloseOperation = JFrame.EXIT_ON_CLOSE // Handle window closing
41 | title = "Sudoku"
42 | isVisible = true
43 | }
44 |
45 |
46 | private fun createBoard(panel: JPanel, sudokuGame: SudokuGame, listener: InputListener, rowStart: Int, rowEnd: Int, colStart: Int, colEnd: Int) {
47 | panel.layout = GridLayout(3, 3)
48 | for (row in rowStart until rowEnd) {
49 | for (col in colStart until colEnd) {
50 | cells[row][col] = JTextField() // Allocate element of array
51 | panel.add(cells[row][col]) // ContentPane adds JTextField
52 | if (!sudokuGame.visibleElements[row][col]) {
53 | cells[row][col]?.text = "" // set to empty string
54 | cells[row][col]?.isEditable = true
55 | cells[row][col]?.background = HIDDEN_NUMBER_CELL_BGCOLOR
56 | cells[row][col]?.addActionListener(listener); // For all editable rows and cols
57 | } else {
58 | cells[row][col]?.text = sudokuGame.solution[row][col].toString() + ""
59 | cells[row][col]?.isEditable = false
60 | cells[row][col]?.background = VISIBLE_NUMBER_CELL_BGCOLOR
61 | cells[row][col]?.foreground = VISIBLE_NUMBER_CELL_TEXT
62 | }
63 | // Beautify all the cells
64 | cells[row][col]?.horizontalAlignment = JTextField.CENTER
65 | cells[row][col]?.font = FONT_NUMBERS
66 | }
67 | }
68 | }
69 |
70 | companion object {
71 | private val GRID_SIZE = 9 // Size of the board with cells
72 | private val CELL_SIZE = 60 // Cell width/height in pixels
73 | private val CANVAS_WIDTH = CELL_SIZE * GRID_SIZE // Board width in pixels
74 | private val CANVAS_HEIGHT = CELL_SIZE * GRID_SIZE // Board height in pixels
75 | private val FONT_NUMBERS = Font("Monospaced", Font.BOLD, 20)
76 | val INCORRECT_NUMBER_CELL_BGCOLOR = Color.RED
77 | val HIDDEN_NUMBER_CELL_BGCOLOR = Color.LIGHT_GRAY
78 | val VISIBLE_NUMBER_CELL_BGCOLOR = Color.WHITE
79 | val VISIBLE_NUMBER_CELL_TEXT = Color.BLACK
80 | }
81 | }
82 |
83 | class InputListener(private val sudokuGame: SudokuGame, private val sudokuFrame: GameFrame) : ActionListener {
84 |
85 | override fun actionPerformed(e: ActionEvent) {
86 | // All the 9x9 JTextFields invoke this handler. We need to determine
87 | // which JTextField (which row and column) is the source for this invocation.
88 | var rowSelected = -1
89 | var colSelected = -1
90 |
91 | // Get the source object that fired the event
92 | val source = e.source as JTextField
93 | // Scan JTextFields for all rows and columns, and match with the source object
94 | var found = false
95 | var row = 0
96 | while (row < sudokuGame.visibleElements.size && !found) {
97 | var col = 0
98 | while (col < sudokuGame.visibleElements[row].size && !found) {
99 | if (sudokuFrame.cells[row][col] === source) {
100 | rowSelected = row
101 | colSelected = col
102 | found = true // break the inner/outer loops
103 | }
104 | col++
105 | }
106 | row++
107 | }
108 |
109 | val intValue = Integer.valueOf(sudokuFrame.cells[rowSelected][colSelected]?.text)
110 | if (sudokuGame.isUserInputCorrect(intValue, rowSelected, colSelected)) {
111 | sudokuGame.visibleElements[rowSelected][colSelected] = true
112 | source.background = GameFrame.VISIBLE_NUMBER_CELL_BGCOLOR
113 | } else {
114 | source.background = GameFrame.INCORRECT_NUMBER_CELL_BGCOLOR
115 | JOptionPane.showMessageDialog(source.parent, "Try Again!");
116 | sudokuFrame.cells[rowSelected][colSelected]?.text = ""
117 | }
118 |
119 | if (sudokuGame.isSolved()) {
120 | JOptionPane.showMessageDialog(source.parent, "Solved!");
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/src/SudokuGame.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * The Sudoku logic.
3 | */
4 | class SudokuGame {
5 |
6 | // This property holds a 2D array of integers with the Sudoku game solution.
7 | val solution = createSolution()
8 |
9 | // This property holds a 2D array of booleans. It is used to show or hide a value from our Sudoku board.
10 | val visibleElements = initVisibleElements()
11 |
12 | // ## STEP_1 ##
13 | // Make this function return a 2D array of integers with the Sudoku game solution. Use these values:
14 | // 3, 5, 9, 6, 1, 8, 4, 2, 7
15 | // 7, 4, 2, 5, 3, 9, 8, 6, 1
16 | // 1, 6, 8, 4, 7, 2, 9, 5, 3
17 | // 4, 2, 3, 8, 9, 5, 7, 1, 6
18 | // 5, 8, 7, 1, 6, 4, 3, 9, 2
19 | // 6, 9, 1, 7, 2, 3, 5, 8, 4
20 | // 2, 7, 5, 9, 4, 6, 1, 3, 8
21 | // 8, 3, 4, 2, 5, 1, 6, 7, 9
22 | // 9, 1, 6, 3, 8, 7, 2, 4, 5
23 | private fun createSolution(): Array {
24 | TODO("STEP1 - You need to initialise the 2D array containing the solution. See the comment in code marked with ## STEP_1 ##")
25 | }
26 |
27 | // ## STEP_2 ##
28 | // Make this function return a 2D array with booleans used to show or hide a value from our sudoku board.
29 | // E.g If you have the value `true` for the element positioned at row 0 and column 0, then the value at the same position
30 | // in the sudoku board will be visible. It is `false`, the user doesn't see the number!
31 | // Let's have 2 values of `false` so that our user will have to enter in 2 numbers in order to solve the sudoku game.
32 | // Something like this - elements at positions (0,0) and (1,8) are `false`:
33 | // false, true, true, true, true, true, true, true, true
34 | // true, true, true, true, true, true, true, true, false
35 | // true, true, true, true, true, true, true, true, true
36 | // true, true, true, true, true, true, true, true, true
37 | // true, true, true, true, true, true, true, true, true
38 | // true, true, true, true, true, true, true, true, true
39 | // true, true, true, true, true, true, true, true, true
40 | // true, true, true, true, true, true, true, true, true
41 | // true, true, true, true, true, true, true, true, true
42 | private fun initVisibleElements(): Array {
43 | TODO("STEP2 - You need to initialise the 2D array containing boolean elements that mark visible items. See the comment in code marked with ## STEP_2 ##")
44 | }
45 |
46 | // ## STEP_3 ##
47 | // This function checks if the number entered by the user is valid in the context of the solution.
48 | // It takes 3 parameters:
49 | // userInput - number entered by the user
50 | // row - the row where the number is placed in the board
51 | // column - the column where the number is placed in the board
52 | // E.g isInputValid(8, 1, 7) means we're asking if the number 8 entered by the user at position (1,7) is correct.
53 | // Correct means the number entered by the user is the same as the value found in our `solution` 2D array.
54 | // If it's the same, return `true`. Otherwise just return `false`, don't change anything else.
55 | fun isUserInputCorrect(userInput: Int, row: Int, col: Int): Boolean {
56 | TODO("STEP3 - You need to check whether the number entered by the user at position (row, col) is correct. See the comment in code marked with ## STEP_3 ##")
57 | }
58 |
59 | // ## STEP_4 ##
60 | // This function checks if the sudoku is solved.
61 | // Previously we checked every time the user entered a number if that was correct.
62 | // After the user entered all numbers and they were correct it means all values in our `visibleElements` property are true.
63 | // In this method we'll just traverse the `visibleElements` 2D array. If all values are `true` it means sudoku is solved and we return `true`.
64 | // If we find at least one value that is `false` then we stop. It means not all numbers were filled in by the user. We return `false`.
65 | fun isSolved(): Boolean {
66 | TODO("STEP4 - You need to traverse our `visibleElements` 2D array and search for elements that are `false`. See the comment in code marked with ## STEP_4 ##")
67 | }
68 | }
--------------------------------------------------------------------------------