Skip to content

Instantly share code, notes, and snippets.

@quinnlas
Last active October 23, 2020 00:40
Show Gist options
  • Save quinnlas/af4b03e42b2b364be4b1dc4309d15e70 to your computer and use it in GitHub Desktop.
Save quinnlas/af4b03e42b2b364be4b1dc4309d15e70 to your computer and use it in GitHub Desktop.
// an array with 0 for empty, 1 for black, and 2 for white
let board = Game.currentState.cellStatus
let remainingEmpty = 0
// number should be 1 for black, 2 for white
async function makeMove(row, col, number) {
for (let i = 0; i < number; i++) {
Game.startMove({ row, col })
Game.endMove()
}
remainingEmpty--
await new Promise(r => setTimeout(r, 1)) // allow the change to be drawn
}
function invert(color) {
return color === 1 ? 2 : 1
}
async function fillVerticalTriples() {
for (let r = 0; r < board.length - 2; r++) {
const row = board[r]
for (let c = 0; c < row.length; c++) {
const first = row[c]
const second = board[r+1][c]
const third = board[r+2][c]
// pretend these are vertical
// ++-
if (first && (first === second) && !third) await makeMove(r+2, c, invert(first))
// +-+
if (first && (first === third) && !second) await makeMove(r+1, c, invert(first))
// -++
if (second && (second === third) && !first) await makeMove(r, c, invert(second))
}
}
}
async function fillHorizontalTriples() {
for(let rowIndex = 0; rowIndex < board.length; rowIndex++) {
row = board[rowIndex]
for (let i = 0; i < row.length - 2; i++) {
const first = row[i]
const second = row[i+1]
const third = row[i+2]
// ++-
if (first && (first === second) && !third) await makeMove(rowIndex, i+2, invert(first))
// +-+
if (first && (first === third) && !second) await makeMove(rowIndex, i+1, invert(first))
// -++
if (second && (second === third) && !first) await makeMove(rowIndex, i, invert(second))
}
}
}
// if there are rows or columns that can only have one color left, fill them
async function fillRowsAndColumnsByCounter() {
// rows
for (let r = 0; r < board.length; r++) {
row = board[r]
let numBlack = 0, numWhite = 0
row.forEach(cell => {
if (cell === 1) numBlack++
else if (cell === 2) numWhite++
})
let fill = 0
if (numBlack === row.length / 2) fill = 2
else if (numWhite === row.length / 2) fill = 1
if (fill) {
for (let c = 0; c < row.length; c++) {
cell = row[c]
if (cell === 0) await makeMove(r, c, fill)
}
}
}
// cols
for (let c = 0; c < board[0].length; c++) {
let numBlack = 0, numWhite = 0
for (let r = 0; r < board.length; r++) {
if (board[r][c] === 1) numBlack++
else if (board[r][c] === 2) numWhite++
}
let fill = 0
if (numBlack === board.length / 2) fill = 2
else if (numWhite === board.length / 2) fill = 1
if (fill) {
for (let r = 0; r < board.length; r++) {
if (board[r][c] === 0) await makeMove(r,c,fill)
}
}
}
}
// given a number of black and white tiles, generate all unique orderings of them
function* permute(black, white) {
const length = black + white
// return all positions of blacks (just fill in whites for the rest)
if (black === 0) {
yield new Array(length).fill(2)
} else if (black === 1) {
for (let i = 0; i < length; i++) {
const result = new Array(length).fill(2)
result[i] = 1
yield result
}
} else {
// move first black around then recurse for the rest
// the last possible position of the first black is length - black
for (let firstBlackIndex = 0; firstBlackIndex <= length - black; firstBlackIndex++) {
const innerBlack = black - 1
const innerWhite = white - firstBlackIndex // index conveniently represents the number of elements to its left
for (innerPerm of permute(innerBlack, innerWhite)) {
const result = new Array(length).fill(2)
result[firstBlackIndex] = 1
result.splice(firstBlackIndex + 1, innerPerm.length, ...innerPerm)
yield result
}
}
}
}
// given an array, return an array with objects for each move, containing index and color
// this can logically replace the triples and counter methods but they are faster
function singleRowTechnique(row) {
// iterate through all possible combinations of remaining colors
let remainingBlack = row.length / 2
let remainingWhite = row.length / 2
row.forEach(cell => {
if (cell === 1) remainingBlack--
else if (cell === 2) remainingWhite--
})
// too expensive to compute
// total number of permutations is actually (remainingBlack + remainingWhite) choose (remainingBlack or remainingWhite)
// so this decision could be more sophisticated
if (remainingBlack + remainingWhite >= 23) return []
let knownColors
let firstSolution = true
for (let permutation of permute(remainingBlack, remainingWhite)) {
// calculate full row
const fullRow = row.slice()
// fill based on permutation
let j = 0
for (let i = 0; i < fullRow.length; i++) {
if (fullRow[i] === 0) {
fullRow[i] = permutation[j]
j++
}
}
// determine if it is a solution by checking for triples
let isSolution = true
for (let i = 0; i < fullRow.length - 2; i++) {
if (fullRow[i] === fullRow[i + 1] && fullRow[i] === fullRow[i+2]) {
isSolution = false
break
}
}
if (!isSolution) continue
// keep track of which colors all solutions have in common
if (firstSolution) {
firstSolution = false
knownColors = fullRow
} else {
// find differences and mark them as 0
for (let i = 0; i < fullRow.length; i++) {
if (fullRow[i] !== knownColors[i]) knownColors[i] = 0
}
}
}
// find differences between knownColors and row (those are moves we can make)
const result = []
for (let i = 0; i < row.length; i++) {
if (row[i] !== knownColors[i]) {
result.push({
index: i,
color: knownColors[i]
})
}
}
return result
}
// if all possible solutions of a row or column have some cells in common, fill those
async function fillOneRowOrColumnTechniques() {
// rows
for (let r = 0; r < board.length; r++) {
const row = board[r]
const moves = singleRowTechnique(row)
for (let move of moves) {
await makeMove(r, move.index, move.color)
}
}
// columns
for (let c = 0; c < board[0].length; c++) {
const column = board.map(row => row[c])
const moves = singleRowTechnique(column)
for (let move of moves) {
await makeMove(move.index, c, move.color)
}
}
}
// fill some cells if you can
async function findMoves() {
await fillRowsAndColumnsByCounter()
await fillHorizontalTriples()
await fillVerticalTriples()
await fillOneRowOrColumnTechniques()
}
async function main() {
board.forEach(row=>{
row.forEach(cell=>{
if (cell === 0)
remainingEmpty++
}
)
}
)
console.log(remainingEmpty)
let previousNumberEmpty = remainingEmpty
while (true) {
await findMoves()
console.log(remainingEmpty)
if ((remainingEmpty <= 0) || (remainingEmpty === previousNumberEmpty)) break
previousNumberEmpty = remainingEmpty
}
if (remainingEmpty > 0)
console.log('could not find any more moves')
else console.log('done')
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment