Doing some C learnin'.
Also, reformatted all the problems.
This commit is contained in:
476
2017/day14gui/main.go
Normal file
476
2017/day14gui/main.go
Normal file
@@ -0,0 +1,476 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user