petli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b61cbff590146e315f4361603b20a21e0f42cb5ce939ace8b937a821ec59e79
4
+ data.tar.gz: 3c902c59a736c9c0500caa56408debc5acb64b2436b3572145e09b4ea5b3a097
5
+ SHA512:
6
+ metadata.gz: a93f33fa7df2d80631d715f63491bef2487752d6e3442ab4acf72c139bda03e0cb32c8853ee7529de7fe8e3ff27bd6168171e4e48310d9457f5f9bf00508b859
7
+ data.tar.gz: 334c94c480d5d0026427bdc3884d26cbe277f73c63c2329172706a6dfaa1d0d8dd5f9d2c9a6ad9bad19684ba792ab4e430759f8e8e5876853587cf3db64470d6
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ petli (0.0.1)
5
+ tty-box (~> 0.7.0)
6
+ tty-color (~> 0.6.0)
7
+ tty-config (~> 0.5.0)
8
+ tty-cursor (~> 0.7.1)
9
+ tty-reader (~> 0.9.0)
10
+ tty-screen (~> 0.8.1)
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ pastel (0.8.0)
16
+ tty-color (~> 0.5)
17
+ strings (0.2.1)
18
+ strings-ansi (~> 0.2)
19
+ unicode-display_width (>= 1.5, < 3.0)
20
+ unicode_utils (~> 1.4)
21
+ strings-ansi (0.2.0)
22
+ tty-box (0.7.0)
23
+ pastel (~> 0.8)
24
+ strings (~> 0.2.0)
25
+ tty-cursor (~> 0.7)
26
+ tty-color (0.6.0)
27
+ tty-config (0.5.0)
28
+ tty-cursor (0.7.1)
29
+ tty-reader (0.9.0)
30
+ tty-cursor (~> 0.7)
31
+ tty-screen (~> 0.8)
32
+ wisper (~> 2.0)
33
+ tty-screen (0.8.1)
34
+ unicode-display_width (2.0.0)
35
+ unicode_utils (1.4.0)
36
+ wisper (2.0.1)
37
+
38
+ PLATFORMS
39
+ arm64-darwin-20
40
+
41
+ DEPENDENCIES
42
+ petli!
43
+
44
+ BUNDLED WITH
45
+ 2.2.3
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright 2021 Tim Anema
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ PETLI
2
+ -----
3
+
4
+ Pet line interface (look I know it should have been command line pet like Clipet? Clet? those just don't sound great)
5
+
6
+ This is a little pet that will live in your console. You must feed it, clean up after it, and play with it to keep it happy!
7
+
8
+ ![image](https://user-images.githubusercontent.com/463193/127347754-a07f71a0-b8c4-4d73-a24f-0bf78fca22b9.png)
9
+
10
+ ### Running the app
11
+
12
+ ```bash
13
+ $> bundle install
14
+ $> rake install
15
+ $> petli
16
+ ```
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'irb'
3
+ require 'irb/completion'
4
+ require_relative '../lib/petli'
5
+ IRB.start
data/bin/petli ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'petli'
3
+ require 'optparse'
4
+
5
+ options = {}
6
+ OptionParser.new do |opts|
7
+ opts.banner = "Usage: petli [options]"
8
+ opts.on("-r", "--reset", "Reset button to start over again") {|v| options[:reset] = v }
9
+ opts.on("-s", "--status", "Dump pet status") {|v| options[:status] = v}
10
+ opts.on("-b", "--bread", "Feed your pet bread without viewing") {|v| options[:bread] = v}
11
+ opts.on("-c", "--candy", "Feed your pet candy without viewing") {|v| options[:candy] = v}
12
+ opts.on("-m", "--medicine", "Feed your pet candy without viewing") {|v| options[:medicine] = v}
13
+ opts.on("-l", "--clean", "Clean up any 'dirt' without viewing") {|v| options[:clean] = v}
14
+ opts.on("-p", "--path [PATH]", "path to your pet data (defaults to system config dir)") do |v|
15
+ $petlidboverride = v
16
+ end
17
+ end.parse!
18
+
19
+ pet = Petli::Pet.new
20
+ pet.display # force pet to update first
21
+
22
+ if options[:reset]
23
+ Petli::DB.clear
24
+ puts "Goodbye. I hope you love your next pet!"
25
+ elsif options[:status]
26
+ puts Petli::DB.dump
27
+ elsif options[:bread]
28
+ pet.feed(food: :bread)
29
+ elsif options[:candy]
30
+ pet.feed(food: :candy)
31
+ elsif options[:medicine]
32
+ pet.feed(food: :medicine)
33
+ elsif options[:clean]
34
+ pet.clean
35
+ else
36
+ Petli::HUD.new.run
37
+ end
@@ -0,0 +1,40 @@
1
+ {
2
+ "eyes": {
3
+ "default": ["O"],
4
+ "mezmerized": ["◉", "✧", "☯"],
5
+ "great": ["^", "O"],
6
+ "happy": ["O", "o"],
7
+ "tired": ["ළ", "ತ"],
8
+ "annoyed": ["⊜", "ಠ"],
9
+ "angry": ["ಠ"],
10
+ "embarassed": ["ಥ"],
11
+ "eating": ["O", ["ᗒ", "ᗕ"]],
12
+ "sick": [["⨴","⨵"],"ꔸ"]
13
+ },
14
+ "mouth": {
15
+ "default": ["_", "."],
16
+ "mezmerized": ["ᴥ", "O"],
17
+ "great": ["▽", "‿"],
18
+ "happy": ["o", "_"],
19
+ "tired": ["-", "."],
20
+ "annoyed": ["_", "-"],
21
+ "angry": ["益", "෴"],
22
+ "embarassed": ["◠", "-"],
23
+ "eating": ["▽", "‿"],
24
+ "sick": ["▱","﹃"]
25
+ },
26
+ "arms": {
27
+ "default": [["ᕠ", "ᕤ"], ["ᕦ", "ᕡ"]],
28
+ "plain": ["_", "~"],
29
+ "tired": ["_", "~"],
30
+ "embarassed": [["乁","ㄏ"], "_"],
31
+ "eating": [["\\", "/"], "_"],
32
+ "sick": ["_", "~"]
33
+ },
34
+ "head": {
35
+ "default": [["(", ")"]]
36
+ },
37
+ "bread": "ʕ=ʔ",
38
+ "candy": "▷☯◁",
39
+ "medicine": "◖◻◗"
40
+ }
data/data/face_ref ADDED
@@ -0,0 +1,73 @@
1
+ ☠⚕
2
+ (o . o)
3
+ (o _ o)
4
+ (o _ O)
5
+ (O _ O)
6
+ (* ^ *)
7
+ (O _ o)
8
+ (ʘ _ ʘ)
9
+ (u _ u)
10
+ (^ _ ^)
11
+ (ಠ _ ಠ)
12
+ (^ o ^)
13
+ (◉ ᴥ ◉)
14
+ (o O o)
15
+ (o - o)
16
+ (- _ -)
17
+ (T _ T)
18
+ (ಥ _ ಥ)
19
+ (ಠ 益ಠ)
20
+ (ಠ ෴ ಠ)
21
+ (ᴗ □ ᴗ)
22
+ ☆゚☆゚☆゚☆゚
23
+ (o ‿ o)
24
+ (ᗒ o ᗕ)
25
+ (⇀ - ↼)
26
+ (⨴ _ ⨵)
27
+ (ꔸ _ ꔸ)
28
+ (ළ _ ළ) # tired
29
+ (ತ _ ತ)
30
+ (⊜ _ ⊜)
31
+ (✧ _ ✧)
32
+ (☯ _ ☯)
33
+
34
+ \(⇀ O ↼)/ ʕ=ʔ
35
+ _(O _ O)_ =ʔ
36
+ \(⇀ O ↼)/ =ʔ
37
+ _(O _ O)_ ʔ
38
+ \(⇀ O ↼)/ ʔ
39
+ _(O _ O)_
40
+ (◉ ᴥ ◉)
41
+ (o O o)
42
+
43
+ ξꔸ ꔸ)
44
+
45
+ ☛(O _ O)☛
46
+ ☚(O _ O)☚
47
+ 乁(O _ O)ㄏ
48
+ _(O _ O)_
49
+ ᕠ(O _ O)ᕤ
50
+ ᕦ(O _ O)ᕡ
51
+ \(O _ O)/
52
+ /(O _ O)\
53
+
54
+ ߍ೧
55
+ ༼ºل͟º༽
56
+
57
+ poop friend
58
+ ı ┐ ┌ ┌ ı ı ı ┌ ┐ ┐ ı ı
59
+ ༼ᵔ◡ᵔ༽ ༼ಠ益ಠ༽ ༼ᵔ◡ᵔ༽ ༼ಠ益ಠ༽
60
+
61
+ ⚀ ⚁ ⚂ ⚃ ⚄ ⚅
62
+
63
+ _
64
+ (O O)
65
+ |-|
66
+
67
+
68
+
69
+ ┌───┐
70
+ │ ☠ │
71
+ │ │
72
+ ```````
73
+
@@ -0,0 +1,137 @@
1
+ module Petli
2
+ require 'json'
3
+
4
+ class Animator
5
+ ANIMATIONS = {
6
+ egg: [" _\n / \\\n \\_/", " _\n((/ \\\n \\_/))", " _\n / \\))\n((\\_/"]*5,
7
+ egg_crack: [" _\n/ \\\n\\_‚/"," _\n/ \\\n\\_¡/"," _\n/ \\\n\\_¦/"," _\n/ ¡\\\n\\_ϟ/"," _\n/ ¦\\\n\\_ϟ/"," _\n/ ϟ\\\n\\_ϟ/","\n/ ϟ\\\n\\_ϟ/"," ☁\n/ ϟ\\\n\\_ϟ/"],
8
+ stand: ["\n\nahe m eha\n", "\n\nahe m eha\n"],
9
+ left: ["\n\nahe m e ha\n", "\n\nahe m e ha\n"],
10
+ right: ["\n\nah e m eha\n", "\n\nah e m eha\n"],
11
+ item: ["\n\nahe m eha fff\n", "\n\nahe m eha fff\n", "\n\nahe m eha fff\n", "\n\nahe m eha fff\n"],
12
+ eat: ["\n\nahe m eha fff\n", "\n\nahe m eha ff\n", "\n\nahe m eha ff\n", "\n\nahe m eha f\n", "\n\nahe m eha f\n", "\n\nahe m eha\n"],
13
+ walk: ["\n\n ahe m eha\n", "\n\nahe m e ha\n", "\n\nahe m eha\n", "\n\nah e m eha\n"],
14
+ hop: ["\n\n ahe m eha\n", "\nahe m e ha\n\n", "\n\nahe m e ha\n", "\n\nahe m eha\n", "\nah e m eha\n\n", "\n\nah e m eha\n"],
15
+ death: [" ┌───┐ \n │ ☠ │ \n │ │ \n ```````", " ┌───┐ \n │ ☠ │ \n │ │ \n '''''''"],
16
+ }
17
+
18
+ attr_writer :mood
19
+ attr_reader :action
20
+
21
+ def initialize(hatching:, mood:, action: :walk)
22
+ @frame = 0
23
+ @food = :bread
24
+ @mood = mood
25
+ @action = action
26
+ if hatching
27
+ @mood_stack = [:mezmerized, :mezmerized, :mezmerized, :mezmerized]
28
+ @action_stack = [:egg, :egg_crack, :stand, :stand]
29
+ else
30
+ @mood_stack = []
31
+ @action_stack = []
32
+ end
33
+ end
34
+
35
+ def step
36
+ self.add_frame
37
+ leftarm, rightarm = getit(:arms, 2)
38
+ lefteye, righteye = getit(:eyes, 2)
39
+ mouth = getit(:mouth)
40
+ lefthead, righthead = getit(:head, 2)
41
+ [leftarm,lefthead,lefteye,mouth,righteye,righthead,rightarm]
42
+
43
+ food_peices = self.food.split('')
44
+
45
+ ANIMATIONS[self.action][self.frame]
46
+ .sub('a', leftarm.to_s).sub('a', rightarm.to_s)
47
+ .sub('h', lefthead).sub('h', righthead)
48
+ .sub('e', lefteye).sub('e', righteye)
49
+ .sub('m', mouth)
50
+ .reverse
51
+ .sub('f', food_peices[2]).sub('f', food_peices[1]).sub('f', food_peices[0])
52
+ .reverse
53
+ end
54
+
55
+ def eat(food: :bread, &block)
56
+ @busy = true
57
+ @frame = 0
58
+ @food = food
59
+ @action_stack = [:item, :eat, :stand, :stand, :stand, :stand]
60
+ @mood_stack = [:mezmerized, :eating, :eating, :mezmerized, :mezmerized]
61
+ @on_complete = block
62
+ end
63
+
64
+ def celebrate
65
+ @action_stack = [:stand, :stand, :stand, :stand]
66
+ @mood_stack = [:mezmerized, :mezmerized, :mezmerized, :mezmerized]
67
+ @frame = 0
68
+ end
69
+
70
+ def embarass
71
+ @action_stack = [:stand, :stand, :stand, :stand]
72
+ @mood_stack = [:embarassed, :embarassed, :embarassed, :embarassed]
73
+ @frame = 0
74
+ end
75
+
76
+ def busy?
77
+ @busy
78
+ end
79
+
80
+ def action=(act)
81
+ @action = act
82
+ @frame = 0
83
+ end
84
+
85
+ def action
86
+ @action_stack.count > 0 ? @action_stack[0] : @action
87
+ end
88
+
89
+ def mood
90
+ @mood_stack.count > 0 ? @mood_stack[0] : @mood
91
+ end
92
+
93
+ private
94
+
95
+ def add_frame
96
+ frame_count = ANIMATIONS[self.action].count
97
+ @frame += 1
98
+ if self.frame == frame_count
99
+ @frame = 0
100
+ @action_stack.shift if @action_stack.count > 0
101
+ @mood_stack.shift if @mood_stack.count > 0
102
+ if @action_stack.count == 0 && @mood_stack.count == 0
103
+ @busy = false
104
+ @on_complete.call unless @on_complete.nil?
105
+ @on_complete = nil
106
+ end
107
+ end
108
+ end
109
+
110
+ def food
111
+ self.data[@food]
112
+ end
113
+
114
+ def frame # to control framerate
115
+ (@frame/3).ceil
116
+ end
117
+
118
+ def getit(part, vals=1)
119
+ mood_part = self.data[part][self.mood] || self.data[part][:default]
120
+ part_frame = self.frame % mood_part.count
121
+ result = mood_part[part_frame]
122
+ if vals == 2 && result.is_a?(String)
123
+ [result, result]
124
+ else
125
+ result
126
+ end
127
+ end
128
+
129
+ def data
130
+ @data ||= JSON.parse(
131
+ File.read(File.expand_path('../../data/character.json', __dir__)),
132
+ {:symbolize_names => true}
133
+ )
134
+ end
135
+ end
136
+ end
137
+
data/lib/petli/db.rb ADDED
@@ -0,0 +1,78 @@
1
+ module Petli
2
+ require "pstore"
3
+ require "forwardable"
4
+ require "tty-platform"
5
+ require 'fileutils'
6
+
7
+ class DB
8
+ module Attributes
9
+ def db_attr(name, default: nil, readonly: false)
10
+ name = name.to_sym
11
+ define_method(name) { DB.get(name, default) }
12
+ define_method("#{name}=".to_sym) { |val| DB.set(name => val) } unless readonly
13
+ end
14
+ end
15
+
16
+ extend SingleForwardable
17
+ def_delegators :new, :keys, :exists?, :set, :get, :del, :clear, :dump
18
+
19
+ attr_reader :db
20
+
21
+ def initialize(path: "petli.pet")
22
+ config_path = if !$petlidboverride.nil?
23
+ $petlidboverride
24
+ elsif TTY::Platform.windows?
25
+ File.join(ENV["APPDATA"], 'petli', 'data.pet')
26
+ elsif TTY::Platform.linux?
27
+ File.join(ENV["XDG_CONFIG_DIRS"] || '/etc/xdg', 'petli', 'data.pet')
28
+ elsif TTY::Platform.mac?
29
+ File.join(File.expand_path('~/Library/Application Support'), 'petli', 'data.pet')
30
+ end
31
+ FileUtils.mkdir_p(File.dirname(config_path))
32
+ @db = PStore.new(config_path)
33
+ end
34
+
35
+ def keys
36
+ db.transaction(true) { db.roots }
37
+ end
38
+
39
+ def exists?(key)
40
+ db.transaction(true) { db.root?(key) }
41
+ end
42
+
43
+ def set(**args)
44
+ db.transaction do
45
+ args.each {|key, val| val.nil? ? db.delete(key) : db[key] = val}
46
+ end
47
+ end
48
+
49
+ def get(key, default=nil)
50
+ val = db.transaction(true) { db[key] }
51
+ if val.nil?
52
+ val = default unless default.nil?
53
+ val = yield if block_given?
54
+ db.transaction { db[key] = val }
55
+ end
56
+ val
57
+ end
58
+
59
+ def del(*args)
60
+ db.transaction { args.each { |key| db.delete(key) } }
61
+ end
62
+
63
+ def clear
64
+ del(*keys)
65
+ end
66
+
67
+ def dump
68
+ require 'json'
69
+ begin
70
+ file = File.new(@db.path, mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT)
71
+ JSON.dump(Marshal::load(file.read))
72
+ ensure
73
+ file.close
74
+ end
75
+ end
76
+ end
77
+ end
78
+
data/lib/petli/hud.rb ADDED
@@ -0,0 +1,42 @@
1
+ require_relative '../tatty/game'
2
+
3
+ module Petli
4
+ class HUD < Tatty::Game
5
+ GAME_WIDTH = 28
6
+ GAME_HEIGHT = 13
7
+
8
+ def initialize
9
+ super()
10
+ @pet = Pet.new
11
+ Rooms.enter(@pet)
12
+ end
13
+
14
+ def keypress(event)
15
+ exit if event.value == "q"
16
+ return if @pet.busy? || @pet.dead?
17
+ Rooms.current.keypress(event)
18
+ end
19
+
20
+ def draw
21
+ h, w = self.screen_size
22
+ left, top = ((w-GAME_WIDTH)/2).round, ((h-GAME_HEIGHT)/2).round
23
+ render_box(
24
+ title: {
25
+ top_left: " Petli ",
26
+ bottom_right: " #{@pet.lifetime} days ",
27
+ },
28
+ width: GAME_WIDTH,
29
+ height: GAME_HEIGHT,
30
+ left: left,
31
+ top: top,
32
+ )
33
+ Rooms.current.draw(self, left, top)
34
+ render_at(left+1, top+1, status_bar)
35
+ render_at(left+1, top+GAME_HEIGHT-2, Rooms.current.action_bar)
36
+ end
37
+
38
+ def status_bar
39
+ "#{"♥"*@pet.health}#{"♡"*(10-@pet.health)} #{"☺"*(10-@pet.happiness)}#{"☻"*@pet.happiness}"
40
+ end
41
+ end
42
+ end
data/lib/petli/pet.rb ADDED
@@ -0,0 +1,129 @@
1
+ module Petli
2
+ class Pet
3
+ include Watch
4
+ extend DB::Attributes
5
+
6
+ MOOD_SCALE = [:angry, :annoyed, :normal, :happy, :great]
7
+
8
+ db_attr :birth, default: Time.now, readonly: true
9
+ db_attr :health, default: 5
10
+ db_attr :mood, default: :normal
11
+ db_attr :happiness, default: 5
12
+ db_attr :sick, default: 0
13
+ db_attr :died_at
14
+ db_attr :last_play, default: Time.now
15
+ db_attr :last_meal, default: Time.now
16
+ db_attr :poops, default: []
17
+
18
+ def initialize
19
+ super()
20
+ @animation = Animator.new(
21
+ hatching: (Time.now - self.birth) < 10,
22
+ mood: self.mood,
23
+ action: self.died_at.nil? ? :walk : :death
24
+ )
25
+ end
26
+
27
+ def reset
28
+ @animation.action = :walk
29
+ end
30
+
31
+ def display
32
+ return @animation.step if self.dead?
33
+
34
+ if hours_since(self.last_meal) > 1
35
+ hours_past = hours_since(self.last_meal)
36
+ self.last_meal = Time.now
37
+ (0...hours_past).each do |i|
38
+ self.health = [1, self.health-1].max
39
+ self.happiness = [1, self.happiness-1].max
40
+ self.poop(hours_past - i) if rand <= 0.8
41
+ end
42
+ self.sick = self.poops.filter{|poop| hours_since(poop.hatch) > 1 }.count
43
+ end
44
+
45
+ if hours_since(self.last_play) > 1
46
+ hours_past = hours_since(self.last_play)
47
+ self.last_play = Time.now
48
+ (0...hours_past).each do
49
+ self.happiness = [1, self.happiness-1].max
50
+ end
51
+ end
52
+
53
+ self.happiness = [self.happiness, 5].min if self.health <= 3
54
+ self.mood = MOOD_SCALE[((self.happiness.to_f/10.0)*(MOOD_SCALE.count - 1)).floor ]
55
+
56
+ self.check_if_dead
57
+
58
+ @animation.mood = self.sick > 0 ? :sick : self.mood
59
+ @animation.step
60
+ end
61
+
62
+ def check_if_dead
63
+ not_happy = self.happiness <= 1
64
+ not_healthy = self.health <= 1
65
+ is_sick = self.sick > 2
66
+ too_much_time = days_since(self.last_meal) >= 1
67
+
68
+ if not_happy && not_healthy && (is_sick || too_much_time)
69
+ self.died_at = Time.now
70
+ @animation.action = :death
71
+ end
72
+ end
73
+
74
+ def feed(food: :bread)
75
+ return self.embarass if ((food == :medicine && self.sick <= 0) || (self.health == 10 && food != :medicine))
76
+ self.last_meal = Time.now unless food == :medicine
77
+ @animation.eat(food: food) do
78
+ self.health = [10, self.health+1].min unless food == :medicine
79
+ self.happiness = [10, self.happiness+1].min if food == :candy
80
+ self.sick = [0, self.sick - 1].max if food == :medicine
81
+ end
82
+ end
83
+
84
+ def win
85
+ self.happiness = [10, self.happiness+1].min
86
+ end
87
+
88
+ def lose
89
+ # todo I dont really want it to become more unhappy if they lose
90
+ end
91
+
92
+ def play(game: :dice)
93
+ self.last_play = Time.now
94
+ @animation.action = :stand
95
+ end
96
+
97
+ def poop(hours_ago)
98
+ self.poops = self.poops + [Poop.new(hours_ago, *Poop::LOCATIONS[self.poops.count])] if self.poops.count < Poop::LOCATIONS.count
99
+ end
100
+
101
+ def clean
102
+ self.poops = []
103
+ end
104
+
105
+ def busy?
106
+ @animation.busy?
107
+ end
108
+
109
+ def dead?
110
+ @animation.action == :death
111
+ end
112
+
113
+ def celebrate
114
+ @animation.celebrate
115
+ end
116
+
117
+ def embarass
118
+ @animation.embarass
119
+ end
120
+
121
+ def lifetime
122
+ if self.died_at.nil?
123
+ days_since(self.birth).to_i
124
+ else
125
+ days_since(self.birth, self.died_at).to_i
126
+ end
127
+ end
128
+ end
129
+ end
data/lib/petli/poop.rb ADDED
@@ -0,0 +1,26 @@
1
+ module Petli
2
+ class Poop
3
+ include Watch
4
+
5
+ LOCATIONS = [[1,1], [1,4], [1,7], [11,1], [11,7], [20,1], [20,4], [20,7]]
6
+ ANIMATION = ["ı ı ı\n༼ᵔ◡ᵔ༽", "ϟ ϟ ϟ\n༼ಠ益ಠ༽"]
7
+
8
+ attr_accessor :hatch
9
+
10
+ def initialize(hrsago)
11
+ @frame = 0
12
+ self.hatch = hours_ago(hrsago)
13
+ end
14
+
15
+ def step
16
+ @frame += 1
17
+ anim_fram = (@frame/2).ceil
18
+ @frame = 0 if anim_fram >= ANIMATION.count
19
+ ANIMATION[anim_fram]
20
+ end
21
+
22
+ def to_json(opts)
23
+ self.hatch.to_json
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,54 @@
1
+ module Petli
2
+ module Rooms
3
+ class Dice < Room
4
+ Symbols = %w(⚀ ⚁ ⚂ ⚃ ⚄ ⚅)
5
+
6
+ def initialize(pet)
7
+ super(pet)
8
+ @value = rand(1..6)
9
+ @countdown = -1
10
+ end
11
+
12
+ def actions
13
+ %w(higher lower)
14
+ end
15
+
16
+ def enter
17
+ pet.play(game: :guess)
18
+ end
19
+
20
+ def leave
21
+ pet.reset
22
+ end
23
+
24
+ def roll
25
+ end
26
+
27
+ def keypress(event)
28
+ return if event.value != "h" and event.value != "l"
29
+ @pickedhigher = event.value == "h"
30
+ @pick = (1..6).to_a.sample
31
+ @won = (event.value == "h" && @pick > @value) || (event.value == "l" && @pick < @value)
32
+ @won ? @pet.celebrate : @pet.embarass
33
+ @countdown = 20
34
+ end
35
+
36
+ def draw(ctx, ox, oy)
37
+ ctx.render_at(ox+9, oy+4, @pet.display)
38
+ if @countdown == -1
39
+ ctx.render_at(ox+4, oy+6, @value.to_s)
40
+ ctx.render_at(ox+23, oy+6, Symbols[(0..5).to_a.sample])
41
+ elsif @countdown == 0
42
+ @won ? @pet.win : @pet.lose
43
+ goto("main")
44
+ else
45
+ ctx.render_at(ox+4, oy+5, "▲") if @pickedhigher
46
+ ctx.render_at(ox+4, oy+6, @value.to_s)
47
+ ctx.render_at(ox+23, oy+6, Symbols[@pick-1])
48
+ ctx.render_at(ox+4, oy+7, "▼") unless @pickedhigher
49
+ @countdown -= 1
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ module Petli
2
+ module Rooms
3
+ class Feed < Room
4
+ def actions
5
+ %w(bread snack med)
6
+ end
7
+
8
+ def keypress(event)
9
+ if event.value == "b"
10
+ @pet.feed(food: :bread)
11
+ elsif event.value == "s"
12
+ @pet.feed(food: :candy)
13
+ elsif event.value == "m"
14
+ @pet.feed(food: :medicine)
15
+ end
16
+ goto("main")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Petli
2
+ module Rooms
3
+ class Guess < Room
4
+ def initialize(pet)
5
+ super(pet)
6
+ @left = true
7
+ @countdown = -1
8
+ end
9
+
10
+ def actions
11
+ %w(left right)
12
+ end
13
+
14
+ def enter
15
+ pet.play(game: :guess)
16
+ end
17
+
18
+ def leave
19
+ pet.reset
20
+ end
21
+
22
+ def keypress(event)
23
+ return if event.value != "l" and event.value != "r"
24
+ @petpickedleft = rand(1..2) == 1
25
+ @pickedleft = event.value == "l"
26
+ (@petpickedleft == @pickedleft) ? @pet.celebrate : @pet.embarass
27
+ @countdown = 20
28
+ end
29
+
30
+ def draw(ctx, ox, oy)
31
+ ctx.render_at(ox+9, oy+4, @pet.display)
32
+ if @countdown == -1
33
+ ctx.render_at(ox+4, oy+5, "☟") if @left
34
+ ctx.render_at(ox+23, oy+5, "☟") unless @left
35
+ ctx.render_at(ox+4, oy+6, "▒") if @left
36
+ ctx.render_at(ox+23, oy+6, "▒") unless @left
37
+ @left = !@left
38
+ elsif @countdown == 0
39
+ (@petpickedleft == @pickedleft) ? @pet.win : @pet.lose
40
+ goto("main")
41
+ else
42
+ ctx.render_at(ox+4, oy+5, "☟") if @pickedleft
43
+ ctx.render_at(ox+23, oy+5, "☟") unless @pickedleft
44
+ ctx.render_at(ox+4, oy+6, "▒") if @petpickedleft
45
+ ctx.render_at(ox+23, oy+6, "▒") unless @petpickedleft
46
+ @countdown -= 1
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ module Petli
2
+ module Rooms
3
+ class Main < Room
4
+ def actions
5
+ acts = %w(play feed)
6
+ acts << "clean" if @pet.poops.count > 0
7
+ acts
8
+ end
9
+
10
+ def keypress(event)
11
+ return goto("feed") if event.value == "f"
12
+ return @pet.clean if event.value == "c"
13
+ return goto("play") if event.value == "p"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Petli
2
+ module Rooms
3
+ class Play < Room
4
+ def actions
5
+ %w(guess dice)
6
+ end
7
+
8
+ def keypress(event)
9
+ return goto("dice") if event.value == "d"
10
+ return goto("guess") if event.value == "g"
11
+ goto("main")
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,69 @@
1
+ module Petli
2
+ module Rooms
3
+ class Room
4
+ attr_reader :pet
5
+
6
+ def initialize(pet)
7
+ @pet = pet
8
+ end
9
+
10
+ def enter
11
+ end
12
+
13
+ def goto(room_name)
14
+ klass = ::Petli.const_get("Rooms::#{room_name.capitalize}")
15
+ self.leave unless klass.nil?
16
+ ::Petli::Rooms.goto(klass)
17
+ end
18
+
19
+ def leave
20
+ end
21
+
22
+ def actions
23
+ %w()
24
+ end
25
+
26
+ def action_bar
27
+ return "" if @pet.dead?
28
+ self.actions.map {|a| "[#{a[0]}]#{a[1..]}"}.join(" ")
29
+ end
30
+
31
+ def keypress(event)
32
+ end
33
+
34
+ def draw(ctx, ox, oy)
35
+ poops = @pet.poops
36
+ poops.each_with_index do |poop, i|
37
+ x, y = Poop::LOCATIONS[i]
38
+ ctx.render_at(ox+1+x, oy+1+y, poop.step)
39
+ end
40
+ @pet.poops = poops
41
+ ctx.render_at(ox+9, oy+4, @pet.display)
42
+ sick = @pet.sick
43
+ if sick > 0 && !@pet.dead?
44
+ ctx.render_at(ox+11-sick, oy+4, "[#{'!'*sick}SICK#{'!'*sick}]")
45
+ end
46
+ end
47
+ end
48
+
49
+ autoload :Main, "petli/rooms/main"
50
+ autoload :Feed, "petli/rooms/feed"
51
+ autoload :Play, "petli/rooms/play"
52
+ autoload :Guess, "petli/rooms/guess"
53
+ autoload :Dice, "petli/rooms/dice"
54
+
55
+ def self.current
56
+ @room
57
+ end
58
+
59
+ def self.enter(pet)
60
+ @pet = pet
61
+ goto(Main)
62
+ end
63
+
64
+ def self.goto(klass)
65
+ @room = klass.new(@pet)
66
+ @room.enter
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Petli
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ module Petli
2
+ DAYS_DIV = 86400
3
+ HOURS_DIV = 3600
4
+ MINS_DIV = 60
5
+
6
+ module Watch
7
+ require 'time'
8
+ private
9
+
10
+ def days_since(last, now=Time.now)
11
+ time_elapsed(last, DAYS_DIV, now)
12
+ end
13
+
14
+ def hours_since(last)
15
+ time_elapsed(last, HOURS_DIV)
16
+ end
17
+
18
+ def hours_ago(hrs)
19
+ Time.now - (hrs * 3600)
20
+ end
21
+
22
+ def mins_since(last)
23
+ time_elapsed(last, MINS_DIV)
24
+ end
25
+
26
+ def time_elapsed(last, div, now=Time.now)
27
+ ((now - Time.parse(last.to_s)) / div)
28
+ end
29
+ end
30
+ end
data/lib/petli.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Petli
2
+ autoload :Animator, "petli/animator"
3
+ autoload :DB, "petli/db"
4
+ autoload :HUD, "petli/hud"
5
+ autoload :Pet, "petli/pet"
6
+ autoload :Poop, "petli/poop"
7
+ autoload :Rooms, "petli/rooms"
8
+ autoload :Watch, "petli/watch"
9
+ autoload :Version, "petli/version"
10
+ end
data/lib/tatty/game.rb ADDED
@@ -0,0 +1,80 @@
1
+ module Tatty
2
+ require 'tty-cursor'
3
+ require 'tty-config'
4
+ require 'tty-screen'
5
+ require 'tty-reader'
6
+ require 'tty-box'
7
+
8
+ class Game
9
+ attr_accessor :framerate, :config
10
+
11
+ def initialize
12
+ @config = TTY::Config.new
13
+ @cursor = TTY::Cursor
14
+ @reader = TTY::Reader.new
15
+ @framerate = 0.1
16
+ @buffer = ""
17
+
18
+ @reader.on(:keypress) do |event|
19
+ self.keypress(event)
20
+ end
21
+ end
22
+
23
+ def screen_size
24
+ TTY::Screen.size
25
+ end
26
+
27
+ def move_to(x, y)
28
+ render @cursor.move_to(x, y)
29
+ end
30
+
31
+ def render(out)
32
+ @buffer += out
33
+ end
34
+
35
+ def render_at(x, y, out)
36
+ out.to_s.each_line do |line|
37
+ render @cursor.move_to(x, y)
38
+ render line
39
+ y += 1
40
+ end
41
+ end
42
+
43
+ def keypress(event)
44
+ end
45
+
46
+ def draw
47
+ render "not implemented"
48
+ end
49
+
50
+ def run
51
+ begin
52
+ @cursor.invisible do
53
+ while true
54
+ @reader.read_keypress(nonblock: true)
55
+ last_buffer = @buffer
56
+ @buffer = ""
57
+ render @cursor.clear_screen
58
+ move_to(0, 0)
59
+ self.draw
60
+ if last_buffer != @buffer
61
+ print @buffer
62
+ end
63
+ sleep(@framerate)
64
+ end
65
+ end
66
+ rescue Interrupt => e
67
+ ensure
68
+ print @cursor.clear_screen
69
+ print @cursor.move_to(0, 0)
70
+ end
71
+ end
72
+
73
+ def render_box(*content, **kwargs, &block)
74
+ render @cursor.save
75
+ move_to(0, 0)
76
+ render TTY::Box.frame(*content, **kwargs, &block)
77
+ render @cursor.restore
78
+ end
79
+ end
80
+ end
data/petli.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'petli/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'petli'
8
+ s.version = Petli::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.author = 'Tim Anema'
11
+ s.email = ['timanema@gmail.com']
12
+ s.homepage = 'https://github.com/tanema/petli'
13
+ s.license = "MIT"
14
+ s.summary = 'A little pet in your console'
15
+ s.description = <<~HERE
16
+ A virtual pet that will live in your command line!
17
+ HERE
18
+ s.post_install_message = <<~HERE
19
+ I am so happy to meet you!
20
+ ʕ=ʔ 乁(☯ ᴥ ☯)ㄏ ▷☯◁
21
+ HERE
22
+ s.metadata = { "source_code_uri" => "https://github.com/tanema/petli" }
23
+
24
+ s.files = Dir.glob('{bin/*,lib/**/*,data/*,[A-Z]*}')
25
+ s.bindir = "bin"
26
+ s.executables = ["petli"]
27
+ s.require_paths = ['lib']
28
+
29
+ s.required_ruby_version = ">= 2.6"
30
+
31
+ s.add_dependency("tty-cursor", "~> 0.7.1")
32
+ s.add_dependency("tty-config", "~> 0.5.0")
33
+ s.add_dependency("tty-screen", "~> 0.8.1")
34
+ s.add_dependency("tty-reader", "~> 0.9.0")
35
+ s.add_dependency("tty-color", "~> 0.6.0")
36
+ s.add_dependency("tty-box", "~> 0.7.0")
37
+ s.add_dependency("tty-platform", "~> 0.3.0")
38
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: petli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Anema
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-cursor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-config
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-screen
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-reader
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-color
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-box
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.7.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.7.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: tty-platform
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.3.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.0
111
+ description: 'A virtual pet that will live in your command line!
112
+
113
+ '
114
+ email:
115
+ - timanema@gmail.com
116
+ executables:
117
+ - petli
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE
124
+ - README.md
125
+ - Rakefile
126
+ - bin/console
127
+ - bin/petli
128
+ - data/character.json
129
+ - data/face_ref
130
+ - lib/petli.rb
131
+ - lib/petli/animator.rb
132
+ - lib/petli/db.rb
133
+ - lib/petli/hud.rb
134
+ - lib/petli/pet.rb
135
+ - lib/petli/poop.rb
136
+ - lib/petli/rooms.rb
137
+ - lib/petli/rooms/dice.rb
138
+ - lib/petli/rooms/feed.rb
139
+ - lib/petli/rooms/guess.rb
140
+ - lib/petli/rooms/main.rb
141
+ - lib/petli/rooms/play.rb
142
+ - lib/petli/version.rb
143
+ - lib/petli/watch.rb
144
+ - lib/tatty/game.rb
145
+ - petli.gemspec
146
+ homepage: https://github.com/tanema/petli
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ source_code_uri: https://github.com/tanema/petli
151
+ post_install_message: |
152
+ I am so happy to meet you!
153
+ ʕ=ʔ 乁(☯ ᴥ ☯)ㄏ ▷☯◁
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '2.6'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubygems_version: 3.2.3
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: A little pet in your console
172
+ test_files: []