Created
March 21, 2018 05:54
-
-
Save mknippen/0e55b1be6810a1b7c49b9cd1febc0aef to your computer and use it in GitHub Desktop.
Go Flood Fill Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: A UIKit based Playground for presenting user interface | |
import UIKit | |
import PlaygroundSupport | |
enum Stone { | |
case Black, White, None | |
func opposite() -> Stone { | |
switch self { | |
case .Black: | |
return .White | |
case .White: | |
return .Black | |
default: | |
return .Black | |
} | |
} | |
} | |
let gridSize = 9 | |
var board: [[Stone]] = [] | |
let row: [Stone] = Array(repeating: .None, count: gridSize) | |
board = Array(repeating: row, count: gridSize) | |
var views: [[UIView]] = [] | |
let boardColor = UIColor(red: 0.75, green: 0.375, blue: 0.2, alpha: 1.0) | |
func drawGrid(view: UIView) { | |
let widthOfCell = round(view.frame.width / CGFloat(gridSize)) | |
let heightOfCell = round(view.frame.height / CGFloat(gridSize)) | |
for (i, row) in board.enumerated() { | |
var viewRow: [UIView] = [] | |
for (j, _) in row.enumerated() { | |
let frame = CGRect(x: CGFloat(i)*widthOfCell, y: CGFloat(j)*heightOfCell, width: widthOfCell, height: heightOfCell) | |
let cell = UIView(frame: frame) | |
cell.backgroundColor = boardColor | |
cell.layer.borderColor = UIColor.black.cgColor | |
cell.layer.borderWidth = 2 | |
view.addSubview(cell) | |
viewRow.append(cell) | |
} | |
views.append(viewRow) | |
} | |
} | |
func createBoard() -> UIView { | |
let frame = CGRect(x: 0, y: 0, width: 500, height: 500) | |
let view = UIView(frame: frame) | |
drawGrid(view: view) | |
return view | |
} | |
var checked: [[Bool]] = [] | |
var spots: [(Int,Int)] = [] | |
//true = surrounded | |
func floodFill(x: Int, y: Int, stone: Stone) -> Bool { | |
//stone is the color that is placed at the spot (x,y) | |
//are we still in the grid? | |
if x >= gridSize || x < 0 || y >= gridSize || y < 0 { | |
return true | |
} | |
//have we already checked this spot | |
if checked[x][y] { | |
return true | |
} | |
checked[x][y] = true | |
let spot = board[x][y] | |
let target = stone.opposite() | |
//is the spot that we're looking at the same color as what we're looking for? If so, we need to look in each direction | |
if spot == stone { | |
spots.append((x,y)) | |
//TODO: This could probably look a bit cleaner | |
//south | |
if floodFill(x: x, y: y+1, stone: stone) { | |
//north | |
if floodFill(x: x, y: y-1, stone: stone) { | |
//west | |
if floodFill(x: x-1, y: y, stone: stone) { | |
if floodFill(x: x+1, y: y, stone: stone) { | |
return true | |
} | |
} | |
} | |
} | |
return false | |
} else if spot == target { | |
return true | |
} else { | |
//empty spot | |
return false | |
} | |
} | |
//clear all of our checks. Use this before calling floodFill | |
func clearChecked() { | |
let row = Array(repeating: false, count: gridSize) | |
checked = Array(repeating: row, count: gridSize) | |
spots = [] | |
} | |
//adds a move to the board. It can also be used to remove a stone from the board | |
func addMove(x: Int, y: Int, stone: Stone) { | |
let cell = views[x][y] | |
board[x][y] = stone | |
switch stone { | |
case .None: | |
cell.backgroundColor = boardColor | |
case .White: | |
cell.backgroundColor = UIColor.white | |
case .Black: | |
cell.backgroundColor = UIColor.black | |
} | |
if stone == .None { | |
return | |
} | |
//Do we need to remove any stones? | |
//TODO: neaten this up, so there's not so much copy paste | |
//check group to the north | |
if y > 0 && board[x][y-1] == stone.opposite() { | |
clearChecked() | |
// print("\(x), \(y)") | |
if floodFill(x: x, y: y-1, stone: stone.opposite()) { | |
print("Need to remove stone(s) to the north: \(spots)") | |
for s in spots { | |
addMove(x: s.0, y: s.1, stone: .None) | |
} | |
} | |
} | |
//south | |
if y < gridSize-1 && board[x][y+1] == stone.opposite() { | |
clearChecked() | |
if floodFill(x: x, y: y+1, stone: stone.opposite()) { | |
print("Need to remove stone(s) to the south: \(spots)") | |
for s in spots { | |
addMove(x: s.0, y: s.1, stone: .None) | |
} | |
} | |
} | |
//east | |
if x < gridSize-1 && board[x+1][y] == stone.opposite() { | |
clearChecked() | |
if floodFill(x: x+1, y: y, stone: stone.opposite()) { | |
print("Need to remove stone(s) to the east: \(spots)") | |
for s in spots { | |
addMove(x: s.0, y: s.1, stone: .None) | |
} | |
} | |
} | |
//west | |
if x > 0 && board[x-1][y] == stone.opposite() { | |
clearChecked() | |
if floodFill(x: x-1, y: y, stone: stone.opposite()) { | |
print("Need to remove stone(s) to the west: \(spots)") | |
for s in spots { | |
addMove(x: s.0, y: s.1, stone: .None) | |
} | |
} | |
} | |
//is this move legal? | |
clearChecked() | |
let illegal = floodFill(x: x, y: y, stone: stone) | |
if illegal { | |
print("Illegal Move: \(spots)") | |
board[x][y] = .None | |
cell.backgroundColor = boardColor | |
} | |
} | |
// Present the view controller in the Live View window | |
PlaygroundPage.current.liveView = createBoard() | |
//standard ponnuki | |
func testPonuki() { | |
addMove(x: 2, y: 3, stone: .Black) | |
addMove(x: 3, y: 4, stone: .Black) | |
addMove(x: 1, y: 4, stone: .Black) | |
addMove(x: 2, y: 4, stone: .White) | |
addMove(x: 2, y: 5, stone: .Black) | |
} | |
func testCorner() { | |
addMove(x: 1, y: 0, stone: .Black) | |
addMove(x: 0, y: 1, stone: .Black) | |
addMove(x: 0, y: 0, stone: .White) | |
} | |
func testSide() { | |
addMove(x: 3, y: 0, stone: .Black) | |
addMove(x: 5, y: 0, stone: .Black) | |
addMove(x: 4, y: 1, stone: .Black) | |
addMove(x: 4, y: 0, stone: .White) | |
} | |
func testSurroundingTwo() { | |
addMove(x: 2, y: 3, stone: .Black) | |
addMove(x: 2, y: 6, stone: .Black) | |
addMove(x: 1, y: 4, stone: .Black) | |
addMove(x: 1, y: 5, stone: .Black) | |
addMove(x: 3, y: 4, stone: .Black) | |
addMove(x: 2, y: 4, stone: .White) | |
addMove(x: 2, y: 5, stone: .White) | |
addMove(x: 3, y: 5, stone: .Black) | |
} | |
func testSingleEye() { | |
addMove(x: 2, y: 3, stone: .Black) | |
addMove(x: 3, y: 3, stone: .Black) | |
addMove(x: 4, y: 3, stone: .Black) | |
addMove(x: 1, y: 4, stone: .Black) | |
addMove(x: 1, y: 5, stone: .Black) | |
addMove(x: 1, y: 6, stone: .Black) | |
addMove(x: 5, y: 4, stone: .Black) | |
addMove(x: 5, y: 5, stone: .Black) | |
addMove(x: 5, y: 6, stone: .Black) | |
addMove(x: 2, y: 7, stone: .Black) | |
addMove(x: 3, y: 7, stone: .Black) | |
addMove(x: 4, y: 7, stone: .Black) | |
addMove(x: 3, y: 4, stone: .White) | |
addMove(x: 4, y: 4, stone: .White) | |
addMove(x: 4, y: 5, stone: .White) | |
addMove(x: 2, y: 6, stone: .White) | |
addMove(x: 3, y: 6, stone: .White) | |
addMove(x: 4, y: 6, stone: .White) | |
addMove(x: 2, y: 4, stone: .White) | |
addMove(x: 2, y: 5, stone: .White) | |
//removes last liberty, illegal move, should be true | |
addMove(x: 3, y: 5, stone: .White) | |
//removes white group, should be false | |
addMove(x: 3, y: 5, stone: .Black) | |
} | |
func testDoubleEye() { | |
addMove(x: 0, y: 7, stone: .White) | |
addMove(x: 1, y: 7, stone: .White) | |
addMove(x: 2, y: 7, stone: .White) | |
addMove(x: 3, y: 7, stone: .White) | |
addMove(x: 1, y: 8, stone: .White) | |
addMove(x: 3, y: 8, stone: .White) | |
addMove(x: 0, y: 6, stone: .Black) | |
addMove(x: 1, y: 6, stone: .Black) | |
addMove(x: 2, y: 6, stone: .Black) | |
addMove(x: 3, y: 6, stone: .Black) | |
addMove(x: 4, y: 7, stone: .Black) | |
addMove(x: 4, y: 8, stone: .Black) | |
//illegal move, double eye | |
addMove(x: 0, y: 8, stone: .Black) | |
//white takes the same spot, allowed | |
addMove(x: 0, y: 8, stone: .White) | |
//black now kills | |
addMove(x: 2, y: 8, stone: .Black) | |
} | |
testDoubleEye() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment