petli 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.
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: []