Created
July 16, 2023 16:51
-
-
Save gregomni/9d65d4cdb14e40810022f166382213d6 to your computer and use it in GitHub Desktop.
Banana-only Scrabble, part 2, a little less slow and naive
This file contains 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
// | |
// 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