Infelizmente não e vou ter que voltar do zero porque vou usar outra linguagem.
Nos meus primeiros dias aprendendo Ruby eu finalizei um bloco da notas e um gerador de senhas, recentemente eu fiz um jogo da cobrinha que segue regras bem únicas e ele funciona no terminal (nele eu usei as gems Curses, para dar mais estética ao terminal, e Set, pra ajustar a localização das armadilhas sem ficar por cima dos pontos).
Se quiser ver, o código é esse:
require 'curses'
require 'set'
class SnakeGame
include Curses
FIXED_HEIGHT = 40
FIXED_WIDTH = 120
TRAP_CLEANUP_TIME = 10
POINTS_FOOD = 20
POINTS_TRAP = -5
PENALTY_NO_FOOD = -30
X_POINTS_CLEANER = 10
X_CLEANER_BASE_CHANCE = 0.0003
TRAP_WEIGHT = 0.0003
MAX_CLEANER_CHANCE = 0.02
X_CLEANER_PERCENT = 0.35
MIN_SNAKE_SIZE = 10
ADD_SNAKE_SIZE = 2
GAME_SPEED = 40
LOGO = [
" ____ _ _ _ ____ _ ",
" / ___|| |__ ___| | |/ ___| _ __ __ _| | _____ ",
" \\___ \\| '_ \\ / _ \\ | |\\___ \\| '_ \\ / _` | |/ / _ \\",
" ___) | | | | __/ | | ___) | | | | (_| | < __/",
" |____/|_| |_|\\___|_|_||____/|_| |_|\\__,_|_|\\_\\___|",
" "
]
def initialize
init_screen
start_color if has_colors?
check_terminal_resolution!
cbreak
noecho
stdscr.keypad(true)
curs_set(0)
if has_colors?
init_pair(1, COLOR_GREEN, COLOR_BLACK)
init_pair(2, COLOR_YELLOW, COLOR_BLACK)
init_pair(3, COLOR_CYAN, COLOR_BLACK)
init_pair(4, COLOR_RED, COLOR_BLACK)
init_pair(5, COLOR_WHITE, COLOR_BLACK)
end
@game_win = Window.new(FIXED_HEIGHT, FIXED_WIDTH, (lines - FIXED_HEIGHT) / 2, (cols - FIXED_WIDTH) / 2)
@game_win.keypad(true)
@max_y, @max_x = FIXED_HEIGHT - 1, FIXED_WIDTH - 1
stdscr.refresh
end
def check_terminal_resolution!
if lines < FIXED_HEIGHT || cols < FIXED_WIDTH
clear
msg = "Terminal too small (Requirement: 120x40). Press any key to exit and resize the terminal."
setpos(lines / 2, [0, (cols - msg.length) / 2].max)
addstr(msg)
refresh
getch
close_screen
exit
end
end
def reset_game
@snake = 12.times.map { |i| [FIXED_HEIGHT/ 2, FIXED_WIDTH / 2 - i] }
@direction = :right
@growth_pending = 0
@score = 0
@traps_eaten = 0
@cleanups_since_food = 0
@cleaners_collected = 0
@total_cleanups = 0
@start_time = Time.now
@last_cleanup = Time.now
@cleaner = nil
@traps = Set.new
@death_reason = ""
@last_shrink_score = 0
spawn_food
end
def spawn_food
loop do
@food = [rand(2..@max_y - 2), rand(2..@max_x - 2)]
break unless @snake.include?(@food) || @traps.include?(@food)
end
end
def shrink_snake!(amount = 1)
if @snake.size - amount < MIN_SNAKE_SIZE
@death_reason = "VERY SMALL SIZE (starvation)"
return false
end
amount.times { @snake.pop if @snake.size > 1 }
true
end
def spawn_cleaner
loop do
pos = [rand(2..@max_y - 2), rand(2..@max_x - 2)]
unless @snake.include?(pos) || @traps.include?(pos) || pos == @food
@cleaner = pos
break
end
end
end
def run
loop do
reset_game
return unless show_start_screen
game_loop
return unless show_game_over_screen
end
ensure
close_screen
end
private
def game_loop
loop do
render
input = stdscr.getch
break unless update(input)
end
end
def render
@game_win.clear
@game_win.box('|', '-')
now = Time.now
total_time = (now - @start_time).to_i
time_left = [0, (TRAP_CLEANUP_TIME - (now - @last_cleanup)).to_i].max
# Barra de Status (Interface)
@game_win.attron(color_pair(3))
@game_win.setpos(0, 2)
status = [
"Score: #{@score}",
"X: #{@cleaners_collected}",
"Time: #{total_time}s",
"Size: #{@snake.size} /+=10"
].join(" | ")
@game_win.addstr(" #{status} ")
@game_win.attroff(color_pair(3))
# Comida
@game_win.attron(color_pair(2) | A_BOLD)
@game_win.setpos(@food[0], @food[1]); @game_win.addch('0')
@game_win.attroff(color_pair(2) | A_BOLD)
# Armadilhas
@game_win.attron(color_pair(4) | A_BOLD)
@traps.each { |y, x| @game_win.setpos(y, x); @game_win.addch('-') if y < @max_y }
@game_win.attroff(color_pair(4) | A_BOLD)
# Limpador (X)
if @cleaner
@game_win.attron(color_pair(5) | A_BOLD)
@game_win.setpos(@cleaner[0], @cleaner[1]); @game_win.addch('X')
@game_win.attroff(color_pair(5) | A_BOLD)
end
# Cobra
@game_win.attron(color_pair(1) | A_BOLD)
@snake.each_with_index do |(y, x), i|
next if y >= @max_y || x >= @max_x
@game_win.setpos(y, x)
@game_win.addch(i == 0 ? '$' : '=')
end
@game_win.attroff(color_pair(1) | A_BOLD)
stdscr.timeout = GAME_SPEED
@game_win.refresh
end
def update(input)
# 1. Processamento de Input
case input
when KEY_UP, 'w', 'W' then @direction = :up unless @direction == :down
when KEY_DOWN, 's', 'S' then @direction = :down unless @direction == :up
when KEY_LEFT, 'a', 'A' then @direction = :left unless @direction == :right
when KEY_RIGHT, 'd', 'D' then @direction = :right unless @direction == :left
when 'q', 'Q' then return false
end # Aqui fecha o CASE
# 2. Cálculo da Nova Cabeça
head_y, head_x = @snake.first
case @direction
when :up then head_y -= 1
when :down then head_y += 1
when :left then head_x -= 1
when :right then head_x += 1
end
new_head = [head_y, head_x]
# 3. Verificação de Colisões (Parede e Corpo)
if head_y <= 0 || head_y >= @max_y || head_x <= 0 || head_x >= @max_x
@death_reason = "WALL COLLISION"
return false
end
if @snake[0...-1].include?(new_head)
@death_reason = "BIT YOURSELF"
return false
end
@snake.unshift(new_head)
# 4. Lógica de Interação com Objetos
if new_head == @food
@score += POINTS_FOOD
@cleanups_since_food = 0
@growth_pending += ADD_SNAKE_SIZE
spawn_food
# Lógica de Auto-Shrink
if @score >= @last_shrink_score + 1000
shrink_amount = (@snake.size * 0.30).to_i
if @snake.size > MIN_SNAKE_SIZE
actual_reduction = [shrink_amount, @snake.size - MIN_SNAKE_SIZE].min
actual_reduction.times { @snake.pop }
end
@last_shrink_score = (@score / 1000) * 1000
end
# Spawna armadilhas ao comer
8.times do
pos = [rand(2..@max_y - 2), rand(2..@max_x - 2)]
@traps << pos unless @snake.include?(pos) || pos == @food
end
elsif @cleaner && new_head == @cleaner
remove_count = (@traps.size * X_CLEANER_PERCENT).ceil
@traps.to_a.sample(remove_count).each { |t| @traps.delete(t) }
@cleaner = nil
@score += X_POINTS_CLEANER
@cleaners_collected += 1
elsif @traps.include?(new_head)
@score += POINTS_TRAP
@traps.delete(new_head)
@traps_eaten += 1
return false unless shrink_snake!(1)
if @traps_eaten >= 2
return false unless shrink_snake!(1)
@traps_eaten = 0
end
end
# 5. Crescimento/Movimento da Cauda
if @growth_pending > 0
@growth_pending -= 1
else
@snake.pop unless new_head == @food
end
# 6. Penalidade por Tempo e Limpeza Automática
if Time.now - @last_cleanup >= TRAP_CLEANUP_TIME
@traps.to_a.first(4).each { |t| @traps.delete(t) }
@last_cleanup = Time.now
@total_cleanups += 1
@cleanups_since_food += 1
if @cleanups_since_food >= 2
@score += PENALTY_NO_FOOD
@cleanups_since_food = 0
return false unless shrink_snake!(2)
end
end
# 7. Spawn Dinâmico do Cleaner (X)
if @cleaner.nil?
dynamic_chance = X_CLEANER_BASE_CHANCE + (@traps.size * TRAP_WEIGHT)
current_spawn_probability = [dynamic_chance, MAX_CLEANER_CHANCE].min
spawn_cleaner if rand < current_spawn_probability
end
true
end
def show_start_screen
@game_win.clear
@game_win.box('|', '-')
start_y = (@max_y / 2) - (LOGO.size / 2) - 3
LOGO.each_with_index do |line, index|
if index < 6
@game_win.attron(color_pair(2) | A_BOLD)
end
x_pos = [0, (@max_x / 2) - (line.length / 2)].max
@game_win.setpos(start_y + index, x_pos)
@game_win.addstr(line)
end
@game_win.attron(A_DIM)
draw_centered(8, "Press ENTER to start | Press Q to exit.")
@game_win.attroff(A_DIM)
@game_win.refresh
@game_win.timeout = -1
loop do
case @game_win.getch
when 10, 13, KEY_ENTER, "\n" then return true
when 'q', 'Q' then return false
end
end
end
def show_game_over_screen
@game_win.clear
@game_win.box('|', '-')
final_time = (Time.now - @start_time).to_i
@game_win.attron(color_pair(4) | A_BOLD)
draw_centered(-2, "GAME OVER")
@game_win.attron(color_pair(4) | A_BOLD)
draw_centered(-1, "Cause of Death: #{@death_reason}")
@game_win.attroff(color_pair(4) | A_BOLD)
draw_centered(0, "Score: #{@score} | X Collected: #{@cleaners_collected} | Time: #{final_time}s")
draw_centered(1, "R to Start Over or Q to Exit")
@game_win.refresh
stdscr.timeout = -1
loop do
case stdscr.getch
when 'r', 'R' then return true
when 'q', 'Q' then return false
end
end
end
def draw_centered(y_offset, text)
@game_win.setpos(@max_y / 2 + y_offset, [0, (@max_x / 2) - (text.length / 2)].max)
@game_win.addstr(text)
end
end
SnakeGame.new.run
Login to reply
Replies (2)
Parabéns amigo. Foque em ferremantas produtivas, jogos são uma grande perda de tempo, nossa vida é muito curta. Tanto consumir jogos quanto produzir jogos é perda de tempo, mesmo que ganhe dinheiro com isso. Faço analogia de jogos com bebidas alcólicas e drogas, se consumir está ferrado se vender vai ferrar outros. Quanto a liguagem, já que esta aprendendo rust do zero, no lugar dedique seu tempo a apreder assembly de processadores sisc como arm e risc-v que são o futuro da computação e se você dominar isso poderá fazer qualquer coisa que quiser. Linguagem de alto nível te aprisiona em bibliotecas mal otimizadas e te distancia do conhecimento pleno da máquina.
Relaxa, Games fazem bem!