├── .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 | } --------------------------------------------------------------------------------