Created
January 9, 2026 15:25
-
-
Save rluders/491bc21a5f80566f06502ff4e39b1808 to your computer and use it in GitHub Desktop.
Angelica Polkadot Counter for fun
This file contains hidden or 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
| # 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 | |
| ``` |
This file contains hidden or 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
| 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 | |
| } |
This file contains hidden or 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
| 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) | |
| } | |
| }) | |
| } | |
| } |
This file contains hidden or 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
| 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) | |
| } |
This file contains hidden or 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
| 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 | |
| } |
This file contains hidden or 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
| 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) | |
| } | |
| }) | |
| } | |
| } |
This file contains hidden or 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
| 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 | |
| } |
This file contains hidden or 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
| 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