Added rendering stuff, updated go mod
This commit is contained in:
parent
f7eaa0d08d
commit
d426892e6a
|
@ -0,0 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sshpong/internal/renderer"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starting renderer")
|
||||
renderer.Render()
|
||||
}
|
55
flake.lock
55
flake.lock
|
@ -18,6 +18,45 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722589758,
|
||||
"narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1722421184,
|
||||
|
@ -37,6 +76,7 @@
|
|||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
|
@ -54,6 +94,21 @@
|
|||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
|
@ -2,15 +2,22 @@
|
|||
description = "An example project using flutter";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.gomod2nix = {
|
||||
url = "github:tweag/gomod2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.utils.follows = "utils";
|
||||
};
|
||||
outputs = {
|
||||
self,
|
||||
flake-utils,
|
||||
nixpkgs,
|
||||
gomod2nix,
|
||||
...
|
||||
} @ inputs:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ gomod2nix.overlays.default ];
|
||||
};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
|
@ -19,6 +26,8 @@
|
|||
gopls
|
||||
gotools
|
||||
go-tools
|
||||
gomod2nix.packages.${system}.default
|
||||
sqlite-interactive
|
||||
];
|
||||
};
|
||||
});
|
||||
|
|
2
go.mod
2
go.mod
|
@ -5,4 +5,6 @@ go 1.22.2
|
|||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/term v0.22.0 // indirect
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -2,3 +2,7 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
|
|
15
gomod2nix.toml
Normal file
15
gomod2nix.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
schema = 3
|
||||
|
||||
[mod]
|
||||
[mod."github.com/google/uuid"]
|
||||
version = "v1.6.0"
|
||||
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.23.0"
|
||||
hash = "sha256-tC6QVLu72bADgINz26FUGdmYqKgsU45bHPg7sa0ZV7w="
|
||||
[mod."golang.org/x/term"]
|
||||
version = "v0.22.0"
|
||||
hash = "sha256-tRx/y4ZIZzGAlDJ/8JW3AycC9bRXlNuRqO4V48sAEEc="
|
||||
[mod."google.golang.org/protobuf"]
|
||||
version = "v1.34.2"
|
||||
hash = "sha256-nMTlrDEE2dbpWz50eQMPBQXCyQh4IdjrTIccaU0F3m0="
|
135
internal/ansii/ansii.go
Normal file
135
internal/ansii/ansii.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package ansii
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type ANSI string
|
||||
|
||||
const (
|
||||
reset ANSI = "\033[0m"
|
||||
plain ANSI = ""
|
||||
bold ANSI = "\033[1m"
|
||||
underline ANSI = "\033[4m"
|
||||
black ANSI = "\033[30m"
|
||||
red ANSI = "\033[31m"
|
||||
green ANSI = "\033[32m"
|
||||
yellow ANSI = "\033[33m"
|
||||
blue ANSI = "\033[34m"
|
||||
purple ANSI = "\033[35m"
|
||||
cyan ANSI = "\033[36m"
|
||||
white ANSI = "\033[37m"
|
||||
blackBg ANSI = "\033[40m"
|
||||
redBg ANSI = "\033[41m"
|
||||
greenBg ANSI = "\033[42m"
|
||||
yellowBg ANSI = "\033[43m"
|
||||
blueBg ANSI = "\033[44m"
|
||||
purpleBg ANSI = "\033[45m"
|
||||
cyanBg ANSI = "\033[46m"
|
||||
whiteBg ANSI = "\033[47m"
|
||||
clearScreen ANSI = "\033[2J"
|
||||
hideCursor ANSI = "\033[?25l"
|
||||
showCursor ANSI = "\033[?25h"
|
||||
)
|
||||
|
||||
type Offset struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
type style struct {
|
||||
Reset ANSI
|
||||
Plain ANSI
|
||||
Bold ANSI
|
||||
Underline ANSI
|
||||
}
|
||||
|
||||
type color struct {
|
||||
Black ANSI
|
||||
Red ANSI
|
||||
Green ANSI
|
||||
Yellow ANSI
|
||||
Blue ANSI
|
||||
Purple ANSI
|
||||
Cyan ANSI
|
||||
White ANSI
|
||||
}
|
||||
|
||||
type screen struct {
|
||||
ClearScreen ANSI
|
||||
HideCursor ANSI
|
||||
ShowCursor ANSI
|
||||
}
|
||||
|
||||
type ascii struct {
|
||||
Block string
|
||||
}
|
||||
|
||||
func GetTermSize() (width int, height int) {
|
||||
var fd int = int(os.Stdout.Fd())
|
||||
width, height, err := term.GetSize(fd)
|
||||
if err != nil {
|
||||
fmt.Println(string(Screen.ClearScreen) + "Fatal: error getting terminal size.")
|
||||
os.Exit(1)
|
||||
}
|
||||
return width, height
|
||||
}
|
||||
|
||||
func MakeTermRaw() (*term.State, error) {
|
||||
var fd int = int(os.Stdout.Fd())
|
||||
return term.MakeRaw(fd)
|
||||
}
|
||||
|
||||
func RestoreTerm(prev *term.State) error {
|
||||
var fd int = int(os.Stdout.Fd())
|
||||
return term.Restore(fd, prev)
|
||||
}
|
||||
|
||||
func (s screen) PlaceCursor(offset Offset) ANSI {
|
||||
return ANSI(fmt.Sprintf("\033[%d;%dH", offset.Y, offset.X))
|
||||
}
|
||||
|
||||
var (
|
||||
Styles = style{Bold: bold, Underline: underline, Reset: reset, Plain: plain}
|
||||
Colors = color{Red: red, Green: green, Yellow: yellow, Blue: blue, Purple: purple, Cyan: cyan, White: white}
|
||||
Screen = screen{ClearScreen: clearScreen, HideCursor: hideCursor, ShowCursor: showCursor}
|
||||
Blocks = ascii{Block: "█"}
|
||||
)
|
||||
|
||||
// Draws a box of dimensions `height` and `width` at `offset`.
|
||||
// The `offset` is the top left cell of the square.
|
||||
// Blocks that would be placed off screen are clipped.
|
||||
func DrawBox(builder *strings.Builder, offset Offset, height int, width int, style ANSI) {
|
||||
builder.WriteString(string(style))
|
||||
for hIdx := 0; hIdx < height; hIdx++ {
|
||||
|
||||
if hIdx == 0 || hIdx == height-1 {
|
||||
for wIdx := 0; wIdx < width; wIdx++ {
|
||||
DrawPixel(builder, Offset{X: offset.X + wIdx, Y: offset.Y + hIdx})
|
||||
}
|
||||
} else {
|
||||
DrawPixel(builder, Offset{X: offset.X, Y: offset.Y + hIdx})
|
||||
DrawPixel(builder, Offset{X: offset.X + width - 1, Y: offset.Y + hIdx})
|
||||
}
|
||||
}
|
||||
builder.WriteString(string(Styles.Reset))
|
||||
return
|
||||
}
|
||||
|
||||
func DrawPixel(builder *strings.Builder, offset Offset) {
|
||||
termWidth, termHeight := GetTermSize()
|
||||
if offset.X > termWidth || offset.Y > termHeight || offset.X < 0 || offset.Y < 0 {
|
||||
return
|
||||
}
|
||||
builder.WriteString(string(Screen.PlaceCursor(offset) + ANSI(Blocks.Block)))
|
||||
}
|
||||
|
||||
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
|
||||
builder.WriteString(string(style))
|
||||
DrawPixel(builder, offset)
|
||||
builder.WriteString(string(Styles.Reset))
|
||||
}
|
|
@ -1,7 +1,195 @@
|
|||
package renderer
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sshpong/internal/ansii"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func render() {
|
||||
fmt.Println("Test")
|
||||
var (
|
||||
targetFps float64 = 60.0
|
||||
targetFpMilli float64 = float64(targetFps) / 1000.0
|
||||
millisecondTimeFrame float64 = float64(1 / targetFpMilli)
|
||||
quit chan bool
|
||||
userInput chan rune
|
||||
playerX int = 10
|
||||
playerY int = 10
|
||||
)
|
||||
|
||||
func Render() {
|
||||
now := time.Now()
|
||||
err := doFpsTest()
|
||||
if err != nil {
|
||||
fmt.Println("Error ", err)
|
||||
return
|
||||
}
|
||||
then := time.Now()
|
||||
|
||||
total := float64(float64(then.UnixMicro()-now.UnixMicro()) / 1000.0)
|
||||
fmt.Printf("\n\nTook %.3f milliseconds\n", total)
|
||||
}
|
||||
|
||||
func writeCheckerBoard(height int, width int, builder *strings.Builder) {
|
||||
for i := 0; i < height; i++ {
|
||||
for j := 0; j < width; j++ {
|
||||
if i%2 == 0 {
|
||||
if j%2 == 0 {
|
||||
builder.WriteString("█")
|
||||
} else {
|
||||
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
} else {
|
||||
if j%2 == 0 {
|
||||
builder.WriteString(" ")
|
||||
} else {
|
||||
builder.WriteString("█")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawScreen(frameNum int, startMs float64) (frameTimeMs float64) {
|
||||
_ = frameNum
|
||||
_, height := ansii.GetTermSize()
|
||||
var builder = strings.Builder{}
|
||||
builder.WriteString(string(ansii.Screen.ClearScreen))
|
||||
// writeCheckerBoard(height, width, &builder)
|
||||
// var xOffset = frameNum % width
|
||||
// ansii.DrawBox(&builder, ansii.Offset{X: 0, Y: 0}, 5, 8, ansii.Colors.Purple)
|
||||
ansii.DrawBox(&builder, ansii.Offset{X: playerX, Y: playerY}, 5, 1, ansii.Colors.Cyan)
|
||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY}, ansii.Colors.Purple)
|
||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY + 5}, ansii.Colors.Purple)
|
||||
// Quit instructions
|
||||
builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
|
||||
builder.WriteString("q to quit")
|
||||
|
||||
os.Stdout.WriteString(builder.String())
|
||||
var frameTimeMilli = (float64(time.Now().UnixNano()) / 1_000_000.0) - startMs
|
||||
// builder.WriteString(string(ansii.Screen.Coordinate(0, 0)))
|
||||
return frameTimeMilli
|
||||
}
|
||||
|
||||
func drawFrameStats(frameNum int, frameTimeMs float64) {
|
||||
width, height := ansii.GetTermSize()
|
||||
var spareTimeMilli = millisecondTimeFrame - frameTimeMs
|
||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 12, Y: height - 2})))
|
||||
os.Stdout.WriteString(fmt.Sprintf("Frame #: %d", frameNum))
|
||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 19, Y: height - 1})))
|
||||
os.Stdout.WriteString(fmt.Sprintf("Frame Time: %.4fms", frameTimeMs))
|
||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 20, Y: height})))
|
||||
os.Stdout.WriteString(fmt.Sprintf("Spare Time: %.4fms", spareTimeMilli))
|
||||
}
|
||||
|
||||
func handleInput(rawInput rune) {
|
||||
action := ProcessInput(rawInput)
|
||||
width, height := ansii.GetTermSize()
|
||||
|
||||
switch action {
|
||||
case Quit:
|
||||
fmt.Println("Quitting...")
|
||||
close(quit)
|
||||
case Left, LeftArrow:
|
||||
playerX = max(playerX-1, 0)
|
||||
case Right, RightArrow:
|
||||
playerX = min(playerX+1, width)
|
||||
case Up, UpArrow:
|
||||
playerY = max(playerY-1, 0)
|
||||
case Down, DownArrow:
|
||||
playerY = min(playerY+1, height)
|
||||
case Unknown:
|
||||
default:
|
||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height - 2})))
|
||||
os.Stdout.WriteString("Unrecognized Input: " + string(action))
|
||||
close(quit)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForFpsLock(startMs float64) {
|
||||
for {
|
||||
var nowMs = float64(time.Now().UnixNano()) / 1_000_000.0
|
||||
if nowMs-startMs >= millisecondTimeFrame {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doFpsTest() error {
|
||||
prev, err := ansii.MakeTermRaw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ansii.RestoreTerm(prev)
|
||||
quit = make(chan bool, 1)
|
||||
userInput = make(chan rune, 1)
|
||||
|
||||
// User input loop
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, 3)
|
||||
n, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
if buf[0] == 0x1b { // ESC
|
||||
if n > 1 && buf[1] == '[' { // ESC [
|
||||
switch buf[2] {
|
||||
case 'A':
|
||||
userInput <- '↑' // Up arrow
|
||||
case 'B':
|
||||
userInput <- '↓' // Down arrow
|
||||
case 'C':
|
||||
userInput <- '→' // Right arrow
|
||||
case 'D':
|
||||
userInput <- '←' // Left arrow
|
||||
default:
|
||||
userInput <- '?'
|
||||
}
|
||||
} else {
|
||||
userInput <- '?'
|
||||
}
|
||||
} else {
|
||||
r, _ := utf8.DecodeRune(buf)
|
||||
userInput <- r
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Rendering loop
|
||||
go func() {
|
||||
for i := 0; i <= 10_000; i++ {
|
||||
startMs := float64(time.Now().UnixNano()) / 1_000_000.0
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
frameTimeMs := drawScreen(i, startMs)
|
||||
drawFrameStats(i, frameTimeMs)
|
||||
waitForFpsLock(startMs)
|
||||
}
|
||||
}
|
||||
close(quit)
|
||||
}()
|
||||
|
||||
os.Stdout.WriteString(string(ansii.Screen.HideCursor))
|
||||
defer os.Stdout.WriteString(string(ansii.Screen.ShowCursor))
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
fmt.Println("Exiting")
|
||||
return nil
|
||||
// case ui := <-userInput:
|
||||
case input := <-userInput:
|
||||
handleInput(input)
|
||||
// fmt.Println(ui)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
internal/renderer/user_inputs.go
Normal file
25
internal/renderer/user_inputs.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package renderer
|
||||
|
||||
type UiAction rune
|
||||
|
||||
const (
|
||||
Unknown UiAction = iota
|
||||
Quit UiAction = 81 // 'Q'
|
||||
Left UiAction = 65
|
||||
Up UiAction = 87
|
||||
Right UiAction = 68
|
||||
Down UiAction = 83
|
||||
LeftArrow UiAction = 8592
|
||||
UpArrow UiAction = 8593
|
||||
RightArrow UiAction = 8594
|
||||
DownArrow UiAction = 8595
|
||||
)
|
||||
|
||||
func ProcessInput(rawInput rune) (action UiAction) {
|
||||
inputVal := int(rawInput)
|
||||
// Convert to UpperCase
|
||||
if inputVal >= 97 && inputVal <= 122 {
|
||||
inputVal = inputVal - 32
|
||||
}
|
||||
return UiAction(inputVal)
|
||||
}
|
Loading…
Reference in New Issue
Block a user