diff --git a/.gitignore b/.gitignore index 6cd1df2..0a9874c 100755 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ _cgo_export.* _testmain.go -*.exe \ No newline at end of file +*.exe +gopher-battle \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..2a4aa47 --- /dev/null +++ b/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "os" + "time" + + "github.com/nsf/termbox-go" + //"syscall" +) + +const programName = "gopher-battle" + +var debugMode bool +var debugText string +var gameMode int + +// ScreenWidth is how wide the Actual screen is +var ScreenWidth int + +// ScreenHeight is how tall the Actual screen is +var ScreenHeight int + +func main() { + debugMode = true + var err error + + err = termbox.Init() + if err != nil { + panic(err) + } + defer termbox.Close() + + style := defaultStyle() + termbox.SetOutputMode(termbox.Output256) + ScreenWidth, ScreenHeight = termbox.Size() + mainLoop(style) +} + +func readUserInput(e chan termbox.Event) { + for { + e <- termbox.PollEvent() + } +} + +func sendNoneEvent(e chan termbox.Event) { + for { + time.Sleep(time.Second / 32) + e <- termbox.Event{Type: termbox.EventNone} + } +} + +// GameState is that. +type GameState struct { + helpText string +} + +var state *GameState + +func mainLoop(style style) { + // Initialize the Game + if state == nil { + state = new(GameState) + } + screens := defaultScreens() + displayScreen := screens[titleScreenIndex] + layoutAndDrawScreen(displayScreen, style) + eventchan := make(chan termbox.Event) + go readUserInput(eventchan) + go sendNoneEvent(eventchan) + + //var fps, fpsTrack, trackTime int + for { + // Read User Input + event := <-eventchan // := termbox.PollEvent() + /* + if getNowTime()-trackTime >= int(time.Second) { + fps = fpsTrack + fpsTrack = 0 + trackTime = getNowTime() + } + fpsTrack++ + */ + var newScreenIndex int + if event.Type == termbox.EventKey { + if event.Key == termbox.KeyCtrlC { + break + } + newScreenIndex = displayScreen.handleKeyEvent(event) + if newScreenIndex < len(screens) { + displayScreen = screens[newScreenIndex] + } else if newScreenIndex == exitScreenIndex { + break + } + gameMode = newScreenIndex + } + // Update Game State + if gameMode == mainScreenIndex { + // In game updates + displayScreen.update() + } + // Draw Screen + layoutAndDrawScreen(displayScreen, style) + //debugText = fmt.Sprintf("FPS: %d", fps) + } +} + +// Screens Setup +const ( + titleScreenIndex = iota + mainScreenIndex + exitScreenIndex +) + +func defaultScreens() []Screen { + //var view_port ViewPort + titleScreen := titleScreen{} + mainScreen := mainScreen{} + screens := [...]Screen{ + &titleScreen, + &mainScreen, + } + return screens[:] +} + +// WriteToLog Writes to the Log +func WriteToLog(d string) { + f, err := os.OpenFile(programName+".log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664) + if err != nil { + panic(err) + } + f.WriteString(d) + f.Close() +} diff --git a/screen.go b/screen.go new file mode 100644 index 0000000..577f3d5 --- /dev/null +++ b/screen.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/nsf/termbox-go" + "gogs.bullercodeworks.com/brian/termbox-util" +) + +// Screen TODO: Comment +type Screen interface { + handleKeyEvent(event termbox.Event) int + performLayout(style style) + drawScreen(style style) + update() +} + +// ViewPort TODO: Comment +type ViewPort struct { + bytesPerRow int + numberOfRows int + firstRow int +} + +func drawBackground(bg termbox.Attribute) { + termbox.Clear(0, bg) +} + +func layoutAndDrawScreen(screen Screen, style style) { + screen.performLayout(style) + drawBackground(style.defaultBg) + screen.drawScreen(style) + if debugMode { + termboxUtil.DrawStringAtPoint(debugText, 0, 0, style.defaultFg, style.defaultBg) + } + termbox.Flush() +} diff --git a/screen_main.go b/screen_main.go new file mode 100644 index 0000000..87b2cef --- /dev/null +++ b/screen_main.go @@ -0,0 +1,204 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + "gogs.bullercodeworks.com/brian/termbox-util" + + "github.com/nsf/termbox-go" +) + +const ( + modeInit = iota + modeRun + modeRunInput +) + +type player struct { + x, y int + projSpeed int + projAngle int + screen *mainScreen +} + +func (p *player) draw() { + drawGopher(p.x, p.y, termbox.ColorCyan) +} + +type building struct { + width, height int + color termbox.Attribute + windowsOn []int +} + +func (b *building) draw(x int) { + // Fill + for i := 0; i < b.height; i++ { + for j := 0; j < b.width; j++ { + c := b.color + /* + 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) + } + } +} + +type mainScreen struct { + GameMode int + PlayerTurn int + tabIdx int + Player1 player + Player2 player + Buildings []building +} + +func (screen *mainScreen) handleKeyEvent(event termbox.Event) int { + if screen.GameMode == modeRunInput { + } + return mainScreenIndex +} + +func (screen *mainScreen) performLayout(style style) { + var bldCity int + if screen.GameMode == modeInit { + // Create a random seed (from time) + seed := fmt.Sprintf("%d", time.Now().UnixNano()) + r := rand.New(rand.NewSource(getSeedFromString(seed + "-world"))) + for bldCity < ScreenWidth { + w := r.Intn(8) + 5 + h := r.Intn(ScreenHeight-10) + 2 + var c termbox.Attribute + switch r.Intn(3) { + case 0: + c = termbox.ColorRed + case 1: + c = termbox.ColorGreen + case 2: + c = termbox.ColorBlue + } + b := building{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 screen + p1x := r.Intn(ScreenWidth / 3) + p1y := 0 + // Make sure that p1x is fully on a building + // Find the building for the player's x + var calcX int + for i := range screen.Buildings { + calcX += screen.Buildings[i].width + if calcX > p1x { + if calcX > p1x+5 { + mv := (p1x + 5) - calcX + p1x -= mv + } + p1y = ScreenHeight - screen.Buildings[i].height + break + } + } + screen.Player1 = player{x: p1x, y: p1y, screen: screen} + // Player 2 should be on the third 1/3 of the screen + p2x := r.Intn(ScreenWidth/3) + ((ScreenWidth * 2) / 3) + p2y := 0 + for i := range screen.Buildings { + calcX += screen.Buildings[i].width + if calcX > p1x { + if calcX > p2x+5 { + mv := (p2x + 5) - calcX + p2x -= mv + } + p2y = ScreenHeight - screen.Buildings[i].height + break + } + } + // Make sure that p2x is fully on a building + screen.Player2 = player{x: p2x, y: p2y, screen: screen} + } + screen.GameMode = modeRun +} + +// Trigger all updates +func (screen *mainScreen) update() { + if screen.GameMode == modeRun { + + } +} + +func (screen *mainScreen) drawScreen(style style) { + // Draw Player 1 + screen.Player1.draw() + // Draw Player 2 + screen.Player2.draw() + // 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) + } else if screen.GameMode == modeRunInput { + // Draw inputs + if screen.PlayerTurn == 1 { + termboxUtil.DrawStringAtPoint("Angle:", 0, 0, style.defaultFg, style.defaultBg) + termboxUtil.DrawStringAtPoint("Speed:", 0, 1, style.defaultFg, style.defaultBg) + } else { + termboxUtil.DrawStringAtPoint("Angle:", ScreenWidth-11, 0, style.defaultFg, style.defaultBg) + termboxUtil.DrawStringAtPoint("Speed:", ScreenWidth-11, 1, style.defaultFg, style.defaultBg) + } + } +} + +func getNowTime() int { + return int(time.Now().UnixNano()) +} + +func getSeedFromString(seed string) int64 { + // How does this algorithm work for string->seed generation? + // ~\_(o.o)_/~ + var ret int64 + for i, k := range seed { + ret += int64(k) * int64(i) + } + return ret +} + +func drawGopher(x, y int, c termbox.Attribute) { + termbox.SetCell(x, y-4, ' ', c, c) + termbox.SetCell(x+1, y-4, ' ', c, c) + termbox.SetCell(x+2, y-4, ' ', c, c) + termbox.SetCell(x+3, y-4, ' ', c, c) + termbox.SetCell(x+4, y-4, ' ', c, c) + + termbox.SetCell(x, y-3, ' ', c, c) + termbox.SetCell(x+1, y-3, '@', termbox.ColorBlack, termbox.ColorWhite) + termbox.SetCell(x+2, y-3, ' ', c, c) + termbox.SetCell(x+3, y-3, '@', termbox.ColorBlack, termbox.ColorWhite) + termbox.SetCell(x+4, y-3, ' ', c, c) + + termbox.SetCell(x, y-2, ' ', c, c) + termbox.SetCell(x+1, y-2, ' ', c, c) + termbox.SetCell(x+2, y-2, 'w', termbox.ColorWhite, c) + termbox.SetCell(x+3, y-2, ' ', c, c) + termbox.SetCell(x+4, y-2, ' ', c, c) + + termbox.SetCell(x, y-1, ' ', c, c) + termbox.SetCell(x+1, y-1, ' ', c, c) + termbox.SetCell(x+2, y-1, ' ', c, c) + termbox.SetCell(x+3, y-1, ' ', c, c) + termbox.SetCell(x+4, y-1, ' ', c, c) + + termbox.SetCell(x, y, ' ', c, c) + termbox.SetCell(x+1, y, ' ', c, c) + termbox.SetCell(x+2, y, ' ', c, c) + termbox.SetCell(x+3, y, ' ', c, c) + termbox.SetCell(x+4, y, ' ', c, c) +} diff --git a/screen_title.go b/screen_title.go new file mode 100644 index 0000000..f2b0f8d --- /dev/null +++ b/screen_title.go @@ -0,0 +1,70 @@ +package main + +import ( + "github.com/nsf/termbox-go" + "gogs.bullercodeworks.com/brian/termbox-util" +) + +type command struct { + key string + description string +} + +type titleScreen struct { + titleArt *termboxUtil.ASCIIArt + menu *termboxUtil.Menu + initialized bool +} + +func (screen *titleScreen) handleKeyEvent(event termbox.Event) int { + if screen.menu.HandleKeyPress(event) { + if screen.menu.IsDone() { + selOpt := screen.menu.GetSelectedOption() + if selOpt.GetText() == "Exit" { + return exitScreenIndex + } else if selOpt.GetText() == "New Game" { + screen.initialized = false + return mainScreenIndex + } + } + } + return titleScreenIndex +} + +func (screen *titleScreen) performLayout(style style) { + if !screen.initialized { + var tmplt []string + tmplt = append(tmplt, " ") + tmplt = append(tmplt, " ") + tmplt = append(tmplt, " gopher.bas ") + tmplt = append(tmplt, " ") + tmplt = append(tmplt, " ") + tmplt = append(tmplt, " ") + + defaultFg := style.defaultFg + defaultBg := style.defaultBg + screen.titleArt = termboxUtil.CreateASCIIArt(tmplt, 0, 0, defaultFg, defaultBg) + w, h := termbox.Size() + screen.titleArt.Align(termboxUtil.AlignCenter, w) + + menuX := w/2 - 10 + menuY := h/2 - 5 + + screen.menu = termboxUtil.CreateMenu("", + []string{"New Game", "Continue", "Exit"}, + menuX, menuY, 20, 10, defaultFg, defaultBg, + ) + // TODO: Check if we have a suspended game + screen.menu.SetOptionDisabled(1) + screen.menu.SetBordered(true) + screen.menu.EnableVimMode() + screen.initialized = true + } +} + +func (screen *titleScreen) update() {} + +func (screen *titleScreen) drawScreen(style style) { + screen.titleArt.Draw() + screen.menu.Draw() +} diff --git a/style.go b/style.go new file mode 100644 index 0000000..167c70c --- /dev/null +++ b/style.go @@ -0,0 +1,23 @@ +package main + +import "github.com/nsf/termbox-go" + +type style struct { + defaultBg termbox.Attribute + defaultFg termbox.Attribute + titleBg termbox.Attribute + titleFg termbox.Attribute + cursorBg termbox.Attribute + cursorFg termbox.Attribute +} + +func defaultStyle() style { + var style style + style.defaultBg = termbox.ColorBlack + style.defaultFg = termbox.ColorWhite + style.titleBg = style.defaultBg + style.titleFg = style.defaultFg | termbox.AttrBold + style.cursorBg = termbox.ColorWhite + style.cursorFg = termbox.ColorBlack + return style +}