adventofcode/2017/day14gui/main.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
}