This commit is contained in:
2024-10-03 22:41:33 -06:00
parent 15e7f20f1a
commit 067d22f3a9
20 changed files with 476 additions and 257 deletions
+73 -80
View File
@@ -6,7 +6,8 @@ import (
"sshpong/internal/ansii"
"sshpong/internal/pong"
"strings"
"time"
"golang.org/x/term"
)
var (
@@ -15,96 +16,88 @@ var (
millisecondTimeFrame float64 = float64(1 / targetFpMilli)
quit chan bool
userInput chan rune
playerX int = 10
playerY int = 10
)
const (
reset string = "\033[0m"
plain string = ""
bold string = "\033[1m"
underline string = "\033[4m"
black string = "\033[30m"
red string = "\033[31m"
green string = "\033[32m"
yellow string = "\033[33m"
blue string = "\033[34m"
purple string = "\033[35m"
cyan string = "\033[36m"
white string = "\033[37m"
blackBg string = "\033[40m"
redBg string = "\033[41m"
greenBg string = "\033[42m"
yellowBg string = "\033[43m"
blueBg string = "\033[44m"
purpleBg string = "\033[45m"
cyanBg string = "\033[46m"
whiteBg string = "\033[47m"
clearScreen string = "\033[2J"
hideCursor string = "\033[?25l"
showCursor string = "\033[?25h"
)
func Render(state pong.GameState) {
// drawScreen(state)
fmt.Print("\033c")
fmt.Println("Player 1", state.Player1.Pos.X, state.Player1.Pos.Y)
fmt.Println("Player 2", state.Player2.Pos.X, state.Player2.Pos.Y)
}
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(state pong.GameState) {
// width := 100
// height := 50
// fmt.Println("Player 1", ((state.Player1.Pos.X+50)/100)*width, ((state.Player1.Pos.Y+50)/100)*height)
// fmt.Println("Player 2", ((state.Player2.Pos.X+50)/100)*width, ((state.Player2.Pos.Y+50)/100)*height)
// fmt.Println("Ball", ((state.Ball.Pos.X+50)/100)*width, ((state.Ball.Pos.Y+50)/100)*height)
var builder = strings.Builder{}
builder.WriteString(string(ansii.Screen.ClearScreen))
ansii.DrawBox(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, ansii.Colors.Purple)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y) + 5}, ansii.Colors.Purple)
ansii.DrawBox(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, ansii.Colors.Purple)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y) + 5}, ansii.Colors.Purple)
// Quit instructions
// builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
// builder.WriteString("q to quit")
x1, y1 := transformToTermPos(state.Player1.Pos)
builder.WriteString(renderBox(&builder, x1+2, y1, 2, 10, cyan))
x2, y2 := transformToTermPos(state.Player2.Pos)
builder.WriteString(renderBox(&builder, x2, y2, 2, 10, purple))
xb, yb := transformToTermPos(state.Ball.Pos)
builder.WriteString(renderPixel(&builder, xb, yb, red))
builder.WriteString(renderMessage(&builder, state.Message))
os.Stdout.WriteString(builder.String())
}
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 setCursorPos(x, y int) string {
return fmt.Sprintf("\033[%d;%dH", y, x)
}
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
// Renders a box with center positioned at X,Y with specified width and height
func renderBox(builder *strings.Builder, X, Y, width, height int, style string) string {
str := ""
for x := X - (width / 2); x < X+(width/2); x++ {
for y := Y - (height / 2); y < Y+(height/2); y++ {
str = str + (setCursorPos(x, y) + style + "█")
}
}
return str
}
func renderPixel(builder *strings.Builder, x, y int, style string) string {
return (setCursorPos(x, y) + style + "█")
}
func renderMessage(builder *strings.Builder, message string) string {
xm, xy := transformToTermPos(pong.Vector{X: 40, Y: 40})
return (setCursorPos(xm, xy) + reset + message)
}
// Returns state x and y positions with center origin and 50 by 50 area
// to scaled, top-left origin coordinates for the user's terminal size.
func transformToTermPos(vec pong.Vector) (int, int) {
iwidth, iheight, _ := term.GetSize(int(os.Stdin.Fd()))
width := float32(iwidth)
height := float32(iheight)
ix := int(((vec.X + 50) / 100) * width)
iy := int(((vec.Y + 50) / 100) * height)
return ix, iy
}