snakegame 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1374a26425313391ad96b5778f0bdb4e033e8348
4
+ data.tar.gz: a9f34aa408f67733705f5e1d4dcc503f0925c3ef
5
+ SHA512:
6
+ metadata.gz: 34ab19addd0ec055b412f0a1727def6c77d3d38108c85fd4297b8e178008382a43a0f1e9e8f105b7cdb20d3c6dce19e8202fb024dd5d2821e36ab679ec891df3
7
+ data.tar.gz: 0dce5b151508924eff1136f6110dd25e60e75f4a673bab0a9a034ccca184aa90ce1e2bc07b0d8978a0f213c7a6098ac46aec2844bc5332fef2700b624b979c65
@@ -0,0 +1 @@
1
+ 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'pry'
4
+ gem 'pry-doc'
5
+ gem 'curses', require: true
6
+ gem 'minitest'
7
+ gem 'rake'
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ coderay (1.1.0)
5
+ curses (1.0.1)
6
+ method_source (0.8.2)
7
+ minitest (5.8.1)
8
+ pry (0.10.2)
9
+ coderay (~> 1.1.0)
10
+ method_source (~> 0.8.1)
11
+ slop (~> 3.4)
12
+ pry-doc (0.8.0)
13
+ pry (~> 0.9)
14
+ yard (~> 0.8)
15
+ rake (10.4.2)
16
+ slop (3.6.0)
17
+ yard (0.8.7.6)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ curses
24
+ minitest
25
+ pry
26
+ pry-doc
27
+ rake
28
+
29
+ BUNDLED WITH
30
+ 1.10.5
@@ -0,0 +1,6 @@
1
+ require 'rake/testtask'
2
+ Rake::TestTask.new do |t|
3
+ t.libs << "test"
4
+ t.test_files = FileList['test/*.rb']
5
+ end
6
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'game'
4
+ Game.new.play
@@ -0,0 +1,61 @@
1
+ class Board
2
+ class CursesBoard
3
+ attr_accessor :win
4
+ def initialize
5
+ Curses.stdscr.keypad(true)
6
+ @win = Curses::Window.new(height, width, 0, 0)
7
+ Curses.stdscr.keypad(true)
8
+ @win.box('|', '-')
9
+ Curses.init_screen
10
+ Curses.curs_set(0)
11
+ Curses.crmode
12
+ Curses.noecho
13
+ Curses.stdscr.nodelay = true
14
+ end
15
+ end
16
+ attr_accessor :width, :height
17
+ def initialize(width: 60, height: 30)
18
+ @top_right = Point.new(width, 0)
19
+ @bottom_left = Point.new(0, height)
20
+
21
+ @width = width
22
+ @height = height
23
+ end
24
+
25
+ def initialize_snake
26
+ x, y = @width / 2, @height / 2
27
+ # TODO Make this adjust based on survival likelihood
28
+ return { starting_position: Point.new(x, y), vector: Point.new(0, 1) }
29
+ end
30
+
31
+ def is_legal?(positions)
32
+ positions.all? do |position|
33
+ position.x > @bottom_left.x &&
34
+ position.x < @top_right.x - 1 &&
35
+ position.y > @top_right.y &&
36
+ position.y < @bottom_left.y - 1
37
+ end
38
+ end
39
+
40
+ def draw(snake:, fruit:)
41
+ @curses_board ||= CursesBoard.new(@height, @width)
42
+ @curses_board.win.clear
43
+ @curses_board.win.box('|', '-')
44
+ Curses.init_screen
45
+
46
+ current_pos = snake.head
47
+ @curses_board.win.setpos(*current_pos.graphics_to_a)
48
+ @curses_board.win << snake.head_direction
49
+ snake.body.each do |body_part|
50
+ @curses_board.win.setpos(*body_part.graphics_to_a)
51
+ @curses_board.win << 'o'
52
+ end
53
+ @curses_board.win.setpos(*fruit.graphics_to_a)
54
+ @curses_board.win << '🍎'
55
+ @curses_board.win.refresh
56
+ end
57
+
58
+ def random_position
59
+ Point.new(Random.rand(width - 2) + 1, Random.rand(height - 2) + 1)
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ class Board
2
+ attr_accessor :width, :height
3
+ def initialize(width: 60, height: 30)
4
+ @top_right = Point.new(width, 0)
5
+ @bottom_left = Point.new(0, height)
6
+
7
+ @width = width
8
+ @height = height
9
+ @_game_board = Hash.new {|hash, key| hash[key] = {}}
10
+ @win = Curses::Window.new(height, width, 0, 0)
11
+ Curses.stdscr.keypad(true)
12
+ @win.box('|', '-')
13
+ Curses.init_screen
14
+ Curses.curs_set(0)
15
+ Curses.crmode
16
+ Curses.noecho
17
+ Curses.stdscr.nodelay = true
18
+ end
19
+
20
+ def initialize_snake
21
+ x, y = @width / 2, @height / 2
22
+ # TODO Make this adjust based on survival likelihood
23
+ @_game_board[x][y] = "^"
24
+ return { starting_position: Point.new(x, y), vector: Point.new(0, 1) }
25
+ end
26
+
27
+ def is_legal?(positions)
28
+ positions.all? do |position|
29
+ position.x > @bottom_left.x &&
30
+ position.x < @top_right.x - 1 &&
31
+ position.y > @top_right.y &&
32
+ position.y < @bottom_left.y - 1
33
+ end
34
+ end
35
+
36
+ def draw(snake:, fruit:)
37
+ @win.clear
38
+ @win.box('|', '-')
39
+ Curses.init_screen
40
+
41
+ current_pos = snake.head
42
+ @win.setpos(*current_pos.graphics_to_a)
43
+ @win << snake.head_direction
44
+ snake.body.each do |body_part|
45
+ @win.setpos(*body_part.graphics_to_a)
46
+ @win << 'o'
47
+ end
48
+ @win.setpos(*fruit.graphics_to_a)
49
+ @win << '🍎'
50
+ @win.refresh
51
+ end
52
+
53
+ def random_position
54
+ Point.new(Random.rand(width - 2) + 1, Random.rand(height - 2) + 1)
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'snake'
2
+ require_relative 'board'
3
+ require_relative 'point'
4
+ require 'curses'
5
+
6
+ class IllegalStateError < StandardError; end
7
+
8
+ class Game
9
+ def initialize
10
+ @board = Board.new
11
+ @snake = Snake.new(@board.initialize_snake)
12
+ end
13
+ def play
14
+ food = @board.random_position
15
+ while true
16
+ change = Curses.getch
17
+ @snake.process_direction(change)
18
+ begin
19
+ food = @snake.tick(food)
20
+ raise unless @board.is_legal?(@snake.positions)
21
+ rescue IllegalStateError
22
+ raise "You died!"
23
+ end
24
+ food ||= @board.random_position
25
+ @board.draw(snake: @snake, fruit: food)
26
+ sleep 0.1
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ class Point
2
+ attr_reader :x, :y
3
+ def initialize(x, y)
4
+ @x = x
5
+ @y = y
6
+ end
7
+
8
+ def +(other)
9
+ Point.new(x + other.x, y + other.y)
10
+ end
11
+
12
+ def to_a
13
+ [x, y]
14
+ end
15
+
16
+ # Yay y,x standard!
17
+ def graphics_to_a
18
+ [y, x]
19
+ end
20
+
21
+ def hash
22
+ [x.hash, y.hash].hash
23
+ end
24
+
25
+ def eql?(other)
26
+ x == other.x && y == other.y
27
+ end
28
+
29
+ def ==(other)
30
+ eql?(other)
31
+ end
32
+
33
+ def -(other)
34
+ Point.new(x - other.x, y - other.y)
35
+ end
36
+ end
@@ -0,0 +1,113 @@
1
+ require 'curses'
2
+ require 'forwardable'
3
+ require_relative 'point'
4
+ class Snake
5
+ class PreviousVectorRecord
6
+ attr_reader :vector, :remaining
7
+ def initialize(vector, remaining)
8
+ @vector = vector
9
+ @remaining = remaining
10
+ end
11
+
12
+ def decrement
13
+ @remaining -= 1
14
+ end
15
+
16
+ def increment
17
+ @remaining += 1
18
+ end
19
+
20
+ def valid?
21
+ @remaining > 0
22
+ end
23
+ end
24
+
25
+ class BodyPart
26
+ extend Forwardable
27
+ def_delegators(:@position, :x, :y, :graphics_to_a)
28
+ attr_accessor :position, :vector
29
+ def initialize(position, vector)
30
+ @position = position
31
+ @vector = vector
32
+ end
33
+ end
34
+ DIRECTION_HASH = {
35
+ # We are in graphics land, so 0, 0 is the origin,
36
+ # and the y axis goes down _positively_
37
+ Curses::KEY_UP => Point.new(0, -1),
38
+ Curses::KEY_DOWN => Point.new(0, 1),
39
+ Curses::KEY_RIGHT => Point.new(1, 0),
40
+ Curses::KEY_LEFT => Point.new(-1, 0)
41
+ }
42
+ attr_accessor :head, :body
43
+
44
+ def initialize(starting_position:, vector:)
45
+ @head = starting_position
46
+ @vector = vector
47
+ @current_direction = DIRECTION_HASH.invert[vector]
48
+ @body = []
49
+ @previous_vector_changes = {}
50
+ end
51
+
52
+ def positions
53
+ [@head] + @body
54
+ end
55
+
56
+ def add_body_part
57
+ if @body.empty?
58
+ @body << BodyPart.new(@head - @vector, @vector)
59
+ else
60
+ @body << BodyPart.new(@body.last.position - @body.last.vector, @body.last.vector)
61
+ end
62
+ @previous_vector_changes.values.each(&:increment)
63
+ end
64
+
65
+ def handle_food(food, next_head_position)
66
+ if next_head_position.eql? food
67
+ 5.times { add_body_part }
68
+ return nil
69
+ end
70
+ food
71
+ end
72
+
73
+ def update_body
74
+ @body.map do |part|
75
+ if new_vector_record = @previous_vector_changes[part.position]
76
+ part.vector = new_vector_record.vector
77
+ end
78
+ part.position = part.position + part.vector
79
+ end
80
+ end
81
+
82
+ def tick(food)
83
+ next_head_position = head + @vector
84
+ raise IllegalStateError if body.any? {|part| part.position == next_head_position }
85
+ food = handle_food(food, next_head_position)
86
+ update_body
87
+ @head = next_head_position
88
+ @previous_vector_changes.values.each(&:decrement)
89
+ @previous_vector_changes.select! { |key, val| val.valid? }
90
+ food
91
+ end
92
+
93
+ def process_direction(direction)
94
+ return if direction.nil?
95
+ @current_direction = direction if DIRECTION_HASH.keys.include?(direction)
96
+ @vector = DIRECTION_HASH[direction] || @vector
97
+ 5.times { add_body_part } if direction == 'a'
98
+ @previous_vector_changes[head] = PreviousVectorRecord.new(@vector, @body.length + 1)
99
+ end
100
+
101
+ def head_direction
102
+ case @current_direction
103
+ when Curses::KEY_UP
104
+ '^'
105
+ when Curses::KEY_DOWN
106
+ 'v'
107
+ when Curses::KEY_RIGHT
108
+ '>'
109
+ when Curses::KEY_LEFT
110
+ '<'
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class BoardTest < Minitest::Test
4
+ def board
5
+ @board ||= Board.new(width: 20, height: 20)
6
+ end
7
+ def test_is_legal?
8
+ refute board.is_legal?([Point.new(21, 21)])
9
+ assert board.is_legal?([Point.new(15, 15)])
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ describe Snake do
4
+ let(:point_one) { Point.new(5, 5) }
5
+ let(:point_two) { Point.new(3, 5) }
6
+ let(:snake) { Snake.new(starting_position: Point.new(5, 5), vector: Point.new(1, 1)) }
7
+ it 'tests handle_food with food' do
8
+ food = snake.handle_food(point_one, point_one)
9
+ assert food.nil?
10
+ assert snake.body.length > 1
11
+ end
12
+ it 'tests handle_food without food' do
13
+ food = snake.handle_food(point_one, point_two)
14
+ assert food == point_one
15
+ snake.body.length.must_equal 0
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require 'minitest'
2
+ require 'minitest/autorun' # the test runner
3
+
4
+ $LOAD_PATH << File.expand_path("../lib/")
5
+ require 'point'
6
+ require 'board'
7
+ require 'snake'
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snakegame
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Steger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Play snake!
14
+ email:
15
+ - mjsteger1@gmail.com
16
+ executables:
17
+ - snake
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".ruby-version"
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - Rakefile
25
+ - bin/snake
26
+ - board.rb
27
+ - lib/board.rb
28
+ - lib/game.rb
29
+ - lib/point.rb
30
+ - lib/snake.rb
31
+ - test/board_test.rb
32
+ - test/snake_test.rb
33
+ - test/test_helper.rb
34
+ homepage: http://stegerwerks.org
35
+ licenses: []
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 2.4.5
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: snake game with curses
57
+ test_files:
58
+ - test/board_test.rb
59
+ - test/snake_test.rb
60
+ - test/test_helper.rb
61
+ has_rdoc: