linotype 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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/lib/linotype/board.rb +37 -0
- data/lib/linotype/dictionary/dictionary.rb +27 -0
- data/lib/linotype/dictionary/words.txt +235886 -0
- data/lib/linotype/game.rb +107 -0
- data/lib/linotype/move.rb +84 -0
- data/lib/linotype/player.rb +5 -0
- data/lib/linotype/tile.rb +63 -0
- data/lib/linotype/version.rb +3 -0
- data/lib/linotype.rb +11 -0
- data/linotype.gemspec +21 -0
- metadata +68 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module Linotype
|
2
|
+
class Game
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@board = Board.new_random(self)
|
6
|
+
@players = [Player.new, Player.new]
|
7
|
+
@current_player = @players[0]
|
8
|
+
@moves = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def play(*tile_coordinates)
|
12
|
+
tiles = find_tiles(tile_coordinates)
|
13
|
+
move = Move.new(self, @current_player, tiles)
|
14
|
+
if move.valid?
|
15
|
+
move.cover_tiles!
|
16
|
+
toggle_current_player
|
17
|
+
end
|
18
|
+
@moves << move
|
19
|
+
@current_player == move.player ? false : true
|
20
|
+
end
|
21
|
+
|
22
|
+
def over?
|
23
|
+
uncovered_tiles.empty? || two_passes_in_a_row?
|
24
|
+
end
|
25
|
+
|
26
|
+
def winner
|
27
|
+
scores.inject(nil) { |winner, p| p[1] > winner.to_i ? p[0] : winner } if over?
|
28
|
+
end
|
29
|
+
|
30
|
+
def scores
|
31
|
+
@players.inject({}) { |scores, player| scores[player_number(player)] = score(player); scores }
|
32
|
+
end
|
33
|
+
|
34
|
+
def board
|
35
|
+
tile_rows.collect { |row| row.collect { |tile| tile.to_hash } }
|
36
|
+
end
|
37
|
+
|
38
|
+
def moves
|
39
|
+
@moves.collect { |move| move.to_hash }
|
40
|
+
end
|
41
|
+
|
42
|
+
def player_number(player)
|
43
|
+
@players.index(player) + 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def score(player)
|
47
|
+
covered_tiles(player).count
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_moves
|
51
|
+
@moves.select(&:valid?)
|
52
|
+
end
|
53
|
+
|
54
|
+
def invalid_moves
|
55
|
+
@moves.select(&:invalid?)
|
56
|
+
end
|
57
|
+
|
58
|
+
def dictionary
|
59
|
+
Linotype::Dictionary.loaded
|
60
|
+
end
|
61
|
+
|
62
|
+
def tile_rows
|
63
|
+
@board.tiles
|
64
|
+
end
|
65
|
+
|
66
|
+
def letters
|
67
|
+
@board.tiles.flatten.collect(&:letter)
|
68
|
+
end
|
69
|
+
|
70
|
+
def other_player
|
71
|
+
@players.index(@current_player) == 0 ? @players[1] : @players[0]
|
72
|
+
end
|
73
|
+
private :other_player
|
74
|
+
|
75
|
+
def find_tiles(tile_coordinates)
|
76
|
+
puts tile_coordinates.inspect
|
77
|
+
return [] if tile_coordinates.empty?
|
78
|
+
tile_coordinates.collect do |tile_coordinate|
|
79
|
+
tile = tile_rows[tile_coordinate[:row]][tile_coordinate[:column]]
|
80
|
+
raise ArgumentError, "The board does not have a tile at that location" unless tile
|
81
|
+
tile
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private :find_tiles
|
85
|
+
|
86
|
+
def toggle_current_player
|
87
|
+
@current_player = other_player
|
88
|
+
end
|
89
|
+
private :toggle_current_player
|
90
|
+
|
91
|
+
def uncovered_tiles
|
92
|
+
covered_tiles(nil)
|
93
|
+
end
|
94
|
+
private :uncovered_tiles
|
95
|
+
|
96
|
+
def covered_tiles(player)
|
97
|
+
tile_rows.flatten.select { |tile| tile.covered_by == player }
|
98
|
+
end
|
99
|
+
private :covered_tiles
|
100
|
+
|
101
|
+
def two_passes_in_a_row?
|
102
|
+
valid_moves.count >= 2 && valid_moves[-2,2].select { |move| move.pass? }.count == 2
|
103
|
+
end
|
104
|
+
private :two_passes_in_a_row?
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Linotype
|
2
|
+
class Move
|
3
|
+
|
4
|
+
attr_reader :game, :player, :invalid_reason
|
5
|
+
|
6
|
+
def initialize(game, player, tiles)
|
7
|
+
@game = game
|
8
|
+
@player = player
|
9
|
+
@tiles = tiles
|
10
|
+
calculate_valid
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
!!@valid
|
15
|
+
end
|
16
|
+
|
17
|
+
def invalid?
|
18
|
+
!valid?
|
19
|
+
end
|
20
|
+
|
21
|
+
def word
|
22
|
+
@tiles.collect(&:letter).join
|
23
|
+
end
|
24
|
+
|
25
|
+
def pass?
|
26
|
+
@tiles.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def cover_tiles!
|
30
|
+
@tiles.each { |tile| tile.covered_by = @player unless tile.defended? }
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_hash
|
34
|
+
{
|
35
|
+
player: game.player_number(@player),
|
36
|
+
word: word,
|
37
|
+
valid: valid?,
|
38
|
+
invalid_reason: @invalid_reason,
|
39
|
+
player_sequence: game.valid_moves.select { |move| move.player == @player }.index(self).to_i + 1,
|
40
|
+
total_sequence: game.valid_moves.index(self).to_i + 1
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def calculate_valid
|
45
|
+
if pass?
|
46
|
+
@valid = true
|
47
|
+
elsif !uses_game_tiles?
|
48
|
+
@invalid_reason = "does not use game tile letters"
|
49
|
+
elsif !in_dictionary?
|
50
|
+
@invalid_reason = "is not in dictionary"
|
51
|
+
elsif !new_word_in_game?
|
52
|
+
@invalid_reason = "has been played before"
|
53
|
+
elsif !enough_characters?
|
54
|
+
@invalid_reason = "is too short"
|
55
|
+
elsif prefix_of_previous_word?
|
56
|
+
@invalid_reason = "is a prefix of a previously played word"
|
57
|
+
else
|
58
|
+
@valid = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def in_dictionary?
|
63
|
+
game.dictionary.valid?(word)
|
64
|
+
end
|
65
|
+
|
66
|
+
def uses_game_tiles?
|
67
|
+
letters = game.letters
|
68
|
+
word.each_char { |letter| return false unless letters.delete(letter) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def new_word_in_game?
|
72
|
+
!game.valid_moves.collect(&:word).include?(word)
|
73
|
+
end
|
74
|
+
|
75
|
+
def enough_characters?
|
76
|
+
word.length >= 2
|
77
|
+
end
|
78
|
+
|
79
|
+
def prefix_of_previous_word?
|
80
|
+
game.valid_moves.find { |move| move.word =~ /\A#{word}/ }
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Linotype
|
2
|
+
class Tile
|
3
|
+
|
4
|
+
attr_accessor :covered_by
|
5
|
+
attr_reader :letter
|
6
|
+
|
7
|
+
def initialize(board, letter=random_letter)
|
8
|
+
@board = board
|
9
|
+
@letter = letter
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
{
|
14
|
+
letter: @letter,
|
15
|
+
row: row,
|
16
|
+
column: column,
|
17
|
+
covered_by: (game.player_number(covered_by) if covered_by),
|
18
|
+
defended: defended?
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def game
|
23
|
+
@board.game
|
24
|
+
end
|
25
|
+
|
26
|
+
def random_letter
|
27
|
+
('A'..'Z').to_a[rand(0..25)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def row
|
31
|
+
@row ||= @board.row(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def column
|
35
|
+
@column ||= @board.column(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def previous?(coordinate_type)
|
39
|
+
send(coordinate_type) > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def next?(coordinate_type)
|
43
|
+
send(coordinate_type) < @board.send("#{coordinate_type}_count") - 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def adjacent_tiles
|
47
|
+
@adjacent_tiles ||= calculate_adjacent_tiles
|
48
|
+
end
|
49
|
+
|
50
|
+
def defended?
|
51
|
+
adjacent_tiles.select { |tile| tile.covered_by == covered_by && covered_by }.count == adjacent_tiles.count
|
52
|
+
end
|
53
|
+
|
54
|
+
def calculate_adjacent_tiles
|
55
|
+
@adjacent_tiles = []
|
56
|
+
@adjacent_tiles << @board.tiles[row - 1][column] if previous?(:row)
|
57
|
+
@adjacent_tiles << @board.tiles[row + 1][column] if next?(:row)
|
58
|
+
@adjacent_tiles << @board.tiles[row][column - 1] if previous?(:column)
|
59
|
+
@adjacent_tiles << @board.tiles[row][column + 1] if next?(:column)
|
60
|
+
@adjacent_tiles
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/linotype.rb
ADDED
data/linotype.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'linotype/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "linotype"
|
8
|
+
gem.version = Linotype::VERSION
|
9
|
+
gem.authors = ["Sean Devine"]
|
10
|
+
gem.email = ["barelyknown@icloud.com"]
|
11
|
+
gem.description = <<-eos
|
12
|
+
linotype is a small program that implements that game mechanic of Letterpress for iOS by atebits software http://www.atebits.com/letterpress/ The program was written to support the automation of letterpress gameplay and to power command line or web-based versions of the game. It was inspired by a tweet by Andy Baio about cheating in letterpress. https://twitter.com/waxpancake/statuses/261966416507465728 The game uses the words file comes with Mac OS X, but any word file can be used.
|
13
|
+
eos
|
14
|
+
gem.summary = %q{Small ruby program that implements the game mechanic of the letterpress iOS game.}
|
15
|
+
gem.homepage = "https://github.com/barelyknown/linotype"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: linotype
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sean Devine
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-28 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! ' linotype is a small program that implements that game mechanic of
|
15
|
+
Letterpress for iOS by atebits software http://www.atebits.com/letterpress/ The
|
16
|
+
program was written to support the automation of letterpress gameplay and to power
|
17
|
+
command line or web-based versions of the game. It was inspired by a tweet by Andy
|
18
|
+
Baio about cheating in letterpress. https://twitter.com/waxpancake/statuses/261966416507465728
|
19
|
+
The game uses the words file comes with Mac OS X, but any word file can be used.
|
20
|
+
|
21
|
+
'
|
22
|
+
email:
|
23
|
+
- barelyknown@icloud.com
|
24
|
+
executables: []
|
25
|
+
extensions: []
|
26
|
+
extra_rdoc_files: []
|
27
|
+
files:
|
28
|
+
- .gitignore
|
29
|
+
- Gemfile
|
30
|
+
- LICENSE.txt
|
31
|
+
- README.md
|
32
|
+
- Rakefile
|
33
|
+
- lib/linotype.rb
|
34
|
+
- lib/linotype/board.rb
|
35
|
+
- lib/linotype/dictionary/dictionary.rb
|
36
|
+
- lib/linotype/dictionary/words.txt
|
37
|
+
- lib/linotype/game.rb
|
38
|
+
- lib/linotype/move.rb
|
39
|
+
- lib/linotype/player.rb
|
40
|
+
- lib/linotype/tile.rb
|
41
|
+
- lib/linotype/version.rb
|
42
|
+
- linotype.gemspec
|
43
|
+
homepage: https://github.com/barelyknown/linotype
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.24
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Small ruby program that implements the game mechanic of the letterpress iOS
|
67
|
+
game.
|
68
|
+
test_files: []
|