Initial import

This commit is contained in:
Glenn Y. Rolland 2022-08-21 13:43:39 +02:00
commit c843003b66
9 changed files with 256 additions and 0 deletions

8
Makefile Normal file
View file

@ -0,0 +1,8 @@
all: build
build:
shards build
clean:
shards clean

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Bordle
Play Wordle in your favorite terminal. Implemented in Crystal.

BIN
bin/bordle Executable file

Binary file not shown.

24
shard.yml Normal file
View 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
View 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
View 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
View 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
View 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
View 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