Throw away and redo most of board

+ filling notation points to score...
Boards
Mathieu GROUSSEAU 1 month ago
parent 9a2495c874
commit 98e4ad2232

@ -1,11 +1,11 @@
import Model
extension Piece: CustomStringConvertible {
extension Player: CustomStringConvertible {
public var description: String {
switch self {
case .PlayerA:
case .A:
"🔴"
case .PlayerB:
case .B:
"🟡"
}
}
@ -17,7 +17,7 @@ extension Board: CustomStringConvertible, CustomDebugStringConvertible {
for row in 0..<self.rows {
for col in 0..<self.columns {
str += self[col, row]?.description ?? " "
str += self[col, row]?.owner.description ?? ""
}
str += "\n"
@ -27,8 +27,7 @@ extension Board: CustomStringConvertible, CustomDebugStringConvertible {
}
public var debugDescription: String {
let (a, b) = self.countPieces()
return "Board[\(self.columns)x\(self.rows), \(a) A and \(b) B pieces]"
return "Board[\(self.columns)x\(self.rows), \(self.countPieces { p in p.owner == .A }) A and \(self.countPieces { p in p.owner == .B }) B pieces]"
}
}

@ -9,13 +9,13 @@ final class CustomTypesTests: XCTestCase {
return
}
board[1, 1] = .PlayerA
board[2, 1] = .PlayerA
board[2, 2] = .PlayerA
board[1, 2] = .PlayerB
board[1, 1] = Piece(owner: .A)
board[2, 1] = Piece(owner: .A)
board[2, 2] = Piece(owner: .A)
board[1, 2] = Piece(owner: .B)
let text: String = board.description
XCTAssertEqual(text, " \n 🔴🔴 \n 🟡🔴 \n \n")
XCTAssertEqual(text, "⚪⚪⚪⚪\n⚪🔴🔴⚪\n⚪🟡🔴⚪\n⚪⚪⚪⚪\n")
}
}

@ -16,128 +16,105 @@ public struct Board {
self.grid = grid
}
private func checkBounds(_ column: Int, _ row: Int) {
precondition(column >= 0 && column < self.columns && row >= 0 && row < self.rows, "Coordinates out of bounds")
private func isInBounds(_ pos: Coords) -> Bool {
pos.col >= 0 && pos.col < self.columns && pos.row >= 0 && pos.row < self.rows
}
private func ensureBounds(_ pos: Coords) {
precondition(isInBounds(pos), "Coordinates out of bounds")
}
public subscript(column: Int, row: Int) -> Piece? {
get { self[Coords(column, row)] }
set { self[Coords(column, row)] = newValue }
}
public subscript(pos: Coords) -> Piece? {
get {
checkBounds(column, row)
return grid[column][row]
ensureBounds(pos)
return grid[pos.col][pos.row]
}
set {
checkBounds(column, row)
grid[column][row] = newValue
set(piece) {
ensureBounds(pos)
grid[pos.col][pos.row] = piece
}
}
public func countPieces() -> (a: Int, b: Int) {
var a = 0
var b = 0
public func countPieces(filter: (Piece) -> Bool = { piece in true }) -> Int {
var count = 0
for column in grid {
for piece in column {
switch piece {
case .PlayerA:
a += 1
case .PlayerB:
b += 1
case nil:
break
if let piece = piece {
if filter(piece) {
count += 1
}
}
}
}
return (a, b)
return count
}
@discardableResult
public mutating func insert(piece: Piece, side: Side = .Top, offset: Int, pushing push: Bool = false) -> Bool {
// insertion coordinate computation is intentionally separated from actual insertion because the user may want to
// display a where the piece would end-up before doing the actual insertion
public func getInsertionCoordinates(direction: Direction, offset: Int) -> Coords {
precondition(offset >= 0, "Offset out of bounds")
switch side {
switch direction {
case .Top:
precondition(offset < self.columns, "Offset (column) out of bounds")
if self.grid[offset].first! != nil {
if (!push) {
return false
}
Board.shiftDown(column: &self.grid[offset])
}
self.grid[offset][0] = piece
return true
return Coords(offset, 0)
case .Left:
precondition(offset < self.rows, "Offset (row) out of bounds")
if self.grid.first![offset] != nil {
if (!push) {
return false
}
self.shiftRight(row: offset)
}
self.grid[0][offset] = piece
return true
return Coords(0, offset)
case .Bottom:
precondition(offset < self.columns, "Offset (column) out of bounds")
if self.grid[offset].last! != nil {
if (!push) {
return false
}
Board.shiftUp(column: &self.grid[offset])
}
let lastIndex = self.grid[offset].count - 1
self.grid[offset][lastIndex] = piece
return true
return Coords(offset, self.rows - 1)
case .Right:
precondition(offset < self.rows, "Offset (row) out of bounds")
if self.grid.last![offset] != nil {
if (!push) {
return false
return Coords(self.columns - 1, offset)
}
}
self.shiftLeft(row: offset)
public enum FallResult: Equatable {
case Border(at: Coords)
case Piece(at: Coords, touched: Coords)
case Occupied
}
self.grid[self.columns - 1][offset] = piece
public func fallCoordinates(initialCoords: Coords, direction: Direction) -> FallResult {
ensureBounds(initialCoords)
return true
}
let dir: (dc: Int, dr: Int) = switch direction {
case .Top: (0, -1)
case .Left: (-1, 0)
case .Bottom: (0, 1)
case .Right: (1, 0)
}
private static func shiftDown(column: inout [Piece?]) {
for i in (1..<column.count).reversed() {
column[i] = column[i - 1]
}
if (self[initialCoords] != nil) {
return FallResult.Occupied
}
private static func shiftUp(column: inout [Piece?]) {
for i in 0..<(column.count - 1) {
column[i] = column[i + 1]
}
}
var coords = initialCoords
var next: Coords
while true {
next = Coords(coords.col + dir.dc, coords.row + dir.dr)
private mutating func shiftLeft(row: Int) {
for i in 0..<(grid.count - 1) {
self.grid[i][row] = self.grid[i + 1][row]
}
if !isInBounds(next) {
return FallResult.Border(at: coords)
} else if self[next] != nil {
return FallResult.Piece(at: coords, touched: next)
}
private mutating func shiftRight(row: Int) {
for i in 1..<grid.count {
self.grid[i - 1][row] = self.grid[i][row]
coords.col = next.col
coords.row = next.row
}
}
// TODO push
}

@ -0,0 +1,14 @@
// public typealias Coords = (col: Int, row: Int)
public struct Coords: Equatable {
var col: Int
var row: Int
public init(_ col: Int, _ row: Int) {
self.col = col
self.row = row
}
public init(pair: (Int, Int)) {
self.init(pair.0, pair.1)
}
}

@ -0,0 +1,16 @@
public enum Direction {
case Top, Left, Bottom, Right
var opposite: Self {
switch self {
case .Top:
.Bottom
case .Left:
.Right
case .Bottom:
.Top
case .Right:
.Left
}
}
}

@ -1,3 +1,8 @@
public enum Piece {
case PlayerA, PlayerB
public struct Piece {
public let owner: Player
// Required for public visibility
public init(owner: Player) {
self.owner = owner
}
}

@ -0,0 +1,3 @@
public enum Player {
case A, B
}

@ -1,3 +0,0 @@
public enum Side {
case Top, Left, Bottom, Right
}

@ -4,7 +4,7 @@ import XCTest
final class EmptyBoardTests: XCTestCase {
private var board: Board!
override func setUp() {
override func setUpWithError() throws {
super.setUp()
guard let board = Board(columns: 5, rows: 6) else {
@ -26,41 +26,128 @@ final class EmptyBoardTests: XCTestCase {
func testEmptyByDefault() throws {
XCTAssertNil(board[1, 1])
let result = board.countPieces()
XCTAssertEqual(result.a, 0)
XCTAssertEqual(result.b, 0)
XCTAssertEqual(result, 0)
}
func testSetGet() throws {
board[1, 1] = Piece.PlayerA;
func testCustomSubscript() throws {
for r in 0..<board.rows {
for c in 0..<board.columns {
for p: Player in [.A, .B] {
try self.setUpWithError()
XCTAssertEqual(board[1, 1], .PlayerA)
try self._testCustomSubscript(at: Coords(c, r), player: p)
try self.tearDownWithError()
}
}
}
}
private func _testCustomSubscript(at: Coords, player: Player) throws {
board[at] = Piece(owner: player)
XCTAssertEqual(board[at]?.owner, player)
}
func testCounts() throws {
board[1, 2] = .PlayerB
board[1, 2] = Piece(owner: .B)
board[0, 2] = Piece(owner: .A)
board[1, 1] = Piece(owner: .A)
let counts = board.countPieces()
XCTAssertEqual(counts.a, 0)
XCTAssertEqual(counts.b, 1)
XCTAssertEqual(board.countPieces { piece in piece.owner == .A }, 2)
XCTAssertEqual(board.countPieces { piece in piece.owner == .B }, 1)
}
func testInsertTopNoPush() {
XCTAssert(board.insert(piece: .PlayerA, side: .Top, offset: 2))
XCTAssertEqual(board[2, 0], .PlayerA)
func testInsertsSides() throws {
for (side, offset, expected): (Direction, Int, (Int, Int)) in [
(.Top, 0, (0, 0)),
(.Left, 0, (0, 0)),
(.Bottom, 0, (0, 5)),
(.Right, 0, (4, 0)),
/* ... */
(.Top, 4, (4, 0)),
(.Left, 5, (0, 5)),
(.Bottom, 4, (4, 5)),
(.Right, 5, (4, 5)),
] {
try self.setUpWithError()
try self._testInsertSide(side: side, offset: offset, expectedCoords: Coords(pair: expected))
try self.tearDownWithError()
}
}
private func _testInsertSide(side: Direction, offset: Int, expectedCoords: Coords) throws {
let result = board.getInsertionCoordinates(direction: side, offset: offset)
func testInsertBottomNoPush() {
XCTAssert(board.insert(piece: .PlayerB, side: .Bottom, offset: 2))
XCTAssertEqual(board[2, board.rows - 1], .PlayerB)
// Note: Not sure I want Coordinates to be a simple tuple or a struct, as both have advantages that the other dont:
// Tuples can be initialized like so (col, row) which I find quite handy
// Structs supports == operators
// ...
XCTAssertEqual(result.col, expectedCoords.col)
XCTAssertEqual(result.row, expectedCoords.row)
}
func testInsertLeftNoPush() {
XCTAssert(board.insert(piece: .PlayerA, side: .Left, offset: 2))
XCTAssertEqual(board[0, 2], .PlayerA)
func testInsertsFallNoPiece() throws {
for (from, direction, expected): ((Int, Int), Direction, (Int, Int)) in [
((0, 0), .Bottom, (0, 5)),
((0, 0), .Left, (0, 0)),
((0, 0), .Right, (4, 0)),
((0, 0), .Top, (0, 0)),
/* ... */
((4, 5), .Bottom, (4, 5)),
((4, 5), .Left, (0, 5)),
((4, 5), .Right, (4, 5)),
((4, 5), .Top, (4, 0)),
] {
try self.setUpWithError()
try self._testInsertFall(from: Coords(pair: from), dir: direction, expectedCoords: .Border(at: Coords(pair: expected)))
try self.tearDownWithError()
}
}
func testInsertsFallOnPiece() throws {
for (from, direction, (expected, touched)): ((Int, Int), Direction, ((Int, Int), (Int, Int))) in [
((0, 0), .Bottom, ((0, 1), (0, 2))),
((0, 0), .Right, ((1, 0), (2, 0))),
((4, 5), .Left, ((3, 5), (2, 5))),
((4, 5), .Top, ((4, 3), (4, 2))),
] {
try self.setUpWithError()
// Place some pieces
for pos in [
(2, 0),
(2, 1),
(0, 2), (1, 2), (2, 2), (3, 2), (4, 2),
(2, 3),
(2, 4),
(2, 5),
] {
// ensure it works with any player
board[Coords(pair: pos)] = if (pos.0 + pos.1) & 1 == 1 {
Piece(owner: .A)
} else {
Piece(owner: .B)
}
}
func testInsertRightNoPush() {
XCTAssert(board.insert(piece: .PlayerB, side: .Right, offset: 2))
XCTAssertEqual(board[board.columns - 1, 2], .PlayerB)
try self._testInsertFall(
from: Coords(pair: from),
dir: direction,
expectedCoords: .Piece(at: Coords(pair: expected), touched: Coords(pair: touched))
)
try self.tearDownWithError()
}
}
private func _testInsertFall(from: Coords, dir: Direction, expectedCoords: Board.FallResult) throws {
let result = board.fallCoordinates(initialCoords: from, direction: dir)
XCTAssertEqual(result, expectedCoords)
}
}

@ -15,9 +15,9 @@ final class FilledBoardTests: XCTestCase {
for row in 0..<board.rows {
for col in 0..<board.columns {
board[col, row] = if (row & 1) == 1 {
.PlayerA
Piece(owner: .A)
} else {
.PlayerB
Piece(owner: .B)
}
}
}
@ -25,29 +25,33 @@ final class FilledBoardTests: XCTestCase {
self.board = board
}
func testInsertFailTopNoPush() {
let before = board[2, 0]
XCTAssertFalse(board.insert(piece: .PlayerA, side: .Top, offset: 2))
XCTAssertEqual(before, board[2, 0])
}
func testRemovePiece() throws {
board[1, 2] = nil
func testInsertFailBottomNoPush() {
let before = board[2, board.rows - 1]
XCTAssertFalse(board.insert(piece: .PlayerB, side: .Bottom, offset: 2))
XCTAssertEqual(before, board[2, board.rows - 1])
XCTAssertNil(board[1, 2])
}
func testInsertFailLeftNoPush() {
let before = board[2, 0]
XCTAssertFalse(board.insert(piece: .PlayerA, side: .Left, offset: 2))
XCTAssertEqual(before, board[0, 2])
}
func testInsertsFallFail() throws {
for (from, direction): ((Int, Int), Direction) in [
((0, 0), .Bottom),
((0, 0), .Right),
((4, 5), .Left),
((4, 5), .Top),
((4, 5), .Bottom),
((4, 5), .Right),
((0, 0), .Left),
((0, 0), .Top),
] {
try self.setUpWithError()
func testInsertFailRightNoPush() {
let before = board[board.columns - 1, 2]
XCTAssertFalse(board.insert(piece: .PlayerB, side: .Right, offset: 2))
XCTAssertEqual(before, board[board.columns - 1, 2])
try self._testInsertFallFail(from: Coords(pair: from), dir: direction)
try self.tearDownWithError()
}
}
// TODO Test shifting
private func _testInsertFallFail(from: Coords, dir: Direction) throws {
XCTAssertEqual(board.fallCoordinates(initialCoords: from, direction: dir), .Occupied)
}
}

Loading…
Cancel
Save