package main import ( _ "embed" "fmt" "log" "strings" "time" "github.com/gdamore/tcell/v3" "github.com/gdamore/tcell/v3/color" ) //go:embed assets/title/title_idle.txt var title_idle string //go:embed assets/title/title_blinking.txt var title_blinking string func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { row := y1 col := x1 var width int for text != "" { text, width = s.Put(col, row, text, style) col += width if col >= x2 { row++ col = x1 } if row > y2 { break } if width == 0 { // incomplete grapheme at end of string break } } } func drawTitle(s tcell.Screen, x, padding_top, frame int, style tcell.Style, titles [][]string) int{ lines := titles[frame] frame = (frame + 1) % 2 // Draw Title xmax, ymax := s.Size() for l:= range lines{ drawText(s,x, l+padding_top, xmax, ymax, style, lines[l]) } return len(lines) } func DrawMainMenu(s tcell.Screen, defStyle tcell.Style, old_session bool) bool{ cats := [][]string{ strings.Split(string(title_idle), "\n"), strings.Split(string(title_blinking), "\n")} var first_item_menu string if (old_session){ first_item_menu = "Continue" } else { first_item_menu = "Start New Game" } curr_menu_item := 0 menu := true gap_title_menu := 3 settings, credits := false, false frame := 0 ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() // Event loop for { s.Clear() title_size := drawTitle(s, 10, 2, frame, defStyle, cats) frame = (frame + 1) % 2 xmax,ymax := s.Size() // Draw Menu if menu { drawText(s, 10, title_size+gap_title_menu, xmax, ymax, defStyle, "Use arrows or WASD to navigate. Press ENTER to select") switch curr_menu_item { case 0: drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, "▶ "+first_item_menu) drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, " Settings") drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, " Credits") case 1: drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, " "+first_item_menu) drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, "▶ Settings") drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, " Credits") case 2: drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, " "+first_item_menu) drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, " Settings") drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, "▶ Credits") } } if credits { drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Abderrahmane Faiz") drawText(s, 10, title_size+gap_title_menu+1, xmax,ymax, defStyle, "\u001B]8;;https://afaiz.dev\u001B\\https://afaiz.dev\u001B]8;;\u001B\\") drawText(s, 10, title_size+gap_title_menu+2, xmax,ymax, defStyle, "ASCII Art found on https://emojicombos.com/cat") drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu") } if settings { drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Nothing to customize for now ^^'") drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu") } // Update screen s.Show() // Poll event (this can be in a select statement as well) select{ case <- ticker.C: continue case ev := <-s.EventQ(): // Process event switch ev := ev.(type) { default: continue case *tcell.EventResize: s.Sync() case *tcell.EventKey: if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { return false } else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") && menu { if curr_menu_item == 0 { curr_menu_item = 2 } else { curr_menu_item = curr_menu_item - 1 } } else if (ev.Key() == tcell.KeyDown || ev.Str() == "s" || ev.Str() == "S") && menu { curr_menu_item = (curr_menu_item + 1) % 3 } else if ev.Key() == tcell.KeyEnter && credits { credits = false menu = true } else if ev.Key() == tcell.KeyEnter && settings { settings = false menu = true } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 2 { menu = false credits = true } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 1 { menu = false settings = true } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 0 { return true } } } } } func DrawCat(s tcell.Screen, x1,y1,x2,y2 int, style tcell.Style, direction rune) { mycat := []string{" /\\_/\\","( o.o )", " > ^ <"} switch direction{ case 'l': mycat[len(mycat)-1] = " < ^ <" case 'r': mycat[len(mycat)-1] = " > ^ >" default : mycat[len(mycat)-1] = " > ^ <" } for index,cat := range mycat{ drawText(s, x1, y1 + index, x2,y2, style, cat) } } func main() { // Initialize screen defStyle := tcell.StyleDefault.Background(color.Black).Foreground(color.NewHexColor(0xf3e5ab)) s, err := tcell.NewScreen() if err != nil { log.Fatalf("%+v", err) } if err := s.Init(); err != nil { log.Fatalf("%+v", err) } s.SetStyle(defStyle) quit := func() { maybePanic := recover() s.Fini() if maybePanic != nil { panic(maybePanic) } } defer quit() // Main Menu start_game := DrawMainMenu(s, defStyle, false) if (!start_game){ return } score := 0 level := 1 direction := 'n' x_offset := 0 game_loop: go_to_menu := false for { if (go_to_menu){ break } s.Clear() xmax,ymax := s.Size() length_factor := 5 width_factor := 4 up_right_border := 3*xmax/width_factor width_map := up_right_border - (xmax/width_factor) + 1 down_right_border := 4*ymax/length_factor length_map := down_right_border - (ymax/length_factor) + 1 // Draw UI drawText(s, xmax/width_factor, ymax/length_factor - 5, xmax, ymax, defStyle, "Use arrows or AD to move. Press q to return to Main Menu.") drawText(s, xmax/width_factor, ymax/length_factor - 3, xmax, ymax, defStyle, fmt.Sprintf("Level: %d", level)) drawText(s, xmax/width_factor, ymax/length_factor - 2, xmax, ymax, defStyle, fmt.Sprintf("Score: %d", score)) // Draw Game Map Borders // upper border for i := range(width_map){ drawText(s,xmax/width_factor+i, ymax/length_factor-1, xmax, ymax, defStyle, "=") } // bottom border for i := range(width_map){ drawText(s,xmax/width_factor+i, down_right_border, xmax, ymax, defStyle, "=") } // left border for i := range(length_map){ drawText(s,xmax/width_factor, ymax/length_factor+i, xmax, ymax, defStyle, "|") } // right border for i := range(length_map){ drawText(s,up_right_border, ymax/length_factor+i, xmax, ymax, defStyle, "|") } starting_point_x := (up_right_border + xmax/width_factor)/2 + x_offset DrawCat(s, starting_point_x, down_right_border - 3, xmax,ymax,defStyle, direction) s.Show() ev := <- s.EventQ() // Process event switch ev := ev.(type) { case *tcell.EventResize: s.Sync() case *tcell.EventKey: if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC { return } else if (ev.Key() == tcell.KeyRight || ev.Str() == "d" || ev.Str() == "D") { direction = 'r' if (starting_point_x + 1 < (up_right_border-6)){ x_offset += 1 } } else if (ev.Key() == tcell.KeyLeft || ev.Str() == "a" || ev.Str() == "A") { direction = 'l' if (starting_point_x + 1 > (xmax/width_factor + 2)){ x_offset -= 1 } } else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") { direction = 'n' } else if (ev.Str() == "q" || ev.Str() == "Q"){ go_to_menu = true } } } start_game = DrawMainMenu(s, defStyle, true) if (start_game){ goto game_loop } }