Skip to content

Instantly share code, notes, and snippets.

@rluders
Created January 9, 2026 15:25
Show Gist options
  • Select an option

  • Save rluders/491bc21a5f80566f06502ff4e39b1808 to your computer and use it in GitHub Desktop.

Select an option

Save rluders/491bc21a5f80566f06502ff4e39b1808 to your computer and use it in GitHub Desktop.
Angelica Polkadot Counter for fun
# Polkadot Score Counter
This Gist contains a self-contained Go program to calculate a "Polkadot score" from an ASCII art input.
## The Solution
The program reads ASCII art from standard input and calculates a score based on the following logic:
- It identifies a "lips" area, defined by a run of tildes (`~`) followed by an apostrophe (`'`).
- It counts the number of 'O' characters that fall "inside" and "outside" this lips area.
- It counts the number of "pupils", represented by `()` tokens.
- The final score is calculated as: `(outside_Os) + (inside_Os * pupil_char_count)`.
## How to Run
To run the program, you can pipe an input file or a string to it.
1. Navigate to the `gist` directory.
2. Execute one of the following commands:
```bash
# Run with a file as input
go run . < path/to/your/art.txt
# Run with a string as input
echo "~~' () OO" | go run .
```
## How to Test
Unit tests are included to verify the functionality.
1. Navigate to the `gist` directory.
2. Run the following command:
```bash
go test
```
package main
import (
"bufio"
"strings"
)
// splitLinesKeepSpaces splits on \n, keeps indentation/spaces, drops empty lines.
func splitLinesKeepSpaces(s string) []string {
// Normalize line endings to \n
s = strings.ReplaceAll(s, "\r\n", "\n")
s = strings.ReplaceAll(s, "\r", "\n")
scanner := bufio.NewScanner(strings.NewReader(s))
out := make([]string, 0)
for scanner.Scan() {
line := scanner.Text()
if line != "" {
out = append(out, line)
}
}
return out
}
package main
import (
"reflect"
"testing"
)
func Test_splitLinesKeepSpaces(t *testing.T) {
tests := []struct {
name string
in string
want []string
}{
{
name: "drops empty lines but keeps spaces",
in: "\n a \n\nb\n c \n",
want: []string{" a ", "b", " c "},
},
{
name: "handles CRLF",
in: "a\r\nb\r\n",
want: []string{"a", "b"},
},
{
name: "handles CR-only",
in: "a\rb\r",
want: []string{"a", "b"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := splitLinesKeepSpaces(tt.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %#v, want %#v", got, tt.want)
}
})
}
}
package main
import (
"fmt"
"io"
"os"
)
func main() {
data, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, "error reading stdin:", err)
os.Exit(1)
}
score := computePolkadotScore(string(data))
fmt.Println(score)
}
package main
// findTildeRunFollowedByApostrophe finds a run of '~' immediately followed by '\''.
// Returns the inclusive x-range of ONLY the '~' run (apostrophe excluded).
func findTildeRunFollowedByApostrophe(lines []string) (startX, endX int, ok bool) {
for _, line := range lines {
r := []rune(line)
for i := 0; i < len(r); i++ {
if r[i] != '~' {
continue
}
runStart := i
j := i
for j < len(r) && r[j] == '~' {
j++
}
if j < len(r) && r[j] == '\'' {
return runStart, j - 1, true
}
// If we are here, a run of tildes was found but not followed by an apostrophe.
// We advance the outer loop's index `i` to the end of the run we just scanned
// to avoid re-checking the same characters.
i = j - 1
}
}
return -1, -1, false
}
// countRuneByXRange counts occurrences of target rune, split into inside/outside
// an inclusive x-range.
func countRuneByXRange(lines []string, target rune, startX, endX int) (inside, outside int) {
for _, line := range lines {
row := []rune(line)
for x, ch := range row {
if ch != target {
continue
}
if x >= startX && x <= endX {
inside++
} else {
outside++
}
}
}
return inside, outside
}
package main
import (
"testing"
)
func Test_findTildeRunFollowedByApostrophe(t *testing.T) {
tests := []struct {
name string
lines []string
wantStart int
wantEnd int
wantOK bool
}{
{
name: "finds lips run",
lines: []string{"nope", "xx ~~~~~~' yy"},
wantStart: 3,
wantEnd: 8,
wantOK: true,
},
{
name: "chooses first matching run",
lines: []string{"aa ~~' bb", "cc ~~~~~' dd"},
wantStart: 3,
wantEnd: 4,
wantOK: true,
},
{
name: "run not followed by apostrophe is ignored",
lines: []string{"aa ~~~~ bb"},
wantOK: false,
},
{
name: "apostrophe not immediately after run",
lines: []string{"aa ~~~ ' bb"},
wantOK: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start, end, ok := findTildeRunFollowedByApostrophe(tt.lines)
if ok != tt.wantOK {
t.Errorf("ok=%v, want %v", ok, tt.wantOK)
}
if !ok {
return
}
if start != tt.wantStart || end != tt.wantEnd {
t.Errorf("got start=%d end=%d, want start=%d end=%d",
start, end, tt.wantStart, tt.wantEnd)
}
})
}
}
func Test_countRuneByXRange(t *testing.T) {
tests := []struct {
name string
lines []string
start, end int
wantIn int
wantOut int
}{
{
name: "counts inside and outside",
lines: []string{" O O", "O O "},
start: 2,
end: 4,
wantIn: 2,
wantOut: 2,
},
{
name: "end before start means all outside",
lines: []string{"OOO"},
start: 5,
end: 1,
wantIn: 0,
wantOut: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in, out := countRuneByXRange(tt.lines, 'O', tt.start, tt.end)
if in != tt.wantIn || out != tt.wantOut {
t.Errorf("got inside=%d outside=%d, want inside=%d outside=%d",
in, out, tt.wantIn, tt.wantOut)
}
})
}
}
package main
import (
"strings"
)
const (
pupilToken = "()"
charsPerPupilToken = 2
defaultPupilMultiplier = 1
)
func computePolkadotScore(art string) int {
lines := splitLinesKeepSpaces(art)
lipsStart, lipsEnd, ok := findTildeRunFollowedByApostrophe(lines)
if !ok {
// If lips not found, make inside impossible.
lipsStart, lipsEnd = 0, -1
}
pupilMultiplier := countPupilMultiplier(lines)
inside, outside := countRuneByXRange(lines, 'O', lipsStart, lipsEnd)
return outside + inside*pupilMultiplier
}
// countPupilMultiplier calculates the multiplier based on the number of pupil tokens.
func countPupilMultiplier(lines []string) int {
tokens := 0
for _, line := range lines {
tokens += strings.Count(line, pupilToken)
}
multiplier := charsPerPupilToken * tokens
if multiplier == 0 {
return defaultPupilMultiplier
}
return multiplier
}
package main
import (
"testing"
)
func Test_computePolkadotScore(t *testing.T) {
tests := []struct {
name string
art string
want int
}{
{
name: "basic happy path",
art: "" +
"~~~~~~'\n" +
"() ()\n" +
"OO O O\n",
want: 13,
},
{
name: "lips not found counts all outside",
art: "" +
"() \n" +
"OOO\n",
want: 3,
},
{
name: "no pupils defaults multiplier to 1",
art: "" +
" ~~' \n" +
" OOO \n",
want: 3,
},
{
name: "apostrophe is ignored in lips range",
art: "" +
"~'\n" +
"()()\n" +
"OO\n",
want: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := computePolkadotScore(tt.art)
if got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment