snake_game_oo.sf 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/ruby
  2. #
  3. ## https://rosettacode.org/wiki/Snake
  4. #
  5. class SnakeGame(w, h) {
  6. const readkey = frequire('Term::ReadKey')
  7. const ansi = frequire('Term::ANSIColor')
  8. enum (VOID, HEAD, BODY, TAIL, FOOD)
  9. define (
  10. LEFT = [+0, -1],
  11. RIGHT = [+0, +1],
  12. UP = [-1, +0],
  13. DOWN = [+1, +0],
  14. )
  15. define BG_COLOR = "on_black"
  16. define FOOD_COLOR = ("red" + " " + BG_COLOR)
  17. define SNAKE_COLOR = ("bold green" + " " + BG_COLOR)
  18. define SLEEP_SEC = 0.02
  19. const (
  20. A_VOID = ansi.colored(' ', BG_COLOR),
  21. A_FOOD = ansi.colored('❇', FOOD_COLOR),
  22. A_BLOCK = ansi.colored('■', SNAKE_COLOR),
  23. )
  24. has dir = LEFT
  25. has grid = [[]]
  26. has head_pos = [0, 0]
  27. has tail_pos = [0, 0]
  28. method init {
  29. grid = h.of { w.of { [VOID] } }
  30. head_pos = [h>>1, w>>1]
  31. tail_pos = [head_pos[0], head_pos[1]+1]
  32. grid[head_pos[0]][head_pos[1]] = [HEAD, dir] # head
  33. grid[tail_pos[0]][tail_pos[1]] = [TAIL, dir] # tail
  34. self.make_food()
  35. }
  36. method make_food {
  37. var (food_x, food_y)
  38. do {
  39. food_x = w.rand.int
  40. food_y = h.rand.int
  41. } while (grid[food_y][food_x][0] != VOID)
  42. grid[food_y][food_x][0] = FOOD
  43. }
  44. method display {
  45. print("\033[H", grid.map { |row|
  46. row.map { |cell|
  47. given (cell[0]) {
  48. when (VOID) { A_VOID }
  49. when (FOOD) { A_FOOD }
  50. default { A_BLOCK }
  51. }
  52. }.join('')
  53. }.join("\n")
  54. )
  55. }
  56. method move {
  57. var grew = false
  58. # Move the head
  59. var (y, x) = head_pos...
  60. var new_y = (y+dir[0] % h)
  61. var new_x = (x+dir[1] % w)
  62. var cell = grid[new_y][new_x]
  63. given (cell[0]) {
  64. when (BODY) { die "\nYou just bit your own body!\n" }
  65. when (TAIL) { die "\nYou just bit your own tail!\n" }
  66. when (FOOD) { grew = true; self.make_food() }
  67. }
  68. # Create a new head
  69. grid[new_y][new_x] = [HEAD, dir]
  70. # Replace the current head with body
  71. grid[y][x] = [BODY, dir]
  72. # Update the head position
  73. head_pos = [new_y, new_x]
  74. # Move the tail
  75. if (!grew) {
  76. var (y, x) = tail_pos...
  77. var pos = grid[y][x][1]
  78. var new_y = (y+pos[0] % h)
  79. var new_x = (x+pos[1] % w)
  80. grid[y][x][0] = VOID # erase the current tail
  81. grid[new_y][new_x][0] = TAIL # create a new tail
  82. tail_pos = [new_y, new_x]
  83. }
  84. }
  85. method play {
  86. STDOUT.autoflush(true)
  87. readkey.ReadMode(3)
  88. try {
  89. loop {
  90. var key
  91. while (!defined(key = readkey.ReadLine(-1))) {
  92. self.move()
  93. self.display()
  94. Sys.sleep(SLEEP_SEC)
  95. }
  96. given (key) {
  97. when ("\e[A") { if (dir != DOWN ) { dir = UP } }
  98. when ("\e[B") { if (dir != UP ) { dir = DOWN } }
  99. when ("\e[C") { if (dir != LEFT ) { dir = RIGHT } }
  100. when ("\e[D") { if (dir != RIGHT) { dir = LEFT } }
  101. }
  102. }
  103. }
  104. catch {
  105. readkey.ReadMode(0)
  106. }
  107. }
  108. }
  109. var w = `tput cols`.to_i
  110. var h = `tput lines`.to_i
  111. SnakeGame(w || 80, h || 24).play