#!/usr/bin/env ruby require 'curses' module MyHunt class Cell attr_accessor :mine, :open def initialize( mine: false, open: false ) @mine = mine @open = open end def explore @open = true @mine end end class Board attr_reader :width, :height, :field, :explorer_x, :explorer_y def initialize( number_of_mines = 20, height = 8, width = 16 ) @height = height @width = width @explorer_x = 0 @explorer_y = 0 @field = {} @width.times do |x| @height.times do |y| @field[[x, y]] = Cell.new( open: x.zero? && y.zero? ) end end placed_mines = 0 while placed_mines < number_of_mines coordinates = [rand( @width ), rand( @height )] next if coordinates == [0, 0] next if @field[ coordinates ].mine @field[ coordinates ].mine = true placed_mines += 1 end end def update_explorer_location( direction ) case direction when 'up' @explorer_y -= 1 if @explorer_y.positive? when 'right' @explorer_x += 1 if @explorer_x < (@width - 1) when 'down' @explorer_y += 1 if @explorer_y < (@height - 1) when 'left' @explorer_x -= 1 if @explorer_x.positive? when 'up-right' if @explorer_x < (@width - 1) && @explorer_y.positive? @explorer_x += 1 @explorer_y -= 1 end when 'down-right' if @explorer_x < (@width - 1) && @explorer_y < (@height - 1) @explorer_x += 1 @explorer_y += 1 end when 'down-left' if @explorer_x.positive? && @explorer_y < (@height - 1) @explorer_x -= 1 @explorer_y += 1 end when 'up-left' if @explorer_x.positive? && @explorer_y.positive? @explorer_x -= 1 @explorer_y -= 1 end end end def move( direction ) update_explorer_location( direction ) { dead: @field[[@explorer_x, @explorer_y]].explore, victory: @explorer_x == (@width - 1) && @explorer_y == (@height - 1) } end def count_nearby_mines counter = 0 [@explorer_x - 1, @explorer_x, @explorer_x + 1].each do |x| [@explorer_y - 1, @explorer_y, @explorer_y + 1].each do |y| next if x.negative? next if y.negative? next if x == @width next if y == @height next if x == @explorer_x && y == @explorer_y counter += 1 if @field[[x, y]].mine end end counter end end end field = MyHunt::Board.new def display_curses( field, state = { dead: false, victory: false } ) Curses.setpos 0, 0 if state[:dead] Curses.addstr 'You died! :(' elsif state[:victory] Curses.addstr 'You won!! :)' else Curses.addstr "nearby: #{field.count_nearby_mines}" end field.height.times do |y| field.width.times do |x| Curses.setpos y + 2, x if [field.explorer_x, field.explorer_y] == [x, y] Curses.addch 'o' elsif [field.width, field.height] == [x + 1, y + 1] Curses.addch '_' elsif field.field[[x, y]].open Curses.addch field.field[[x, y]].mine ? '*' : ' ' else Curses.addch 'H' end end end Curses.refresh end Curses.init_screen Curses.cbreak Curses.noecho Curses.stdscr.keypad = true at_exit do Curses.close_screen end finished = false display_curses( field ) loop do direction = '' ch = Curses.getch if finished begin case ch when 'r' field = MyHunt::Board.new finished = false else break end rescue Curses::RequestDeniedError 'prout' end else begin case ch when '1' direction = 'down-left' when '2', Curses::KEY_DOWN direction = 'down' when '3' direction = 'down-right' when '4', Curses::KEY_LEFT direction = 'left' when '6', Curses::KEY_RIGHT direction = 'right' when '7' direction = 'up-left' when '8', Curses::KEY_UP direction = 'up' when '9' direction = 'up-right' when 'q' break end rescue Curses::RequestDeniedError 'prout' end end state = field.move( direction ) finished = state[:dead] || state[:victory] display_curses( field, state ) end