snakegame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: