Initial commit

main
Jake 10 months ago
commit 06d64bd3cb
  1. 9
      .editorconfig
  2. 212
      .gitignore
  3. 7
      LICENSE.md
  4. 54
      game/board.go
  5. 110
      game/score/score.go
  6. 11
      game/util/util.go
  7. 10657
      game/words/dictionary.txt
  8. 66
      game/words/words.go
  9. 2315
      game/words/words.txt
  10. 108
      game/words/words_test.go
  11. 22
      go.mod
  12. 35
      go.sum
  13. 170
      main.go
  14. 45
      styles.go

@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

212
.gitignore vendored

@ -0,0 +1,212 @@
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### Go Patch ###
/vendor/
/Godeps/
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/*
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk

@ -0,0 +1,7 @@
Copyright 2022 Jake Walker
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,54 @@
package game
import "tildegit.org/jakew/wordle/game/words"
type Guess struct {
Letters []rune
Correct []int
Present []int
}
type Board struct {
Guesses [6]Guess
CurrentGuess int
Solution string
Done bool
Day int
}
func NewBoard() *Board {
solution, day := words.GetSolution()
return &Board{
Guesses: [6]Guess{},
CurrentGuess: 0,
Solution: solution,
Day: day,
}
}
func (b *Board) AddWord(word string) string {
if b.Done {
return "You are done!"
}
if len(word) != 5 {
return "Word must be 5 long!"
}
if !words.IsValid(word) {
return "Word is not in dictionary!"
}
correct, present := words.Check(word, b.Solution)
b.Guesses[b.CurrentGuess] = Guess{
Letters: []rune(word),
Correct: correct,
Present: present,
}
b.CurrentGuess += 1
if len(correct) == 5 || b.CurrentGuess >= 6 {
b.Done = true
}
return ""
}

@ -0,0 +1,110 @@
package score
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
"tildegit.org/jakew/wordle/game"
"time"
)
type Line struct {
Word string `json:"word"`
Correct []int `json:"correct"`
Present []int `json:"present"`
}
type Game struct {
Day int `json:"day"`
Date time.Time `json:"date"`
Score int `json:"score"`
Lines []Line `json:"lines"`
}
type Scores struct {
Games []Game `json:"games"`
}
func ToGame(b *game.Board) *Game {
g := &Game{
Day: b.Day,
Date: time.Now().In(time.UTC),
Score: b.CurrentGuess,
Lines: []Line{},
}
for i := 0; i < b.CurrentGuess; i++ {
g.Lines = append(g.Lines, Line{
Word: string(b.Guesses[i].Letters),
Correct: b.Guesses[i].Correct,
Present: b.Guesses[i].Present,
})
}
return g
}
func getSaveLocation() (string, error) {
dirname, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(dirname, "/.wordle.json"), nil
}
func Load() (*Scores, error) {
path, err := getSaveLocation()
if err != nil {
return &Scores{}, err
}
b, err := ioutil.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return &Scores{Games: []Game{}}, nil
} else if err != nil {
return &Scores{}, err
}
var scores Scores
err = json.Unmarshal(b, &scores)
return &scores, err
}
func Save(scores *Scores) error {
path, err := getSaveLocation()
if err != nil {
return err
}
b, err := json.Marshal(scores)
if err != nil {
return err
}
err = ioutil.WriteFile(path, b, 0644)
return err
}
func SaveBoard(b *game.Board) error {
scores, err := Load()
if err != nil {
return err
}
scores.Games = append(scores.Games, *ToGame(b))
err = Save(scores)
return err
}
func GetLastGame() (*Game, error) {
scores, err := Load()
if err != nil {
return &Game{}, err
}
if len(scores.Games) <= 0 {
return nil, nil
}
return &scores.Games[len(scores.Games)-1], nil
}

@ -0,0 +1,11 @@
package util
func ContainsInt(slice []int, n int) bool {
for _, i := range slice {
if i == n {
return true
}
}
return false
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,66 @@
package words
import (
_ "embed"
"strings"
"tildegit.org/jakew/wordle/game/util"
"time"
)
//go:embed words.txt
var words string
//go:embed dictionary.txt
var dictionary string
func GetSolution() (string, int) {
wordSlice := strings.Split(words, "\n")
today := time.Now().In(time.UTC).Truncate(24 * time.Hour)
day := (today.UnixMilli() - 1624057200000) / 864e5
index := day % int64(len(wordSlice))
return strings.ToLower(strings.TrimSpace(wordSlice[index])), int(day)
}
func contains(words string, word string) bool {
slice := strings.Split(words, "\n")
for _, item := range slice {
if item != "" && item == word {
return true
}
}
return false
}
func IsValid(word string) bool {
word = strings.TrimSpace(strings.ToLower(word))
if len(word) != 5 {
return false
}
return contains(words, word) || contains(dictionary, word)
}
func Check(word string, solution string) (correct, present []int) {
correct = []int{}
present = []int{}
exclude := []int{}
word = strings.TrimSpace(strings.ToLower(word))
for i, letter := range word {
// check if correct position
if letter == int32(solution[i]) {
correct = append(correct, i)
continue
}
// is the letter in the word
for j, letter1 := range solution {
if letter == letter1 && !util.ContainsInt(exclude, j) {
present = append(present, i)
exclude = append(exclude, j)
break
}
}
}
return
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,108 @@
package words
import (
"reflect"
"testing"
)
func TestCheck(t *testing.T) {
type args struct {
word string
solution string
}
tests := []struct {
name string
args args
wantCorrect []int
wantPresent []int
}{
{
name: "incorrect",
args: args{word: "grass", solution: "hello"},
wantCorrect: []int{},
wantPresent: []int{},
},
{
name: "incorrect position",
args: args{word: "crash", solution: "hello"},
wantCorrect: []int{},
wantPresent: []int{4},
},
{
name: "correct position",
args: args{word: "hrass", solution: "hello"},
wantCorrect: []int{0},
wantPresent: []int{},
},
{
name: "incorrect position double letter",
// here there is a double letter in the word, only one being in the solution
args: args{word: "green", solution: "hello"},
wantCorrect: []int{},
wantPresent: []int{2},
},
{
name: "partial double letter",
// here there is a double letter in the word, with one being in the correct place
args: args{word: "hllxy", solution: "hello"},
wantCorrect: []int{0, 2},
wantPresent: []int{1},
},
{
name: "correct",
args: args{word: "hello", solution: "hello"},
wantCorrect: []int{0, 1, 2, 3, 4},
wantPresent: []int{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCorrect, gotPresent := Check(tt.args.word, tt.args.solution)
if !reflect.DeepEqual(gotCorrect, tt.wantCorrect) {
t.Errorf("Check() gotCorrect = %v, want %v", gotCorrect, tt.wantCorrect)
}
if !reflect.DeepEqual(gotPresent, tt.wantPresent) {
t.Errorf("Check() gotPresent = %v, want %v", gotPresent, tt.wantPresent)
}
})
}
}
func TestIsValid(t *testing.T) {
type args struct {
word string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "valid",
args: args{word: "hello"},
want: true,
},
{
name: "too short",
args: args{word: "egg"},
want: false,
},
{
name: "too long",
args: args{word: "jellyfish"},
want: false,
},
{
name: "not a word",
args: args{word: "aaaaa"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsValid(tt.args.word); got != tt.want {
t.Errorf("IsValid() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,22 @@
module tildegit.org/jakew/wordle
go 1.17
require (
github.com/charmbracelet/bubbletea v0.19.3
github.com/charmbracelet/lipgloss v0.4.0
)
require (
github.com/containerd/console v1.0.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.9.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed // indirect
)

@ -0,0 +1,35 @@
github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

@ -0,0 +1,170 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"os"
"tildegit.org/jakew/wordle/game"
"tildegit.org/jakew/wordle/game/score"
"tildegit.org/jakew/wordle/game/util"
"tildegit.org/jakew/wordle/game/words"
"unicode"
)
type model struct {
board *game.Board
buffer []rune
error string
}
func initialModel() model {
m := model{
board: game.NewBoard(),
}
lastGame, err := score.GetLastGame()
if err != nil {
m.error = fmt.Sprintf("Could not load last game: %v", err.Error())
return m
}
if lastGame != nil {
m.error = fmt.Sprintf("Come back tomorrow for a new word")
m.board = &game.Board{
Guesses: [6]game.Guess{},
CurrentGuess: lastGame.Score,
Solution: "",
Done: true,
Day: lastGame.Day,
}
for i, line := range lastGame.Lines {
m.board.Guesses[i] = game.Guess{
Letters: []rune(line.Word),
Correct: line.Correct,
Present: line.Present,
}
}
}
return m
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
m.error = ""
switch msg.Type {
case tea.KeyCtrlC:
return m, tea.Quit
case tea.KeyBackspace:
if !m.board.Done && len(m.buffer) > 0 {
m.buffer = m.buffer[:len(m.buffer)-1]
}
case tea.KeyEnter:
if m.board.Done {
return m, nil
}
if len(m.buffer) != 5 {
m.error = "Word must be 5 long"
} else if !words.IsValid(string(m.buffer)) {
m.error = "Word not in dictionary"
} else {
msg := m.board.AddWord(string(m.buffer))
if msg != "" {
m.error = msg
return m, nil
}
// clear input
m.buffer = []rune{}
// save if win
if m.board.Done {
err := score.SaveBoard(m.board)
if err != nil {
m.error = fmt.Sprintf("Something went wrong saving the game: %v", err.Error())
}
}
}
default:
if m.board.Done || len(msg.Runes) < 1 {
return m, nil
}
r := unicode.ToLower(msg.Runes[0])
if r >= 'a' && r <= 'z' && len(m.buffer) < 5 {
m.buffer = append(m.buffer, r)
}
}
}
return m, nil
}
func (m model) View() string {
// header
s := headerStyle.Render(fmt.Sprintf("WORDLE #%d", m.board.Day)) + "\n"
for i, guess := range m.board.Guesses {
line := " "
for j := 0; j < 5; j++ {
if i < m.board.CurrentGuess {
style := charEmptyStyle
if util.ContainsInt(guess.Correct, j) {
style = charCorrectStyle
} else if util.ContainsInt(guess.Present, j) {
style = charWrongPositionStyle
}
line += style.Render(string(unicode.ToUpper(guess.Letters[j])) + " ")
} else {
line += charEmptyStyle.Render("_ ")
}
}
s += lineStyle.Render(line) + "\n"
}
if !m.board.Done {
footer := "> "
for i := 0; i < 5; i++ {
if i < len(m.buffer) {
footer += string(unicode.ToUpper(m.buffer[i]))
} else {
footer += "_"
}
footer += " "
}
footer += "<"
s += promptStyle.Render(footer) + "\n"
} else {
msg := ""
if m.board.CurrentGuess >= 6 {
msg += fmt.Sprintf("Oof! The word was %s.", m.board.Solution)
} else {
msg += fmt.Sprintf("Congrats! You guessed in %d.", m.board.CurrentGuess)
}
msg += " Come back tomorrow for the next word. (Press CTRL+C to exit)"
s += promptStyle.Render(msg) + "\n"
}
if m.error != "" {
s += errorStyle.Render(m.error) + "\n"
}
return s
}
func main() {
p := tea.NewProgram(initialModel())
if err := p.Start(); err != nil {
fmt.Printf("Something has gone wrong: %v", err)
os.Exit(1)
}
}

@ -0,0 +1,45 @@
package main
import "github.com/charmbracelet/lipgloss"
var width = 30
// ---- colours ----
var colorGrey = lipgloss.AdaptiveColor{Light: "8", Dark: "7"}
// ---- main game ----
// the title of the game (i.e. wordle #123)
var headerStyle = lipgloss.NewStyle().
Foreground(colorGrey).
Italic(true).
Align(lipgloss.Center).
Width(width)
// the bit where you type in the word
var promptStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("12")).
Align(lipgloss.Center).
Width(width)
// the line of letters
var lineStyle = lipgloss.NewStyle().Width(width).Align(lipgloss.Center)
// empty space or character not in word
var charEmptyStyle = lipgloss.NewStyle().
Foreground(colorGrey)
// character in wrong position
var charWrongPositionStyle = charEmptyStyle.Copy().
Foreground(lipgloss.Color("11"))
// character in correct position
var charCorrectStyle = charEmptyStyle.Copy().
Foreground(lipgloss.Color("10"))
var errorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("9")).
Italic(true).
Align(lipgloss.Center).
Width(width)
Loading…
Cancel
Save