├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── GameWidget.swift ├── MainWindow.swift ├── NewGameDialog.swift ├── Random.swift ├── Tile.swift ├── UI_GameWidget.swift ├── UI_MainWindow.swift ├── UI_NewGameDialog.swift └── main.swift ├── UI ├── UI_GameWidget.ui ├── UI_MainWindow.ui └── UI_NewGameDialog.ui └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | .idea/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andreas Schulz 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 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Qlift", 6 | "repositoryURL": "https://github.com/Longhanks/qlift", 7 | "state": { 8 | "branch": "master", 9 | "revision": "a6da5b2b37503b37d7059383f0004749b404c967", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "CQlift", 15 | "repositoryURL": "https://github.com/Longhanks/qlift-c-api", 16 | "state": { 17 | "branch": "master", 18 | "revision": "a5900ae0a3b85230573255fb46774eb8223696ba", 19 | "version": null 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "swiftmine", 6 | products: [ 7 | .executable( 8 | name: "swiftmine", 9 | targets: [ 10 | "swiftmine" 11 | ] 12 | ) 13 | ], 14 | dependencies: [ 15 | .package( 16 | name: "Qlift", 17 | url: "https://github.com/Longhanks/qlift", 18 | .branch("master") 19 | ) 20 | ], 21 | targets: [ 22 | .target( 23 | name: "swiftmine", 24 | dependencies: [ 25 | "Qlift" 26 | ], 27 | path: "Sources", 28 | cxxSettings: [ 29 | .unsafeFlags([ 30 | "-I/usr/local/opt/qt/lib/QtCore.framework/Headers", 31 | "-I/usr/local/opt/qt/lib/QtGui.framework/Headers", 32 | "-I/usr/local/opt/qt/lib/QtWidgets.framework/Headers", 33 | "-I/usr/local/opt/qt/include" 34 | ], 35 | .when(platforms: [.macOS]) 36 | ) 37 | ], 38 | linkerSettings: [ 39 | .unsafeFlags([ 40 | "/usr/local/opt/qt/lib/QtCore.framework/QtCore", 41 | "/usr/local/opt/qt/lib/QtGui.framework/QtGui", 42 | "/usr/local/opt/qt/lib/QtWidgets.framework/QtWidgets", 43 | ], 44 | .when(platforms: [.macOS]) 45 | ) 46 | ] 47 | ) 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftmine 2 | Minesweeper with Swift 5 and [Qlift](https://github.com/Longhanks/Qlift "Qlift") 3 | 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Longhanks/swiftmine/master/LICENSE) 5 | 6 | ## Building 7 | 8 | Qt must be installed, of coures. On Linux, `pkgconfig` is used to find the build flags. On macOS, the installation is expected to be at `/usr/local/opt/qt/`, which is also where Homebrew would install Qt per default. 9 | 10 | ### swiftpm 11 | 12 | `swift build` 13 | 14 | [Qlift](https://github.com/Longhanks/Qlift "Qlift") provides the *.ui file compiler, `qlift-uic`, which must be used to compile the files from the UI directory into Swift source files in the Sources directory. This must be repeated whenever the *.ui files are changed. 15 | 16 | ## Screenshot 17 | 18 | ![Screenshot](./screenshot.png "Screenshot") 19 | -------------------------------------------------------------------------------- /Sources/GameWidget.swift: -------------------------------------------------------------------------------- 1 | import Qlift 2 | 3 | 4 | class GameWidget: UI_GameWidget { 5 | var onGameIsWon: (() -> Void)? 6 | var onGameIsLost: (() -> Void)? 7 | let rows: Int32 8 | let columns: Int32 9 | var matrix = [[Tile]]() 10 | 11 | init(rows: Int32, columns: Int32, mines: Int32, parent: QWidget? = nil) { 12 | self.rows = rows 13 | self.columns = columns 14 | super.init(parent: parent) 15 | 16 | for column in 0.. [(Int32, Int32)] { 70 | var indices: [(Int32, Int32)] = [] 71 | indices.append((x - 1, y - 1)) 72 | indices.append((x, y - 1)) 73 | indices.append((x + 1, y - 1)) 74 | indices.append((x - 1, y)) 75 | indices.append((x + 1, y)) 76 | indices.append((x - 1, y + 1)) 77 | indices.append((x, y + 1)) 78 | indices.append((x + 1, y + 1)) 79 | 80 | return indices.filter { (x, y) in 81 | if x >= 0 && y >= 0 { 82 | let columns = self.matrix.count 83 | let rows = self.matrix[0].count 84 | if x < columns && y < rows { 85 | return true 86 | } 87 | } 88 | return false 89 | } 90 | } 91 | 92 | func checkIfGameIsWon() { 93 | for column in self.matrix { 94 | for btn in column { 95 | let countIsVisible = !btn.label.text.isEmpty && btn.label.text != "F" 96 | if !(countIsVisible || btn.isMine) { 97 | return 98 | } 99 | } 100 | } 101 | self.onGameIsWon?() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/MainWindow.swift: -------------------------------------------------------------------------------- 1 | import Qlift 2 | 3 | 4 | class MainWindow: UI_MainWindow { 5 | var dialogIsVisible = false 6 | 7 | init() { 8 | super.init() 9 | self.actionNewGame.connectTriggered { _ in 10 | self.showNewGameDialog() 11 | } 12 | self.pushButtonNewGame.connectClicked { _ in 13 | self.showNewGameDialog() 14 | } 15 | self.actionExit.connectTriggered { _ in 16 | _ = self.close() 17 | } 18 | let desktopRect = QApplication.desktop.availableGeometry(for: self) 19 | let center = desktopRect.center 20 | self.move(to: QPoint(x: center.x - self.width / 2, y: center.y - self.height / 2)) 21 | } 22 | 23 | func showNewGameDialog() { 24 | if self.dialogIsVisible { 25 | return 26 | } 27 | self.dialogIsVisible = true 28 | 29 | let dlg = NewGameDialog(parent: self) 30 | if dlg.exec() == .Accepted { 31 | let game = GameWidget(rows: dlg.spinBoxRows.value, columns: dlg.spinBoxColumns.value, mines: dlg.spinBoxMines.value, parent: self) 32 | game.onGameIsWon = { [weak self] in 33 | self?.gameIsWon() 34 | } 35 | game.onGameIsLost = { [weak self] in 36 | self?.gameIsLost() 37 | } 38 | QTimer.singleShot(msec: 0, timerType: .CoarseTimer) { [unowned self] in 39 | self.centralWidget = game 40 | QTimer.singleShot(msec: 0, timerType: .CoarseTimer) { [unowned self] in 41 | self.resize(width: 0, height: 0) 42 | self.setFixedSize(self.size) 43 | } 44 | } 45 | self.dialogIsVisible = false 46 | } else { 47 | QCoreApplication.instance.quit() 48 | } 49 | } 50 | 51 | func gameIsWon() { 52 | self.dialogIsVisible = true 53 | let msgBox = QMessageBox(parent: self) 54 | msgBox.windowModality = .WindowModal 55 | msgBox.windowTitle = "You won!" 56 | msgBox.icon = .Question 57 | msgBox.text = "Congratulations! New game?" 58 | msgBox.standardButtons = [.Yes, .No] 59 | msgBox.setDefaultStandardButton(.No) 60 | if msgBox.exec() == .No { 61 | QCoreApplication.instance.quit() 62 | } else { 63 | self.dialogIsVisible = false 64 | self.showNewGameDialog() 65 | } 66 | } 67 | 68 | func gameIsLost() { 69 | self.dialogIsVisible = true 70 | let msgBox = QMessageBox(parent: self) 71 | msgBox.windowModality = .WindowModal 72 | msgBox.windowTitle = "You lost!" 73 | msgBox.icon = .Question 74 | msgBox.text = "Game over. New game?" 75 | msgBox.standardButtons = [.Yes, .No] 76 | msgBox.setDefaultStandardButton(.No) 77 | if msgBox.exec() == .No { 78 | QCoreApplication.instance.quit() 79 | } else { 80 | self.dialogIsVisible = false 81 | self.showNewGameDialog() 82 | } 83 | } 84 | 85 | override func closeEvent(event: QCloseEvent) { 86 | if self.dialogIsVisible { 87 | event.ignore() 88 | return 89 | } 90 | let msgBox = QMessageBox(parent: self) 91 | msgBox.windowModality = .WindowModal 92 | msgBox.windowTitle = "Confirm exit" 93 | msgBox.icon = .Question 94 | msgBox.text = "Are you sure you want to quit?" 95 | msgBox.standardButtons = [.Yes, .No] 96 | msgBox.setDefaultStandardButton(.No) 97 | if msgBox.exec() == .Yes { 98 | event.accept() 99 | } else { 100 | event.ignore() 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/NewGameDialog.swift: -------------------------------------------------------------------------------- 1 | import Qlift 2 | 3 | 4 | class NewGameDialog: UI_NewGameDialog { 5 | init(parent: QWidget? = nil) { 6 | super.init(parent: parent, flags: .Sheet) 7 | self.comboBoxDefaultModes.connectCurrentIndexChanged(to: self.checkNewMode) 8 | if let parentWidget = parent { 9 | self.move(to: parentWidget.window.frameGeometry.topLeft + parentWidget.window.rect.center - self.rect.center) 10 | } 11 | } 12 | 13 | func checkNewMode(_ newMode: String) { 14 | switch newMode { 15 | case "Beginner": 16 | self.spinBoxRows.enabled = false 17 | self.spinBoxRows.value = 9 18 | self.spinBoxColumns.enabled = false 19 | self.spinBoxColumns.value = 9 20 | self.spinBoxMines.enabled = false 21 | self.spinBoxMines.value = 10 22 | case "Intermediate": 23 | self.spinBoxRows.enabled = false 24 | self.spinBoxRows.value = 16 25 | self.spinBoxColumns.enabled = false 26 | self.spinBoxColumns.value = 16 27 | self.spinBoxMines.enabled = false 28 | self.spinBoxMines.value = 40 29 | case "Expert": 30 | self.spinBoxRows.enabled = false 31 | self.spinBoxRows.value = 30 32 | self.spinBoxColumns.enabled = false 33 | self.spinBoxColumns.value = 16 34 | self.spinBoxMines.enabled = false 35 | self.spinBoxMines.value = 99 36 | case "Custom": 37 | self.spinBoxRows.enabled = true 38 | self.spinBoxColumns.enabled = true 39 | self.spinBoxMines.enabled = true 40 | default: 41 | fatalError("Unknown game mode.") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Random.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin 5 | #endif 6 | 7 | 8 | func randint(bound: UInt32) -> Int { 9 | #if os(Linux) 10 | return random() % (Int(bound) + 1) 11 | #else 12 | return Int(arc4random_uniform(bound + 1)) 13 | #endif 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Tile.swift: -------------------------------------------------------------------------------- 1 | import Qlift 2 | 3 | 4 | class Tile: QFrame { 5 | var onClickedSuccessfully: ((Int32, Int32) -> Void)? 6 | var onClickedMine: (() -> Void)? 7 | var isMine = false 8 | var count = 0 9 | let x: Int32 10 | let y: Int32 11 | var label: QLabel! 12 | 13 | init(x: Int32, y: Int32, parent: QWidget) { 14 | self.x = x 15 | self.y = y 16 | super.init(parent: parent) 17 | let layout = QVBoxLayout(parent: self) 18 | layout.contentsMargins = QMargins(left: 0, top: 0, right: 0, bottom: 0) 19 | self.layout = layout 20 | self.label = QLabel(parent: self) 21 | self.label.alignment = .AlignCenter 22 | layout.add(widget: self.label) 23 | self.styleSheet = "QFrame { background-color: white; }" 24 | self.sizePolicy = QSizePolicy(horizontal: .Minimum, vertical: .Minimum) 25 | } 26 | 27 | private var _size = QSize(width: 20, height: 20) 28 | 29 | override var sizeHint: QSize { 30 | get { 31 | return self._size 32 | } 33 | } 34 | 35 | override func mousePressEvent(event: QMouseEvent) { 36 | if event.button == .LeftButton { 37 | self.clickedTile() 38 | } else if event.button == .RightButton { 39 | self.rightClickedTile() 40 | } 41 | } 42 | } 43 | 44 | extension Tile { 45 | func clickedTile(ignoreMark: Bool = false) { 46 | if self.label.text == "F" { 47 | if !ignoreMark { 48 | return 49 | } else { 50 | self.rightClickedTile() 51 | } 52 | } 53 | 54 | else if !self.label.text.isEmpty { 55 | return 56 | } 57 | 58 | else if self.isMine { 59 | self.onClickedMine?() 60 | return 61 | } 62 | 63 | self.label.text = String(self.count) 64 | self.onClickedSuccessfully?(self.x, self.y) 65 | } 66 | 67 | func rightClickedTile() { 68 | if !self.label.text.isEmpty && self.label.text != "F" { 69 | return 70 | } 71 | 72 | if self.label.text == "F" { 73 | self.label.styleSheet = "QLabel {color: black;}" 74 | self.label.text = "" 75 | } else { 76 | self.label.styleSheet = "QLabel {color: red;}" 77 | self.label.text = "F" 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/UI_GameWidget.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | ** Form generated from reading UI file 'UI_GameWidget.ui' 3 | ** 4 | ** Created by: Qlift User Interface Compiler version 5 | ** 6 | ** WARNING! All changes made in this file will be lost when recompiling UI file! 7 | ********************************************************************************/ 8 | 9 | import Qlift 10 | 11 | 12 | class UI_GameWidget: QWidget { 13 | var verticalLayout: QVBoxLayout! 14 | var verticalSpacerTop: QSpacerItem! 15 | var horizontalLayout: QHBoxLayout! 16 | var horizontalSpacerLeft: QSpacerItem! 17 | var mainLayout: QGridLayout! 18 | var horizontalSpacerRight: QSpacerItem! 19 | var verticalSpacerBottom: QSpacerItem! 20 | 21 | override init(parent: QWidget? = nil, flags: Qt.WindowFlags = .Widget) { 22 | super.init(parent: parent, flags: flags) 23 | self.geometry = QRect(x: 0, y: 0, width: 128, height: 152) 24 | self.windowTitle = "Form" 25 | verticalLayout = QVBoxLayout(parent: self) 26 | verticalLayout.spacing = 0 27 | verticalSpacerTop = QSpacerItem(width: 20, height: 40, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 28 | verticalLayout.add(item: verticalSpacerTop) 29 | horizontalLayout = QHBoxLayout(parent: nil) 30 | horizontalSpacerLeft = QSpacerItem(width: 40, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 31 | horizontalLayout.add(item: horizontalSpacerLeft) 32 | mainLayout = QGridLayout(parent: nil) 33 | mainLayout.spacing = 1 34 | horizontalLayout.add(layout: mainLayout) 35 | horizontalSpacerRight = QSpacerItem(width: 40, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 36 | horizontalLayout.add(item: horizontalSpacerRight) 37 | verticalLayout.add(layout: horizontalLayout) 38 | verticalSpacerBottom = QSpacerItem(width: 20, height: 40, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 39 | verticalLayout.add(item: verticalSpacerBottom) 40 | verticalLayout.contentsMargins = QMargins(left: 0, top: 0, right: 0, bottom: 0) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Sources/UI_MainWindow.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | ** Form generated from reading UI file 'UI_MainWindow.ui' 3 | ** 4 | ** Created by: Qlift User Interface Compiler version 5 | ** 6 | ** WARNING! All changes made in this file will be lost when recompiling UI file! 7 | ********************************************************************************/ 8 | 9 | import Qlift 10 | 11 | 12 | class UI_MainWindow: QMainWindow { 13 | var centralwidget: QWidget! 14 | var verticalLayout_2: QVBoxLayout! 15 | var Main: QWidget! 16 | var verticalLayout: QVBoxLayout! 17 | var verticalSpacerTop: QSpacerItem! 18 | var horizontalLayout: QHBoxLayout! 19 | var horizontalSpacerLeft: QSpacerItem! 20 | var pushButtonNewGame: QPushButton! 21 | var horizontalSpacerRight: QSpacerItem! 22 | var verticalSpacerBottom: QSpacerItem! 23 | var menubar: QMenuBar! 24 | var menuFile: QMenu! 25 | var actionNewGame: QAction! 26 | var actionExit: QAction! 27 | 28 | override init(parent: QWidget? = nil, flags: Qt.WindowFlags = .Widget) { 29 | super.init(parent: parent, flags: flags) 30 | actionNewGame = QAction(parent: self) 31 | actionNewGame.text = "New Game" 32 | actionExit = QAction(parent: self) 33 | actionExit.text = "Exit" 34 | self.geometry = QRect(x: 0, y: 0, width: 205, height: 169) 35 | self.windowTitle = "swiftmine" 36 | centralwidget = QWidget(parent: self) 37 | verticalLayout_2 = QVBoxLayout(parent: centralwidget) 38 | verticalLayout_2.spacing = 0 39 | Main = QWidget(parent: centralwidget) 40 | verticalLayout = QVBoxLayout(parent: Main) 41 | verticalSpacerTop = QSpacerItem(width: 20, height: 32, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 42 | verticalLayout.add(item: verticalSpacerTop) 43 | horizontalLayout = QHBoxLayout(parent: nil) 44 | horizontalSpacerLeft = QSpacerItem(width: 40, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 45 | horizontalLayout.add(item: horizontalSpacerLeft) 46 | pushButtonNewGame = QPushButton(parent: Main) 47 | pushButtonNewGame.text = "New Game" 48 | horizontalLayout.add(widget: pushButtonNewGame) 49 | horizontalSpacerRight = QSpacerItem(width: 40, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 50 | horizontalLayout.add(item: horizontalSpacerRight) 51 | verticalLayout.add(layout: horizontalLayout) 52 | verticalSpacerBottom = QSpacerItem(width: 20, height: 40, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 53 | verticalLayout.add(item: verticalSpacerBottom) 54 | verticalLayout_2.add(widget: Main) 55 | verticalLayout_2.contentsMargins = QMargins(left: 0, top: 0, right: 0, bottom: 0) 56 | self.centralWidget = centralwidget 57 | menubar = QMenuBar(parent: self) 58 | menubar.geometry = QRect(x: 0, y: 0, width: 205, height: 22) 59 | menuFile = QMenu(parent: menubar) 60 | menuFile.title = "File" 61 | menuFile.add(action: actionNewGame) 62 | menuFile.add(action: actionExit) 63 | menubar.add(action: menuFile.menuAction()) 64 | self.menuBar = menubar 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /Sources/UI_NewGameDialog.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | ** Form generated from reading UI file 'UI_NewGameDialog.ui' 3 | ** 4 | ** Created by: Qlift User Interface Compiler version 5 | ** 6 | ** WARNING! All changes made in this file will be lost when recompiling UI file! 7 | ********************************************************************************/ 8 | 9 | import Qlift 10 | 11 | 12 | class UI_NewGameDialog: QDialog { 13 | var verticalLayoutMain: QVBoxLayout! 14 | var labelHeader: QLabel! 15 | var verticalSpacerUpper: QSpacerItem! 16 | var groupBoxGameSettings: QGroupBox! 17 | var horizontalLayout: QHBoxLayout! 18 | var comboBoxDefaultModes: QComboBox! 19 | var horizontalSpacerComboBox: QSpacerItem! 20 | var verticalLayoutLabels: QVBoxLayout! 21 | var labelRows: QLabel! 22 | var labelColumns: QLabel! 23 | var labelMines: QLabel! 24 | var horizontalSpacerSpinBoxes: QSpacerItem! 25 | var verticalLayoutSpinBoxes: QVBoxLayout! 26 | var spinBoxRows: QSpinBox! 27 | var spinBoxColumns: QSpinBox! 28 | var spinBoxMines: QSpinBox! 29 | var verticalSpacerLower: QSpacerItem! 30 | var buttonBox: QDialogButtonBox! 31 | 32 | override init(parent: QWidget? = nil, flags: Qt.WindowFlags = .Widget) { 33 | super.init(parent: parent, flags: flags) 34 | self.geometry = QRect(x: 0, y: 0, width: 336, height: 247) 35 | self.windowTitle = "New Game" 36 | verticalLayoutMain = QVBoxLayout(parent: self) 37 | verticalLayoutMain.sizeConstraint = .SetFixedSize 38 | labelHeader = QLabel(parent: self) 39 | labelHeader.text = "Select a default mode or enter custom settings:" 40 | labelHeader.alignment = .AlignCenter 41 | verticalLayoutMain.add(widget: labelHeader) 42 | verticalSpacerUpper = QSpacerItem(width: 20, height: 20, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 43 | verticalLayoutMain.add(item: verticalSpacerUpper) 44 | groupBoxGameSettings = QGroupBox(parent: self) 45 | groupBoxGameSettings.title = "Game Settings" 46 | horizontalLayout = QHBoxLayout(parent: groupBoxGameSettings) 47 | comboBoxDefaultModes = QComboBox(parent: groupBoxGameSettings) 48 | comboBoxDefaultModes.add(item: "Beginner") 49 | comboBoxDefaultModes.add(item: "Intermediate") 50 | comboBoxDefaultModes.add(item: "Expert") 51 | comboBoxDefaultModes.add(item: "Custom") 52 | horizontalLayout.add(widget: comboBoxDefaultModes) 53 | horizontalSpacerComboBox = QSpacerItem(width: 40, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 54 | horizontalLayout.add(item: horizontalSpacerComboBox) 55 | verticalLayoutLabels = QVBoxLayout(parent: nil) 56 | labelRows = QLabel(parent: groupBoxGameSettings) 57 | labelRows.text = "Rows:" 58 | verticalLayoutLabels.add(widget: labelRows) 59 | labelColumns = QLabel(parent: groupBoxGameSettings) 60 | labelColumns.text = "Columns:" 61 | verticalLayoutLabels.add(widget: labelColumns) 62 | labelMines = QLabel(parent: groupBoxGameSettings) 63 | labelMines.text = "Mines:" 64 | verticalLayoutLabels.add(widget: labelMines) 65 | horizontalLayout.add(layout: verticalLayoutLabels) 66 | horizontalSpacerSpinBoxes = QSpacerItem(width: 28, height: 20, horizontalPolicy: .Expanding, verticalPolicy: .Minimum) 67 | horizontalLayout.add(item: horizontalSpacerSpinBoxes) 68 | verticalLayoutSpinBoxes = QVBoxLayout(parent: nil) 69 | spinBoxRows = QSpinBox(parent: groupBoxGameSettings) 70 | spinBoxRows.enabled = false 71 | spinBoxRows.minimum = 8 72 | spinBoxRows.maximum = 30 73 | spinBoxRows.value = 9 74 | verticalLayoutSpinBoxes.add(widget: spinBoxRows) 75 | spinBoxColumns = QSpinBox(parent: groupBoxGameSettings) 76 | spinBoxColumns.enabled = false 77 | spinBoxColumns.minimum = 8 78 | spinBoxColumns.maximum = 24 79 | spinBoxColumns.value = 9 80 | verticalLayoutSpinBoxes.add(widget: spinBoxColumns) 81 | spinBoxMines = QSpinBox(parent: groupBoxGameSettings) 82 | spinBoxMines.enabled = false 83 | spinBoxMines.minimum = 10 84 | spinBoxMines.maximum = 668 85 | spinBoxMines.value = 10 86 | verticalLayoutSpinBoxes.add(widget: spinBoxMines) 87 | horizontalLayout.add(layout: verticalLayoutSpinBoxes) 88 | verticalLayoutMain.add(widget: groupBoxGameSettings) 89 | verticalSpacerLower = QSpacerItem(width: 20, height: 20, horizontalPolicy: .Minimum, verticalPolicy: .Expanding) 90 | verticalLayoutMain.add(item: verticalSpacerLower) 91 | buttonBox = QDialogButtonBox(parent: self) 92 | buttonBox.orientation = .Horizontal 93 | buttonBox.standardButtons = [.Cancel, .Ok] 94 | verticalLayoutMain.add(widget: buttonBox) 95 | buttonBox.connectAccepted(to: self.accept) 96 | buttonBox.connectRejected(to: self.reject) 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Sources/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Qlift 3 | 4 | #if os(Linux) 5 | srand(UInt32(time(nil))) 6 | #endif 7 | 8 | 9 | func main() -> Int32 { 10 | let application = QApplication() 11 | let mainWindow = MainWindow() 12 | mainWindow.show() 13 | return application.exec() 14 | } 15 | 16 | exit(main()) 17 | 18 | -------------------------------------------------------------------------------- /UI/UI_GameWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | UI_GameWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 128 10 | 152 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | Qt::Vertical 36 | 37 | 38 | 39 | 20 40 | 40 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | 54 | 40 55 | 20 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 1 64 | 65 | 66 | 67 | 68 | 69 | 70 | Qt::Horizontal 71 | 72 | 73 | 74 | 40 75 | 20 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Qt::Vertical 86 | 87 | 88 | 89 | 20 90 | 40 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /UI/UI_MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | UI_MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 205 10 | 169 11 | 12 | 13 | 14 | swiftmine 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 0 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Qt::Vertical 40 | 41 | 42 | 43 | 20 44 | 32 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Qt::Horizontal 55 | 56 | 57 | 58 | 40 59 | 20 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | New Game 68 | 69 | 70 | 71 | 72 | 73 | 74 | Qt::Horizontal 75 | 76 | 77 | 78 | 40 79 | 20 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Qt::Vertical 90 | 91 | 92 | 93 | 20 94 | 40 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 0 108 | 0 109 | 205 110 | 22 111 | 112 | 113 | 114 | 115 | File 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | New Game 125 | 126 | 127 | 128 | 129 | Exit 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /UI/UI_NewGameDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | UI_NewGameDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 336 10 | 247 11 | 12 | 13 | 14 | New Game 15 | 16 | 17 | 18 | QLayout::SetFixedSize 19 | 20 | 21 | 22 | 23 | Select a default mode or enter custom settings: 24 | 25 | 26 | Qt::AlignCenter 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Vertical 34 | 35 | 36 | 37 | 20 38 | 20 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Game Settings 47 | 48 | 49 | 50 | 51 | 52 | 53 | Beginner 54 | 55 | 56 | 57 | 58 | Intermediate 59 | 60 | 61 | 62 | 63 | Expert 64 | 65 | 66 | 67 | 68 | Custom 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Qt::Horizontal 77 | 78 | 79 | 80 | 40 81 | 20 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Rows: 92 | 93 | 94 | 95 | 96 | 97 | 98 | Columns: 99 | 100 | 101 | 102 | 103 | 104 | 105 | Mines: 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Qt::Horizontal 115 | 116 | 117 | 118 | 28 119 | 20 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | false 130 | 131 | 132 | 8 133 | 134 | 135 | 30 136 | 137 | 138 | 9 139 | 140 | 141 | 142 | 143 | 144 | 145 | false 146 | 147 | 148 | 8 149 | 150 | 151 | 24 152 | 153 | 154 | 9 155 | 156 | 157 | 158 | 159 | 160 | 161 | false 162 | 163 | 164 | 10 165 | 166 | 167 | 668 168 | 169 | 170 | 10 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Qt::Vertical 183 | 184 | 185 | 186 | 20 187 | 20 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | Qt::Horizontal 196 | 197 | 198 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | buttonBox 208 | accepted() 209 | UI_NewGameDialog 210 | accept() 211 | 212 | 213 | 248 214 | 254 215 | 216 | 217 | 157 218 | 274 219 | 220 | 221 | 222 | 223 | buttonBox 224 | rejected() 225 | UI_NewGameDialog 226 | reject() 227 | 228 | 229 | 316 230 | 260 231 | 232 | 233 | 286 234 | 274 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Longhanks/swiftmine/bd02c03b4d8eceff2db703dbc5340c09042736c5/screenshot.png --------------------------------------------------------------------------------