477 lines
9.7 KiB
Go
477 lines
9.7 KiB
Go
package main
|
|
|
|
/* OpenGL & Go Tutorial
|
|
* https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
|
*
|
|
* Vertex/Fragment Shader Explanation
|
|
* https://www.quora.com/What-is-a-vertex-shader-and-what-is-a-fragment-shader/answer/Harold-Serrano?srid=aVb
|
|
*/
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-gl/gl/v2.1/gl"
|
|
"github.com/go-gl/glfw/v3.1/glfw"
|
|
)
|
|
|
|
// gl_Position = vec4(vp, 1.0);
|
|
const (
|
|
width = 500
|
|
height = 500
|
|
vertexShaderSource = `
|
|
#version 410
|
|
in vec3 vp;
|
|
void main() {
|
|
gl_Position = vec4(vp, 1.0);
|
|
}
|
|
` + "\x00"
|
|
fragmentShaderSource = `
|
|
#version 410
|
|
out vec4 frag_colour;
|
|
void main() {
|
|
frag_colour = vec4(1.0, 1.0, 1.0, 1.0);
|
|
}
|
|
` + "\x00"
|
|
|
|
rows = 128
|
|
cols = 128
|
|
fps = 10
|
|
|
|
threshold = 0.15
|
|
)
|
|
|
|
var (
|
|
square = []float32{
|
|
-0.5, 0.5, 0,
|
|
-0.5, -0.5, 0,
|
|
0.5, -0.5, 0,
|
|
|
|
-0.5, 0.5, 0,
|
|
0.5, 0.5, 0,
|
|
0.5, -0.5, 0,
|
|
}
|
|
)
|
|
|
|
var diskGrid map[string]bool
|
|
var groups map[string]int
|
|
var inp string
|
|
|
|
func init() {
|
|
runtime.LockOSThread()
|
|
|
|
inp = "vbqugkhl" // My puzzle input
|
|
if len(os.Args) > 1 {
|
|
inp = os.Args[1]
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
diskGrid = make(map[string]bool)
|
|
groups = make(map[string]int)
|
|
|
|
in := inp
|
|
window := initGlfw()
|
|
defer glfw.Terminate()
|
|
|
|
program := initOpenGL()
|
|
|
|
log.Println("Building DiskGrid...")
|
|
var grpCnt int
|
|
for i := 0; i < 128; i++ {
|
|
row := GetBinaryString(KnotHash(fmt.Sprintf("%s-%d", in, i)))
|
|
for j := range row {
|
|
diskGrid[cs(i, j)] = (row[j] == '1')
|
|
if row[j] == '1' {
|
|
groups[cs(i, j)] = grpCnt
|
|
grpCnt++
|
|
}
|
|
}
|
|
}
|
|
|
|
cells := makeCells()
|
|
for !window.ShouldClose() {
|
|
t := time.Now()
|
|
|
|
//ReduceGroups()
|
|
|
|
for x := range cells {
|
|
for _, c := range cells[x] {
|
|
c.checkState(cells)
|
|
}
|
|
}
|
|
|
|
draw(cells, window, program)
|
|
|
|
time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
|
|
}
|
|
}
|
|
|
|
// initGlfw initializes glfw and returns a Window to use.
|
|
func initGlfw() *glfw.Window {
|
|
if err := glfw.Init(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
glfw.WindowHint(glfw.Resizable, glfw.False)
|
|
glfw.WindowHint(glfw.ContextVersionMajor, 4)
|
|
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
|
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
|
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
|
|
|
window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
window.MakeContextCurrent()
|
|
|
|
return window
|
|
}
|
|
|
|
// initOpenGL initializes OpenGL and returns an initialized program.
|
|
func initOpenGL() uint32 {
|
|
if err := gl.Init(); err != nil {
|
|
panic(err)
|
|
}
|
|
version := gl.GoStr(gl.GetString(gl.VERSION))
|
|
log.Println("OpenGL version", version)
|
|
|
|
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
prog := gl.CreateProgram()
|
|
gl.AttachShader(prog, vertexShader)
|
|
gl.AttachShader(prog, fragmentShader)
|
|
gl.LinkProgram(prog)
|
|
return prog
|
|
}
|
|
|
|
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
|
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
|
gl.UseProgram(program)
|
|
|
|
for x := range cells {
|
|
for _, c := range cells[x] {
|
|
c.draw()
|
|
}
|
|
}
|
|
|
|
glfw.PollEvents()
|
|
window.SwapBuffers()
|
|
}
|
|
|
|
// makeVao initializes and returns a vertex array from the points provided.
|
|
func makeVao(points []float32) uint32 {
|
|
var vbo uint32
|
|
gl.GenBuffers(1, &vbo)
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
|
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
|
|
|
|
var vao uint32
|
|
gl.GenVertexArrays(1, &vao)
|
|
gl.BindVertexArray(vao)
|
|
gl.EnableVertexAttribArray(0)
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
|
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
|
|
|
return vao
|
|
}
|
|
|
|
func compileShader(source string, shaderType uint32) (uint32, error) {
|
|
shader := gl.CreateShader(shaderType)
|
|
|
|
csources, free := gl.Strs(source)
|
|
gl.ShaderSource(shader, 1, csources, nil)
|
|
free()
|
|
gl.CompileShader(shader)
|
|
|
|
var status int32
|
|
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
|
if status == gl.FALSE {
|
|
var logLength int32
|
|
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
|
|
|
log := strings.Repeat("\x00", int(logLength+1))
|
|
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
|
|
|
return 0, fmt.Errorf("failed to compile %v: %v", source, log)
|
|
}
|
|
|
|
return shader, nil
|
|
}
|
|
|
|
func makeCells() [][]*cell {
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
cells := make([][]*cell, rows, rows)
|
|
for x := 0; x < rows; x++ {
|
|
for y := 0; y < cols; y++ {
|
|
var gr int
|
|
var ok bool
|
|
c := newCell(x, y)
|
|
if gr, ok = groups[cs(x, y)]; ok {
|
|
c.group = gr
|
|
}
|
|
|
|
c.alive = rand.Float64() < threshold
|
|
c.aliveNext = c.alive
|
|
|
|
cells[x] = append(cells[x], c)
|
|
}
|
|
}
|
|
|
|
return cells
|
|
}
|
|
|
|
type cell struct {
|
|
drawable uint32
|
|
|
|
alive bool
|
|
aliveNext bool
|
|
|
|
group int
|
|
|
|
x int
|
|
y int
|
|
}
|
|
|
|
func newCell(x, y int) *cell {
|
|
points := make([]float32, len(square), len(square))
|
|
copy(points, square)
|
|
|
|
for i := 0; i < len(points); i++ {
|
|
var position float32
|
|
var size float32
|
|
switch i % 3 {
|
|
case 0:
|
|
size = 1.0 / float32(cols)
|
|
position = float32(x) * size
|
|
case 1:
|
|
size = 1.0 / float32(rows)
|
|
position = float32(y) * size
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if points[i] < 0 {
|
|
points[i] = (position * 2) - 1
|
|
} else {
|
|
points[i] = ((position + size) * 2) - 1
|
|
}
|
|
}
|
|
|
|
return &cell{
|
|
drawable: makeVao(points),
|
|
x: x,
|
|
y: y,
|
|
group: 0,
|
|
}
|
|
}
|
|
|
|
func (c *cell) draw() {
|
|
if !c.alive {
|
|
return
|
|
}
|
|
|
|
gl.BindVertexArray(c.drawable)
|
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
|
|
}
|
|
|
|
// checkState determines the state of the cell for the next tick of the game
|
|
func (c *cell) checkState(cells [][]*cell) {
|
|
c.alive = c.aliveNext
|
|
c.aliveNext = c.alive
|
|
|
|
liveCount := c.liveNeighbors(cells)
|
|
if c.alive {
|
|
// 1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation
|
|
if liveCount < 2 {
|
|
c.alive = false
|
|
}
|
|
|
|
// 2. Any live cell with two or three live neighbors lives on to the next generation
|
|
if liveCount == 2 || liveCount == 3 {
|
|
c.aliveNext = true
|
|
}
|
|
|
|
// 3. Any live cell with more than three live neighbors dies, as if by overpopulation
|
|
if liveCount > 3 {
|
|
c.aliveNext = false
|
|
}
|
|
} else {
|
|
// 4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction
|
|
if liveCount == 3 {
|
|
c.aliveNext = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// liveNeighbors returns the number of live neighbors for a cell
|
|
func (c *cell) liveNeighbors(cells [][]*cell) int {
|
|
var liveCount int
|
|
add := func(x, y int) {
|
|
// If we're at an edge, check the other side of the board.
|
|
if x == len(cells) {
|
|
x = 0
|
|
} else if x == -1 {
|
|
x = len(cells) - 1
|
|
}
|
|
if y == len(cells[x]) {
|
|
y = 0
|
|
} else if y == -1 {
|
|
y = len(cells[x]) - 1
|
|
}
|
|
|
|
if cells[x][y].alive {
|
|
liveCount++
|
|
}
|
|
}
|
|
|
|
add(c.x-1, c.y) // To the left
|
|
add(c.x+1, c.y) // To the right
|
|
add(c.x, c.y+1) // Up
|
|
add(c.x, c.y-1) // Down
|
|
add(c.x-1, c.y+1) // Top-Left
|
|
add(c.x+1, c.y+1) // Top-Right
|
|
add(c.x-1, c.y-1) // Bottom-Left
|
|
add(c.x+1, c.y-1) // Bottom-Right
|
|
|
|
return liveCount
|
|
}
|
|
|
|
func ReduceGroups() bool {
|
|
var ret bool
|
|
for x := 0; x < 128; x++ {
|
|
for y := 0; y < 128; y++ {
|
|
if oV, oOk := diskGrid[cs(x, y)]; oOk && oV {
|
|
if dV, dOk := diskGrid[cs(x, y-1)]; dOk && dV && groups[cs(x, y-1)] != groups[cs(x, y)] {
|
|
CombineBlockGroups(cs(x, y), cs(x, y-1))
|
|
ret = true
|
|
}
|
|
if dV, dOk := diskGrid[cs(x-1, y)]; dOk && dV && groups[cs(x-1, y)] != groups[cs(x, y)] {
|
|
CombineBlockGroups(cs(x, y), cs(x-1, y))
|
|
ret = true
|
|
}
|
|
if dV, dOk := diskGrid[cs(x+1, y)]; dOk && dV && groups[cs(x+1, y)] != groups[cs(x, y)] {
|
|
CombineBlockGroups(cs(x+1, y), cs(x, y))
|
|
ret = true
|
|
}
|
|
if dV, dOk := diskGrid[cs(x, y+1)]; dOk && dV && groups[cs(x, y+1)] != groups[cs(x, y)] {
|
|
CombineBlockGroups(cs(x, y+1), cs(x, y))
|
|
ret = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func CombineBlockGroups(b1, b2 string) {
|
|
if groups[b1] < groups[b2] {
|
|
groups[b1] = groups[b2]
|
|
} else {
|
|
groups[b2] = groups[b1]
|
|
}
|
|
}
|
|
|
|
// Get a map coordinate string for x, y
|
|
func cs(x, y int) string {
|
|
return fmt.Sprint(x, "-", y)
|
|
}
|
|
|
|
// Get the x, y coordinate from a string
|
|
func sc(c string) (int, int) {
|
|
pts := strings.Split(c, "-")
|
|
return Atoi(pts[0]), Atoi(pts[1])
|
|
}
|
|
|
|
func KnotHash(in string) string {
|
|
var idx, skip int
|
|
var list []int
|
|
for i := 0; i < 256; i++ {
|
|
list = append(list, i)
|
|
}
|
|
inpBts := []byte(in)
|
|
inpBts = append(inpBts, []byte{17, 31, 73, 47, 23}...)
|
|
for j := 0; j < 64; j++ {
|
|
for i := range inpBts {
|
|
idx, skip, list = khRound(int(inpBts[i]), idx, skip, list)
|
|
}
|
|
}
|
|
// Now calculate the dense hash
|
|
var dense []byte
|
|
for i := 0; i < len(list); i += 16 {
|
|
dense = append(dense, xorList(list[i:i+16]))
|
|
}
|
|
return fmt.Sprintf("%x", dense)
|
|
}
|
|
|
|
func khRound(i, idx, skip int, list []int) (int, int, []int) {
|
|
// if idx+i overflows, pull from the front
|
|
var revList []int
|
|
for j := idx; j < idx+i; j++ {
|
|
revList = append([]int{list[j%256]}, revList...)
|
|
}
|
|
for j := 0; j < len(revList); j++ {
|
|
list[(idx+j)%256] = revList[j]
|
|
}
|
|
|
|
idx += i + skip
|
|
skip++
|
|
return idx, skip, list
|
|
}
|
|
|
|
func xorList(in []int) byte {
|
|
var ret byte
|
|
for i := range in {
|
|
ret ^= byte(in[i])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func GetBinaryString(in string) string {
|
|
var bin string
|
|
for i := range in {
|
|
var v int
|
|
if in[i] >= '0' && in[i] <= '9' {
|
|
v = int(in[i] - '0')
|
|
} else if in[i] >= 'a' && in[i] <= 'f' {
|
|
v = int(in[i] - 'a' + 10)
|
|
}
|
|
nibble := fmt.Sprintf("%04s", strconv.FormatInt(int64(v), 2))
|
|
bin += nibble
|
|
}
|
|
return bin
|
|
}
|
|
|
|
func StdinToString() string {
|
|
var input string
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
|
input = scanner.Text()
|
|
}
|
|
return input
|
|
}
|
|
|
|
func Atoi(i string) int {
|
|
var ret int
|
|
var err error
|
|
if ret, err = strconv.Atoi(i); err != nil {
|
|
log.Fatal("Invalid Atoi")
|
|
}
|
|
return ret
|
|
}
|