Initial import
This commit is contained in:
commit
c843003b66
9 changed files with 256 additions and 0 deletions
8
Makefile
Normal file
8
Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
all: build
|
||||
|
||||
build:
|
||||
shards build
|
||||
|
||||
clean:
|
||||
shards clean
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Bordle
|
||||
|
||||
Play Wordle in your favorite terminal. Implemented in Crystal.
|
||||
|
||||
|
BIN
bin/bordle
Executable file
BIN
bin/bordle
Executable file
Binary file not shown.
24
shard.yml
Normal file
24
shard.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: draft-bordle
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Glenn Y. Rolland <glenux@glenux.net>
|
||||
|
||||
description: |
|
||||
A stupid wordle-like game for your terminal
|
||||
|
||||
# dependencies:
|
||||
# pg:
|
||||
# github: will/crystal-pg
|
||||
# version: "~> 0.5"
|
||||
|
||||
# development_dependencies:
|
||||
# webmock:
|
||||
# github: manastech/webmock.cr
|
||||
|
||||
license: LGPL-3
|
||||
|
||||
targets:
|
||||
bordle:
|
||||
main: src/main.cr
|
||||
|
40
src/dictionary.cr
Normal file
40
src/dictionary.cr
Normal file
|
@ -0,0 +1,40 @@
|
|||
|
||||
class Bordle
|
||||
class Dictionary
|
||||
DICTIONARY_FILE = "/usr/share/dict/french"
|
||||
|
||||
property length : UInt8
|
||||
property data : Array(String)
|
||||
|
||||
def initialize(length : UInt8)
|
||||
@length = length
|
||||
@data = [] of String
|
||||
load_data
|
||||
|
||||
end
|
||||
|
||||
def load_data
|
||||
# use french dictionary from wfrench package
|
||||
if ! File.exists? DICTIONARY_FILE
|
||||
STDERR.puts "ERROR: dictionary file missing! (#{DICTIONARY_FILE})"
|
||||
STDERR.puts " Please install then wfrench package on your system"
|
||||
exit 1
|
||||
end
|
||||
|
||||
lines = File.read_lines(DICTIONARY_FILE)
|
||||
@data =
|
||||
lines
|
||||
.select {|word| word.size == @length }
|
||||
.map { |word| word.tr( TR_DIACRITICS, TR_ASCII ) }
|
||||
end
|
||||
def includes?(word)
|
||||
@data.includes? word
|
||||
end
|
||||
|
||||
def choose_word
|
||||
@data
|
||||
.sample(1)
|
||||
.first
|
||||
end
|
||||
end
|
||||
end
|
68
src/game.cr
Normal file
68
src/game.cr
Normal file
|
@ -0,0 +1,68 @@
|
|||
|
||||
require "colorize"
|
||||
|
||||
require "./types"
|
||||
require "./dictionary"
|
||||
require "./target_word"
|
||||
|
||||
class Bordle
|
||||
class Game
|
||||
def initialize
|
||||
@length = 5_u8
|
||||
@dict = Dictionary.new(@length)
|
||||
@target = TargetWord.new(@dict.choose_word)
|
||||
end
|
||||
|
||||
def display(diff : Array(LetterScore), try)
|
||||
printf("#{try}. ")
|
||||
diff.each do |ls|
|
||||
colors = case ls[1]
|
||||
when Score::NotOk then {:white, :black}
|
||||
when Score::WrongPlace then {:white, :yellow}
|
||||
when Score::RightPlace then {:white, :green}
|
||||
else {:white, :black}
|
||||
end
|
||||
str = ("%s" % ls[0]).colorize.fore(colors[0]).back(colors[1])
|
||||
printf("%s", str)
|
||||
end
|
||||
puts ""
|
||||
end
|
||||
|
||||
def input_word(try)
|
||||
word = ""
|
||||
loop do
|
||||
printf "#{try}. "
|
||||
word = gets()
|
||||
|
||||
word = "" if word.nil?
|
||||
word.tr(TR_DIACRITICS, TR_ASCII).downcase
|
||||
|
||||
break if word.size == @length && @dict.includes? word
|
||||
printf("\x1B[A\x1B[2K")
|
||||
end
|
||||
word
|
||||
end
|
||||
|
||||
def run
|
||||
printf " .....\n"
|
||||
try = 0
|
||||
while true
|
||||
try += 1
|
||||
word = input_word(try)
|
||||
printf("\x1B[A\x1B[2K")
|
||||
|
||||
diff = @target.diff(word)
|
||||
display(diff, try)
|
||||
if @target.equals?(word)
|
||||
puts "-- GAGNÉ ! --".colorize.fore(:green)
|
||||
return
|
||||
end
|
||||
if try >= 5
|
||||
puts "-- PERDU ! --".colorize.fore(:red)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
36
src/main.cr
Normal file
36
src/main.cr
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
require "option_parser"
|
||||
|
||||
require "./game"
|
||||
|
||||
class Bordle
|
||||
class BordleCli
|
||||
|
||||
property options : String?
|
||||
|
||||
def initialize()
|
||||
@options = nil
|
||||
end
|
||||
|
||||
def self.parse_options(args)
|
||||
return nil
|
||||
end
|
||||
|
||||
# FIXME: add --length LEN option (length of words)
|
||||
# FIXME: add --lang LANG option (choose dictionnary)
|
||||
# FIXME: add --tries TRIES option (how many tries are allowed)
|
||||
# FIXME: add --with-letters (show used/unused letters)
|
||||
def self.run(args)
|
||||
app = BordleCli.new
|
||||
options = BordleCli.parse_options(args)
|
||||
app.options = options
|
||||
|
||||
game = Game.new
|
||||
game.run()
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Bordle::BordleCli.run(ARGV)
|
||||
|
61
src/target_word.cr
Normal file
61
src/target_word.cr
Normal file
|
@ -0,0 +1,61 @@
|
|||
|
||||
class Bordle
|
||||
class TargetWord
|
||||
def initialize(@target_word : String)
|
||||
end
|
||||
|
||||
def equals?(word)
|
||||
@target_word == word
|
||||
end
|
||||
|
||||
def to_h()
|
||||
hash = Hash(Char, Array(Int32)).new
|
||||
@target_word.each_char_with_index do |char, index|
|
||||
hash[char] = [] of Int32 unless hash.has_key? char
|
||||
hash[char] << index
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
def diff(word)
|
||||
hash = self.to_h
|
||||
result = [] of LetterScore
|
||||
|
||||
# REF = r a t e s
|
||||
# TEST= c e r e t
|
||||
|
||||
word.each_char_with_index do |char, index|
|
||||
if ! hash.has_key? char
|
||||
result << {char, Score::NotOk}
|
||||
next
|
||||
end
|
||||
|
||||
char_values = hash[char]
|
||||
char_misplaced = char_values.select {|pos| @target_word[pos] != word[pos] }
|
||||
char_wellplaced = char_values.select {|pos| @target_word[pos] == word[pos] }
|
||||
# puts "values(#{char}) = #{char_values}"
|
||||
# puts "misplaced(#{char}) = #{char_misplaced}"
|
||||
# puts "wellplaced(#{char}) = #{char_wellplaced}"
|
||||
|
||||
if char_wellplaced.includes? index
|
||||
result << {char, Score::RightPlace}
|
||||
char_wellplaced.reject! {|pos| pos == index }
|
||||
hash[char] = char_misplaced + char_wellplaced
|
||||
hash.delete(char) if hash[char].empty?
|
||||
next
|
||||
end
|
||||
|
||||
if ! char_misplaced.empty?
|
||||
result << {char, Score::WrongPlace}
|
||||
char_misplaced = char_misplaced.skip(1)
|
||||
hash[char] = char_misplaced + char_wellplaced
|
||||
hash.delete(char) if hash[char].empty?
|
||||
next
|
||||
end
|
||||
|
||||
result << {char, Score::NotOk}
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
14
src/types.cr
Normal file
14
src/types.cr
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
class Bordle
|
||||
TR_DIACRITICS = "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšȘșſŢţŤťŦŧȚțÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž"
|
||||
TR_ASCII = "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSsSssTtTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
|
||||
|
||||
|
||||
enum Score
|
||||
RightPlace = 0
|
||||
WrongPlace = 1
|
||||
NotOk = 2
|
||||
end
|
||||
|
||||
alias LetterScore = {Char, Score}
|
||||
end
|
Loading…
Reference in a new issue