main.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. package main
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "log"
  6. "math/rand"
  7. "strings"
  8. "time"
  9. "github.com/gdamore/tcell/v3"
  10. "github.com/gdamore/tcell/v3/color"
  11. )
  12. type coords struct {
  13. x, y int
  14. }
  15. //go:embed assets/title/title_idle.txt
  16. var title_idle string
  17. //go:embed assets/title/title_blinking.txt
  18. var title_blinking string
  19. func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
  20. row := y1
  21. col := x1
  22. var width int
  23. for text != "" {
  24. text, width = s.Put(col, row, text, style)
  25. col += width
  26. if col >= x2 {
  27. row++
  28. col = x1
  29. }
  30. if row > y2 {
  31. break
  32. }
  33. if width == 0 {
  34. // incomplete grapheme at end of string
  35. break
  36. }
  37. }
  38. }
  39. func drawTitle(s tcell.Screen, x, padding_top, frame int, style tcell.Style, titles [][]string) int{
  40. lines := titles[frame]
  41. frame = (frame + 1) % 2
  42. // Draw Title
  43. xmax, ymax := s.Size()
  44. for l:= range lines{
  45. drawText(s,x, l+padding_top, xmax, ymax, style, lines[l])
  46. }
  47. return len(lines)
  48. }
  49. func DrawMainMenu(s tcell.Screen, defStyle tcell.Style, old_session bool) bool{
  50. cats := [][]string{
  51. strings.Split(string(title_idle), "\n"),
  52. strings.Split(string(title_blinking), "\n")}
  53. var first_item_menu string
  54. if (old_session){
  55. first_item_menu = "Continue"
  56. } else {
  57. first_item_menu = "Start New Game"
  58. }
  59. curr_menu_item := 0
  60. menu := true
  61. gap_title_menu := 3
  62. settings, credits := false, false
  63. frame := 0
  64. ticker := time.NewTicker(500 * time.Millisecond)
  65. defer ticker.Stop()
  66. // Event loop
  67. for {
  68. s.Clear()
  69. title_size := drawTitle(s, 10, 2, frame, defStyle, cats)
  70. frame = (frame + 1) % 2
  71. xmax,ymax := s.Size()
  72. // Draw Menu
  73. if menu {
  74. drawText(s, 10, title_size+gap_title_menu, xmax, ymax, defStyle, "Use arrows or WASD to navigate. Press ENTER to select")
  75. switch curr_menu_item {
  76. case 0:
  77. drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, "▶ "+first_item_menu)
  78. drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, " Settings")
  79. drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, " Credits")
  80. case 1:
  81. drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, " "+first_item_menu)
  82. drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, "▶ Settings")
  83. drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, " Credits")
  84. case 2:
  85. drawText(s, 10, title_size+gap_title_menu+2, xmax, ymax, defStyle, " "+first_item_menu)
  86. drawText(s, 10, title_size+gap_title_menu+3, xmax, ymax, defStyle, " Settings")
  87. drawText(s, 10, title_size+gap_title_menu+4, xmax, ymax, defStyle, "▶ Credits")
  88. }
  89. }
  90. if credits {
  91. drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Abderrahmane Faiz")
  92. drawText(s, 10, title_size+gap_title_menu+1, xmax,ymax, defStyle, "\u001B]8;;https://afaiz.dev\u001B\\https://afaiz.dev\u001B]8;;\u001B\\")
  93. drawText(s, 10, title_size+gap_title_menu+2, xmax,ymax, defStyle, "ASCII Art found on https://emojicombos.com/cat")
  94. drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu")
  95. }
  96. if settings {
  97. drawText(s, 10, title_size+gap_title_menu, xmax,ymax, defStyle, "Nothing to customize for now ^^'")
  98. drawText(s, 10, title_size+gap_title_menu+4, xmax,ymax, defStyle, "Press ENTER to go back to the menu")
  99. }
  100. // Update screen
  101. s.Show()
  102. // Poll event (this can be in a select statement as well)
  103. select{
  104. case <- ticker.C:
  105. continue
  106. case ev := <-s.EventQ():
  107. // Process event
  108. switch ev := ev.(type) {
  109. default:
  110. continue
  111. case *tcell.EventResize:
  112. s.Sync()
  113. case *tcell.EventKey:
  114. if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
  115. return false
  116. } else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") && menu {
  117. if curr_menu_item == 0 {
  118. curr_menu_item = 2
  119. } else {
  120. curr_menu_item = curr_menu_item - 1
  121. }
  122. } else if (ev.Key() == tcell.KeyDown || ev.Str() == "s" || ev.Str() == "S") && menu {
  123. curr_menu_item = (curr_menu_item + 1) % 3
  124. } else if ev.Key() == tcell.KeyEnter && credits {
  125. credits = false
  126. menu = true
  127. } else if ev.Key() == tcell.KeyEnter && settings {
  128. settings = false
  129. menu = true
  130. } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 2 {
  131. menu = false
  132. credits = true
  133. } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 1 {
  134. menu = false
  135. settings = true
  136. } else if ev.Key() == tcell.KeyEnter && curr_menu_item == 0 {
  137. return true
  138. }
  139. }
  140. }
  141. }
  142. }
  143. func DrawCat(s tcell.Screen, x1,y1,x2,y2 int, style tcell.Style, direction rune) {
  144. mycat := []string{" /\\_/\\","( o.o )", " > ^ <"}
  145. switch direction{
  146. case 'l':
  147. mycat[len(mycat)-1] = " < ^ <"
  148. case 'r':
  149. mycat[len(mycat)-1] = " > ^ >"
  150. default :
  151. mycat[len(mycat)-1] = " > ^ <"
  152. }
  153. for index,cat := range mycat{
  154. drawText(s, x1, y1 + index, x2,y2, style, cat)
  155. }
  156. }
  157. // x1,y1 ------- x2,y1
  158. // | |
  159. // | |
  160. // x1,y2 ------- x2,y2
  161. func GetFoodPositions(x1,y1,x2,y2,level int) []*coords{
  162. var res []*coords
  163. nb_food := 5*level
  164. for range nb_food {
  165. x := rand.Intn((x2-1)-(x1+1)) + (x1+1)
  166. y := y1
  167. food_pos := coords{x: x, y: y}
  168. res = append(res, &food_pos)
  169. }
  170. return res
  171. }
  172. func main() {
  173. // Initialize screen
  174. defStyle := tcell.StyleDefault.Background(color.Black).Foreground(color.NewHexColor(0xf3e5ab))
  175. s, err := tcell.NewScreen()
  176. if err != nil {
  177. log.Fatalf("%+v", err)
  178. }
  179. if err := s.Init(); err != nil {
  180. log.Fatalf("%+v", err)
  181. }
  182. s.SetStyle(defStyle)
  183. quit := func() {
  184. maybePanic := recover()
  185. s.Fini()
  186. if maybePanic != nil {
  187. panic(maybePanic)
  188. }
  189. }
  190. defer quit()
  191. // Main Menu
  192. start_game := DrawMainMenu(s, defStyle, false)
  193. if (!start_game){
  194. return
  195. }
  196. score := 0
  197. level := 1
  198. direction := 'n'
  199. x_offset := 0
  200. new_level := true
  201. var food_positions []*coords
  202. game_loop:
  203. go_to_menu := false
  204. for {
  205. if (go_to_menu){
  206. break
  207. }
  208. s.Clear()
  209. xmax,ymax := s.Size()
  210. length_factor := 5
  211. width_factor := 4
  212. // x1,y1 ------- x2,y1
  213. // | |
  214. // | |
  215. // x1,y2 ------- x2,y2
  216. x1 := xmax/width_factor
  217. x2 := 3*xmax/width_factor
  218. width_map := x2 - x1 + 1
  219. y1 := ymax/length_factor
  220. y2 := 4*ymax/length_factor
  221. length_map := y2 - y1 + 1
  222. // Draw UI
  223. drawText(s, x1, y1 - 5, xmax, ymax, defStyle, "Use arrows or AD to move. Press q to return to Main Menu.")
  224. drawText(s, x1, y1 - 3, xmax, ymax, defStyle, fmt.Sprintf("Level: %d", level))
  225. drawText(s, x1, y1 - 2, xmax, ymax, defStyle, fmt.Sprintf("Score: %d", score))
  226. // Draw Game Map Borders
  227. // upper border
  228. for i := range(width_map){
  229. drawText(s,x1+i, y1-1, xmax, ymax, defStyle, "=")
  230. }
  231. // bottom border
  232. for i := range(width_map){
  233. drawText(s,x1+i, y2, xmax, ymax, defStyle, "=")
  234. }
  235. // left border
  236. for i := range(length_map){
  237. drawText(s,x1, y1+i, xmax, ymax, defStyle, "|")
  238. }
  239. // right border
  240. for i := range(length_map){
  241. drawText(s,x2, y1+i, xmax, ymax, defStyle, "|")
  242. }
  243. position_cat_x := (x2 + x1)/2 + x_offset
  244. DrawCat(s, position_cat_x, y2 - 3, xmax,ymax,defStyle, direction)
  245. if (new_level){
  246. food_positions = GetFoodPositions(x1,y1,x2,y2,level)
  247. new_level = false
  248. }
  249. for i, food_pos := range food_positions {
  250. drawText(s,food_pos.x,food_pos.y,xmax,ymax,defStyle,"@")
  251. if i == rand.Intn(len(food_positions)) && food_pos.y < (y2-1) {
  252. food_pos.y += 1
  253. }
  254. }
  255. s.Show()
  256. ev := <- s.EventQ()
  257. // Process event
  258. switch ev := ev.(type) {
  259. case *tcell.EventResize:
  260. s.Sync()
  261. case *tcell.EventKey:
  262. if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
  263. return
  264. } else if (ev.Key() == tcell.KeyRight || ev.Str() == "d" || ev.Str() == "D") {
  265. direction = 'r'
  266. if (position_cat_x + 1 < (x2-6)){
  267. x_offset += 1
  268. }
  269. } else if (ev.Key() == tcell.KeyLeft || ev.Str() == "a" || ev.Str() == "A") {
  270. direction = 'l'
  271. if (position_cat_x + 1 > (x1 + 2)){
  272. x_offset -= 1
  273. }
  274. } else if (ev.Key() == tcell.KeyUp || ev.Str() == "w" || ev.Str() == "W") {
  275. direction = 'n'
  276. } else if (ev.Str() == "q" || ev.Str() == "Q"){
  277. go_to_menu = true
  278. }
  279. }
  280. }
  281. start_game = DrawMainMenu(s, defStyle, true)
  282. if (start_game){
  283. goto game_loop
  284. }
  285. }