| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- package main
- import (
- _ "embed"
- "fmt"
- "log"
- "math/rand"
- "strings"
- "time"
- "github.com/gdamore/tcell/v3"
- "github.com/gdamore/tcell/v3/color"
- )
- type coords struct {
- x, y int
- }
- var fallingQuotes = []string{
- "IT DESCENDS.",
- "The sky provides.",
- "I sense tuna.",
- "Ah yes. The prophecy.",
- "Rain harder.",
- "Something edible approaches.",
- }
- var catchQuotes = []string{
- "Mine.",
- "Calculated.",
- "Too easy.",
- "As foretold.",
- "Nom.",
- "You may drop another.",
- }
- var missQuotes = []string{
- "This is sabotage.",
- "The sky betrayed me.",
- "I meant to let that one go.",
- "It lacked seasoning.",
- "Unacceptable.",
- }
- func randomQuote(pool []string) string {
- return pool[rand.Intn(len(pool))]
- }
- //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)
- 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:
- frame = (frame + 1) % 2
- 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)
- }
- }
- // x1,y1 ------- x2,y1
- // | |
- // | |
- // x1,y2 ------- x2,y2
- func GetFoodPositions(x1,y1,x2,y2,level int) []*coords{
- var res []*coords
- nb_food := 5 + level
- for i := range nb_food {
- x := rand.Intn((x2-1)-(x1+1)) + (x1+1)
- y := y1 - i*3
- food_pos := coords{x: x, y: y}
- res = append(res, &food_pos)
- }
- return res
- }
- func DrawFood(s tcell.Screen, y1, y2 int, food_positions []*coords, defStyle tcell.Style, decrement bool) {
- xmax, ymax := s.Size()
- for _, food_pos := range food_positions {
- if food_pos.y > y1 && food_pos.y < (y2-1) {
- drawText(s,food_pos.x,food_pos.y,xmax,ymax,defStyle,"@")
- }
- if food_pos.y < (y2-1) && decrement {
- food_pos.y += 1
- }
- }
- }
- 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
- new_level := true
- var food_positions []*coords
- speed := 0
- message := randomQuote(fallingQuotes)
- duration := time.Duration(1000/level)
- ticker := time.NewTicker(duration * time.Millisecond)
- game_loop:
- go_to_menu := false
- for {
- if (go_to_menu){
- break
- }
- s.Clear()
-
- xmax,ymax := s.Size()
- length_factor := 5
- width_factor := 4
- // x1,y1 ------- x2,y1
- // | |
- // | |
- // x1,y2 ------- x2,y2
- x1 := xmax/width_factor
- x2 := 3*xmax/width_factor
- width_map := x2 - x1 + 1
- y1 := ymax/length_factor
- y2 := 4*ymax/length_factor
- length_map := y2 - y1 + 1
-
- // Draw Game Map Borders
- // upper border
- for i := range(width_map){
- drawText(s,x1+i, y1-1, xmax, ymax, defStyle, "=")
- }
- // bottom border
- for i := range(width_map){
- drawText(s,x1+i, y2, xmax, ymax, defStyle, "=")
- }
- // left border
- for i := range(length_map){
- drawText(s,x1, y1+i, xmax, ymax, defStyle, "|")
- }
- // right border
- for i := range(length_map){
- drawText(s,x2, y1+i, xmax, ymax, defStyle, "|")
- }
- position_cat_x := (x2 + x1)/2 + x_offset
- speed = 2 + (level * 0)
- DrawCat(s, position_cat_x, y2 - 3, xmax,ymax,defStyle, direction)
- if (new_level){
- food_positions = GetFoodPositions(x1,y1,x2,y2,level)
- new_level = false
- }
-
- DrawFood(s,y1,y2,food_positions,defStyle, false)
-
- // Draw UI
- drawText(s, x1, y1 - 6, xmax, ymax, defStyle, "Use arrows or AD to move. Use Up Arrow or W to speed up")
- drawText(s, x1, y1 - 5, xmax, ymax, defStyle, "Press P to Pause.")
- drawText(s, x1, y1 - 3, xmax, ymax, defStyle, fmt.Sprintf("Level: %d", level))
- drawText(s, x1, y1 - 2, xmax, ymax, defStyle, fmt.Sprintf("Score: %d", score))
- drawText(s, x1, y2 + 1, xmax, ymax, defStyle, fmt.Sprintf("Vanilla: \"%s\"", message))
- s.Show()
- defer ticker.Stop()
- select{
- case <- ticker.C:
- DrawFood(s,y1,y2,food_positions,defStyle,true)
- s.Sync()
- case 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 (position_cat_x + speed < (x2-7)){
- x_offset += speed
- } else {
- x_offset = x2 - 7 - (x2 + x1)/2
- }
- } else if (ev.Key() == tcell.KeyLeft || ev.Str() == "a" || ev.Str() == "A") {
- direction = 'l'
- if (position_cat_x - speed > (x1+1)){
- x_offset -= speed
- } else {
- x_offset = (x1+1) - (x2 + x1)/2
- }
- } else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") {
- direction = 'n'
- } else if (ev.Str() == "p" || ev.Str() == "P"){
- go_to_menu = true
- }
- }
- }
- // collect_food
- for _, food_pos := range food_positions{
- if (food_pos.y == y2 - 2){
- if (position_cat_x <= food_pos.x && food_pos.x <= position_cat_x + 7) {
- message = randomQuote(catchQuotes)
- score++
- } else {
- message = randomQuote(missQuotes)
- }
- }
- }
- new_level = true
- level++
- for _,food_pos := range food_positions{
- if food_pos.y < y2-3{
- level--
- new_level = false
- break
- }
- }
-
- }
- start_game = DrawMainMenu(s, defStyle, true)
- if (start_game){
- goto game_loop
- }
- }
|