Compare commits
3 Commits
f50b9232f3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 55e0860a2f | |||
| 289bba2e3f | |||
| 86e93fa8bc |
@@ -1 +1,2 @@
|
|||||||
.vscode/**
|
.vscode/**
|
||||||
|
coverage.out
|
||||||
|
|||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
# git-cliff ~ configuration file
|
||||||
|
# https://git-cliff.org/docs/configuration
|
||||||
|
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
# A Tera template to be rendered for each release in the changelog.
|
||||||
|
# See https://keats.github.io/tera/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{% if version %}\
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else %}\
|
||||||
|
## [unreleased]
|
||||||
|
{% endif %}\
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | striptags | trim | upper_first }}
|
||||||
|
{% for commit in commits %}
|
||||||
|
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||||
|
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||||
|
{{ commit.message | upper_first }}\
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
||||||
|
# Remove leading and trailing whitespaces from the changelog's body.
|
||||||
|
trim = true
|
||||||
|
# Render body even when there are no releases to process.
|
||||||
|
render_always = true
|
||||||
|
# An array of regex based postprocessors to modify the changelog.
|
||||||
|
postprocessors = [
|
||||||
|
# Replace the placeholder <REPO> with a URL.
|
||||||
|
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
|
||||||
|
]
|
||||||
|
# render body even when there are no releases to process
|
||||||
|
# render_always = true
|
||||||
|
# output file path
|
||||||
|
# output = "test.md"
|
||||||
|
|
||||||
|
[git]
|
||||||
|
# Parse commits according to the conventional commits specification.
|
||||||
|
# See https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# Exclude commits that do not match the conventional commits specification.
|
||||||
|
filter_unconventional = false
|
||||||
|
# Require all commits to be conventional.
|
||||||
|
# Takes precedence over filter_unconventional.
|
||||||
|
require_conventional = false
|
||||||
|
# Split commits on newlines, treating each line as an individual commit.
|
||||||
|
split_commits = false
|
||||||
|
# An array of regex based parsers to modify commit messages prior to further processing.
|
||||||
|
commit_preprocessors = [
|
||||||
|
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
|
||||||
|
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
||||||
|
# Check spelling of the commit message using https://github.com/crate-ci/typos.
|
||||||
|
# If the spelling is incorrect, it will be fixed automatically.
|
||||||
|
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
||||||
|
]
|
||||||
|
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||||
|
protect_breaking_commits = false
|
||||||
|
# An array of regex based parsers for extracting data from the commit message.
|
||||||
|
# Assigns commits to groups.
|
||||||
|
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
|
||||||
|
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
|
||||||
|
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
|
||||||
|
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
||||||
|
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
||||||
|
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
||||||
|
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
||||||
|
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||||
|
{ message = "^chore\\(deps.*\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pr\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pull\\)", skip = true },
|
||||||
|
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
||||||
|
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
|
||||||
|
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
|
||||||
|
{ message = ".*", group = "<!-- 10 -->💼 Other" },
|
||||||
|
]
|
||||||
|
# Exclude commits that are not matched by any commit parser.
|
||||||
|
filter_commits = false
|
||||||
|
# Fail on a commit that is not matched by any commit parser.
|
||||||
|
fail_on_unmatched_commit = false
|
||||||
|
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
|
||||||
|
link_parsers = []
|
||||||
|
# Include only the tags that belong to the current branch.
|
||||||
|
use_branch_tags = false
|
||||||
|
# Order releases topologically instead of chronologically.
|
||||||
|
topo_order = false
|
||||||
|
# Order commits topologically instead of chronologically.
|
||||||
|
topo_order_commits = true
|
||||||
|
# Order of commits in each group/release within the changelog.
|
||||||
|
# Allowed values: newest, oldest
|
||||||
|
sort_commits = "oldest"
|
||||||
|
# Process submodules commits
|
||||||
|
recurse_submodules = false
|
||||||
+13
-12
@@ -34,26 +34,26 @@ func main() {
|
|||||||
game(*initrandom)
|
game(*initrandom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initArena(front gol.Arena, initRandom bool) {
|
func initArena(front *gol.Arena, initRandom bool) {
|
||||||
if initRandom {
|
if initRandom {
|
||||||
for y := range front {
|
for y := 0; y < front.Height(); y++ {
|
||||||
for x := range front[y] {
|
for x := 0; x < front.Width(); x++ {
|
||||||
front[y][x] = rand.Intn(2) == 1
|
front.Set(x, y, rand.Intn(2) == 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// draw initial objects
|
// draw initial objects
|
||||||
// glider
|
// glider
|
||||||
front[0][3] = true
|
front.Set(3, 0, true)
|
||||||
front[1][4] = true
|
front.Set(4, 1, true)
|
||||||
front[2][2] = true
|
front.Set(2, 2, true)
|
||||||
front[2][3] = true
|
front.Set(3, 2, true)
|
||||||
front[2][4] = true
|
front.Set(4, 2, true)
|
||||||
|
|
||||||
// cross
|
// cross
|
||||||
front[16][21] = true
|
front.Set(21, 16, true)
|
||||||
front[17][21] = true
|
front.Set(21, 17, true)
|
||||||
front[18][21] = true
|
front.Set(21, 18, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ func game(initrandom bool) {
|
|||||||
// catch interrupt signals
|
// catch interrupt signals
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
defer signal.Stop(sigs)
|
||||||
|
|
||||||
// initialize screen
|
// initialize screen
|
||||||
term.StartFullscreen()
|
term.StartFullscreen()
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module go.ar76.eu/arek/gol
|
module go.ar76.eu/arek/gol
|
||||||
|
|
||||||
go 1.24
|
go 1.26
|
||||||
|
|||||||
+105
-32
@@ -3,17 +3,37 @@ package gol
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Arena [][]bool
|
type Arena struct {
|
||||||
|
w, h int
|
||||||
func New(ysize, xsize int) Arena {
|
data []bool
|
||||||
arena := make([][]bool, ysize)
|
|
||||||
for i := range ysize {
|
|
||||||
arena[i] = make([]bool, xsize)
|
|
||||||
}
|
}
|
||||||
return arena
|
|
||||||
|
func New(ysize, xsize int) *Arena {
|
||||||
|
return &Arena{
|
||||||
|
w: xsize,
|
||||||
|
h: ysize,
|
||||||
|
data: make([]bool, xsize*ysize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arena) Set(x, y int, val bool) {
|
||||||
|
a.data[y*a.w+x] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arena) Get(x, y int) bool {
|
||||||
|
return a.data[y*a.w+x]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arena) Width() int {
|
||||||
|
return a.w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arena) Height() int {
|
||||||
|
return a.h
|
||||||
}
|
}
|
||||||
|
|
||||||
// bufPool
|
// bufPool
|
||||||
@@ -28,13 +48,14 @@ var (
|
|||||||
spriteOff = ([]byte)("\u2022\u2022")
|
spriteOff = ([]byte)("\u2022\u2022")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Arena) PrintMe() {
|
func (a *Arena) PrintMe() {
|
||||||
b := bufPool.Get().(*bytes.Buffer)
|
b := bufPool.Get().(*bytes.Buffer)
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
|
||||||
for i := range a {
|
for y := 0; y < a.h; y++ {
|
||||||
for _, v := range a[i] {
|
base := y * a.w
|
||||||
if v {
|
for x := 0; x < a.w; x++ {
|
||||||
|
if a.data[base+x] {
|
||||||
b.Write(spriteOn)
|
b.Write(spriteOn)
|
||||||
} else {
|
} else {
|
||||||
b.Write(spriteOff)
|
b.Write(spriteOff)
|
||||||
@@ -46,34 +67,86 @@ func (a Arena) PrintMe() {
|
|||||||
bufPool.Put(b)
|
bufPool.Put(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Arena) NextGen(to Arena) {
|
func (a *Arena) NextGen(to *Arena) {
|
||||||
for y := range a {
|
var wg sync.WaitGroup
|
||||||
for x := range a[y] {
|
numWorkers := runtime.NumCPU()
|
||||||
to[y][x] = a.life(x, y)
|
rowsPerWorker := a.h / numWorkers
|
||||||
}
|
if rowsPerWorker == 0 {
|
||||||
}
|
rowsPerWorker = 1
|
||||||
|
numWorkers = a.h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Arena) life(x, y int) bool {
|
for i := 0; i < numWorkers; i++ {
|
||||||
ysize := len(a)
|
startY := i * rowsPerWorker
|
||||||
xsize := len(a[0])
|
endY := (i + 1) * rowsPerWorker
|
||||||
|
if i == numWorkers-1 {
|
||||||
|
endY = a.h
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(startY, endY int) {
|
||||||
|
defer wg.Done()
|
||||||
|
a.processChunk(to, startY, endY)
|
||||||
|
}(startY, endY)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arena) processChunk(to *Arena, startY, endY int) {
|
||||||
|
w, h := a.w, a.h
|
||||||
|
for y := startY; y < endY; y++ {
|
||||||
|
// Precompute y indices
|
||||||
|
yPrev := (y - 1 + h) % h
|
||||||
|
yNext := (y + 1) % h
|
||||||
|
|
||||||
|
yPrevOffset := yPrev * w
|
||||||
|
yCurrOffset := y * w
|
||||||
|
yNextOffset := yNext * w
|
||||||
|
|
||||||
|
for x := range w {
|
||||||
|
// Precompute x indices
|
||||||
|
xPrev := (x - 1 + w) % w
|
||||||
|
xNext := (x + 1) % w
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
for dy := -1; dy <= 1; dy++ {
|
|
||||||
for dx := -1; dx <= 1; dx++ {
|
// Top row
|
||||||
if dx == 0 && dy == 0 {
|
if a.data[yPrevOffset+xPrev] {
|
||||||
continue
|
|
||||||
}
|
|
||||||
ny := (y + dy + ysize) % ysize
|
|
||||||
nx := (x + dx + xsize) % xsize
|
|
||||||
if a[ny][nx] {
|
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
if a.data[yPrevOffset+x] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if a.data[yPrevOffset+xNext] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middle row (excluding self)
|
||||||
|
if a.data[yCurrOffset+xPrev] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if a.data[yCurrOffset+xNext] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom row
|
||||||
|
if a.data[yNextOffset+xPrev] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if a.data[yNextOffset+x] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if a.data[yNextOffset+xNext] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
if count > 3 {
|
if count > 3 {
|
||||||
return false // early exit
|
to.data[yCurrOffset+x] = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
iAmAlive := a.data[yCurrOffset+x]
|
||||||
|
to.data[yCurrOffset+x] = count == 3 || (iAmAlive && count == 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
iAmAlive := a[y][x]
|
|
||||||
return count == 3 || (iAmAlive && count == 2)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package gol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
w, h := 10, 20
|
||||||
|
a := New(h, w)
|
||||||
|
if a.Width() != w {
|
||||||
|
t.Errorf("expected width %d, got %d", w, a.Width())
|
||||||
|
}
|
||||||
|
if a.Height() != h {
|
||||||
|
t.Errorf("expected height %d, got %d", h, a.Height())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetGet(t *testing.T) {
|
||||||
|
a := New(10, 10)
|
||||||
|
a.Set(1, 1, true)
|
||||||
|
if !a.Get(1, 1) {
|
||||||
|
t.Error("expected cell at 1,1 to be true")
|
||||||
|
}
|
||||||
|
if a.Get(0, 0) {
|
||||||
|
t.Error("expected cell at 0,0 to be false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextGen(t *testing.T) {
|
||||||
|
// Blinker pattern (period 2)
|
||||||
|
// . . . . .
|
||||||
|
// . . 1 . .
|
||||||
|
// . . 1 . .
|
||||||
|
// . . 1 . .
|
||||||
|
// . . . . .
|
||||||
|
// becomes
|
||||||
|
// . . . . .
|
||||||
|
// . . . . .
|
||||||
|
// . 1 1 1 .
|
||||||
|
// . . . . .
|
||||||
|
// . . . . .
|
||||||
|
|
||||||
|
a := New(5, 5)
|
||||||
|
a.Set(2, 1, true)
|
||||||
|
a.Set(2, 2, true)
|
||||||
|
a.Set(2, 3, true)
|
||||||
|
|
||||||
|
next := New(5, 5)
|
||||||
|
a.NextGen(next)
|
||||||
|
|
||||||
|
if !next.Get(1, 2) || !next.Get(2, 2) || !next.Get(3, 2) {
|
||||||
|
t.Error("expected horizontal blinker")
|
||||||
|
}
|
||||||
|
if next.Get(2, 1) || next.Get(2, 3) {
|
||||||
|
t.Error("expected vertical cells to die")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRules(t *testing.T) {
|
||||||
|
// Neighbors indices around (1,1)
|
||||||
|
neighbors := [][2]int{
|
||||||
|
{0, 0}, {1, 0}, {2, 0},
|
||||||
|
{0, 1} /*...*/, {2, 1},
|
||||||
|
{0, 2}, {1, 2}, {2, 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
check := func(name string, alive bool, activeNeighbors int, expected bool) {
|
||||||
|
a := New(3, 3)
|
||||||
|
// Set center
|
||||||
|
a.Set(1, 1, alive)
|
||||||
|
// Set neighbors
|
||||||
|
for i := range activeNeighbors {
|
||||||
|
nx, ny := neighbors[i][0], neighbors[i][1]
|
||||||
|
a.Set(nx, ny, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := New(3, 3)
|
||||||
|
a.NextGen(next)
|
||||||
|
|
||||||
|
if next.Get(1, 1) != expected {
|
||||||
|
t.Errorf("%s: expected %v, got %v (alive=%v, neighbors=%d)", name, expected, next.Get(1, 1), alive, activeNeighbors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underpopulation
|
||||||
|
check("Underpopulation 0", true, 0, false)
|
||||||
|
check("Underpopulation 1", true, 1, false)
|
||||||
|
|
||||||
|
// Survival
|
||||||
|
check("Survival 2", true, 2, true)
|
||||||
|
check("Survival 3", true, 3, true)
|
||||||
|
|
||||||
|
// Overpopulation
|
||||||
|
check("Overpopulation 4", true, 4, false)
|
||||||
|
check("Overpopulation 5", true, 5, false)
|
||||||
|
check("Overpopulation 8", true, 8, false)
|
||||||
|
|
||||||
|
// Reproduction
|
||||||
|
check("Reproduction 3", false, 3, true)
|
||||||
|
|
||||||
|
// Stay Dead
|
||||||
|
check("Stay Dead 2", false, 2, false)
|
||||||
|
check("Stay Dead 4", false, 4, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintMe(t *testing.T) {
|
||||||
|
// Capture stdout
|
||||||
|
old := os.Stdout
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
a := New(2, 2)
|
||||||
|
a.Set(0, 0, true)
|
||||||
|
a.PrintMe()
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = old
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, r)
|
||||||
|
|
||||||
|
// We expect some output
|
||||||
|
if buf.Len() == 0 {
|
||||||
|
t.Error("expected output from PrintMe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNextGen(b *testing.B) {
|
||||||
|
size := 100
|
||||||
|
arena := New(size, size)
|
||||||
|
next := New(size, size)
|
||||||
|
|
||||||
|
// Initialize with some pattern (e.g., random-ish)
|
||||||
|
for y := range size {
|
||||||
|
for x := range size {
|
||||||
|
if (x+y)%2 == 0 {
|
||||||
|
arena.Set(x, y, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for b.Loop() {
|
||||||
|
arena.NextGen(next)
|
||||||
|
arena, next = next, arena
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user