├── lib ├── piece.rb ├── player.rb ├── board.rb ├── game.rb ├── display.rb └── cursorable.rb └── README.md /lib/piece.rb: -------------------------------------------------------------------------------- 1 | class Piece 2 | def present? 3 | true 4 | end 5 | 6 | def to_s 7 | " x " 8 | end 9 | end 10 | 11 | class NullPiece 12 | def present? 13 | false 14 | end 15 | 16 | def to_s 17 | " " 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/player.rb: -------------------------------------------------------------------------------- 1 | require_relative "display" 2 | 3 | class Player 4 | def initialize(board) 5 | @display = Display.new(board) 6 | end 7 | 8 | def move 9 | result = nil 10 | until result 11 | @display.render 12 | result = @display.get_input 13 | end 14 | result 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/board.rb: -------------------------------------------------------------------------------- 1 | require_relative "piece" 2 | 3 | class Board 4 | def initialize 5 | @grid = Array.new(3) { Array.new(3) { NullPiece.new } } 6 | end 7 | 8 | def full? 9 | @grid.all? do |row| 10 | row.all? { |piece| piece.present? } 11 | end 12 | end 13 | 14 | def mark(pos) 15 | x, y = pos 16 | @grid[x][y] = Piece.new 17 | end 18 | 19 | def in_bounds?(pos) 20 | pos.all? { |x| x.between?(0, 2) } 21 | end 22 | 23 | def rows 24 | @grid 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/game.rb: -------------------------------------------------------------------------------- 1 | require_relative "board" 2 | require_relative "player" 3 | 4 | class Game 5 | def initialize 6 | @board = Board.new 7 | @player = Player.new(@board) 8 | end 9 | 10 | def run 11 | puts "Mark all the spaces on the board!" 12 | puts "WASD or arrow keys to move the cursor, enter or space to confirm." 13 | until @board.full? 14 | pos = @player.move 15 | @board.mark(pos) 16 | end 17 | puts "Hooray, the board is filled!" 18 | end 19 | end 20 | 21 | if __FILE__ == $PROGRAM_NAME 22 | Game.new.run 23 | end 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby Cursor Input 2 | 3 | This is just a quick repository I cooked up to demo cursor input for 4 | Ruby console games. The nuts and bolts of getting input were taken 5 | almost completely from [this gist][cursor-gist]. The rest is just a 6 | bare-bones game that I think encapsulates the core behavior in a pretty 7 | reasonable way. 8 | 9 | Clone the repo, `ruby lib/game.rb`, and fill some boxes! The most 10 | important bits live in the [display][display] and 11 | [cursorable][cursorable] files. 12 | 13 | [cursor-gist]: https://gist.github.com/acook/4190379 14 | [cursorable]: ./lib/cursorable.rb 15 | [display]: ./lib/display.rb 16 | -------------------------------------------------------------------------------- /lib/display.rb: -------------------------------------------------------------------------------- 1 | require "colorize" 2 | require_relative "cursorable" 3 | 4 | class Display 5 | include Cursorable 6 | 7 | def initialize(board) 8 | @board = board 9 | @cursor_pos = [0, 0] 10 | end 11 | 12 | def build_grid 13 | @board.rows.map.with_index do |row, i| 14 | build_row(row, i) 15 | end 16 | end 17 | 18 | def build_row(row, i) 19 | row.map.with_index do |piece, j| 20 | color_options = colors_for(i, j) 21 | piece.to_s.colorize(color_options) 22 | end 23 | end 24 | 25 | def colors_for(i, j) 26 | if [i, j] == @cursor_pos 27 | bg = :light_red 28 | elsif (i + j).odd? 29 | bg = :light_blue 30 | else 31 | bg = :blue 32 | end 33 | { background: bg, color: :white } 34 | end 35 | 36 | def render 37 | system("clear") 38 | puts "Fill the grid!" 39 | puts "Arrow keys, WASD, or vim to move, space or enter to confirm." 40 | build_grid.each { |row| puts row.join } 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/cursorable.rb: -------------------------------------------------------------------------------- 1 | require "io/console" 2 | 3 | module Cursorable 4 | KEYMAP = { 5 | " " => :space, 6 | "h" => :left, 7 | "j" => :down, 8 | "k" => :up, 9 | "l" => :right, 10 | "w" => :up, 11 | "a" => :left, 12 | "s" => :down, 13 | "d" => :right, 14 | "\t" => :tab, 15 | "\r" => :return, 16 | "\n" => :newline, 17 | "\e" => :escape, 18 | "\e[A" => :up, 19 | "\e[B" => :down, 20 | "\e[C" => :right, 21 | "\e[D" => :left, 22 | "\177" => :backspace, 23 | "\004" => :delete, 24 | "\u0003" => :ctrl_c, 25 | } 26 | 27 | MOVES = { 28 | left: [0, -1], 29 | right: [0, 1], 30 | up: [-1, 0], 31 | down: [1, 0] 32 | } 33 | 34 | def get_input 35 | key = KEYMAP[read_char] 36 | handle_key(key) 37 | end 38 | 39 | def handle_key(key) 40 | case key 41 | when :ctrl_c 42 | exit 0 43 | when :return, :space 44 | @cursor_pos 45 | when :left, :right, :up, :down 46 | update_pos(MOVES[key]) 47 | nil 48 | else 49 | puts key 50 | end 51 | end 52 | 53 | def read_char 54 | STDIN.echo = false 55 | STDIN.raw! 56 | 57 | input = STDIN.getc.chr 58 | if input == "\e" then 59 | input << STDIN.read_nonblock(3) rescue nil 60 | input << STDIN.read_nonblock(2) rescue nil 61 | end 62 | ensure 63 | STDIN.echo = true 64 | STDIN.cooked! 65 | 66 | return input 67 | end 68 | 69 | def update_pos(diff) 70 | new_pos = [@cursor_pos[0] + diff[0], @cursor_pos[1] + diff[1]] 71 | @cursor_pos = new_pos if @board.in_bounds?(new_pos) 72 | end 73 | end 74 | --------------------------------------------------------------------------------