aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJP Appel <jeanpierre.appel01@gmail.com>2024-09-11 22:27:00 -0400
committerJP Appel <jeanpierre.appel01@gmail.com>2024-09-11 22:27:00 -0400
commita0ab77f2e8f21a7096dc9b0828907d6e4e34fc2d (patch)
tree2929f087116ba6b2ee1bd3b2482f8197509313cb
parent4d9a3334fbfea46ac1d17648ac532b8ff8b0363b (diff)
Start work on bingo functionality
Created a simple struct for game state. Started work on board generators and their seeds.
-rw-r--r--.gitignore3
-rw-r--r--README.md3
-rw-r--r--bingo/board.go101
-rw-r--r--bingo/board_test.go60
-rw-r--r--bingo/generators.go41
-rw-r--r--bingo/tiles.go101
-rw-r--r--go.mod3
7 files changed, 309 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 6f72f89..f40e964 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,3 @@
-# If you prefer the allow list template instead of the deny list, see community template:
-# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
-#
# Binaries for programs and plugins
*.exe
*.exe~
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ba57c9b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Bingo Factory
+
+A bingo maker
diff --git a/bingo/board.go b/bingo/board.go
new file mode 100644
index 0000000..19264a1
--- /dev/null
+++ b/bingo/board.go
@@ -0,0 +1,101 @@
+package bingo
+
+import "iter"
+
+type WinDirection int
+
+const (
+ Row WinDirection = iota
+ Column
+ Diagonal
+)
+
+type Game struct {
+ Board []string
+ Checked []bool
+ Length int
+ FreeSquare bool
+ Seed GameSeed
+}
+
+func all(group []bool) bool {
+ allTrue := true
+
+ for _, v := range group {
+ allTrue = allTrue && v
+ }
+
+ return allTrue
+}
+
+// Return if a game has been won
+func (g Game) Win() bool {
+ length := g.Length
+
+ for row := range g.Rows(length) {
+ if all(row) {
+ return true
+ }
+ }
+
+ for col := range g.Cols(length) {
+ if all(col) {
+ return true
+ }
+ }
+
+ for diag := range g.Diags(length) {
+ if all(diag) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Iterator for rows of a board
+func (g Game) Rows(length int) iter.Seq[[]bool] {
+ return func(yield func([]bool) bool) {
+ for row := 0; (row+1)*length > len(g.Checked); row++ {
+ if !yield(g.Checked[row*length : (row+1)*length]) {
+ return
+ }
+ }
+ }
+}
+
+// Iterator for columns of a board
+func (g Game) Cols(length int) iter.Seq[[]bool] {
+ return func(yield func([]bool) bool) {
+ for col := 0; col*length+1 > len(g.Checked); col++ {
+ column := make([]bool, length)
+ for i := range length {
+ column[i] = g.Checked[i*length+col]
+ }
+ if !yield(column) {
+ return
+ }
+ }
+ }
+}
+
+// Iterator for diagonals of square boards
+func (g Game) Diags(length int) iter.Seq[[]bool] {
+ return func(yield func([]bool) bool) {
+ if length*length != len(g.Checked) {
+ return
+ }
+
+ diagonal := make([]bool, length)
+ for i := 0; i < length; i++ {
+ diagonal[i] = g.Checked[i*length+i]
+ }
+ if !yield(diagonal) {
+ return
+ }
+
+ for i := 0; i < length; i++ {
+ diagonal[i] = g.Checked[i*length+(length-1-i)]
+ }
+ }
+}
diff --git a/bingo/board_test.go b/bingo/board_test.go
new file mode 100644
index 0000000..5df20ee
--- /dev/null
+++ b/bingo/board_test.go
@@ -0,0 +1,60 @@
+package bingo_test
+
+import (
+ "iter"
+ "testing"
+
+ "github.com/jpappel/bingo-factory/bingo"
+)
+
+func TestRows(t *testing.T) {
+ g := bingo.Game{}
+
+ testGame := func(size int, length int) {
+
+ g.Checked = make([]bool, size)
+
+ testGroup := func(name string, iter iter.Seq[[]bool]) {
+ for i := range size {
+ g.Checked[i] = false
+ }
+ for group := range iter {
+ if len(group) != length {
+ t.Logf("Mismatching %s length: %d != %d", name, length, len(group))
+ t.FailNow()
+ }
+
+ for i := range length {
+ if group[i] != false {
+ t.Errorf("Incorrect value in %s!\n", name)
+ }
+ }
+ }
+ for i := range size {
+ g.Checked[i] = true
+ }
+ for group := range iter {
+ if len(group) != length {
+ t.Logf("Mismatching %s length: %d != %d\n", name, length, len(group))
+ }
+
+ for i := range length {
+ if group[i] != true {
+ t.Errorf("Incorrect value in %s!\n", name)
+ }
+ }
+ }
+ }
+
+ testGroup("row", g.Rows(length))
+ testGroup("col", g.Cols(length))
+ testGroup("diag", g.Diags(length))
+
+ }
+
+ t.Log("Testing Square Games")
+ testGame(9, 3)
+ testGame(25, 5)
+ t.Log("Testing Non-Square Games")
+ testGame(22, 2)
+}
diff --git a/bingo/generators.go b/bingo/generators.go
new file mode 100644
index 0000000..1365f2b
--- /dev/null
+++ b/bingo/generators.go
@@ -0,0 +1,41 @@
+package bingo
+
+type GameSeed string
+
+type Generator interface {
+ New(int, int) *Game
+ SetSeed(int64)
+ Seed() int64
+}
+
+type RandomGenerator struct {
+ tiles TilePool
+ picker TilePicker
+ seed int64
+}
+
+func (g RandomGenerator) New(size int, length int) *Game {
+ g.picker.Reset()
+
+ board := make([]string, 0, size)
+ checked := make([]bool, size)
+
+ for _, tile := range g.picker.Iter(size) {
+ board = append(board, tile)
+ }
+
+ game := new(Game)
+ game.Board = board
+ game.Checked = checked
+ game.Length = length
+
+ return game
+}
+
+func (g RandomGenerator) Seed() int64 {
+ return g.seed
+}
+
+func (g RandomGenerator) SetSeed(seed int64) {
+ g.seed = seed
+}
diff --git a/bingo/tiles.go b/bingo/tiles.go
new file mode 100644
index 0000000..81e945f
--- /dev/null
+++ b/bingo/tiles.go
@@ -0,0 +1,101 @@
+package bingo
+
+import (
+ "iter"
+ "math/rand"
+ "slices"
+)
+
+type TilePool map[string][]string
+
+func (pool TilePool) All() iter.Seq[string] {
+ return func(yield func(string) bool) {
+ for _, list := range pool {
+ for _, tile := range list {
+ if !yield(tile) {
+ return
+ }
+ }
+ }
+ }
+}
+
+type TilePicker interface {
+ All() iter.Seq[string] // provides an iterator over an entire TilePool
+ Iter(int) iter.Seq2[string, string] // provides an iterator over n elements of a TilePool
+ Reset() // reset the internal state of a TilePicker
+}
+
+type RandomTilePicker struct {
+ ChosenTags []string
+ tilePool TilePool
+ rand rand.Rand
+}
+
+func NewRandomTilePicker(tiles TilePool, r rand.Rand) *RandomTilePicker {
+ tp := new(RandomTilePicker)
+ tp.tilePool = tiles
+ tp.rand = r
+
+ return tp
+}
+
+// Iterate over all elements of a TilePool in a random order
+func (tp RandomTilePicker) All() iter.Seq[string] {
+ return func(yield func(string) bool) {
+ tiles := slices.Collect(tp.tilePool.All())
+ if len(tiles) == 0 {
+ return
+ }
+
+ tp.rand.Shuffle(len(tiles), func(i int, j int) {
+ tiles[i], tiles[j] = tiles[j], tiles[i]
+ })
+
+ for _, tile := range tiles {
+ if !yield(tile) {
+ return
+ }
+ }
+ }
+}
+
+// Iterator over a TilePool by choosing one tile per tag until pool is exhausted or size tiles have been yielded
+func (tp RandomTilePicker) Iter(size int) iter.Seq2[string, string] {
+ return func(yield func(string, string) bool) {
+ if len(tp.tilePool) == 0 {
+ return
+ }
+
+ tags := make([]string, len(tp.tilePool))
+ for tag := range tp.tilePool {
+ tags = append(tags, tag)
+ }
+ tp.rand.Shuffle(len(tags), func(i int, j int) {
+ tags[i], tags[j] = tags[j], tags[i]
+ })
+
+ yielded := 0
+ for _, tag := range tags {
+ if yielded == size {
+ return
+ }
+
+ list := tp.tilePool[tag]
+ if len(list) == 0 {
+ continue
+ }
+
+ tile := list[tp.rand.Intn(len(list))]
+ if !yield(tag, tile) {
+ return
+ }
+ yielded++
+ }
+
+ }
+}
+
+func (tp RandomTilePicker) Reset() {
+ tp.ChosenTags = make([]string, len(tp.ChosenTags))
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..31021d4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/jpappel/bingo-factory
+
+go 1.23.0