Skip to content

Commit a319712

Browse files
feat: add Conway's Game of Life realm (#10)
* feat: add Conway's Game of Life realm - Complete Conway's Game of Life implementation for Gno - Dual coordinate systems (numeric and letter-based) - Pre-defined patterns: blinker, block, glider - Web interface with grid rendering - Gas-optimized for blockchain deployment - Comprehensive API and documentation * chore: remove README from root of namespace * feat: add tests for coordinate parsing and column conversion * feat: add unit tests for grid, game engine, pattern manager, and renderer creation * feat: add unit tests for grid operations, generation, and clearing functionality * feat: add tests for pattern manager initialization and loading functionality * feat: add unit tests for renderer functionality including grid rendering and help output * refactor: reorganize game initialization and improve function documentation * refactor: improve test function documentation for clarity and consistency * refactor: enhance documentation for grid functions to improve clarity * refactor: improve documentation for GameEngine methods for clarity and completeness * refactor: enhance documentation for Pattern and PatternManager methods for clarity * refactor: enhance documentation for Renderer methods for clarity and completeness * feat: add GitHub Actions workflow for testing Conway package * feat: add GitHub Actions workflow for testing Conway package * fix: ensure Gno binary path is added to GITHUB_PATH during installation * style: remove unnecessary whitespace in code files for cleaner formatting * del: Removing the CI/CD * feat: optimize grid handling by introducing a temporary grid for in-place updates * feat: add optimized neighbor counting method to improve performance in GameEngine * fix: update field name from 'cells' to 'Cells' for consistency and optimization * fix: update help text for 'Step' command to reflect optimized gas usage and remove 'NewGame' command example * fix: update command examples in RenderHelp to use correct package path and not my test package path * fix: update README to replace 'NewGame' command with 'Clear' and adjust coordinate system details for expanded grid * fix: Fixing the coding style --------- Co-authored-by: Leon <[email protected]>
1 parent cbbdcda commit a319712

File tree

12 files changed

+1043
-0
lines changed

12 files changed

+1043
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Conway's Game of Life
2+
3+
A fully-featured implementation of Conway's Game of Life as a Gno realm (smart contract). This implementation includes multiple coordinate systems, pre-defined patterns, and an intuitive web interface.
4+
5+
## Features
6+
7+
- **Dual Coordinate Systems**: Support for both numeric (x,y) and letter-based (e.g., "F5") coordinates
8+
- **Pre-defined Patterns**: Includes classic patterns like blinker, block, and glider
9+
- **Web Rendering**: Beautiful grid display with column labels and proper formatting
10+
- **Interactive Help**: Built-in documentation and command examples
11+
- **Gas-Optimized**: Efficient state management for blockchain deployment
12+
13+
## Package Path
14+
15+
When deployed to gnoverse, this package will be available at:
16+
```
17+
gno.land/r/karma1337/conway
18+
```
19+
20+
## Usage
21+
22+
### Clearing the Grid
23+
24+
```bash
25+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "Clear" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" --broadcast [your-key]
26+
```
27+
28+
### Setting Individual Cells
29+
30+
**Using numeric coordinates (x, y):**
31+
```bash
32+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "SetCell" -args "5" -args "5" -args "true" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" --broadcast [your-key]
33+
```
34+
35+
**Using letter coordinates (e.g., F5):**
36+
```bash
37+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "SetCellAt" -args "F5" -args "true" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" --broadcast [your-key]
38+
```
39+
40+
### Loading Patterns
41+
42+
**Available patterns:** `blinker`, `block`, `glider`
43+
44+
**Using numeric coordinates:**
45+
```bash
46+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "LoadPattern" -args "blinker" -args "5" -args "5" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" --broadcast [your-key]
47+
```
48+
49+
**Using letter coordinates:**
50+
```bash
51+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "LoadPatternAt" -args "glider" -args "C3" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" --broadcast [your-key]
52+
```
53+
54+
### Advancing the Game
55+
56+
```bash
57+
gnokey maketx call -pkgpath "gno.land/r/karma1337/conway" -func "Step" -gas-fee 2000000ugnot -gas-wanted 20000000 -send "" --broadcast [your-key]
58+
```
59+
60+
**Note:** The Step function requires significant gas due to calculating the next generation for all cells.
61+
62+
### Viewing the Game State
63+
64+
Visit the web interface at:
65+
```
66+
https://gno.land/r/karma1337/conway
67+
```
68+
69+
Or query programmatically:
70+
```bash
71+
gnokey query vm/qrender -data "gno.land/r/karma1337/conway"
72+
```
73+
74+
## API Reference
75+
76+
### Game Management Functions
77+
78+
- `Step()` - Advance the game by one generation
79+
- `Clear()` - Reset the grid to empty state
80+
81+
### Cell Manipulation Functions
82+
83+
#### Numeric Coordinates
84+
- `SetCell(x, y, alive)` - Set cell state using numeric coordinates
85+
- `GetCell(x, y)` - Get cell state using numeric coordinates
86+
- `LoadPattern(pattern, x, y)` - Load pattern at numeric coordinates
87+
88+
#### Letter Coordinates
89+
- `SetCellAt(coordinate, alive)` - Set cell state using letter coordinates (e.g., "F5")
90+
- `GetCellAt(coordinate)` - Get cell state using letter coordinates
91+
- `LoadPatternAt(pattern, coordinate)` - Load pattern at letter coordinates
92+
93+
### Information Functions
94+
95+
- `GetGeneration()` - Get current generation number
96+
- `GetAvailablePatterns()` - List all available patterns
97+
- `Render()` - Get formatted grid display (called automatically by web interface)
98+
99+
## Coordinate Systems
100+
101+
### Numeric Coordinates
102+
- x: 0-19 (columns, left to right)
103+
- y: 0-19 (rows, top to bottom)
104+
- Example: `(10, 10)` is near the center
105+
106+
### Letter Coordinates
107+
- Columns: A-T (left to right, 20 columns)
108+
- Rows: 0-19 (top to bottom, 20 rows)
109+
- Example: `"K10"` is near the center
110+
- Case insensitive: `"k10"` and `"K10"` are equivalent
111+
112+
## Available Patterns
113+
114+
### Blinker (Oscillator)
115+
A simple 3-cell pattern that oscillates between horizontal and vertical:
116+
```
117+
.#. ...
118+
.#. -> ###
119+
.#. ...
120+
```
121+
122+
### Block (Still Life)
123+
A stable 2x2 pattern that never changes:
124+
```
125+
##
126+
##
127+
```
128+
129+
### Glider (Spaceship)
130+
A 5-cell pattern that moves diagonally across the grid:
131+
```
132+
.#.
133+
..#
134+
###
135+
```
136+
137+
## Game Rules
138+
139+
Conway's Game of Life follows these simple rules:
140+
141+
1. **Underpopulation**: Live cells with fewer than 2 neighbors die
142+
2. **Survival**: Live cells with 2-3 neighbors survive
143+
3. **Overpopulation**: Live cells with more than 3 neighbors die
144+
4. **Reproduction**: Dead cells with exactly 3 neighbors become alive
145+
146+
## Grid Display
147+
148+
The web interface displays the grid with:
149+
- Capital letters (A-T) for column headers (20 columns)
150+
- Numbers (0-19) for row headers (20 rows)
151+
- `` for alive cells
152+
- `.` for dead cells
153+
- Proper spacing for readability
154+
155+
Example display:
156+
```
157+
A B C D E F G H I J K L M N O P Q R S T
158+
0 . . . . . . . . . . . . . . . . . . . .
159+
1 . . . . . . . . . . . . . . . . . . . .
160+
2 . . ● . . . . . . . . . . . . . . . . .
161+
3 . . . ● . . . . . . . . . . . . . . . .
162+
4 . ● ● ● . . . . . . . . . . . . . . . .
163+
5 . . . . . . . . . . . . . . . . . . . .
164+
...
165+
```
166+
167+
## Development
168+
169+
This package consists of several modules:
170+
171+
- **conway.gno**: Main API and coordinate handling
172+
- **grid.gno**: Core game logic and grid management
173+
- **patterns.gno**: Pattern definitions and loading
174+
- **renderer.gno**: Web display and help text
175+
- **game.gno**: Game state management
176+
177+
## Contributing
178+
179+
This package is part of the Gnoverse community repository. To contribute:
180+
181+
1. Fork the [gnoverse/community](https://github.com/gnoverse/community) repository
182+
2. Make your changes in `packages/r/karma1337/conway/`
183+
3. Test your changes locally
184+
4. Submit a pull request
185+
186+
## License
187+
188+
This implementation is part of the Gno ecosystem and follows the same licensing terms.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package conway
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
)
7+
8+
var (
9+
// currentGrid holds the current state of the Conway's Game of Life grid
10+
currentGrid = NewGrid()
11+
// tempGrid is a reusable grid to avoid memory allocation on each step
12+
tempGrid = NewGrid()
13+
// gameEngine handles the game logic and evolution rules
14+
gameEngine = NewGameEngine()
15+
// patternManager manages predefined patterns that can be loaded into the grid
16+
patternManager = NewPatternManager()
17+
// renderer handles the display and formatting of the game state
18+
renderer = NewRenderer()
19+
)
20+
21+
// SetCell sets a cell at coordinates (x, y) to alive (true) or dead (false).
22+
func SetCell(_ realm, x, y int, alive bool) {
23+
currentGrid.SetCell(x, y, alive)
24+
}
25+
26+
// GetCell returns the state of the cell at coordinates (x, y).
27+
func GetCell(_ realm, x, y int) bool {
28+
return currentGrid.GetCell(x, y)
29+
}
30+
31+
// Step advances the game by one generation according to Conway's rules.
32+
func Step(_ realm) {
33+
gameEngine.StepInPlace(currentGrid, tempGrid)
34+
// Swap the grids to avoid copying
35+
currentGrid, tempGrid = tempGrid, currentGrid
36+
}
37+
38+
// GetGeneration returns the current generation number.
39+
func GetGeneration(_ realm) int {
40+
return currentGrid.GetGeneration()
41+
}
42+
43+
// Clear resets the grid to all dead cells.
44+
func Clear(_ realm) {
45+
currentGrid.Clear()
46+
}
47+
48+
// LoadPattern loads a predefined pattern into the grid at the specified position.
49+
// Returns true if the pattern was successfully loaded, false otherwise.
50+
func LoadPattern(_ realm, pattern string, startX, startY int) bool {
51+
return patternManager.LoadPattern(currentGrid, pattern, startX, startY)
52+
}
53+
54+
// GetAvailablePatterns returns a list of available pattern names.
55+
func GetAvailablePatterns(_ realm) []string {
56+
return patternManager.GetAvailablePatterns()
57+
}
58+
59+
// SetCellAt sets a cell using letter-number coordinates (e.g., "F5").
60+
// Returns true if the coordinate was valid and the cell was set, false otherwise.
61+
func SetCellAt(_ realm, coordinate string, alive bool) bool {
62+
col, row, valid := parseCoordinate(coordinate)
63+
if !valid {
64+
return false
65+
}
66+
currentGrid.SetCell(col, row, alive)
67+
return true
68+
}
69+
70+
// GetCellAt returns the state of a cell using letter-number coordinates (e.g., "F5").
71+
// Returns the cell state and a boolean indicating if the coordinate was valid.
72+
func GetCellAt(_ realm, coordinate string) (bool, bool) {
73+
col, row, valid := parseCoordinate(coordinate)
74+
if !valid {
75+
return false, false
76+
}
77+
return currentGrid.GetCell(col, row), true
78+
}
79+
80+
// LoadPatternAt loads a pattern using letter-number coordinates (e.g., "F5").
81+
// Returns true if the coordinate was valid and the pattern was loaded, false otherwise.
82+
func LoadPatternAt(_ realm, pattern string, coordinate string) bool {
83+
col, row, valid := parseCoordinate(coordinate)
84+
if !valid {
85+
return false
86+
}
87+
return patternManager.LoadPattern(currentGrid, pattern, col, row)
88+
}
89+
90+
// Render displays the current state of the game as a formatted string.
91+
func Render(path string) string {
92+
var output strings.Builder
93+
94+
output.WriteString("# Conway's Game of Life\n\n")
95+
output.WriteString("Generation: " + strconv.Itoa(currentGrid.GetGeneration()) + "\n\n")
96+
97+
output.WriteString(renderer.RenderHelp(patternManager))
98+
output.WriteString(renderer.RenderGrid(currentGrid))
99+
100+
return output.String()
101+
}
102+
103+
// letterToColumn converts a letter to column number (A=0, B=1, etc.).
104+
// Returns -1 if the letter is invalid or out of range.
105+
func letterToColumn(letter string) int {
106+
if len(letter) == 0 {
107+
return -1
108+
}
109+
// Convert to uppercase and get first character
110+
upper := strings.ToUpper(letter)
111+
col := int(upper[0] - 'A')
112+
if col < 0 || col >= GRID_WIDTH {
113+
return -1
114+
}
115+
return col
116+
}
117+
118+
// columnToLetter converts a column number to letter (0=A, 1=B, etc.).
119+
// Returns an empty string if the column is out of range.
120+
func columnToLetter(col int) string {
121+
if col < 0 || col >= GRID_WIDTH {
122+
return ""
123+
}
124+
return string(rune('A' + col))
125+
}
126+
127+
// parseCoordinate parses coordinates like "F5" into column and row numbers.
128+
// Returns the column, row, and a boolean indicating if the coordinate was valid.
129+
func parseCoordinate(coordinate string) (int, int, bool) {
130+
if len(coordinate) < 2 {
131+
return -1, -1, false
132+
}
133+
134+
// Extract letter part and number part
135+
letter := coordinate[:1]
136+
numberStr := coordinate[1:]
137+
138+
// Convert letter to column
139+
col := letterToColumn(letter)
140+
if col == -1 {
141+
return -1, -1, false
142+
}
143+
144+
// Convert number string to row
145+
row, err := strconv.Atoi(numberStr)
146+
if err != nil || row < 0 || row >= GRID_HEIGHT {
147+
return -1, -1, false
148+
}
149+
150+
return col, row, true
151+
}

0 commit comments

Comments
 (0)