hop on puyo puyo tetris 2! and help me count polyominoes. submissions may be written in any language.
polyominoes are geometric shapes formed of some number of unit squares put together edge-to-edge. they look like this.
the above are pentaminoes: polyominoes consisting of 5 squares. there are 12 free pentaminoes, 18 if you consider reflections distinct (one-sided), and 63 if you consider reflections and rotations distinct (fixed).
I want to know how many polyominoes there are of any size, not just 5. your challenge, given a non-negative integer, is to compute the number of polyominoes consisting of that number of squares. you may provide a free, one-sided, or fixed count. (or all 3!)
as any language is allowed, there is no fixed API.
-- https://play.luau.org/
-- I am absolutely losing it over this polyomino nonsense.
-- I swear these rotations are conspiring behind my back.
-- I may evaporate.
-- This code will either enumerate every polyomino or take me down with it.
-- I've decided to neglect reflections and rotations because I value survival.
type Polyomino = { { number } }
local known = {} :: { [number]: { Polyomino } }
local function doPolyominoesMatch(polyomino1: Polyomino, polyomino2: Polyomino): boolean
if #polyomino1 ~= #polyomino2 then return false end
if #polyomino1[1] ~= #polyomino2[1] then return false end
for y = 1, #polyomino1 do
for x = 1, #polyomino1[y] do
if polyomino1[y][x] ~= polyomino2[y][x] then
return false
end
end
end
return true
end
local function isUnique(polyominoToCheck: Polyomino, n: number): boolean
for _, polyomino in known[n] do
if doPolyominoesMatch(polyomino, polyominoToCheck) then
return false
end
end
return true
end
local function addToPolyomino(polyomino: Polyomino, posx: number, posy: number): Polyomino
local height = #polyomino
local width = #polyomino[1]
local offsetX = if posx < 1 then 1 else 0
local offsetY = if posy < 1 then 1 else 0
local newWidth = math.max(width + offsetX, posx)
local newHeight = math.max(height + offsetY, posy)
local newPolyomino = table.create(newHeight)
for y = 1, newHeight do
newPolyomino[y] = table.create(newWidth, 0)
end
for y = 1, height do
for x = 1, width do
newPolyomino[y + offsetY][x + offsetX] = polyomino[y][x]
end
end
newPolyomino[posy + offsetY][posx + offsetX] = 1
return newPolyomino
end
local function polyominoes(n: number): number
known[n] = {}
if n == 0 then
table.insert(known[n], { { 0 } })
elseif n == 1 then
table.insert(known[n], { { 1 } })
else
for _, polyomino in known[n - 1] do
local height = #polyomino
local width = #polyomino[1]
for y = 1, height do
for x = 1, width do
if polyomino[y][x] == 0 then continue end
local neighbors = {
{x + 1, y},
{x - 1, y},
{x, y + 1},
{x, y - 1}
}
for _, neighbor in neighbors do
local neighborx, neighbory = neighbor[1], neighbor[2]
local isEmpty = neighborx < 1 or neighbory < 1 or neighborx > width or neighbory > height or polyomino[neighbory][neighborx] == 0
if isEmpty then
local newPolyomino = addToPolyomino(polyomino, neighborx, neighbory)
if isUnique(newPolyomino, n) then
table.insert(known[n], newPolyomino)
end
end
end
end
end
end
end
return known[n] and #known[n] or 0
end
local function printPolyomino(polyomino: Polyomino)
for y = 1, #polyomino do
local row = ''
for x = 1, #polyomino[y] do
row ..= if polyomino[y][x] == 1 then '██' else ' '
end
print(` {row}`)
end
end
for i = 0, 4 do
local count = polyominoes(i)
print(('='):rep(40))
print(`n = {i} ({count} polyominoes)`)
print(('='):rep(40))
for j, polyomino in known[i] do
print(` #{j}`)
printPolyomino(polyomino)
if j < #known[i] then
print(('-'):rep(20))
end
end
print()
end
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "ac0acd49",
"metadata": {
"vscode": {
"languageId": "typescript"
}
},
"outputs": [
{
"data": {
"text/plain": [
"hello world of multi-nominoes!"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"console.log(\"hello world of multi-nominoes!\");"
]
},
{
"cell_type": "markdown",
"id": "da28ef04",
"metadata": {},
"source": [
"ok, so the idea here is to know that there are a few types of symmetry and these hyperpose. the algorithm to build them up comes in 2 steps if the first one is not enough, and is fairly simple. you take a n-stick nomino and you rotate one block about its neighbor in all possible positions, but where the next position would be symmetrical but different to the another you log only one, knowing to double-count it. obviously for one square/quad connecting one other, there are only 2 symmetries: vertical or horizontal"
]
},
{
"cell_type": "markdown",
"id": "3acf516f",
"metadata": {},
"source": [
"we create a numerical representation of the nomino in binary as `0b0000` for a 5-stick or `0b111` for a 4-square (and a few other positions), where `0` means above-below current direction and `1` means left-right current direction. we must be careful to note when an arrangement by some interpretation is impossible, such as `0b00` where the last zero means below current (can't bend backwards into itself), or `0b0111` where all the `1`s are \"turn right\" (can't turn into itself)"
]
},
{
"cell_type": "markdown",
"id": "f999ebb6",
"metadata": {},
"source": [
"the two steps are to (1) collect symmetries from all possible bit-strings and (2) place the superpositions on a grid of prime numbers that are symmetrical in the ways you want to check symmetry then tally products of occupied squares"
]
},
{
"cell_type": "markdown",
"id": "e826b97d",
"metadata": {},
"source": [
"#### notes\n",
"- only need half the possible bit-strings by flipping the bit-string to check upside-down symmetry\n",
" - might be more complex than that though because some asymmetrical (non-palindrome) combinations might be late in the enumeration\n",
" - numerically represent those non-palindrome combinations and iterate them?\n",
" - negate the bit-string?\n",
"- might be able to check multiple symmetries at the same time on the grid with clever factoring\n",
"- in general if I write this out mathematically there might be a formula to generate combination count without having to compute each permutation if I can find a way to cancel out permutation generation if I can express some divisor within the with-permutations counter as a function of the permutations too\n",
"- there might be a better way than primes (finite fields representing primes b0 -> first prime, b11 -> fourth prime)\n",
"- could try priming the bit-strings\n",
"- is flipping a prime gradient on the grid sufficient for catching symmetries and mirrors (as opposed to checking against multiple in-grid mirrror gradient)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af0a59fa",
"metadata": {
"vscode": {
"languageId": "typescript"
}
},
"outputs": [],
"source": [
"// actual implementation"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
structPolyomnio(Vec<(i8,i8)>);implPolyomnio{fnnormalize_translation(&mutself){let(min_x,min_y)=self.0.iter().fold((127,127),|(x,y),(z,w)|(x.min(*z),y.min(*w)));for(x,y)in&mutself.0{*x-=min_x;*y-=min_y;}}fnpush_superominos(&self,v:&mutVec<Polyomnio>){forxyin&self.0{fordxyin[(0,-1),(0,1),(1,0),(-1,0)]{letnew_xy=(xy.0+dxy.0,xy.1+dxy.1);ifself.0.contains(&new_xy){continue;}letmutnew_polyomino=self.0.clone();new_polyomino.push(new_xy);v.push(Polyomnio(new_polyomino));}}}}implPartialEqforPolyomnio{fneq(&self,rhs:&Self)->bool{ifself.0.len()!=rhs.0.len(){returnfalse;}let(max_x,max_y)=self.0.iter().fold((0,0),|(x,y),(z,w)|(x.max(*z),y.max(*w)));fntest_under_transformation(a:&Polyomnio,b:&Polyomnio,f:implFn(i8,i8)->(i8,i8))->bool{forxyin&a.0{if!b.0.contains(&f(xy.0,xy.1)){returnfalse;}}true}test_under_transformation(self,rhs,|x,y|(x,y))||test_under_transformation(self,rhs,|x,y|(max_x-x,y))||test_under_transformation(self,rhs,|x,y|(max_x-x,max_y-y))||test_under_transformation(self,rhs,|x,y|(x,max_y-y))||test_under_transformation(self,rhs,|x,y|(y,x))||test_under_transformation(self,rhs,|x,y|(max_y-y,x))||test_under_transformation(self,rhs,|x,y|(max_y-y,max_x-x))||test_under_transformation(self,rhs,|x,y|(y,max_x-x))}}fnpolyominos_of_size_n(n:usize)->Vec<Polyomnio>{matchn{0=>returnvec![],1=>returnvec![Polyomnio(vec![(0,0)])],_=>(),};letprevious=polyominos_of_size_n(n-1);letmutpolyominos=vec![];forpinprevious{p.push_superominos(&mutpolyominos);}// translate so min_x == 0 and min_y == 0forpin&mutpolyominos{p.normalize_translation();}// deduplicateforiin(0..polyominos.len()).rev(){forjin0..i{ifpolyominos[i]==polyominos[j]{polyominos.remove(i);break;}}}polyominos}fnmain(){fornin0..=10{println!("number of {}-omnios: {}",n,polyominos_of_size_n(n).len());}}
fromcopyimportdeepcopyfromitertoolsimportproductdefis_valid_spot(n,m,i,j):ifi==0andj<n:returnFalseif0<iandm[i-1][j]:returnTrueifi<n-1andm[i+1][j]:returnTrueif0<jandm[i][j-1]:returnTrueifj<2*n-2andm[i][j+1]:returnTruereturnFalsedeff(n):m=tuple((0,)*(2*n-1)for_inrange(n-1))m=((0,)*(n-1)+(1,)+(0,)*(n-1),)+ms={m}for_inrange(n-1):s2=set()formins:fori,jinproduct(range(n),range(2*n-1)):ifis_valid_spot(n,m,i,j)andm[i][j]==0:m2=list(m)m2[i]=tuple(1ifk==jelsem[i][k]forkinrange(2*n-1))s2.add(tuple(m2))s=s2returnlen(s)print(f(int(input("Enter the number of cells: "))))
# no i dont think ill do this actually# you can have a haiku though# eggs eggs eggs eggs eggs# eggs eggs eggs eggs eggs eggs eggs# eggs eggs eggs eggs eggs
post a comment