First things first: This probably isn't a quick-to-answer-question :(
If you have enough time - read on.
My little project
I want to recreate the Circle The Dot Game by Ketchapp in less than 500 Lines of code. You can try a replica online here. I implemented this using a 2D-Array of UIButtons. Here you can take a quick look of how it looks (the labels are for debugging)
Screenshot of the game scene. I use this GitHub project for orientation (its Java Code).
Complete code
You can just download the whole project: DOWNLOAD PROJECT
Or copy the code:
(just added a view called dotView in the Main.storyboard with measures: X=20, Y=296, Width = 394, Height 390.) Thats all:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var dotView: UIView!
var rowBtnArr: [UIButton] = [] // Simple 1D-Array of ALL Buttons
var btnArr: [[UIButton]] = [] // 2D-Array with ALL Buttons
let defaultColor = UIColor.lightGray
let playerColor = UIColor.systemBlue
let objColor = UIColor.orange
var button: UIButton = UIButton()
var playerX = 4
var playerY = 4
override func viewDidLoad() {
super.viewDidLoad()
buildBoard()
btnArr = Utils.arrayToArrays(arr: rowBtnArr, size: 9)
for btnline in btnArr{
for btn in btnline{
dotView.addSubview(btn)
}
}
btnArr[playerX][playerY].backgroundColor = playerColor
updateTitleColors()
}
#objc func buttonAction(sender: UIButton!) {
print("Button " + sender.title(for: .normal)! + " tapped")
if(sender.backgroundColor == defaultColor){
btnArr[getX(btn: sender)][getY(btn: sender)].backgroundColor = objColor
btnArr[playerX][playerY].backgroundColor = defaultColor
oneStep()
}
//btnArr[playerX][playerY].backgroundColor = defaultColor
//btnArr[playerX][playerY].backgroundColor = playerColor
updateTitleColors()
}
func setupBtn( x: Int, y: Int)->UIButton{
let btnSize = 40
if(y%2==0){ //Gerade Zeilen normal
button = UIButton(frame: CGRect(x: x*btnSize, y: y*btnSize, width: btnSize-3, height: btnSize-3))
}else{ // Ungerade Zeilen versetzt
button = UIButton(frame: CGRect(x: x*btnSize+20, y: y*btnSize, width: btnSize-3, height: btnSize-3))
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.clipsToBounds = true
button.backgroundColor = defaultColor
button.setTitle(String(x) + " " + String(y), for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
//button.setTitleColor(button.backgroundColor, for: .normal)
return button
}
func buildBoard(){
for i in 0...8{
for j in 0...8{
rowBtnArr.append(setupBtn(x: i, y: j))
}
}
}
func getXFromString(string: String) -> Int{ //"2 4"
let separator = " "
let newstr = string
guard let x = newstr.components(separatedBy: separator).first else { return 99 }
return Int(x)!
}
func getYFromString(string: String) -> Int{ //"2 4"
let separator = " "
let newstr = string
guard let x = newstr.components(separatedBy: separator).last else { return 99 }
return Int(x)!
}
func getX(btn: UIButton) -> Int{
let separator = " "
let newstr = btn.title(for: .normal)
guard let x = newstr?.components(separatedBy: separator).first else { return 99 }
return Int(x)!
}
func getY(btn: UIButton) -> Int{
let separator = " "
let newstr = btn.title(for: .normal)
guard let y = newstr?.components(separatedBy: separator).last else { return 99 }
return Int(y)!
}
func updateTitleColors(){
// for btnline in btnArr{
// for btn in btnline{
// btn.setTitleColor(btn.backgroundColor, for: .normal)
// }
// }
}
func oneStep(){
let point = String(playerX) + " " + String(playerY)
if(isOnBorder(point: point)){
playerX = -1
playerY = -1
// YOU LOST ENDSCREEN
print("YOU LOST")
reset()
}else{
let direction = findDirection()
if(getXFromString(string: direction) == -1){
// YOU WON
print("YOU WON")
reset()
}else{
playerX = getXFromString(string: direction)
playerY = getYFromString(string: direction)
btnArr[playerX][playerY].backgroundColor = playerColor
}
}
}
func findDirection()->String{
var blocked: [String] = [] //Array enthält z.B. "3 5", "5 5"
let myQueue = LinkedQueue<Pair>()
let pair = Pair()
var possibleNeighbours = findPossibleNeighbours(btn: btnArr[playerX][playerY], blockedArr: blocked)
print(String(possibleNeighbours.description) + " possibeNeighs beginning" )
possibleNeighbours.shuffle()
for neighbour in possibleNeighbours{
if(isOnBorder(point: neighbour)){
print("Is on border")
return neighbour
}
pair.setPair(firstValue: neighbour, secondValue: neighbour)
myQueue.enqueue(value: pair)
blocked.append(neighbour)
}
while(!myQueue.isEmpty){
print("SCHLEIFE")
print((myQueue.tail?.value.first)! + " " + (myQueue.tail?.value.second)!)
let pointPair = myQueue.dequeue()
possibleNeighbours = findPossibleNeighbours(btn: btnArr[getXFromString(string: (pointPair?.value.first)!)][getYFromString(string: (pointPair?.value.first)!)], blockedArr: blocked)
for neighbour in possibleNeighbours{
if isOnBorder(point: neighbour){
return (pointPair?.value.getSecond())!
}
pair.setPair(firstValue: neighbour, secondValue: (pointPair?.value.getSecond())!)
myQueue.enqueue(value: pair)
blocked.append(neighbour)
}
}
return "-1 -1"
}
func isOnBorder(point: String)->Bool{
return (getXFromString(string: point) == 0 || getXFromString(string: point) == 8 || getYFromString(string: point) == 0 || getYFromString(string: point) == 8)
}
func findPossibleNeighbours(btn: UIButton,blockedArr: [String])->[String]{
var neighbours: [String] = []
let x = getX(btn: btn)
let y = getY(btn: btn)
if(y%2==1){
if(isFree(btn: btnArr[x][y-1])){ //OBEN
let a = String(x) + " " + String(y-1)
if(!blockedArr.contains(a)){
neighbours.append(a)
}
}
if(isFree(btn: btnArr[x+1][y-1])){ //OBENRECHTS
let b = String(x+1) + " " + String(y-1)
if(!blockedArr.contains(b)){
neighbours.append(b)
}
}
if(isFree(btn: btnArr[x+1][y])){ //RECHTS
let c = String(x+1) + " " + String(y)
if(!blockedArr.contains(c)){
neighbours.append(c)
}
}
if(isFree(btn: btnArr[x+1][y+1])){ //UNTENRECHTS
let d = String(x+1) + " " + String(y+1)
if(!blockedArr.contains(d)){
neighbours.append(d)
}
}
if(isFree(btn: btnArr[x][y+1])){ //UNTEN
let e = String(x) + " " + String(y+1)
if(!blockedArr.contains(e)){
neighbours.append(e)
}
}
if(isFree(btn: btnArr[x-1][y])){ //LINKS
let f = String(x-1) + " " + String(y)
if(!blockedArr.contains(f)){
neighbours.append(f)
}
}
}else{
if(isFree(btn: btnArr[x][y-1])){ //OBEN
let aa = String(x) + " " + String(y-1)
if(!blockedArr.contains(aa)){
neighbours.append(aa)
}
}
if(isFree(btn: btnArr[x+1][y])){ //RECHTS
let bb = String(x+1) + " " + String(y)
if(!blockedArr.contains(bb)){
neighbours.append(bb)
}
}
if(isFree(btn: btnArr[x][y+1])){ //UNTEN
let cc = String(x) + " " + String(y+1)
if(!blockedArr.contains(cc)){
neighbours.append(cc)
}
}
if(isFree(btn: btnArr[x-1][y+1])){ //UNTENLINKS
let dd = String(x-1) + " " + String(y+1)
if(!blockedArr.contains(dd)){
neighbours.append(dd)
}
}
if(isFree(btn: btnArr[x-1][y])){ //LINKS
let ee = String(x-1) + " " + String(y)
if(!blockedArr.contains(ee)){
neighbours.append(ee)
}
}
if(isFree(btn: btnArr[x-1][y-1])){ //OBENLINKS
let ff = String(x-1) + " " + String(y-1)
if(!blockedArr.contains(ff)){
neighbours.append(ff)
}
}
}
return neighbours
}
func isFree(btn: UIButton)->Bool{
if(btn.backgroundColor == defaultColor){
return true
}
return false
}
func reset(){
playerX = 4
playerY = 4
for btnline in btnArr{
for btn in btnline{
btn.backgroundColor = defaultColor
}
}
btnArr[4][4].backgroundColor = playerColor
}
}
// (0,0) (1,0) (2,0) (3,0) (4,0) (5,0) (6,0) (7,0) (8,0)
// (0,1) (1,1) (2,1) (3,1) (4,1) (5,1) (6,1) (7,1) (8,1)
// (0,2) (1,2) (2,2) (3,2) (4,2) (5,2) (6,2) (7,2) (8,2)
// (0,3) (1,3) (2,3) (3,3) (4,3) (5,3) (6,3) (7,3) (8,3)
// (0,4) (1,4) (2,4) (3,4) (4,4) (5,4) (6,4) (7,4) (8,4)
// (0,5) (1,5) (2,5) (3,5) (4,5) (5,5) (6,5) (7,5) (8,5)
// (0,6) (1,6) (2,6) (3,6) (4,6) (5,6) (6,6) (7,6) (8,6)
// (0,7) (1,7) (2,7) (3,7) (4,7) (5,7) (6,7) (7,7) (8,7)
// (0,8) (1,8) (2,8) (3,8) (4,8) (5,8) (6,8) (7,8) (8,8)
// _____________________________________________________
// row1 row2 row3 row4 row5 row6 row7 row8 row9
// for btnline in btnArr{
// print("-----------")
// for btn in btnline{
// print(btn.title(for: .normal)!)
// }
// }
//____________________________________________________
class Node<T> {
var value:T
var next:Node?
init(value:T) {
self.value = value
}
}
class LinkedQueue<T> {
var tail:Node<T>?
var head:Node<T>?
var count:Int = 0
var isEmpty: Bool{
return (count == 0)
}
func dequeue () -> Node<T>? {
if let node = head {
head = head?.next
count -= 1
return node
}
return nil
}
func enqueue(value:T) {
let newNode = Node(value:value)
if let tailNode = tail {
tailNode.next = newNode
newNode.next = nil
tail = newNode
} else {
head = newNode
tail = newNode
}
count += 1
}
}
import Foundation
class Utils{
static func arrayToArrays<T>(arr: Array<T>,size: Int)->Array<Array<T>>{
var result : Array<Array<T>> = Array<Array<T>>(repeating: Array<T>(), count: size);
var set = -1;
let expectedSize = (arr.count / size) + (arr.count % size);
for i in 0..<arr.count {
if i % expectedSize == 0{
set+=1;
}
result[set].append(arr[i]);
}
return result;
}
}
import Foundation
class Pair{
var first: String = ""
var second: String = ""
func setPair(firstValue: String, secondValue: String){
first = firstValue
second = secondValue
}
func getFirst() -> String{
return first
}
func getSecond() -> String{
return second
}
}
My Problem
When I play it without removing the trail (Button stays orange when visited) it ALMOST works perfectly, you can test this by uncommenting line 41. But with removing the trail (Like original Circle the dot) it is very confusing. I tried to find the problem for multiple hours now - thats why I post everything here :/
When I click button (2,3) at the very beginning it stops working - found no way to the border, but I only blocked ONE button... I just makes no sense when you compare it to the findDirection method, which includes the Breadth-First-Search-Algorithm. Same thing with some other buttons.
Its a little better when uncommenting line 135, which adds random to the game.
It would be sooo awesome if someone can help me or join the project. Its just for fun, I want to learn new things.
Greetings
SwiftHobby
in the picture a part of screenshot with a xcode printed code and the same result on an iPad simulator, can any one tell me why please?
here is the code
func loadLevel() {
var clueString = ""
var solutionString = ""
var letterBits = [String]()
if let levelFileURL = Bundle.main.url(forResource: "level\(level)", withExtension: ".txt") {
if let levelContents = try? String(contentsOf: levelFileURL) {
var lines = levelContents.components(separatedBy: "\n")
lines.shuffle()
for (index, line) in lines.enumerated() {
let parts = line.components(separatedBy: ": ")
let answer = parts[0]
let clue = parts[1]
clueString += "\(index + 1). \(clue)\n"
let solutionWord = answer.replacingOccurrences(of: "|", with: "")
solutionString += "\(solutionWord.count) letters\n"
solutions.append(solutionWord)
let bits = answer.components(separatedBy: "|")
letterBits += bits
}
}
}
print(clueString)
cluesLable.text = clueString.trimmingCharacters(in: .whitespacesAndNewlines)
answerLable.text = solutionString.trimmingCharacters(in: .whitespacesAndNewlines)
letterButtons.shuffle()
if letterButtons.count == letterBits.count {
for i in 0..<letterButtons.count {
letterButtons[i].setTitle(letterBits[i], for: .normal)
}
}
}
you need to remove labels line limitation in your code or your storyboard.
in code:
#IBOutlet weak var descriptionLabel: UILabel!{
didSet {
self.descriptionLabel.numberOfLines = 0
}
}
I'm making a binary calculator app.
No buildtime issues but in the app only adding binary numbers give the right answer, multiplying, subtracting and dividing don't.
Dividing - every answer = 1 (1010-0001 = 1; 10101010-1010 = 1)
Multiplying - every answer is wrong, (11111 x 101010 = 1111000001, should be 10100010110)
Subtracting - every answer = 0 ( 1010-0001 = 0; 1010101-0001 = 0)
I tried making the code more explanatory and it worked in the playground (ending with print() ) but when I changed the apps code, the answers were the same.
var numberOnScreen:Int = 0;
var previousNumber:Int = 0;
var doingMath = false
var operation = 0;
var decimal = 0;
var decimal1 = 0;
var binary:String = ""
var binary1:String = ""
#IBOutlet weak var label: UILabel!
#IBAction func Numbers(_ sender: UIButton) {
if doingMath == true
{
label.text = String(sender.tag-1)
numberOnScreen = Int(label.text!)!
doingMath = false
}
else
{
label.text = label.text! + String(sender.tag-1)
numberOnScreen = Int(label.text!)!
}
}
#IBAction func buttons(_ sender: UIButton) {
if label.text != "" && sender.tag != 6 && sender.tag != 8
{
previousNumber = Int(label.text!)!
binary = "\(previousNumber)"
decimal = Int(binary, radix: 2)!
binary1 = "\(numberOnScreen)"
decimal1 = Int(binary1, radix: 2)!
operation = sender.tag
doingMath = true;
}
else if sender.tag == 8
{
if operation == 3 //adding
{
label.text = String((decimal + decimal1), radix: 2)
}
else if operation == 4 //subtracting
{
label.text = String((decimal - decimal1), radix: 2)
}
else if operation == 5 //multiplying
{
label.text = String((decimal * decimal1), radix: 2)
}
else if operation == 7 //dividing
{
label.text = String((decimal / decimal1), radix: 2)
}
So this is the class for my Operation:
class Calculation {
var currentNumber: String = ""
var resultNumber = Int()
var operationInput = String()
func operationIdentifier() {
if operationInput == "=" {
resultNumber = Int(currentNumber)!
print("\(resultNumber)")
} else if operationInput == "+" {
resultNumber += Int(currentNumber)!
print("\(resultNumber)")
} else if operationInput == "-" {
resultNumber -= Int(currentNumber)!
print("\(resultNumber)")
} else if operationInput == "*" {
resultNumber *= Int(currentNumber)!
print("\(resultNumber)")
} else if operationInput == "/" {
resultNumber /= Int(currentNumber)!
print("\(resultNumber)")
} else {
print("Operation does not exist.")
}
print("\(resultNumber)")
}
And this is where I get this button to get the title for numbers:
#IBAction func numberPressed(_ sender: UIButton) {
calculation.currentNumber = (sender.titleLabel?.text!)!
calculation.currentNumber.append(<#T##other: String##String#>)
//calculation.currentNumber.append((sender.titleLabel?.text)!) This is how I use it to append.
calculation.operationIdentifier()
resultTextField.text = "\(calculation.currentNumber)"
}
The append command is for strings as showed in the code section which I use to add to the end of the previous number, and the result is always a double of some Int e.g : If you press 5, it returns 55.
First of all, you are overwriting value here: calculation.currentNumber = (sender.titleLabel?.text!)!. Second, you will need to clear a current number after pressing.
Here is my concept for you(your edited code), you can try it in Playground.
enum Operator: String {
case plus = "+"
case minus = "-"
case multiply = "*"
case divide = "/"
case equal = "="
}
class Calculation {
var currentNumber: String = ""
var resultNumber = Int()
var operationInput = String()
func recalculate() {
if resultNumber == 0 {
resultNumber = Int(currentNumber) ?? 0
return
}
guard let sign = Operator(rawValue: operationInput), let number = Int(currentNumber) else { return }
switch sign {
case .plus: resultNumber += number
case .minus: resultNumber -= number
case .divide: resultNumber /= number
case .multiply: resultNumber *= number
case .equal: resultNumber = number
}
}
func operatorPressed(_ op: Operator) {
recalculate()
operationInput = op.rawValue
currentNumber = ""
print(resultNumber)
}
func numberPressed(_ number: String) {
currentNumber.append(number)
}
}
let c = Calculation()
c.numberPressed("5")
c.numberPressed("5")
c.operatorPressed(.plus)
c.numberPressed("5")
c.operatorPressed(.multiply)
c.numberPressed("5")
c.operatorPressed(.equal)
c.operatorPressed(.plus)
c.numberPressed("5")
c.operatorPressed(.equal)
In numberPressed you are setting currentNumber in from the label text and then appending the same value to it again. It should be something like:
#IBAction func numberPressed(_ sender: UIButton) {
guard let label = sender.titleLabel, let numberString = label.text else{
return
}
calculation.currentNumber.append(numberString)
print("currentNumber = \(calculation.currentNumber)")
}
Also, why do you call calculation.operationIdentifier() in numberPressed? Wouldn't that go in an operatorPressed method?
I have an app where I'm getting some data from Firebase by days and I'm trying to display each day's data in a separate chart, using iOS Charts, but it crashes with an error 'fatal error: Index out of range'. Below is my code:
func setChart(dataPoints: [String], values: [Double]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value: values[i], xIndex: i)
dataEntries.append(dataEntry)
}
let lineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Units Sold")
let lineChartData = LineChartData(xVals: dataPoints, dataSet: lineChartDataSet)
firstDayChart.data = lineChartData
}
func setChart2(dataPoints: [String], values: [Double]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value: values[i], xIndex: i)
dataEntries.append(dataEntry)
}
let lineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Units Sold")
let lineChartData = LineChartData(xVals: dataPoints, dataSet: lineChartDataSet)
secondDayChart.data = lineChartData
}
//BUTTON
#IBAction func getResults(sender: UIButton) {
let doubleArray = BGdataOutsideDay1.map { Double($0)!}
// print(doubleArray)
// var newArray:[Double] = []
self.doublesArrayDay1 = doubleArray
if doublesArrayDay1.count != 0 {
let bgAvg = doubleArray.reduce(0, combine: +) / Double(doubleArray.count)
let highest = doubleArray.maxElement()
let lowest = doubleArray.minElement()
self.bgHighestDay1 = String(format: "%.1f", highest!)
self.bgAvgDay1 = String(format: "%.1f", bgAvg)
self.bgLowestDay1 = String(format: "%.1f", lowest!)
self.firstDayHighest.text = self.bgHighestDay1! + " " + "\(self.units!)"
self.firstDayAvg.text = self.bgAvgDay1! + " " + "\(self.units!)"
self.firstDayLowest.text = self.bgLowestDay1! + " " + "\(self.units!)"
} else {
self.firstDayHighest.text = "0" + " " + "\(self.units!)"
self.firstDayAvg.text = "0" + " " + "\(self.units!)"
self.firstDayLowest.text = "0" + " " + "\(self.units!)"
}
let doubleArray2 = BGdataOutsideDay2.map { Double($0)!}
// print(doubleArray)
// var newArray:[Double] = []
self.doublesArrayDay2 = doubleArray
if doublesArrayDay2.count != 0 {
let bgAvg2 = doubleArray2.reduce(0, combine: +) / Double(doubleArray.count)
let highest2 = doubleArray2.maxElement()
let lowest2 = doubleArray2.minElement()
self.bgHighestDay2 = String(format: "%.1f", highest2!)
self.bgAvgDay2 = String(format: "%.1f", bgAvg2)
self.bgLowestDay2 = String(format: "%.1f", lowest2!)
self.secondDayHighest.text = self.bgHighestDay2! + " " + "\(self.units!)"
self.secondDayAvg.text = self.bgAvgDay2! + " " + "\(self.units!)"
self.secondDayLowest.text = self.bgLowestDay2! + " " + "\(self.units!)"
} else {
self.secondDayHighest.text = "0" + " " + "\(self.units!)"
self.secondDayAvg.text = "0" + " " + "\(self.units!)"
self.secondDayLowest.text = "0" + " " + "\(self.units!)"
setChart(DateOutSideDay1, values: doublesArrayDay1)
setChart(DateOutSideDay2, values: doublesArrayDay2)
}
Any help would be greatly appreciated! Thanks in advance! :)
First of all i think it's weird that you have a set of data points and another set of values, but they aren't the same size? Imo those sets should be the same size since the values represent the data points.
Anywho..
You can fix this issue by replacing let dataEntry = ChartDataEntry(value: values[i], xIndex: i) with let dataEntry = ChartDataEntry(value: i < values.count ? values[i] : 0, xIndex: i)
You can define a default value to be used when there's no value available at that index in the values array, I set it to 0.