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