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 }