This commit is contained in:
Bruno Rybársky 2024-08-29 00:05:28 +02:00
commit 352c7af7ce
14 changed files with 1402 additions and 0 deletions

8
.idea/.gitignore vendored Normal file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

7
.idea/discord.xml Normal file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
</project>

8
.idea/modules.xml Normal file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tunelar.iml" filepath="$PROJECT_DIR$/.idea/tunelar.iml" />
</modules>
</component>
</project>

9
.idea/tunelar.iml Normal file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

144
base.go Normal file

@ -0,0 +1,144 @@
package main
import "github.com/veandco/go-sdl2/sdl"
const (
openingWidth = 7
)
type Base struct {
rect sdl.Rect
owner *Player
}
func (base *Base) tick(players *[]*Player) {
for _, player := range *players {
if player.cullingRect.HasIntersection(&base.rect) {
if player.rechargeCooldown == 0 && player.energy < MaxEnergy {
player.energy++
if player == base.owner {
player.rechargeCooldown = RechargeCooldownOwn
} else {
player.rechargeCooldown = RechargeCooldownOpponent
}
}
if player == base.owner && player.repairCooldown == 0 && player.health < MaxHealth {
player.health++
player.repairCooldown = RepairCooldown
}
}
}
}
func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface) {
borderWidth := base.rect.W - openingWidth
borderWidthA := borderWidth / 2
borderWidthB := borderWidth - borderWidthA + 1
if borderWidth < 0 {
panic("Bad border width")
}
rects := []sdl.Rect{
{
X: base.rect.X,
Y: base.rect.Y,
W: 1,
H: base.rect.H,
},
{
X: base.rect.X + base.rect.W,
Y: base.rect.Y,
W: 1,
H: base.rect.H,
},
{
X: base.rect.X,
Y: base.rect.Y,
W: borderWidthA,
H: 1,
},
{
X: base.rect.X + borderWidthA + openingWidth,
Y: base.rect.Y,
W: borderWidthB,
H: 1,
},
{
X: base.rect.X,
Y: base.rect.Y + base.rect.H,
W: borderWidthA,
H: 1,
},
{
X: base.rect.X + borderWidthA + openingWidth,
Y: base.rect.Y + base.rect.H,
W: borderWidthB,
H: 1,
},
}
for _, rect := range rects {
if camera.HasIntersection(&rect) {
cameraCompensatedRect := sdl.Rect{
X: rect.X - camera.X,
Y: rect.Y - camera.Y,
W: rect.W,
H: rect.H,
}
surface.FillRect(&cameraCompensatedRect, (*base.owner).color2)
}
}
}
func (base *Base) build(gameMap *GameMap) {
borderWidth := base.rect.W - openingWidth
borderWidthA := borderWidth / 2
if borderWidth < 0 {
panic("Bad border width")
}
if base.rect.H < 9 {
panic("Bad border height")
}
if gameMap.width-base.rect.X-base.rect.W <= 0 {
panic("Bad base x location")
}
if gameMap.height-base.rect.Y-base.rect.H <= 0 {
panic("Bad base y location")
}
if base.rect.X < 0 || base.rect.Y < 0 {
panic("Bad base negative location")
}
for x := base.rect.X; x < base.rect.X+base.rect.W+1; x++ {
for y := base.rect.Y; y < base.rect.Y+base.rect.H+1; y++ {
gameMap.tiles[x][y] = 0
}
}
for y := base.rect.Y; y < base.rect.Y+base.rect.H; y++ {
gameMap.tiles[base.rect.X][y] = 4
gameMap.tiles[base.rect.X+base.rect.W][y] = 4
}
for x := base.rect.X; x < base.rect.X+borderWidthA; x++ {
gameMap.tiles[x][base.rect.Y] = 4
gameMap.tiles[x][base.rect.Y+base.rect.H] = 4
}
for x := base.rect.X + borderWidthA + openingWidth; x < base.rect.X+base.rect.W+1; x++ {
gameMap.tiles[x][base.rect.Y] = 4
gameMap.tiles[x][base.rect.Y+base.rect.H] = 4
}
}
func createBases(players *[]*Player, gameMap *GameMap) *[]*Base {
bases := &[]*Base{}
for ownerID, player := range *players {
*bases = append(*bases, &Base{
rect: sdl.Rect{
X: player.posX - 14,
Y: player.posY - 14,
W: 35,
H: 35,
},
owner: (*players)[ownerID],
})
(*bases)[ownerID].build(gameMap)
}
return bases
}

172
bullet.go Normal file

@ -0,0 +1,172 @@
package main
import (
"github.com/veandco/go-sdl2/sdl"
"image/color"
"math/rand"
)
type Bullet struct {
posX, posY int32
direction uint8
color color.Color
super bool
}
type BulletParticle struct {
posX, posY int32
expirationTimer uint8
color color.Color
}
func (bulletParticle *BulletParticle) render(camera *sdl.Rect, surface *sdl.Surface) {
if camera.IntersectLine(&bulletParticle.posX, &bulletParticle.posY, &bulletParticle.posX, &bulletParticle.posY) {
relativeBulletX := bulletParticle.posX - camera.X
relativeBulletY := bulletParticle.posY - camera.Y
surface.Set(int(relativeBulletX), int(relativeBulletY), bulletParticle.color)
}
}
func (bulletParticle *BulletParticle) tick(particles *[]*BulletParticle) {
if bulletParticle.expirationTimer <= 0 {
for i, particle := range *particles {
if particle == bulletParticle {
*particles = append((*particles)[:i], (*particles)[i+1:]...)
}
}
}
bulletParticle.expirationTimer--
}
func (bullet *Bullet) render(camera *sdl.Rect, surface *sdl.Surface) {
if camera.IntersectLine(&bullet.posX, &bullet.posY, &bullet.posX, &bullet.posY) {
relativeBulletX := bullet.posX - camera.X
relativeBulletY := bullet.posY - camera.Y
surface.Set(int(relativeBulletX), int(relativeBulletY), bullet.color)
}
}
func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap *[]*BulletParticle) {
// Define the possible directions with their x and y velocity components.
directions := [][2]int{
{0, -1}, // Up
{0, 1}, // Down
{-1, 0}, // Left
{1, 0}, // Right
{-1, -1}, // Up-Left Diagonal
{1, -1}, // Up-Right Diagonal
{-1, 1}, // Down-Left Diagonal
{1, 1}, // Down-Right Diagonal
}
if !bullet.super {
// Loop through each direction to generate particle streams.
for _, dir := range directions {
// Randomize the number of particles in each stream.
particlesInStream := rand.Intn(5)
for i := 0; i < particlesInStream; i++ {
// Determine particle's position based on its direction and step count.
step := rand.Intn(3) + 1 // Step can be 1, 2, or 3 units.
xOffset := dir[0] * step
yOffset := dir[1] * step
xPos, yPos := bullet.posX+int32(xOffset), bullet.posY+int32(yOffset)
collision := gameMap.checkCollision(xPos, yPos)
// Check for collision and bullet behavior (normal or super).
if collision == 1 || collision == 0 || (bullet.super && collision == 2) {
*bulletParticleMap = append(*bulletParticleMap, &BulletParticle{
posX: xPos,
posY: yPos,
expirationTimer: uint8(5 + rand.Intn(10)), // Randomize expiration time between 15 and 25.
color: bullet.color,
})
if xPos > 0 && yPos > 0 && xPos < MapWidth && yPos < MapHeight {
gameMap.tiles[xPos][yPos] = 0
}
}
}
}
} else {
for xOffset := -3; xOffset <= 3; xOffset++ {
for yOffset := -3; yOffset <= 3; yOffset++ {
xPos, yPos := bullet.posX+int32(xOffset), bullet.posY+int32(yOffset)
collision := gameMap.checkCollision(xPos, yPos)
if collision == 1 || collision == 0 || (bullet.super && collision == 2) {
*bulletParticleMap = append(*bulletParticleMap, &BulletParticle{
posX: xPos,
posY: yPos,
expirationTimer: uint8(15 + rand.Intn(15)),
color: bullet.color,
})
gameMap.tiles[xPos][yPos] = 0
}
}
}
}
}
func (bullet *Bullet) tick(gameMap *GameMap,
bulletParticleMap *[]*BulletParticle, bulletMap *[]*Bullet, players *[]*Player) {
var nextX, nextY int32
nextX, nextY = bullet.posX, bullet.posY
switch bullet.direction {
case 0: //up
nextY -= 1
break
case 1: //right
nextX += 1
break
case 2: //down
nextY += 1
break
case 3: //left
nextX -= 1
break
case 4: //up right
nextY -= 1
nextX += 1
break
case 5: //up left
nextY -= 1
nextX -= 1
break
case 6: //down right
nextY += 1
nextX += 1
break
case 7: //down left
nextY += 1
nextX -= 1
break
}
collisionResult := gameMap.checkCollision(nextX, nextY)
bulletRect := sdl.Rect{
X: bullet.posX,
Y: bullet.posY,
W: 1,
H: 1,
}
hitPlayer := false
for _, player := range *players {
if player.rect1.HasIntersection(&bulletRect) ||
player.rect2.HasIntersection(&bulletRect) ||
player.rect3.HasIntersection(&bulletRect) ||
player.rect4.HasIntersection(&bulletRect) {
hitPlayer = true
player.health -= 20
break
}
}
if collisionResult != 0 || hitPlayer {
bullet.explode(gameMap, bulletParticleMap)
for i, bulletInMap := range *bulletMap {
if bulletInMap == bullet {
*bulletMap = append((*bulletMap)[:i], (*bulletMap)[i+1:]...)
}
}
} else {
bullet.posX = nextX
bullet.posY = nextY
}
}

8
go.mod Normal file

@ -0,0 +1,8 @@
module goingtunneling
go 1.21
require (
github.com/aquilax/go-perlin v1.1.0
github.com/veandco/go-sdl2 v0.4.40
)

4
go.sum Normal file

@ -0,0 +1,4 @@
github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s=
github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U=
github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=

156
graphics.go Normal file

@ -0,0 +1,156 @@
package main
import (
"github.com/veandco/go-sdl2/sdl"
)
func initializeSDL() {
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
panic(err)
}
}
func setupWindowAndSurface() (*sdl.Window, *sdl.Surface) {
const windowWidth, windowHeight = 160, 100
window, err := sdl.CreateWindow("test", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, windowWidth, windowHeight, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
if err != nil {
panic(err)
}
logicalSurface, err := sdl.CreateRGBSurface(0, windowWidth, windowHeight, 32, 0, 0, 0, 0)
if err != nil {
panic(err)
}
return window, logicalSurface
}
func setupPlaySurface() (*sdl.Surface, *sdl.Rect, *sdl.Rect) {
playSurface, err := sdl.CreateRGBSurface(0, 76, 76, 32, 0, 0, 0, 0)
if err != nil {
panic(err)
}
playSurfaceRect := &sdl.Rect{X: 0, Y: 0, W: 76, H: 76}
playSurfaceTargetRect := &sdl.Rect{X: 42, Y: 0, W: 76, H: 76}
return playSurface, playSurfaceRect, playSurfaceTargetRect
}
func setupHUDSurface() (*sdl.Surface, *sdl.Rect, *sdl.Rect) {
HUDSurface, err := sdl.CreateRGBSurface(0, 112, 25, 32, 0, 0, 0, 0)
if err != nil {
panic(err)
}
HUDSurfaceRect := &sdl.Rect{X: 0, Y: 0, W: 112, H: 25}
HUDSurfaceTargetRect := &sdl.Rect{X: 24, Y: 76, W: 112, H: 25}
return HUDSurface, HUDSurfaceRect, HUDSurfaceTargetRect
}
func handleEvents(window *sdl.Window, logicalSurface *sdl.Surface) bool {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch e := event.(type) {
case *sdl.QuitEvent:
return false
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_RESIZED {
handleWindowResize(window, logicalSurface)
}
}
}
return true
}
func handleWindowResize(window *sdl.Window, logicalSurface *sdl.Surface) {
windowSurface, err := window.GetSurface()
if err != nil {
panic(err)
}
windowWidth, windowHeight := windowSurface.W, windowSurface.H
aspectRatio := float64(logicalSurface.W) / float64(logicalSurface.H)
newWidth := windowWidth
newHeight := int32(float64(windowWidth) / aspectRatio)
if newHeight > windowHeight {
newHeight = windowHeight
newWidth = int32(float64(windowHeight) * aspectRatio)
}
letterboxX := (windowWidth - newWidth) / 2
letterboxY := (windowHeight - newHeight) / 2
windowSurface.FillRect(nil, 0)
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
window.UpdateSurface()
}
func renderScene(logicalSurface, playSurface, HUDSurface *sdl.Surface, gameMap *GameMap, players *[]*Player, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle, camera *sdl.Rect, playSurfaceRect, playSurfaceTargetRect, HUDSurfaceRect, HUDSurfaceTargetRect *sdl.Rect) {
HUDColor := sdl.MapRGBA(HUDSurface.Format, 101, 101, 101, 255)
logicalColor := sdl.MapRGBA(HUDSurface.Format, 101, 101, 0, 255)
playColor := sdl.MapRGBA(HUDSurface.Format, 101, 0, 101, 255)
logicalSurface.FillRect(nil, logicalColor)
playSurface.FillRect(nil, playColor)
HUDSurface.FillRect(nil, HUDColor)
(*players)[0].track(camera)
gameMap.render(camera, playSurface)
for _, bullet := range *bullets {
(*bullet).render(camera, playSurface)
(*bullet).tick(gameMap, bulletParticles, bullets, players)
}
for _, base := range *bases {
(*base).render(camera, playSurface)
(*base).tick(players)
}
for _, player := range *players {
(*player).render(camera, playSurface)
(*player).tick()
}
for _, bulletParticle := range *bulletParticles {
bulletParticle.render(camera, playSurface)
bulletParticle.tick(bulletParticles)
}
renderHud((*players)[0], HUDSurface)
playSurface.BlitScaled(playSurfaceRect, logicalSurface, playSurfaceTargetRect)
HUDSurface.BlitScaled(HUDSurfaceRect, logicalSurface, HUDSurfaceTargetRect)
}
func adjustWindow(window *sdl.Window, logicalSurface *sdl.Surface, pixelBG uint32) {
windowSurface, err := window.GetSurface()
if err != nil {
panic(err)
}
windowWidth, windowHeight := windowSurface.W, windowSurface.H
aspectRatio := float64(logicalSurface.W) / float64(logicalSurface.H)
newWidth := windowWidth
newHeight := int32(float64(windowWidth) / aspectRatio)
if newHeight > windowHeight {
newHeight = windowHeight
newWidth = int32(float64(windowHeight) * aspectRatio)
}
letterboxX := (windowWidth - newWidth) / 2
letterboxY := (windowHeight - newHeight) / 2
windowSurface.FillRect(nil, pixelBG)
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
window.UpdateSurface()
}

78
hud.go Normal file

@ -0,0 +1,78 @@
package main
import "github.com/veandco/go-sdl2/sdl"
func renderHud(player *Player, surface *sdl.Surface) {
eRects := []sdl.Rect{
{
X: 5,
Y: 4,
W: 6,
H: 1,
},
{
X: 5,
Y: 4,
W: 1,
H: 5,
},
{
X: 5,
Y: 6,
W: 6,
H: 1,
},
{
X: 5,
Y: 8,
W: 6,
H: 1,
},
}
sRects := []sdl.Rect{
{
X: 5,
Y: 18,
W: 6,
H: 1,
},
{
X: 5,
Y: 18,
W: 1,
H: 3,
},
{
X: 5,
Y: 20,
W: 6,
H: 1,
},
{
X: 10,
Y: 20,
W: 1,
H: 3,
},
{
X: 5,
Y: 22,
W: 6,
H: 1,
},
}
for _, rect := range eRects {
surface.FillRect(&rect, sdl.MapRGBA(surface.Format, 243, 235, 28, 255))
}
for _, rect := range sRects {
surface.FillRect(&rect, sdl.MapRGBA(surface.Format, 40, 243, 243, 255))
}
surface.FillRect(&sdl.Rect{X: 16, Y: 3, W: 90, H: 7}, sdl.MapRGBA(surface.Format, 0, 0, 0, 255))
surface.FillRect(&sdl.Rect{X: 16, Y: 17, W: 90, H: 7}, sdl.MapRGBA(surface.Format, 0, 0, 0, 255))
surface.FillRect(&sdl.Rect{X: 17, Y: 4, W: int32(player.energy / 20), H: 5}, sdl.MapRGBA(surface.Format, 243, 235, 28, 255))
surface.FillRect(&sdl.Rect{X: 17, Y: 18, W: int32(player.health), H: 5}, sdl.MapRGBA(surface.Format, 40, 243, 243, 255))
}

158
main.go Normal file

@ -0,0 +1,158 @@
package main
import (
"github.com/veandco/go-sdl2/sdl"
)
const (
MapWidth = 1000 // Width of the map
MapHeight = 1000 // Height of the map
MaxEnergy = 1760
MaxHealth = 88
NormalShotCost = 3
SuperShotCost = 20
MovementCost = 1
DiggingCost = 2
ShootDiggingCostBonus = 1
ShootCooldown = 8
RechargeCooldownOwn = 0
DiggingCooldown = 4
RechargeCooldownOpponent = 6
RepairCooldown = 4
MovementCooldown = 2
MovementCooldownNoEnergy = 4
DiggingCooldownNoEnergy = 8
)
func main() {
initializeSDL()
defer sdl.Quit()
window, logicalSurface := setupWindowAndSurface()
window.SetTitle("GOing to tunnel")
defer window.Destroy()
defer logicalSurface.Free()
playSurface, playSurfaceRect, playSurfaceTargetRect := setupPlaySurface()
defer playSurface.Free()
HUDSurface, HUDSurfaceRect, HUDSurfaceTargetRect := setupHUDSurface()
defer HUDSurface.Free()
pixelBG := sdl.MapRGBA(logicalSurface.Format, 80, 20, 10, 255)
gameMap := GameMap{}
gameMap.createGameMap(MapWidth, MapHeight)
players := &[]*Player{}
createPlayer(
sdl.MapRGBA(playSurface.Format, 0, 0, 182, 255),
sdl.MapRGBA(playSurface.Format, 44, 44, 255, 255),
sdl.MapRGBA(playSurface.Format, 243, 235, 28, 255),
&gameMap,
players)
createPlayer(
sdl.MapRGBA(playSurface.Format, 44, 184, 44, 255),
sdl.MapRGBA(playSurface.Format, 0, 249, 0, 255),
sdl.MapRGBA(playSurface.Format, 243, 235, 28, 255),
&gameMap,
players)
bases := createBases(players, &gameMap)
bulletMap := &[]*Bullet{}
bulletParticleMap := &[]*BulletParticle{}
camera := sdl.Rect{X: 0, Y: 0, W: 76, H: 76}
running := true
for running {
currentTime := sdl.GetTicks64()
running = handleEvents(window, logicalSurface)
keyboard := sdl.GetKeyboardState()
running = running && handleInput(keyboard, bulletMap, (*players)[0], &gameMap, playSurface.Format, players)
renderScene(logicalSurface, playSurface, HUDSurface, &gameMap, players, bases, bulletMap, bulletParticleMap, &camera, playSurfaceRect, playSurfaceTargetRect, HUDSurfaceRect, HUDSurfaceTargetRect)
adjustWindow(window, logicalSurface, pixelBG)
// Calculate delay to achieve roughly 60 FPS
frameDuration := 1000 / 60
elapsed := sdl.GetTicks64() - currentTime
if delay := frameDuration - int(elapsed); delay > 0 {
sdl.Delay(uint32(delay))
}
}
}
func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *GameMap, format *sdl.PixelFormat, players *[]*Player) bool {
shoot := false
super := false
// Flags to track movement in each direction
moveUp := false
moveDown := false
moveLeft := false
moveRight := false
// Process keyboard input
for key, value := range keyboard {
if value == 0 {
continue
}
if key == sdl.SCANCODE_ESCAPE {
return false
} else if key == sdl.SCANCODE_SPACE {
shoot = true
} else if key == sdl.SCANCODE_LCTRL {
super = true
} else if key == sdl.SCANCODE_W {
moveUp = true
} else if key == sdl.SCANCODE_S {
moveDown = true
} else if key == sdl.SCANCODE_A {
moveLeft = true
} else if key == sdl.SCANCODE_D {
moveRight = true
}
}
// Handle shooting after the loop
if shoot {
player.shoot(super, bullets, format)
}
// Determine player orientation for diagonal movement
if moveUp && moveRight {
player.orientation = 4 // Up-Right
} else if moveUp && moveLeft {
player.orientation = 5 // Up-Left
} else if moveDown && moveRight {
player.orientation = 6 // Down-Right
} else if moveDown && moveLeft {
player.orientation = 7 // Down-Left
} else if moveUp {
player.orientation = 0 // Up
} else if moveRight {
player.orientation = 1 // Right
} else if moveDown {
player.orientation = 2 // Down
} else if moveLeft {
player.orientation = 3 // Left
}
// Handle movement after the loop
if moveUp || moveDown || moveLeft || moveRight {
if player.tryMove(gameMap, shoot, players) {
player.energy -= MovementCost
}
}
return true
}

116
map.go Normal file

@ -0,0 +1,116 @@
package main
import (
"github.com/aquilax/go-perlin"
"github.com/veandco/go-sdl2/sdl"
"image/color"
"time"
)
type GameMap struct {
tiles [][]uint8
//tile 0 - air
//tile 1 - rock
//tile 2 - soil 1
//tile 3 - soil 2
//tile 4 - base
//tile 5 - world edge (should not be present in the map object)
width int32
height int32
}
func GetTileColorValue(value uint8) color.Color {
switch value {
case 0:
return color.RGBA{}
case 1:
return color.RGBA{R: 154, G: 154, B: 154, A: 255}
case 2:
return color.RGBA{R: 195, G: 121, B: 48, A: 255}
case 3:
return color.RGBA{R: 186, G: 89, B: 4, A: 255}
case 4:
return color.RGBA{R: 255, G: 44, B: 255, A: 255}
case 5:
return color.RGBA{R: 86, G: 86, B: 86, A: 255}
default:
return color.RGBA{R: 255, G: 255, B: 255, A: 255}
}
}
func (gameMap *GameMap) getTileColor(x int32, y int32) color.Color {
if x < 0 || x >= gameMap.width || y < 0 || y >= gameMap.height {
return GetTileColorValue(5)
} else {
tileValue := gameMap.tiles[x][y]
return GetTileColorValue(tileValue)
}
}
func (gameMap *GameMap) createGameMap(width int32, height int32) {
gameMap.tiles = make([][]uint8, width)
perlinNoiseRock := perlin.NewPerlin(2, 2, 4, time.Now().Unix())
perlinNoiseSoil := perlin.NewPerlin(2, 8, 4, time.Now().Unix())
for i := range gameMap.tiles {
gameMap.tiles[i] = make([]uint8, height)
for j := range gameMap.tiles[i] {
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*3, float64(j)/float64(height)*3)
if perlinRock > 0.2 {
gameMap.tiles[i][j] = 1
} else {
perlinSoil := perlinNoiseSoil.Noise2D(float64(i)/float64(width)*10, float64(j)/float64(height)*10)
if perlinSoil >= 0 {
gameMap.tiles[i][j] = 2
} else {
gameMap.tiles[i][j] = 3
}
}
}
}
gameMap.width = width
gameMap.height = height
}
func (gameMap *GameMap) render(camera *sdl.Rect, surface *sdl.Surface) {
fromX := camera.X
fromY := camera.Y
toX := camera.X + camera.W
toY := camera.Y + camera.H
for x := fromX; x < toX; x++ {
for y := fromY; y < toY; y++ {
cameraY := y - camera.Y
cameraX := x - camera.X
pixelColor := gameMap.getTileColor(x, y)
surface.Set(int(cameraX), int(cameraY), pixelColor)
}
}
}
func (gameMap *GameMap) checkCollision(posX, posY int32) uint8 {
//0 no collision
//1 destructible collision
//2 rock collision
//3 indestructible collision
if posX >= gameMap.width || posX < 0 || posY >= gameMap.height || posY < 0 {
return 3
}
switch gameMap.tiles[posX][posY] {
case 0:
return 0
case 1:
return 2
case 2:
return 1
case 3:
return 1
case 4:
return 3
case 5:
return 3
default:
return 3
}
}

528
player.go Normal file

@ -0,0 +1,528 @@
package main
import (
"github.com/veandco/go-sdl2/sdl"
"image/color"
"math/rand"
)
type Player struct {
posX int32
posY int32
orientation uint8
color1 uint32
color2 uint32
color3 uint32
energy uint16
health uint16
digCooldown uint8
shootCooldown uint8
repairCooldown uint8
rechargeCooldown uint8
movementCooldown uint8
rect1 sdl.Rect
rect2 sdl.Rect
rect3 sdl.Rect
rect4 sdl.Rect
cullingRect sdl.Rect
}
func (player *Player) track(camera *sdl.Rect) {
camera.X = player.posX - 37
camera.Y = player.posY - 38
}
func (player *Player) render(camera *sdl.Rect, surface *sdl.Surface) {
relativePlayerX := player.posX - camera.X
relativePlayerY := player.posY - camera.Y
switch player.orientation {
case 0: // Up
player.rect1 = sdl.Rect{X: relativePlayerX, Y: relativePlayerY + 1, W: 1, H: 6}
player.rect2 = sdl.Rect{X: relativePlayerX + 4, Y: relativePlayerY + 1, W: 1, H: 6}
player.rect3 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 3, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY, W: 1, H: 4}
case 1: // Right
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 1, W: 6, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 5, W: 6, H: 1}
player.rect3 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY + 2, W: 4, H: 3}
player.rect4 = sdl.Rect{X: relativePlayerX + 4, Y: relativePlayerY + 3, W: 4, H: 1}
case 2: // Down
player.rect1 = sdl.Rect{X: relativePlayerX, Y: relativePlayerY + 1, W: 1, H: 6}
player.rect2 = sdl.Rect{X: relativePlayerX + 4, Y: relativePlayerY + 1, W: 1, H: 6}
player.rect3 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 3, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY + 4, W: 1, H: 4}
case 3: // Left
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 1, W: 6, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 5, W: 6, H: 1}
player.rect3 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY + 2, W: 4, H: 3}
player.rect4 = sdl.Rect{X: relativePlayerX, Y: relativePlayerY + 3, W: 4, H: 1}
case 4: // Up-Right
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 6, W: 5, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 1, H: 4}
player.rect3 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY + 2, W: 4, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 4, Y: relativePlayerY + 2, W: 2, H: 2}
case 5: // Up-Left
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 6, W: 5, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 5, Y: relativePlayerY + 2, W: 1, H: 5}
player.rect3 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 4, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 2, H: 2}
case 6: // Down-Right
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 1, W: 5, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 1, W: 1, H: 5}
player.rect3 = sdl.Rect{X: relativePlayerX + 2, Y: relativePlayerY + 2, W: 4, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 4, Y: relativePlayerY + 4, W: 2, H: 2}
case 7: // Down-Left
player.rect1 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 1, W: 4, H: 1}
player.rect2 = sdl.Rect{X: relativePlayerX + 5, Y: relativePlayerY + 1, W: 1, H: 5}
player.rect3 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 2, W: 4, H: 4}
player.rect4 = sdl.Rect{X: relativePlayerX + 1, Y: relativePlayerY + 4, W: 2, H: 2}
}
// Common part: Rendering player
player.cullingRect = sdl.Rect{X: player.posX, Y: player.posY, W: 7, H: 7}
cameraAdjustedCullingRect := sdl.Rect{
X: player.cullingRect.X - camera.X,
Y: player.cullingRect.Y - camera.Y,
W: player.cullingRect.W,
H: player.cullingRect.H,
}
cameraAdjustedCullingRectBorder := sdl.Rect{
X: player.cullingRect.X - camera.X - 1,
Y: player.cullingRect.Y - camera.Y - 1,
W: player.cullingRect.W + 2,
H: player.cullingRect.H + 2,
}
if camera.HasIntersection(&player.cullingRect) {
surface.FillRect(&cameraAdjustedCullingRectBorder, sdl.MapRGBA(surface.Format, 10, 255, 255, 64))
surface.FillRect(&cameraAdjustedCullingRect, sdl.MapRGBA(surface.Format, 255, 20, 10, 64))
surface.FillRect(&player.rect1, player.color1)
surface.FillRect(&player.rect2, player.color1)
surface.FillRect(&player.rect3, player.color2)
surface.FillRect(&player.rect4, player.color3)
}
}
func (player *Player) digBlock(posX, posY int32, gameMap *GameMap, isShooting bool) uint8 {
collisionAtDigSite := gameMap.checkCollision(posX, posY)
if collisionAtDigSite == 0 {
return 0
} else if collisionAtDigSite == 1 && (player.digCooldown == 0 || isShooting) {
gameMap.tiles[posX][posY] = 0
player.digCooldown = DiggingCooldown
return 1
}
return 2
}
func (player *Player) tick() {
if player.digCooldown > 0 {
player.digCooldown--
}
if player.shootCooldown > 0 {
player.shootCooldown--
}
if player.repairCooldown > 0 {
player.repairCooldown--
}
if player.rechargeCooldown > 0 {
player.rechargeCooldown--
}
if player.movementCooldown > 0 {
player.movementCooldown--
}
}
func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players *[]*Player) (moved bool) {
if player.movementCooldown > 0 {
return false
}
ranOutOfEnergy := (isShooting && player.energy <= DiggingCost+ShootDiggingCostBonus) || player.energy <= DiggingCost
if ranOutOfEnergy {
isShooting = false
}
shouldPenalizeDigging := false
stopped := false
switch player.orientation {
case 0: // Up
collisionRect := sdl.Rect{
X: player.posX,
Y: player.posY - 1,
W: 5,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX; x < player.posX+5 && !stopped; x++ {
for y := player.posY - 1; y < player.posY+7; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posY--
}
case 1: // Right
collisionRect := sdl.Rect{
X: player.posX + 1,
Y: player.posY,
W: 7,
H: 5,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for y := player.posY; y < player.posY+7 && !stopped; y++ {
for x := player.posX + 1; x < player.posX+8; x++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX++
}
case 2: // Down
collisionRect := sdl.Rect{
X: player.posX,
Y: player.posY + 1,
W: 5,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX; x < player.posX+5 && !stopped; x++ {
for y := player.posY + 1; y < player.posY+8; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posY++
}
case 3: // Left
collisionRect := sdl.Rect{
X: player.posX - 1,
Y: player.posY,
W: 7,
H: 5,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for y := player.posY; y < player.posY+6 && !stopped; y++ {
for x := player.posX - 1; x < player.posX+6; x++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX--
}
case 4: // Up-Right
collisionRect := sdl.Rect{
X: player.posX + 1,
Y: player.posY - 1,
W: 7,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX + 1; x < player.posX+8 && !stopped; x++ {
for y := player.posY - 1; y < player.posY+7; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX++
player.posY--
}
case 5: // Up-Left
collisionRect := sdl.Rect{
X: player.posX - 1,
Y: player.posY - 1,
W: 7,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX - 1; x < player.posX+6 && !stopped; x++ {
for y := player.posY - 1; y < player.posY+7; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX--
player.posY--
}
case 6: // Down-Right
collisionRect := sdl.Rect{
X: player.posX + 1,
Y: player.posY + 1,
W: 7,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX + 1; x < player.posX+8 && !stopped; x++ {
for y := player.posY + 1; y < player.posY+8; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX++
player.posY++
}
case 7: // Down-Left
collisionRect := sdl.Rect{
X: player.posX - 1,
Y: player.posY + 1,
W: 7,
H: 7,
}
for _, opponent := range *players {
if opponent != player && opponent.cullingRect.HasIntersection(&collisionRect) {
return false
}
}
for x := player.posX - 1; x < player.posX+6 && !stopped; x++ {
for y := player.posY + 1; y < player.posY+8; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
player.posX--
player.posY++
}
}
// Penalties and cooldown handling
if shouldPenalizeDigging {
if ranOutOfEnergy {
player.digCooldown = DiggingCooldownNoEnergy
}
player.energy -= DiggingCost
if isShooting {
player.energy -= ShootDiggingCostBonus
}
}
if moved {
if ranOutOfEnergy {
player.movementCooldown = MovementCooldownNoEnergy
} else {
player.movementCooldown = MovementCooldown
}
}
return
}
func (player *Player) shoot(super bool, bullets *[]*Bullet, format *sdl.PixelFormat) {
if (super && player.energy <= SuperShotCost) || (!super && player.energy <= NormalShotCost) {
return
}
if player.shootCooldown == 0 {
var shootX, shootY int32
switch player.orientation {
case 0: // Up
shootY = player.posY - 1
shootX = player.posX + 2
break
case 1: // Right
shootY = player.posY + 3
shootX = player.posX + 8
break
case 2: // Down
shootX = player.posX + 2
shootY = player.posY + 8
break
case 3: // Left
shootY = player.posY + 3
shootX = player.posX - 2
break
case 4: // Up-Right
shootY = player.posY - 1
shootX = player.posX + 5
break
case 5: // Up-Left
shootY = player.posY - 1
shootX = player.posX - 1
break
case 6: // Down-Right
shootY = player.posY + 5
shootX = player.posX + 5
break
case 7: // Down-Left
shootY = player.posY + 5
shootX = player.posX - 1
break
}
// Set bullet color
r, g, b, a := sdl.GetRGBA(player.color2, format)
bulletColor := color.RGBA{R: r, G: g, B: b, A: a}
// Create and add the bullet
*bullets = append(*bullets, &Bullet{
posX: shootX,
posY: shootY,
direction: player.orientation,
color: bulletColor,
super: super,
})
// Set cooldown and decrease energy
player.shootCooldown = ShootCooldown
if super {
player.energy -= SuperShotCost
} else {
player.energy -= NormalShotCost
}
}
}
func createPlayer(color1 uint32, color2 uint32, color3 uint32, gameMap *GameMap, players *[]*Player) {
coordsAreValid := false
var posX, posY int32
for !coordsAreValid {
posX = int32(16 + rand.Intn(int(gameMap.width-36)))
posY = int32(16 + rand.Intn(int(gameMap.height-36)))
coordsAreValid = true
for _, player := range *players {
distance := (player.posX-posX)*(player.posX-posX) + (player.posY-posY)*(player.posY-posY)
if distance < 300*300 { // Check if distance is less than 300 units
coordsAreValid = false
break
}
}
}
*players = append(*players, &Player{
posX: posX,
posY: posY,
orientation: 3,
color1: color1,
color2: color2,
color3: color3,
health: MaxHealth,
energy: MaxEnergy,
})
}