Initial commit
This commit is contained in:
commit
8aebe12f51
6 changed files with 399 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.idea/
|
||||
dist/
|
||||
vendor/
|
||||
*.sh
|
||||
lunarcrafter
|
21
.gitlab-ci.yml
Normal file
21
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
|
||||
fmt:
|
||||
stage: validate
|
||||
script:
|
||||
- gofmt -l -s -e .
|
||||
- exit $(gofmt -l -s -e . | wc -l)
|
||||
|
||||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- go test -race -v ./...
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Trevor Slocum <trevor@rocketnine.space>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
12
go.mod
Normal file
12
go.mod
Normal file
|
@ -0,0 +1,12 @@
|
|||
module gitlab.com/tslocum/lunarcrafter
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/ByteArena/box2d v1.0.2
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201109052606-7d87d8188c8d
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gitlab.com/tslocum/cbind v0.1.4
|
||||
gitlab.com/tslocum/cview v1.5.2
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88 // indirect
|
||||
)
|
31
go.sum
Normal file
31
go.sum
Normal file
|
@ -0,0 +1,31 @@
|
|||
github.com/ByteArena/box2d v1.0.2 h1:f7f9KEQWhCs1n516DMLzi5w6u0MeeE78Mes4fWMcj9k=
|
||||
github.com/ByteArena/box2d v1.0.2/go.mod h1:LzEuxY9iCz+tskfWCY3o0ywYBRafDDugdSj+/YGI6sE=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201109052606-7d87d8188c8d h1:C1FQEuzw5kUUveSXpZp3v0+qOR+VEnzHsQ7K6n39LsM=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201109052606-7d87d8188c8d/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
gitlab.com/tslocum/cbind v0.1.4 h1:cbZXPPcieXspk8cShoT6efz7HAT8yMNQcofYWNizis4=
|
||||
gitlab.com/tslocum/cbind v0.1.4/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI=
|
||||
gitlab.com/tslocum/cview v1.5.2 h1:pt1d4L0LGghwpGRDXN5dPTp0+M5cZhoYooblLLNOA1A=
|
||||
gitlab.com/tslocum/cview v1.5.2/go.mod h1:tnNYj71jg9ZvvF9Eu9/2f0Yji0Rwjy2JK36UoS/Hxvo=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk=
|
||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
309
main.go
Normal file
309
main.go
Normal file
|
@ -0,0 +1,309 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
|
||||
"github.com/ByteArena/box2d"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
var app *cview.Application
|
||||
|
||||
var tv *cview.TextView
|
||||
|
||||
var actors = make(map[string]*box2d.B2Body)
|
||||
|
||||
var updates = make(chan func(), 10)
|
||||
|
||||
var lastAT = time.Time{}
|
||||
var lastLT = time.Time{}
|
||||
|
||||
const moonGravity = -1.625 * .1
|
||||
|
||||
const thrustMagnitude = 1000.0
|
||||
|
||||
const rotateSpeed = 0.005
|
||||
|
||||
const debug = true
|
||||
|
||||
const (
|
||||
startAngle = math.Pi / 3
|
||||
startLVX = 1
|
||||
startLVY = 0
|
||||
startX = 15
|
||||
startY = 32
|
||||
)
|
||||
|
||||
var (
|
||||
playerAngle float64
|
||||
playerX, playerY float64
|
||||
playerAV float64
|
||||
playerLV float64
|
||||
playerLanded bool
|
||||
)
|
||||
|
||||
const (
|
||||
screenHeight = 32
|
||||
screenWidth = 120
|
||||
)
|
||||
|
||||
var stars = map[int]map[int]string{
|
||||
4: {7: "*"},
|
||||
9: {23: "."},
|
||||
11: {42: "."},
|
||||
32: {16: "."},
|
||||
87: {22: "."},
|
||||
58: {28: "*"},
|
||||
72: {17: "*"},
|
||||
102: {25: "*"},
|
||||
96: {9: "."},
|
||||
56: {11: "."},
|
||||
25: {17: "*"},
|
||||
42: {24: "."},
|
||||
28: {14: "."},
|
||||
}
|
||||
|
||||
func setupInput() {
|
||||
inputConfig := cbind.NewConfiguration()
|
||||
|
||||
_ = inputConfig.Set("Left", func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
updates <- func() {
|
||||
if playerLanded {
|
||||
return
|
||||
}
|
||||
|
||||
v := actors["player"].GetAngularVelocity()
|
||||
v -= rotateSpeed
|
||||
actors["player"].SetAngularVelocity(v)
|
||||
lastAT = time.Now()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = inputConfig.Set("Right", func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
updates <- func() {
|
||||
if playerLanded {
|
||||
return
|
||||
}
|
||||
|
||||
v := actors["player"].GetAngularVelocity()
|
||||
v += rotateSpeed
|
||||
actors["player"].SetAngularVelocity(v)
|
||||
lastAT = time.Now()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
moveFunc :=
|
||||
func() {
|
||||
updates <- func() {
|
||||
angle := actors["player"].GetAngle() - math.Pi/2
|
||||
//actors["player"].ApplyLinearImpulseToCenter(box2d.B2Vec2{math.Cos(angle), math.Sin(angle)}, true)
|
||||
actors["player"].ApplyLinearImpulse(box2d.B2Vec2{math.Sin(angle) * thrustMagnitude, math.Cos(angle) * thrustMagnitude}, actors["player"].GetWorldCenter(), true)
|
||||
lastLT = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
_ = inputConfig.Set("Up", func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
moveFunc()
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = inputConfig.Set("Space", func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
moveFunc()
|
||||
return nil
|
||||
})
|
||||
|
||||
app.SetInputCapture(inputConfig.Capture)
|
||||
}
|
||||
|
||||
func renderPoint(area int, x int, y int) string {
|
||||
sx, ok := stars[x]
|
||||
if ok {
|
||||
sy, ok := sx[y]
|
||||
if ok {
|
||||
return sy
|
||||
}
|
||||
}
|
||||
return " "
|
||||
}
|
||||
|
||||
func renderWorld() {
|
||||
px, py := int(math.Floor(playerX)), int(math.Floor(playerY))
|
||||
a := playerAngle
|
||||
var buf bytes.Buffer
|
||||
for y := screenHeight - 1; y >= 0; y-- {
|
||||
if y != screenHeight-1 {
|
||||
buf.Write([]byte("\n"))
|
||||
}
|
||||
for x := 0; x < screenWidth; x++ {
|
||||
if x == px && y == py {
|
||||
if a > 337.5 || a < 22.5 {
|
||||
buf.Write([]byte("⬆"))
|
||||
} else if a < 67.5 {
|
||||
buf.Write([]byte("⬈"))
|
||||
} else if a < 112.5 {
|
||||
buf.Write([]byte("➡"))
|
||||
} else if a < 157.5 {
|
||||
buf.Write([]byte("⬊"))
|
||||
} else if a < 202.5 {
|
||||
buf.Write([]byte("⬇"))
|
||||
} else if a < 247.5 {
|
||||
buf.Write([]byte("⬋"))
|
||||
} else if a < 292.5 {
|
||||
buf.Write([]byte("⬅"))
|
||||
} else {
|
||||
buf.Write([]byte("⬉"))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Write([]byte(renderPoint(1, x, y)))
|
||||
}
|
||||
}
|
||||
buf.Write([]byte("\n"))
|
||||
buf.Write([]byte("[gray]███▓████████████████▓▓▓██████▓▓████████▓▓▓████████▓▓▓▓█████████████▓▓████████▓████▓▓███████████▓▓██████▓▓▓▓▓██████████[-]"))
|
||||
buf.Write([]byte("\n"))
|
||||
buf.Write([]byte("\n"))
|
||||
if time.Since(lastAT) < 150*time.Millisecond {
|
||||
buf.Write([]byte(" [ ROTATIONAL THRUSTERS ] "))
|
||||
} else {
|
||||
buf.Write([]byte(strings.Repeat(" ", 27)))
|
||||
}
|
||||
if time.Since(lastLT) < 150*time.Millisecond {
|
||||
buf.Write([]byte(" [ POSITIONAL THRUSTERS ] "))
|
||||
} else {
|
||||
buf.Write([]byte(strings.Repeat(" ", 27)))
|
||||
}
|
||||
buf.Write([]byte("\n"))
|
||||
|
||||
// Debug
|
||||
buf.Write([]byte("\n"))
|
||||
buf.Write([]byte(fmt.Sprintf("%.00f,%.00f @ %.02f - Rotation angle: %.00f - Rotation speed: %.02f", playerX, playerY, playerLV, playerAngle, playerAV)))
|
||||
|
||||
tv.SetBytes(buf.Bytes())
|
||||
}
|
||||
|
||||
func runSimulation() {
|
||||
// Define the gravity vector.
|
||||
gravity := box2d.MakeB2Vec2(0.0, moonGravity)
|
||||
|
||||
// Construct a world object, which will hold and simulate the rigid bodies.
|
||||
world := box2d.MakeB2World(gravity)
|
||||
|
||||
{
|
||||
bd := box2d.MakeB2BodyDef()
|
||||
ground := world.CreateBody(&bd)
|
||||
|
||||
shape := box2d.MakeB2EdgeShape()
|
||||
shape.Set(box2d.MakeB2Vec2(-20000.0, 0.0), box2d.MakeB2Vec2(20000.0, -1.0))
|
||||
ground.CreateFixture(&shape, 0.0)
|
||||
actors["ground"] = ground
|
||||
}
|
||||
|
||||
// Circle character
|
||||
{
|
||||
bd := box2d.MakeB2BodyDef()
|
||||
bd.Position.Set(startX, startY)
|
||||
bd.LinearVelocity.Set(startLVX, startLVY)
|
||||
bd.Type = box2d.B2BodyType.B2_dynamicBody
|
||||
bd.FixedRotation = true
|
||||
bd.AllowSleep = false
|
||||
|
||||
body := world.CreateBody(&bd)
|
||||
|
||||
body.SetTransform(body.GetPosition(), startAngle)
|
||||
|
||||
shape := box2d.MakeB2CircleShape()
|
||||
shape.M_radius = 0.5
|
||||
|
||||
fd := box2d.MakeB2FixtureDef()
|
||||
fd.Shape = &shape
|
||||
fd.Density = 15103
|
||||
body.CreateFixtureFromDef(&fd)
|
||||
actors["player"] = body
|
||||
}
|
||||
|
||||
// Prepare for simulation. Typically we use a time step of 1/60 of a
|
||||
// second (60Hz) and 10 iterations. This provides a high quality simulation
|
||||
// in most game scenarios.
|
||||
timeStep := 1.0 / 60.0
|
||||
velocityIterations := 8
|
||||
positionIterations := 3
|
||||
|
||||
t := time.NewTicker(time.Second / 60)
|
||||
for {
|
||||
UPDATELOOP:
|
||||
for {
|
||||
select {
|
||||
case f := <-updates:
|
||||
f()
|
||||
default:
|
||||
break UPDATELOOP
|
||||
}
|
||||
}
|
||||
|
||||
WAITLOOP:
|
||||
for {
|
||||
select {
|
||||
case f := <-updates:
|
||||
f()
|
||||
case <-t.C:
|
||||
break WAITLOOP
|
||||
}
|
||||
}
|
||||
|
||||
// Instruct the world to perform a single step of simulation.
|
||||
// It is generally best to keep the time step and iterations fixed.
|
||||
//runtime.Breakpoint()
|
||||
world.Step(timeStep, velocityIterations, positionIterations)
|
||||
|
||||
list := actors["player"].GetContactList()
|
||||
playerLanded = list != nil
|
||||
if playerLanded && !debug {
|
||||
v := actors["player"].GetLinearVelocity()
|
||||
if v.X > 2.5 || v.X < -2.5 || v.Y > 2.5 || v.Y < -2.5 {
|
||||
log.Panicf("you crashed! %f,%f", v.X, v.Y)
|
||||
}
|
||||
actors["player"].SetLinearVelocity(box2d.MakeB2Vec2(0, 0))
|
||||
log.Panicf("you landed safely!")
|
||||
}
|
||||
|
||||
playerAngle = math.Mod(((actors["player"].GetAngle() - 0.5*math.Pi) * (180 / math.Pi)), 360)
|
||||
if playerAngle < 0 {
|
||||
playerAngle = 360 + playerAngle
|
||||
}
|
||||
position := actors["player"].GetPosition()
|
||||
playerX, playerY = position.X, position.Y
|
||||
playerAV = actors["player"].GetAngularVelocity()
|
||||
playerLV = actors["player"].GetLinearVelocity().Y
|
||||
app.QueueUpdateDraw(renderWorld)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
app = cview.NewApplication()
|
||||
|
||||
setupInput()
|
||||
|
||||
tv = cview.NewTextView()
|
||||
tv.SetDynamicColors(true)
|
||||
|
||||
go runSimulation()
|
||||
|
||||
app.SetRoot(tv, true)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue