MVP
This commit is contained in:
+21
-16
@@ -2,6 +2,8 @@ package ansii
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -37,8 +39,8 @@ const (
|
||||
)
|
||||
|
||||
type Offset struct {
|
||||
X int
|
||||
Y int
|
||||
X float32
|
||||
Y float32
|
||||
}
|
||||
|
||||
type style struct {
|
||||
@@ -89,8 +91,8 @@ func RestoreTerm(prev *term.State) error {
|
||||
return term.Restore(fd, prev)
|
||||
}
|
||||
|
||||
func (s screen) PlaceCursor(offset Offset) ANSI {
|
||||
return ANSI(fmt.Sprintf("\033[%d;%dH", offset.Y, offset.X))
|
||||
func (s screen) PlaceCursor(X, Y int) ANSI {
|
||||
return ANSI(fmt.Sprintf("\033[%d;%dH", Y, X))
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -105,31 +107,34 @@ var (
|
||||
// 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++ {
|
||||
|
||||
for hIdx := range height {
|
||||
if hIdx == 0 || hIdx == height-1 {
|
||||
for wIdx := 0; wIdx < width; wIdx++ {
|
||||
DrawPixel(builder, Offset{X: offset.X + wIdx, Y: offset.Y + hIdx})
|
||||
for wIdx := range width {
|
||||
drawPixel(builder, offset.X+float32(wIdx), offset.Y+float32(hIdx))
|
||||
}
|
||||
} else {
|
||||
DrawPixel(builder, Offset{X: offset.X, Y: offset.Y + hIdx})
|
||||
DrawPixel(builder, Offset{X: offset.X + width - 1, Y: offset.Y + hIdx})
|
||||
drawPixel(builder, offset.X, offset.Y+float32(hIdx))
|
||||
drawPixel(builder, offset.X+float32(width-1), offset.Y+float32(hIdx))
|
||||
}
|
||||
}
|
||||
builder.WriteString(string(Styles.Reset))
|
||||
return
|
||||
}
|
||||
|
||||
func DrawPixel(builder *strings.Builder, offset Offset) {
|
||||
func drawPixel(builder *strings.Builder, offsetX, offsetY float32) {
|
||||
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)))
|
||||
|
||||
scaledX := math.Floor(float64(offsetX * float32(termWidth)))
|
||||
scaledY := math.Floor(float64(offsetY * float32(termHeight)))
|
||||
|
||||
slog.Debug("positions", slog.Any("x", scaledX), slog.Any("y", scaledY))
|
||||
|
||||
// TODO: Does float to int convertion cause many problems?
|
||||
builder.WriteString(string(Screen.PlaceCursor(int(scaledX), int(scaledY)) + ANSI(Blocks.Block)))
|
||||
}
|
||||
|
||||
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
|
||||
builder.WriteString(string(style))
|
||||
DrawPixel(builder, offset)
|
||||
drawPixel(builder, offset.X, offset.Y)
|
||||
builder.WriteString(string(Styles.Reset))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"log/slog"
|
||||
"sshpong/internal/lobby"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type InterrupterMessage struct {
|
||||
@@ -16,8 +18,8 @@ type InterrupterMessage struct {
|
||||
|
||||
var help = fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
|
||||
|
||||
var red = "\x1b[31m"
|
||||
var normal = "\033[0m"
|
||||
var Red = "\x1b[31m"
|
||||
var Normal = "\033[0m"
|
||||
|
||||
func HandleUserInput(args []string, username string) ([]byte, error) {
|
||||
if len(args) == 0 {
|
||||
@@ -26,11 +28,15 @@ func HandleUserInput(args []string, username string) ([]byte, error) {
|
||||
switch args[0] {
|
||||
case "invite":
|
||||
if args[1] != "" {
|
||||
msg, err := lobby.Marshal(lobby.InviteData{From: username, To: args[1]}, lobby.Invite)
|
||||
if err != nil {
|
||||
slog.Debug("invite message was not properly marshalled", "error", err)
|
||||
if args[1] == username {
|
||||
fmt.Println("You cannot invite yourself to a game ;)")
|
||||
} else {
|
||||
msg, err := lobby.Marshal(lobby.InviteData{From: username, To: args[1]}, lobby.Invite)
|
||||
if err != nil {
|
||||
slog.Debug("invite message was not properly marshalled", "error", err)
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
return msg, err
|
||||
} else {
|
||||
fmt.Println("Please provide a player to invite ")
|
||||
}
|
||||
@@ -84,13 +90,15 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||
switch incoming.InterruptType {
|
||||
// Respond with yes if you accept game
|
||||
case "invite":
|
||||
slog.Debug("handling invite interrupt")
|
||||
if len(args) < 1 {
|
||||
return []byte{}, nil
|
||||
} else {
|
||||
if strings.ToLower(args[0]) == "y" || strings.ToLower(args[0]) == "yes" {
|
||||
msg, err := lobby.Marshal(lobby.AcceptData{
|
||||
From: username,
|
||||
To: incoming.Content,
|
||||
From: username,
|
||||
To: incoming.Content,
|
||||
GameID: uuid.NewString(),
|
||||
}, lobby.Accept)
|
||||
if err != nil {
|
||||
slog.Debug("accept message was not properly marshalled", "error", err)
|
||||
@@ -99,20 +107,21 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect and connect to game
|
||||
case "accepted":
|
||||
msg, err := lobby.Marshal(lobby.DisconnectData{
|
||||
From: incoming.Content,
|
||||
}, lobby.Disconnect)
|
||||
if err != nil {
|
||||
slog.Debug("disconnect message was not properly marshalled", "error", err)
|
||||
}
|
||||
return msg, err
|
||||
// TODO: Do we need this accepted? Disconnect and connect to game
|
||||
// case "accepted":
|
||||
// msg, err := lobby.Marshal(lobby.DisconnectData{
|
||||
// From: incoming.Content,
|
||||
// }, lobby.Disconnect)
|
||||
// if err != nil {
|
||||
// slog.Debug("disconnect message was not properly marshalled", "error", err)
|
||||
// }
|
||||
// return msg, err
|
||||
|
||||
case "start_game":
|
||||
msg, err := lobby.Marshal(lobby.StartGameData{
|
||||
To: "",
|
||||
GameID: incoming.Content,
|
||||
}, lobby.Chat)
|
||||
}, lobby.StartGame)
|
||||
if err != nil {
|
||||
slog.Debug("start game message was not properly marshalled", "error", err)
|
||||
}
|
||||
@@ -124,7 +133,6 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||
|
||||
func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||
header := msg[0]
|
||||
|
||||
switch header {
|
||||
case lobby.Invite:
|
||||
imsg, err := lobby.Unmarshal[lobby.InviteData](msg)
|
||||
@@ -160,6 +168,7 @@ func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||
if err != nil {
|
||||
return InterrupterMessage{}, errors.New("Not a properly formatted start game message")
|
||||
}
|
||||
fmt.Println("Your invite was accepted. Press Enter to join game")
|
||||
return InterrupterMessage{
|
||||
InterruptType: "start_game",
|
||||
Content: sgmsg.GameID,
|
||||
@@ -206,7 +215,7 @@ func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||
if err != nil {
|
||||
slog.Debug("Received an indecipherable error message...", slog.Any("msg", msg[1:]))
|
||||
}
|
||||
fmt.Println(red, em.Message, normal)
|
||||
fmt.Println(Red, em.Message, Normal)
|
||||
|
||||
}
|
||||
return InterrupterMessage{}, nil
|
||||
|
||||
+16
-16
@@ -166,9 +166,9 @@ func handleGameInput(bytes []byte) {
|
||||
// Up
|
||||
case 'w':
|
||||
if isPlayer1 {
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player1.Pos.Y,
|
||||
X: -50, Y: state.Player1.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
@@ -180,9 +180,9 @@ func handleGameInput(bytes []byte) {
|
||||
|
||||
egress <- update
|
||||
} else {
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player2.Pos.Y,
|
||||
X: 50, Y: state.Player2.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||
@@ -197,9 +197,9 @@ func handleGameInput(bytes []byte) {
|
||||
// Down
|
||||
case 's':
|
||||
if isPlayer1 {
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player1.Pos.Y,
|
||||
X: -50, Y: state.Player1.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
@@ -211,9 +211,9 @@ func handleGameInput(bytes []byte) {
|
||||
|
||||
egress <- update
|
||||
} else {
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player2.Pos.Y,
|
||||
X: 50, Y: state.Player2.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
@@ -252,9 +252,9 @@ func handleGameInput(bytes []byte) {
|
||||
// Up
|
||||
case 65:
|
||||
if isPlayer1 {
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player1.Pos.Y,
|
||||
X: -50, Y: state.Player1.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
@@ -266,9 +266,9 @@ func handleGameInput(bytes []byte) {
|
||||
|
||||
egress <- update
|
||||
} else {
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player2.Pos.Y,
|
||||
X: 50, Y: state.Player2.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||
@@ -283,9 +283,9 @@ func handleGameInput(bytes []byte) {
|
||||
// Down
|
||||
case 66:
|
||||
if isPlayer1 {
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player1.Pos.Y,
|
||||
X: -50, Y: state.Player1.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
@@ -297,9 +297,9 @@ func handleGameInput(bytes []byte) {
|
||||
|
||||
egress <- update
|
||||
} else {
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||
v, err := json.Marshal(pong.Vector{
|
||||
X: 0, Y: state.Player2.Pos.Y,
|
||||
X: 50, Y: state.Player2.Pos.Y,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Config Configuration
|
||||
|
||||
type Configuration struct {
|
||||
LogLevel int `json:"logLevel"`
|
||||
}
|
||||
|
||||
func LoadConfig(path string) {
|
||||
var c = Configuration{}
|
||||
|
||||
var cf []byte
|
||||
var err error
|
||||
if path != "" {
|
||||
cf, err = os.ReadFile(path)
|
||||
} else {
|
||||
cf, err = os.ReadFile("config.json")
|
||||
}
|
||||
if err != nil {
|
||||
slog.Info("failed to open config at path provided, using default config instead")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cf, &c)
|
||||
if err != nil {
|
||||
slog.Info("failed to read configuration, using default config instead...", err)
|
||||
}
|
||||
|
||||
Config = c
|
||||
return
|
||||
}
|
||||
+30
-23
@@ -6,8 +6,6 @@ import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Lobby struct {
|
||||
@@ -37,6 +35,7 @@ func CreateLobby() *Lobby {
|
||||
go func(lm *sync.Map) {
|
||||
for {
|
||||
msg := <-externalMessageChan
|
||||
slog.Debug("forwarding external message")
|
||||
|
||||
tc, ok := lm.Load(msg.Target)
|
||||
if !ok {
|
||||
@@ -170,10 +169,17 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
l.ExternalMessageChannel <- ExternalMessage{
|
||||
From: i.From,
|
||||
Target: i.To,
|
||||
Message: msg,
|
||||
_, ok := l.lobbyMembers.Load(i.To)
|
||||
if !ok {
|
||||
return Marshal(ErrorData{
|
||||
Message: fmt.Sprintf("Sorry, player %s is not available.", i.To),
|
||||
}, Error)
|
||||
} else {
|
||||
l.ExternalMessageChannel <- ExternalMessage{
|
||||
From: i.From,
|
||||
Target: i.To,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
return Marshal(PendingInviteData{
|
||||
@@ -195,12 +201,11 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
gID := uuid.NewString()
|
||||
gID := a.GameID
|
||||
|
||||
msg, err := Marshal(AcceptedData{
|
||||
Accepter: a.From,
|
||||
GameID: gID,
|
||||
}, Accepted)
|
||||
msg, err := Marshal(StartGameData{
|
||||
GameID: gID,
|
||||
}, StartGame)
|
||||
|
||||
l.ExternalMessageChannel <- ExternalMessage{
|
||||
From: a.From,
|
||||
@@ -208,23 +213,25 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
slog.Debug("Sent start game message to inviter")
|
||||
|
||||
return Marshal(StartGameData{
|
||||
To: a.From,
|
||||
From: a.To,
|
||||
GameID: gID,
|
||||
}, StartGame)
|
||||
|
||||
case Accepted:
|
||||
a, err := Unmarshal[AcceptedData](msg)
|
||||
if err != nil {
|
||||
slog.Debug("error unmarshalling accpeted message", "error", err)
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
// TODO: figure out the accepted and start game data situation... To field is a little hard to fill.
|
||||
return Marshal(StartGameData{
|
||||
To: "",
|
||||
GameID: a.GameID,
|
||||
}, StartGame)
|
||||
// TODO: figure out the accepted and start game data situation... To field is a little hard to fill.
|
||||
// case Accepted:
|
||||
// a, err := Unmarshal[AcceptedData](msg)
|
||||
// if err != nil {
|
||||
// slog.Debug("error unmarshalling accpeted message", "error", err)
|
||||
// return []byte{}, err
|
||||
// }
|
||||
// return Marshal(StartGameData{
|
||||
// To: "",
|
||||
// GameID: a.GameID,
|
||||
// }, StartGame)
|
||||
|
||||
// TODO: Like pending invite, I think start game is only a client message
|
||||
// case StartGame:
|
||||
|
||||
@@ -36,18 +36,20 @@ type PendingInviteData struct {
|
||||
}
|
||||
|
||||
type AcceptData struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
GameID string `json:"gameID"`
|
||||
}
|
||||
|
||||
type AcceptedData struct {
|
||||
Accepter string `json:"accepter"`
|
||||
GameID string `json:"game_id"`
|
||||
GameID string `json:"gameID"`
|
||||
}
|
||||
|
||||
type StartGameData struct {
|
||||
To string `json:"to"`
|
||||
GameID string `json:"game_id"`
|
||||
From string `json:"from"`
|
||||
GameID string `json:"gameID"`
|
||||
}
|
||||
|
||||
type DeclineData struct {
|
||||
|
||||
+25
-6
@@ -23,7 +23,7 @@ var player2 GameClient
|
||||
var ingress chan StateUpdate
|
||||
var egress chan StateUpdate
|
||||
|
||||
const posXBound = 100
|
||||
const posXBound = 52
|
||||
const negXBound = posXBound * -1
|
||||
const posYBound = 50
|
||||
const negYBound = posYBound * -1
|
||||
@@ -141,6 +141,9 @@ func gameLoop(state *GameState) {
|
||||
return
|
||||
}
|
||||
ingress <- msg
|
||||
if msg.FieldPath == "Winner" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -157,6 +160,9 @@ func gameLoop(state *GameState) {
|
||||
return
|
||||
}
|
||||
ingress <- msg
|
||||
if msg.FieldPath == "Winner" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -164,6 +170,9 @@ func gameLoop(state *GameState) {
|
||||
for {
|
||||
msg := <-egress
|
||||
broadcastUpdate(msg)
|
||||
if msg.FieldPath == "Winner" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -176,17 +185,23 @@ func gameLoop(state *GameState) {
|
||||
if err != nil {
|
||||
fmt.Println("FUCK!~", err)
|
||||
}
|
||||
if msg.FieldPath == "Winner" {
|
||||
slog.Debug("Closing game loop on winner message")
|
||||
return
|
||||
}
|
||||
|
||||
case _ = <-ticker.C:
|
||||
update := process(state)
|
||||
egress <- update
|
||||
if update.FieldPath == "Winner" {
|
||||
slog.Debug("Closing game loop")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func process(state *GameState) StateUpdate {
|
||||
|
||||
// Move players
|
||||
// Check if player edge is out of bounds
|
||||
// If out of bounds reset velocity to zero and position to edge
|
||||
@@ -229,11 +244,13 @@ func process(state *GameState) StateUpdate {
|
||||
if state.Ball.Pos.X <= negXBound+1 && state.Ball.Vel.X < 0 {
|
||||
// Paddle hit!
|
||||
if state.Ball.Pos.Y <= state.Player1.Pos.Y+state.Player1.Size.Y && state.Ball.Pos.Y >= state.Player1.Pos.Y-state.Player1.Size.Y {
|
||||
slog.Debug("Player1 paddle hit!")
|
||||
state.Ball.Pos.X = (negXBound + 1) - (state.Ball.Pos.X - (negXBound + 1))
|
||||
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
||||
angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2)
|
||||
state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak
|
||||
angleTweak := (state.Ball.Pos.Y - state.Player1.Pos.Y) / (state.Player1.Size.Y / 2)
|
||||
state.Ball.Vel.Y = angleTweak / 10
|
||||
} else {
|
||||
slog.Debug("Player1 paddle miss...")
|
||||
state.Ball.Pos.X = 0
|
||||
state.Ball.Pos.Y = 0
|
||||
state.Ball.Vel.X = 1
|
||||
@@ -252,11 +269,13 @@ func process(state *GameState) StateUpdate {
|
||||
if state.Ball.Pos.X > posXBound-1 && state.Ball.Vel.X > 0 {
|
||||
// Paddle hit!
|
||||
if state.Ball.Pos.Y <= state.Player2.Pos.Y+state.Player2.Size.Y && state.Ball.Pos.Y >= state.Player2.Pos.Y-state.Player2.Size.Y {
|
||||
slog.Debug("Player2 paddle hit!")
|
||||
state.Ball.Pos.X = (posXBound - 1) - (state.Ball.Pos.X - (posXBound - 1))
|
||||
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
||||
angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2)
|
||||
state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak
|
||||
state.Ball.Vel.Y = angleTweak / 10
|
||||
} else {
|
||||
slog.Debug("Player2 paddle miss...")
|
||||
state.Ball.Pos.X = 0
|
||||
state.Ball.Pos.Y = 0
|
||||
state.Ball.Vel.X = -1
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user