package main import ( "fmt" "math" "math/rand" "time" "gogs.bullercodeworks.com/brian/termbox-util" "github.com/nsf/termbox-go" ) const ( modeInit = iota modeRun modeRunInput modePause modeGameOver ) type mainScreen struct { GameMode int PlayerTurn int Player1 *player Player2 *player Bullet *projectile Buildings []building PauseMenu *termboxUtil.Menu r *rand.Rand animating bool gravity float64 } func (screen *mainScreen) handleKeyPress(event termbox.Event) int { if event.Key == termbox.KeyEsc { if screen.GameMode != modePause { screen.GameMode = modePause } else { screen.GameMode = modeRun } } if screen.GameMode == modePause || screen.GameMode == modeGameOver { screen.PauseMenu.HandleKeyPress(event) if screen.PauseMenu.IsDone() { selOpt := screen.PauseMenu.GetSelectedOption() if selOpt.GetText() == "Exit" { return exitScreenIndex } else if selOpt.GetText() == "Resume" { screen.GameMode = modeRun } else if selOpt.GetText() == "Restart" { screen.GameMode = modeInit return titleScreenIndex } } } if screen.GameMode == modeRunInput { p := screen.Player1 if screen.PlayerTurn == 2 { p = screen.Player2 } if event.Key == termbox.KeySpace { screen.Bullet.init(p) screen.animating = true screen.GameMode = modeRun } else if event.Key == termbox.KeyArrowLeft || event.Ch == 'a' { p.projAngle-- } else if event.Key == termbox.KeyArrowRight || event.Ch == 'd' { p.projAngle++ } else if event.Key == termbox.KeyArrowUp || event.Ch == 'w' { p.projSpeed++ } else if event.Key == termbox.KeyArrowDown || event.Ch == 's' { p.projSpeed-- } } return mainScreenIndex } func (screen *mainScreen) performLayout(style style) { var bldCity int if screen.GameMode == modeInit { // Default screen gravity: screen.gravity = float64(-9.81) b := projectile{gravity: screen.gravity} screen.Bullet = &b // TODO: Reset Buildings screen.Buildings = []building{} // Create a random seed (from time) seed := fmt.Sprintf("%d", time.Now().UnixNano()) screen.r = rand.New(rand.NewSource(getSeedFromString(seed + "-world"))) screen.PlayerTurn = (screen.r.Int() % 2) + 1 for bldCity < ScreenWidth { w := screen.r.Intn(8) + 6 h := screen.r.Intn(ScreenHeight-10) + 2 var c termbox.Attribute switch screen.r.Intn(3) { case 0: c = termbox.ColorRed case 1: c = termbox.ColorGreen case 2: c = termbox.ColorBlue } if ScreenWidth-(bldCity+w) < 5 { w += ScreenWidth - (bldCity + w) } b := building{startX: bldCity, width: w, height: h, color: c} screen.Buildings = append(screen.Buildings, b) bldCity += w } // Player 1 should be on the first 1/3 of the buildings bld := screen.r.Intn(len(screen.Buildings) / 3) p1x, p1y := screen.getRndPosForBuilding(bld) screen.Player1 = createPlayer(p1x, p1y) screen.Player1.gravity = screen.gravity // Player 2 should be on the third 1/3 of the buildings bld = screen.r.Intn(len(screen.Buildings)/3) + ((len(screen.Buildings) * 2) / 3) p2x, p2y := screen.getRndPosForBuilding(bld) if ScreenWidth-p2x < 5 { p2x, p2y = screen.getRndPosForBuilding(bld - 1) } screen.Player2 = createPlayer(p2x, p2y) screen.Player2.projAngle = 225 screen.Player2.gravity = screen.gravity screen.PauseMenu = termboxUtil.CreateMenu("** GOPHER BATTLE **", []string{"Resume", "Restart", "Exit"}, (ScreenWidth/2)-11, (ScreenHeight/2)-3, 22, 6, style.defaultFg, style.defaultBg) screen.PauseMenu.SetBordered(true) screen.PauseMenu.EnableVimMode() screen.GameMode = modeRun } } func createPlayer(px, py int) *player { p := player{ x: px, y: py, projSpeed: 10, projAngle: 315, baseColor: termbox.ColorCyan, } return &p } // Trigger all updates func (screen *mainScreen) update() { if screen.GameMode == modeRun { if !screen.animating { if screen.PlayerTurn == 1 { screen.PlayerTurn = 2 screen.Player1.turn = false screen.Player2.turn = true } else { screen.PlayerTurn = 1 screen.Player1.turn = true screen.Player2.turn = false } screen.GameMode = modeRunInput } else { if screen.Bullet.x > 0 && screen.Bullet.x < ScreenWidth && screen.Bullet.y < ScreenHeight && screen.Bullet.y > -5000 { screen.Bullet.update() // Check for collisions if screen.Player1.didCollide(screen.Bullet) { screen.Player1.dead = true screen.PauseMenu.SetOptionDisabled(0) if screen.PlayerTurn == 1 { // Self kill screen.PauseMenu.SetTitle("Player 1 LOSES.") } else { // Victory! screen.PauseMenu.SetTitle("Player 2 Wins!") } screen.GameMode = modePause } if screen.Player2.didCollide(screen.Bullet) { screen.Player2.dead = true screen.PauseMenu.SetOptionDisabled(0) if screen.PlayerTurn == 2 { // Self kill screen.PauseMenu.SetTitle("Player 2 LOSES.") } else { // Victory! screen.PauseMenu.SetTitle("Player 1 Wins!") } screen.GameMode = modePause } for i := range screen.Buildings { if screen.Buildings[i].didCollide(screen.Bullet) { // Collision screen.animating = false break } } } else { // Bullet went off the sides of the screen screen.animating = false } } } } func (screen *mainScreen) drawScreen(style style) { // Draw Landscape var x int for i := range screen.Buildings { screen.Buildings[i].draw(x) x += screen.Buildings[i].width } if screen.GameMode == modeRun { // Draw Projectile(s) if screen.Bullet != nil { screen.Bullet.draw() } } else if screen.GameMode == modeRunInput { // Draw inputs if screen.PlayerTurn == 1 { angleText := fmt.Sprintf("Angle: %d", screen.Player1.projAngle) speedText := fmt.Sprintf("Speed: %d", screen.Player1.projSpeed) termboxUtil.DrawStringAtPoint(angleText, 0, 0, style.defaultFg, style.defaultBg) termboxUtil.DrawStringAtPoint(speedText, 0, 1, style.defaultFg, style.defaultBg) termboxUtil.DrawStringAtPoint("'space' to fire", 0, 2, style.defaultFg, style.defaultBg) } else { angleText := fmt.Sprintf("Angle: %d", screen.Player2.projAngle) speedText := fmt.Sprintf("Speed: %d", screen.Player2.projSpeed) termboxUtil.DrawStringAtPoint(angleText, ScreenWidth-11, 0, style.defaultFg, style.defaultBg) termboxUtil.DrawStringAtPoint(speedText, ScreenWidth-11, 1, style.defaultFg, style.defaultBg) termboxUtil.DrawStringAtPoint("'space' to fire", ScreenWidth-15, 2, style.defaultFg, style.defaultBg) } } // Draw Player 1 screen.Player1.draw() // Draw Player 2 screen.Player2.draw() if screen.GameMode == modePause { // Draw the pause menu screen.PauseMenu.Draw() } } func (screen *mainScreen) getRndPosForBuilding(i int) (int, int) { retY := ScreenHeight - screen.Buildings[i].height bldStX := 0 for idx := 0; idx < i; idx++ { bldStX += screen.Buildings[idx].width } retX := screen.r.Intn(screen.Buildings[i].width-5) + bldStX return retX, retY } func getSeedFromString(seed string) int64 { // Does this algorithm work for string->seed generation? // ~\_(o.o)_/~ - seems to. var ret int64 for i, k := range seed { ret += int64(k) * int64(i) } return ret } func degToRad(d int) float64 { return (float64(d) * (math.Pi / float64(180))) } type projectile struct { x, y int startX, startY int speed, angle int gravity float64 launchTime time.Time } func (p *projectile) init(pl *player) { p.launchTime = time.Now() p.angle = pl.projAngle p.speed = pl.projSpeed p.startX, p.startY = pl.GetClosestTrajectory() //p.startX, p.startY = pl.x+2, pl.y+2 p.x = p.startX p.y = p.startY } func (p *projectile) update() { tm := time.Since(p.launchTime).Seconds() useSpeed := float64(p.speed) useX, useY := float64(p.startX), float64(p.startY) useA := degToRad(p.angle) p.x = int(useX + (useSpeed * math.Cos(useA) * tm)) p.y = int(useY + (useSpeed*math.Sin(useA)*tm - p.gravity*tm*tm/2)) } func (p *projectile) draw() { if p.y < 0 { termbox.SetCell(p.x, 0, '^', termbox.ColorYellow, termbox.ColorBlack) } else { termbox.SetCell(p.x, p.y, '0', termbox.ColorYellow, termbox.ColorBlack) } } type player struct { x, y int projSpeed int projAngle int gravity float64 baseColor termbox.Attribute turn bool dead bool } func (p *player) draw() { termbox.SetCell(p.x, p.y-4, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+1, p.y-4, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+2, p.y-4, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+3, p.y-4, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+4, p.y-4, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x, p.y-3, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+2, p.y-3, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+4, p.y-3, ' ', p.baseColor, p.baseColor) if p.dead { termbox.SetCell(p.x+1, p.y-3, 'X', termbox.ColorRed, termbox.ColorBlack) termbox.SetCell(p.x+3, p.y-3, 'X', termbox.ColorRed, termbox.ColorBlack) } else { termbox.SetCell(p.x+1, p.y-3, '@', termbox.ColorBlack, termbox.ColorWhite) termbox.SetCell(p.x+3, p.y-3, '@', termbox.ColorBlack, termbox.ColorWhite) } termbox.SetCell(p.x, p.y-2, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+1, p.y-2, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+2, p.y-2, 'w', termbox.ColorWhite, p.baseColor) termbox.SetCell(p.x+3, p.y-2, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+4, p.y-2, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x, p.y-1, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+1, p.y-1, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+2, p.y-1, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+3, p.y-1, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+4, p.y-1, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x, p.y, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+1, p.y, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+2, p.y, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+3, p.y, ' ', p.baseColor, p.baseColor) termbox.SetCell(p.x+4, p.y, ' ', p.baseColor, p.baseColor) // Now draw the arms if p.turn { bulletHandX, bulletHandY := p.GetBulletHandPos() termbox.SetCell(bulletHandX, bulletHandY, 'X', termbox.ColorRed|termbox.AttrBold, termbox.ColorBlack) } } func (p *player) didCollide(pr *projectile) bool { if pr.x >= p.x && pr.x <= p.x+4 { return pr.y <= p.y && pr.y >= p.y-4 } return false } func (p *player) GetClosestTrajectory() (int, int) { // Find the spot closest to the player in a direct line with the target bHx, bHy := p.GetBulletHandPos() gx, gy := p.x+2, p.y-2 for gx >= p.x && gx <= p.x+4 && gy >= p.y-4 && gy <= p.y { if math.Abs(float64(bHx-gx)) > math.Abs(float64(bHy-gy)) { if bHx > gx { gx++ } else { gx-- } } else { if bHy > gy { gy++ } else { gy-- } } } return gx, gy } func (p *player) GetBulletHandPos() (int, int) { tm := 0.5 useSpeed := float64(p.projSpeed) useX, useY := float64(p.x+2), float64(p.y-2) useA := degToRad(p.projAngle) retX := int(useX + (useSpeed * math.Cos(useA) * tm)) retY := int(useY + (useSpeed*math.Sin(useA)*tm - p.gravity*tm*tm/2)) return retX, retY } type building struct { startX int width, height int color termbox.Attribute windowsOn []int } func (b *building) draw(x int) { for i := 0; i < b.height; i++ { for j := 0; j < b.width; j++ { c := b.color /* TODO: Windows if i > 0 && i < b.height && j > 0 && j < b.width { if i%2 == 1 && j%2 == 1 { c = termbox.ColorBlack } } */ termbox.SetCell(x+j, ScreenHeight-i, ' ', c, c) } } } func (b *building) didCollide(p *projectile) bool { if p.x >= b.startX && p.x <= b.startX+b.width { return p.y >= ScreenHeight-b.height } return false }