TwentyFortyEight 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +257 -1
- data/bin/2048 +3 -1
- data/bin/console +0 -4
- data/lib/TwentyFortyEight.rb +18 -9
- data/lib/TwentyFortyEight/dsl.rb +4 -0
- data/lib/TwentyFortyEight/logger.rb +9 -14
- data/lib/TwentyFortyEight/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fb1feb3e76c7900e15f9700547e6a5df69d6323
|
4
|
+
data.tar.gz: 9578d3cdea1b2af011db0f36d49b3bb2be6e35e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccfacf294d14e754d8636dc33929ee9f5c6392d9f1360e30978a87055e620d0d98cb0cb97c63e3bf53ae7226599619a52eaa4cf07db2e2e3c5a68c608b93389f
|
7
|
+
data.tar.gz: 4069736151f29122c3cef3c0394ac7cb4b1ef93f3467a42a3aad9a001fa0ad1433a1785f0f919a27d3b83f6e756677df7f2c1186cde441c0c6d5804ced283212
|
data/README.md
CHANGED
@@ -1 +1,257 @@
|
|
1
|
-
|
1
|
+
# TwentyFortyEight
|
2
|
+
Play a game of 2048 in your terminal, there are options that allow you to do various things such as log the moves of a game using `-l`, letting it auto-play endlessly using mode `endless`, and optionally record history using `-h` (only useful in `endless` mode).
|
3
|
+
|
4
|
+
The game is played using either the **arrow keys**, **W**, **A**, **S**, **D** or the vim keys: **K**, **H**, **J**, **L**. After a game has ended, you will be prompted to press either **Q** or **R**. pressing **Q** exits the command completely.
|
5
|
+
|
6
|
+
## Status
|
7
|
+
|
8
|
+
![Licence](https://img.shields.io/badge/license-MIT-E9573F.svg)
|
9
|
+
[![Gem Version](https://img.shields.io/gem/v/TwentyFortyEight.svg?colorB=E9573F&style=square)](rubygems.org/gems/TwentyFortyEight)
|
10
|
+
[![Issues](https://img.shields.io/github/issues/SidOfc/TwentyFortyEight.svg)](https://github.com/SidOfc/TwentyFortyEight/issues)
|
11
|
+
[![Build Status](https://img.shields.io/travis/SidOfc/TwentyFortyEight.svg)](https://travis-ci.org/SidOfc/TwentyFortyEight)
|
12
|
+
[![Coverage Status](https://img.shields.io/coveralls/SidOfc/TwentyFortyEight.svg)](https://coveralls.io/github/SidOfc/TwentyFortyEight?branch=master)
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
## Changelog
|
17
|
+
|
18
|
+
_dates are in dd-mm-yyyy format_
|
19
|
+
|
20
|
+
#### 08-12-2017 VERSION 0.2.2
|
21
|
+
|
22
|
+
- Logger no longer throws exceptions if no `logs` dir exists in the current working directory.
|
23
|
+
|
24
|
+
#### 24-03-2017 VERSION 0.2.1
|
25
|
+
|
26
|
+
- Fixed `TwentyFortyEight::Game#won?` method, it no longer causes an exception
|
27
|
+
- Added tests for core components
|
28
|
+
- Updated some gemspec info
|
29
|
+
- Added config files for travis CI and Coveralls
|
30
|
+
- Added more defaults
|
31
|
+
- `TwentyFortyEight::Board` now has `{ size: 4, fill: 0, empty: 0 }` as defaults
|
32
|
+
- `TwentyFortyEight::Cli` now has the _history_ option enabled by default in _endless_ mode
|
33
|
+
- `TwentyFortyEight::Logger` can now `load!` an existing log file
|
34
|
+
- Added `sequence` method to `TwentyFortyEight::Dsl` which allows you to either execute a sequence of moves completely, or not at all
|
35
|
+
|
36
|
+
#### 22-03-2017 VERSION 0.2.0
|
37
|
+
|
38
|
+
- Highscore is stored in a file
|
39
|
+
- each fieldsize has it's own highscore
|
40
|
+
- Added vim keykinds
|
41
|
+
- Added keybinds to the `--help` flag
|
42
|
+
- Only 'relevant' info is shown in a specified game mode
|
43
|
+
- Removed display of 'id' from the 'play' mode
|
44
|
+
- Removed solver option completely
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
Add this line to your application's Gemfile:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem 'TwentyFortyEight'
|
52
|
+
```
|
53
|
+
|
54
|
+
And then execute:
|
55
|
+
|
56
|
+
```
|
57
|
+
$ bundle
|
58
|
+
```
|
59
|
+
|
60
|
+
Or install it yourself as:
|
61
|
+
|
62
|
+
```
|
63
|
+
$ gem install TwentyFortyEight
|
64
|
+
```
|
65
|
+
|
66
|
+
## Usage
|
67
|
+
|
68
|
+
The game will store a highscore for each field size played. This means that playing a 4x4 will show a different highscore then playing a 5x5. The file in which this is stored can be found at `~/.2048`, it will contain these scores in the JSON format.
|
69
|
+
|
70
|
+
### From the commandline
|
71
|
+
|
72
|
+
To simply play a game:
|
73
|
+
|
74
|
+
```
|
75
|
+
$ 2048
|
76
|
+
```
|
77
|
+
|
78
|
+
To let it automatically play using the predefined order: `:left`, `:down`, `:right`, `:up`
|
79
|
+
|
80
|
+
```
|
81
|
+
$ 2048 play
|
82
|
+
```
|
83
|
+
|
84
|
+
Need help?
|
85
|
+
|
86
|
+
```
|
87
|
+
$ 2048 --help
|
88
|
+
```
|
89
|
+
|
90
|
+
### General
|
91
|
+
|
92
|
+
After installing the gem globally or in your application you'll have to `require` the gem before being able to use it.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
require 'TwentyFortyEight'
|
96
|
+
```
|
97
|
+
|
98
|
+
When the gem is loaded, you can proceed to call one of the following methods:
|
99
|
+
|
100
|
+
* `TwentyFortyEight#play options = { size: 4, fill: 0, empty: 0 }, &block`
|
101
|
+
* `TwentyFortyEight#endless options = { size: 4, fill: 0, empty: 0 }, &block`
|
102
|
+
|
103
|
+
The options can be a hash containing any of the options available to the CLI.
|
104
|
+
This means you can set the following keys:
|
105
|
+
|
106
|
+
* `:interactive` - A `Boolean` indicating wether or not to play the game manually (thereby printing the game as well regardless of `:verbose`)
|
107
|
+
* `:exclude` - An `Array` of directions to exclude by default when playing the game. Once the remaining moves become unavailable, all moves will be allowed for use to get unstuck
|
108
|
+
* `:only` - The opposite of `:exclude`
|
109
|
+
* `:delay` - An `Integer` representing a delay in milliseconds to `sleep` between each move
|
110
|
+
* `:size` - An `Integer` representing the size of the board, default 4
|
111
|
+
* `:log` - A `Boolean` indicating wether or not to save a log file to `./logs`
|
112
|
+
* `:history` - A `Boolean` indicating wether or not to show a game history log next to the game
|
113
|
+
* `:verbose` - A `Boolean` indicating wether or not to display the game, overridden when `:interactive` is `true`
|
114
|
+
|
115
|
+
When the game ends, the `TwentyFortyEight::Game` instance will be returned.
|
116
|
+
This object contains all the information there is about the game, it will also contain an accessible _log_ if `:log` is set to `true`.
|
117
|
+
|
118
|
+
The returned object has the following useful calls:
|
119
|
+
|
120
|
+
* `Game#won?` - Returns a `Boolean` result of wether the highest tile was `> 2048`
|
121
|
+
* `Game#lost?` - Opposite of `Game#won?`
|
122
|
+
* `Game#end?` - Returns a `Boolean` which tells you if the game has properly ended (unmergeable and board full)
|
123
|
+
* `Game#log` - Returns an instance of `TwentyFortyEight::Logger`, a simple wrapper class containing an array of entries accessible through `Logger#entries`
|
124
|
+
* `Game#score` - Returns an `Integer` representing the final score of the game
|
125
|
+
|
126
|
+
### Connect your script!
|
127
|
+
|
128
|
+
You can also connect your script to the game. A block can also be passed to `TwentyFortyEight#play` and `TwentyFortyEight#endless`.
|
129
|
+
Within that block, all you need to do is return a direction. There is a small DSL within that allows you to do moves based on availability.
|
130
|
+
With that, you can create simple patterns, e.g:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
require 'TwentyFortyEight'
|
134
|
+
|
135
|
+
game = TwentyFortyEight.play log: true do
|
136
|
+
left || down || right || up
|
137
|
+
end
|
138
|
+
|
139
|
+
puts game.score # => 2345
|
140
|
+
puts game.log.entries # => [{... log: 'entry' ...}, [{...}, {...}]]
|
141
|
+
puts game.won? # => false
|
142
|
+
puts game.end? # => true
|
143
|
+
```
|
144
|
+
|
145
|
+
### The DSL
|
146
|
+
|
147
|
+
A small DSL built into the game allows for easier creation of patterns. A small example:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
require 'TwentyFortyEight'
|
151
|
+
|
152
|
+
game = TwentyFortyEight.play do
|
153
|
+
left || down || right || up
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
What the above does is copy the current state of the game, execute the move on the copy and return the direction if the board changed or nothing (`nil`) otherwise. The `||` statements will continue to the next expression until something returns truthy. If no move is possible the game will simply end.
|
158
|
+
|
159
|
+
The problem with this setup is that there is no real way of adding complex patterns, you can basically only chain those 4 in different orders to eventually create a maximum of 16 patterns.
|
160
|
+
|
161
|
+
#### Helpers
|
162
|
+
|
163
|
+
Below is a list of helpers available within the _block_ passed to the game:
|
164
|
+
|
165
|
+
| helper | description |
|
166
|
+
|-----------------------|-----------------------------------------------|
|
167
|
+
|`left` | returns `:left` if possible, otherwise `nil` |
|
168
|
+
|`right` | returns `:right` if possible, otherwise `nil` |
|
169
|
+
|`up` | returns `:up` if possible, otherwise `nil` |
|
170
|
+
|`down` | returns `:down` if possible, otherwise `nil` |
|
171
|
+
|`sequence(*dir_syms)` | attempts to execute each move consecutively, if all change the game the pattern will be executed else `nil` is returned. |
|
172
|
+
|`available` | an `Array` of `Hash`es, each containing `:x` and `:y` keys that represent an empty tile in the current board. |
|
173
|
+
|`score` | the current score |
|
174
|
+
|`prev_score` | the previous score (before last move was executed) |
|
175
|
+
|`changed?` | wether the board changed since last turn (the move helpers (`sequence`, `left`, `right`, `up` and `down`) do this for you) |
|
176
|
+
|`won?` | wether you beat the game by achieving the `2048` tile |
|
177
|
+
|`lost?` | you'll have lost if you can no longer merge and the board is full |
|
178
|
+
|`quit!` | force quit the game |
|
179
|
+
|
180
|
+
#### Code examples
|
181
|
+
|
182
|
+
The following will give you some insight on how to get started and what goes on in the background.
|
183
|
+
|
184
|
+
##### Overview
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
require 'TwentyFortyEight'
|
188
|
+
|
189
|
+
game = TwentyFortyEight.play do
|
190
|
+
left # => :left or nil
|
191
|
+
right # => :right or nil
|
192
|
+
up # => :up or nil
|
193
|
+
down # => :right or nil
|
194
|
+
sequence :down, :left # => the next direction until the end has been reached
|
195
|
+
available # => [{ x: 0, y: 0 }, { x: 1, y: 1 }, ...]
|
196
|
+
score # => 2222
|
197
|
+
won? # => true or false
|
198
|
+
changed? # => true or false
|
199
|
+
lost? # => true or false
|
200
|
+
quit! # => :quit
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
##### Simple pattern
|
205
|
+
|
206
|
+
executes in order:
|
207
|
+
|
208
|
+
1. `:left` else
|
209
|
+
2. `:down` else
|
210
|
+
3. `:right` else
|
211
|
+
4. `:up`
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
require 'TwentyFortyEight'
|
215
|
+
|
216
|
+
game = TwentyFortyEight.play do
|
217
|
+
left || down || right || up
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
#### Sequences
|
222
|
+
|
223
|
+
executes in order:
|
224
|
+
|
225
|
+
1. `:down` twice, `:left` twice else
|
226
|
+
2. `:down` and `:left` else
|
227
|
+
3. `:down` and `:right` else
|
228
|
+
4. `:right` and `:left` else
|
229
|
+
5. `:left` else
|
230
|
+
6. `:down` else
|
231
|
+
7. `:right` else
|
232
|
+
8. `:up`
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
require 'TwentyFortyEight'
|
236
|
+
|
237
|
+
game = TwentyFortyEight.play do
|
238
|
+
sequence(:down, :down, :left, :left) ||
|
239
|
+
sequence(:down, :left) ||
|
240
|
+
sequence(:down, :right) ||
|
241
|
+
sequence(:right, :left) ||
|
242
|
+
left || down || right || up # => to not get stuck
|
243
|
+
end
|
244
|
+
```
|
245
|
+
|
246
|
+
## Roadmap
|
247
|
+
|
248
|
+
* Improve logging options
|
249
|
+
|
250
|
+
## Contributing
|
251
|
+
|
252
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Sidney Liebrand/TwentyFortyEight. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
253
|
+
|
254
|
+
|
255
|
+
## License
|
256
|
+
|
257
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/2048
CHANGED
@@ -4,5 +4,7 @@ require_relative '../lib/TwentyFortyEight'
|
|
4
4
|
options = TwentyFortyEight::Cli.parse!
|
5
5
|
|
6
6
|
TwentyFortyEight.send options.mode, options do
|
7
|
-
down || left ||
|
7
|
+
sequence(:left, :down) || sequence(:down, :left) ||
|
8
|
+
sequence(:right, :down) || sequence(:right, :left) ||
|
9
|
+
down || left || right || up
|
8
10
|
end
|
data/bin/console
CHANGED
@@ -6,9 +6,5 @@ require 'TwentyFortyEight'
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
8
8
|
|
9
|
-
@game = TwentyFortyEight.endless verbose: true do
|
10
|
-
down || left || right || up
|
11
|
-
end
|
12
|
-
|
13
9
|
require 'pry'
|
14
10
|
Pry.start
|
data/lib/TwentyFortyEight.rb
CHANGED
@@ -14,8 +14,9 @@ module TwentyFortyEight
|
|
14
14
|
MODES = [:play, :endless].freeze
|
15
15
|
SETTINGS = { size: 4, fill: 0, empty: 0 }.freeze
|
16
16
|
|
17
|
-
@@games
|
18
|
-
@@
|
17
|
+
@@games = []
|
18
|
+
@@session_high = 0
|
19
|
+
@@highscore = nil
|
19
20
|
|
20
21
|
def self.play(settings = {}, &block)
|
21
22
|
settings = Options.new SETTINGS.merge(settings)
|
@@ -87,13 +88,16 @@ module TwentyFortyEight
|
|
87
88
|
end
|
88
89
|
|
89
90
|
def self.load_or_set_highscore!(current_score, settings, path = '~/.2048')
|
90
|
-
|
91
|
-
@@highscore[settings.size.to_s] ||= 0
|
91
|
+
skey = settings.size.to_s
|
92
92
|
|
93
|
-
|
93
|
+
@@highscore ||= load_highscore
|
94
|
+
@@highscore[skey] ||= 0
|
95
|
+
@@session_high = current_score if current_score > @@session_high
|
94
96
|
|
95
|
-
@@highscore[
|
96
|
-
|
97
|
+
return unless current_score > @@highscore[skey]
|
98
|
+
|
99
|
+
@@highscore[skey] = current_score
|
100
|
+
write_highscore path
|
97
101
|
end
|
98
102
|
|
99
103
|
def self.write_highscore(path = '~/.2048')
|
@@ -120,9 +124,14 @@ module TwentyFortyEight
|
|
120
124
|
|
121
125
|
def self.render_game(game, settings, final = false)
|
122
126
|
h = { interactive: settings.interactive?, info: [] }
|
127
|
+
s = settings.key.to_s
|
123
128
|
|
124
|
-
|
125
|
-
|
129
|
+
if settings.mode? :endless
|
130
|
+
h[:info] << { highscore: "#{@@session_high}/#{@@highscore[settings.size.to_s]}" }
|
131
|
+
h[:info] << { game: (1 + game.id), move: game.moves }
|
132
|
+
else
|
133
|
+
h[:info] << { highscore: @@highscore[settings.size.to_s] , move: game.moves}
|
134
|
+
end
|
126
135
|
h[:info] << { score: game.score, dir: game.current_dir}
|
127
136
|
h[:history] = (@@games + [game]) if settings.history?
|
128
137
|
|
data/lib/TwentyFortyEight/dsl.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TwentyFortyEight
|
3
4
|
# Logger
|
4
5
|
class Logger
|
@@ -13,31 +14,25 @@ module TwentyFortyEight
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def write!(options = {})
|
16
|
-
name
|
17
|
-
path
|
18
|
-
path
|
19
|
-
path = (File.join Dir.pwd, options[:dir], name) if options[:dir]
|
17
|
+
name = (options[:name] || "2048-#{Time.now.to_i}") + '.log.json'
|
18
|
+
path = options[:path] || options[:dir] || Dir.pwd
|
19
|
+
path = File.expand_path('./' + path) unless path.start_with? '/'
|
20
20
|
|
21
|
-
|
21
|
+
return unless Dir.exist? path
|
22
|
+
File.open(File.join(path, name), 'w') { |f| f.write @entries.to_json }
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.load!(path)
|
25
26
|
full_path = File.expand_path path
|
26
|
-
|
27
|
-
|
27
|
+
return unless File.exist? full_path
|
28
28
|
new JSON.parse(File.read(full_path), symbolize_names: true)
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.destroy!(path)
|
32
32
|
full_path = File.expand_path path
|
33
|
-
|
33
|
+
return unless File.exist? full_path
|
34
34
|
File.delete full_path
|
35
|
+
true
|
35
36
|
end
|
36
|
-
|
37
|
-
def self.check_file_existence!(path)
|
38
|
-
raise FileNotFound, 'Log does not exist' unless File.exist? path
|
39
|
-
end
|
40
|
-
|
41
|
-
class FileNotFound < StandardError; end
|
42
37
|
end
|
43
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: TwentyFortyEight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sidney Liebrand
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
159
|
version: '0'
|
160
160
|
requirements: []
|
161
161
|
rubyforge_project:
|
162
|
-
rubygems_version: 2.6.
|
162
|
+
rubygems_version: 2.6.11
|
163
163
|
signing_key:
|
164
164
|
specification_version: 4
|
165
165
|
summary: A 2048 game for terminals
|