Skip to content

Instantly share code, notes, and snippets.

@gregomni
Created July 16, 2023 16:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregomni/9d65d4cdb14e40810022f166382213d6 to your computer and use it in GitHub Desktop.
Save gregomni/9d65d4cdb14e40810022f166382213d6 to your computer and use it in GitHub Desktop.
Banana-only Scrabble, part 2, a little less slow and naive
//
// main.swift
// banana
//
// Created by Greg Titus on 7/15/23.
//
import Foundation
import Cocoa
enum Letter: Hashable {
case empty
case b
case a
case n
static let banana: [Letter] = [.b, .a, .n, .a, .n, .a]
}
struct Position: Hashable {
let row: Int
let column: Int
var up: Position { Position(row: row-1, column: column) }
var down: Position { Position(row: row+1, column: column) }
var left: Position { Position(row: row, column: column-1) }
var right: Position { Position(row: row, column: column+1) }
}
struct Move: Hashable {
let position: Position
let vertical: Bool
let firstPlayer: Bool
}
struct Board: Hashable {
static let size = 15
static let bananaLength = 6
var history: Set<Move>
var playable: Set<Position>
init() {
self.history = []
self.playable = []
}
static func ==(lhs: Board, rhs: Board) -> Bool { lhs.history == rhs.history }
func hash(into hasher: inout Hasher) { history.hash(into: &hasher) }
mutating func addBanana(_ p: Position, vertical: Bool) {
history.insert(Move(position: p, vertical: vertical, firstPlayer: history.count % 2 == 0))
var p = p
for _ in Letter.banana {
playable.insert(p)
p = vertical ? p.down : p.right
}
}
func byAdding(_ p: Position, vertical: Bool) -> Board {
var result = self
result.addBanana(p, vertical: vertical)
return result
}
func moveThatPlayed(_ p: Position) -> Move? {
history.first(where: { move in
if move.vertical {
guard move.position.column == p.column else { return false }
return move.position.row <= p.row && move.position.row > (p.row - Self.bananaLength)
} else {
guard move.position.row == p.row else { return false }
return move.position.column <= p.column && move.position.column > (p.column - Self.bananaLength)
}
})
}
func isEmpty(_ p: Position) -> Bool { moveThatPlayed(p) == nil }
func tileAt(_ p: Position) -> Letter {
guard let move = moveThatPlayed(p) else { return .empty }
let index = move.vertical ? p.row - move.position.row : p.column - move.position.column
return Letter.banana[index]
}
func canAddVertical(at p: Position) -> Bool {
if !isEmpty(p.left) { return true }
if !isEmpty(p.right) { return true }
return false
}
func canAddBanana(_ at: Position, vertical: Bool, index: Int) -> Bool {
guard tileAt(at) == Letter.banana[index] else { return false }
var laidTile = false
if vertical {
guard at.row - index >= 0 else { return false }
guard at.row + (5-index) < Board.size else { return false}
var p = Position(row: at.row - index, column: at.column)
guard isEmpty(p.up) else { return false }
for tile in Letter.banana {
let space = tileAt(p)
if space == tile {
} else if space == .empty {
if !isEmpty(p.left) { return false }
if !isEmpty(p.right) { return false }
laidTile = true
} else {
return false
}
p = p.down
}
guard isEmpty(p) else { return false }
} else {
guard at.column - index >= 0 else { return false }
guard at.column + (5-index) < Board.size else { return false}
var p = Position(row: at.row, column: at.column - index)
guard isEmpty(p.left) else { return false }
for tile in Letter.banana {
let space = tileAt(p)
if space == tile {
} else if space == .empty {
if !isEmpty(p.up) { return false }
if !isEmpty(p.down) { return false }
laidTile = true
} else {
return false
}
p = p.right
}
guard isEmpty(p) else { return false }
}
return laidTile
}
mutating func stripNowUnplayables() {
var noLonger: [Position] = []
for p in playable {
var stillPlayable = false
let canAddVertical = canAddVertical(at: p)
for index in Letter.banana.indices {
if canAddBanana(p, vertical: canAddVertical, index: index) {
stillPlayable = true
break
}
}
if !stillPlayable {
noLonger.append(p)
}
}
playable.subtract(noLonger)
}
mutating func nextMoves() -> [Board] {
self.stripNowUnplayables()
var result: [Board] = []
for p in playable {
let vertical = canAddVertical(at: p)
for index in Letter.banana.indices {
if canAddBanana(p, vertical: vertical, index: index) {
let p = vertical ? Position(row: p.row-index, column: p.column) : Position(row: p.row, column: p.column-index)
result.append(self.byAdding(p, vertical: vertical))
}
}
}
return result
}
func description() -> String {
var result = ""
for row in 0..<15 {
for column in 0..<15 {
let c: Character
switch tileAt(Position(row: row, column: column)) {
case .a: c = "A"
case .b: c = "B"
case .n: c = "N"
case .empty: c = "."
}
result.append(c)
}
result.append("\n")
}
return result
}
}
var start = Board()
start.addBanana(Position(row: 7, column: 7), vertical: false)
var possibilities: [Set<Board>] = []
possibilities.append([start])
print("move 1: \(possibilities[0].count) moves")
for move in 1..<16 {
var new = Set<Board>()
for board in possibilities[move-1] {
var b = board
new.formUnion(b.nextMoves())
}
possibilities.append(new)
let moves = possibilities[move].count
let lastMoves = possibilities[move-1].count
let branching = Double(moves) / Double(lastMoves)
print("move \(move+1): \(moves.formatted()) moves, branch \(branching.formatted(.number.precision(.significantDigits(3))))")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment