GithubHelp home page GithubHelp logo

domino14 / macondo Goto Github PK

View Code? Open in Web Editor NEW
48.0 48.0 10.0 247.74 MB

A crossword board game AI, written in Go

Home Page: https://domino14.github.io/macondo

License: GNU General Public License v3.0

Go 16.07% Dockerfile 0.01% Jupyter Notebook 83.03% Python 0.33% Makefile 0.02% Ruby 0.01% Lua 0.10% JavaScript 0.44%

macondo's Introduction

What I do

Hey there! I'm César. I am a former electrical engineer turned software developer. I am also an entrepreneur and have co-founded a few companies. You can find my blog here: https://cesardelsolar.com

I am currently CTO of CodeComet, a startup in the dev-tools-plus-AI space. Check us out at https://codecomet.io. I previously was a late cofounder at a YC startup named Leftronic; we were acquired in 2014.

Scrabble and Word Games

I have been working on a few Scrabble-related projects in my spare time. I am a top 10 tournament player in North America, which is where the interest comes from. My projects are:

If you would like to help out with any of my open-source projects please let me know!

domino14's Stats

domino14's Streak

macondo's People

Contributors

andy-k avatar ddugovic avatar dependabot[bot] avatar domino14 avatar jvc56 avatar magratheazaphod avatar martindemello avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

macondo's Issues

config should be a global constant

We currently pass the config struct around all over the code; however, it contains paths to resources and as such is a property of the local environment, not a runtime variable. It should therefore be read once at start time, and stored as a single globally accessible read-only variable thereafter.

Note that the sole reason it needs to be passed up and down the entire stack is to load files in the event of their not being cached.

montecarlo sim should use old method of picking random tiles

For the sim to be most accurate (exhibit the least "noise") for every iteration both us and the opponent should draw the same tiles from the bag for each play being considered in that iteration.

We should shuffle the bag after every iteration and establish its order as the drawing order, instead of the approach we currently use. One idea would be to just make a new bag type to be used only for sims that can do this.

bot passes with FFGKMVW

all exchanges get evaluated negatively with this rack, and thus macondo evaluates pass above them (which is evaluated as a 0). pass should be evaluated as the average of all 6-tile leaves or something of the sort (Quackle uses -1000 as a shortcut which isn't a bad idea)

add some getting started instructions

$ ./main 
{"level":"info","time":"2020-05-30T17:06:22-07:00","message":"Terminal color support is on."}
{"level":"info","time":"2020-05-30T17:06:22-07:00","message":"Loaded config: &{false ./data/strategy ./data/lexica NWL18 English}"}
{"level":"info","path":"/home/mdemello/github/macondo/data/lexica","time":"2020-05-30T17:06:22-07:00","message":"new lexicon path"}
{"level":"info","sppath":"/home/mdemello/github/macondo/data/strategy","time":"2020-05-30T17:06:22-07:00","message":"new strat params path"}
macondo> new
Error: open /home/mdemello/github/macondo/data/lexica/gaddag/NWL18.gaddag: no such file or directory
macondo> exit
{"level":"info","time":"2020-05-30T17:06:34-07:00","message":"got quit signal..."}
{"level":"info","time":"2020-05-30T17:06:34-07:00","message":"server gracefully shutting down"}

play legality check is incomplete

ErrorIfIllegalPlay doesn't seem to validate that the word isn't immediately after or immediately before a tile, this can potentially lead to interesting exploits like playing "word" between p and q (taking a lesser score) in void challenge or not being able to challenge off pwordq in other challenge modes.

macondo/board/board.go

Lines 367 to 427 in 57f213b

// ErrorIfIllegalPlay returns an error if the play is illegal, or nil otherwise.
// We are not checking the actual validity of the word, but whether it is a
// legal Crossword Game move.
func (g *GameBoard) ErrorIfIllegalPlay(row, col int, vertical bool,
word alphabet.MachineWord) error {
ri, ci := 0, 1
if vertical {
ri, ci = ci, ri
}
boardEmpty := g.IsEmpty()
touchesCenterSquare := false
bordersATile := false
for idx, ml := range word {
newrow, newcol := row+(ri*idx), col+(ci*idx)
if boardEmpty && newrow == g.Dim()>>1 && newcol == g.Dim()>>1 {
touchesCenterSquare = true
}
if newrow < 0 || newrow >= g.Dim() || newcol < 0 || newcol >= g.Dim() {
return errors.New("play extends off of the board")
}
if ml == alphabet.PlayedThroughMarker {
ml = g.GetLetter(newrow, newcol)
if ml == alphabet.EmptySquareMarker {
return errors.New("a played-through marker was specified, but " +
"there is no tile at the given location")
}
} else {
ml = g.GetLetter(newrow, newcol)
if ml != alphabet.EmptySquareMarker {
return fmt.Errorf("tried to play through a letter already on "+
"the board; please use the played-through marker (.) instead ("+
"(row %v col %v ml %v)", newrow, newcol, ml)
}
// We are placing a tile on this empty square. Check if we border
// any other tiles.
for _, places := range [][2]int{
{0, 1}, {0, -1}, {1, 0}, {-1, 0},
} {
checkrow, checkcol := newrow+places[0], newcol+places[1]
if g.PosExists(checkrow, checkcol) && g.GetLetter(checkrow, checkcol) != alphabet.EmptySquareMarker {
bordersATile = true
}
}
}
}
if boardEmpty && !touchesCenterSquare {
return errors.New("the first play must touch the center square")
}
if !boardEmpty && !bordersATile {
return errors.New("your play must border a tile already on the board")
}
return nil
}

also small typo (two (s) here:

macondo/board/board.go

Lines 400 to 402 in 57f213b

return fmt.Errorf("tried to play through a letter already on "+
"the board; please use the played-through marker (.) instead ("+
"(row %v col %v ml %v)", newrow, newcol, ml)

6-zero export is broken

Game that ends after six zeroes exports incorrectly:

#character-encoding UTF-8
#description Created with Macondo
#id org.aerolith 5j8yWLhNHfkws3ix4GeqCH
#player1 arcadio José Arcadio Buendía
#player2 úrsula Úrsula Iguarán Buendía
>arcadio: AHNRSTV 8H SNATH +24 24
>úrsula: EIKOSTT - +0 0
>arcadio: EELRVVW - +0 24
>úrsula: EIKOSTT - +0 0
>arcadio: EELRVVW - +0 24
>úrsula: EIKOSTT - +0 0
>arcadio: EELRVVW - +0 24
>arcadio: EELRVVW (EELRVVW) -16 8
>arcadio: EELRVVW - +0 24
>arcadio: EELRVVW (EELRVVW) -16 8

mysterious endgame crash

> load woogles iUsasmWy
> turn 23
macondo> endgame
plies 4, deepening true, simpleEval false, pruningDisabled false

   A B C D E F G H I J K L M N O                    HastyBot           383
   ------------------------------    ->                cesar  AEEOSW?  340
 1|V     '       =       '     =
 2|I -       "       " G     -      Bag + unseen: (7)
 3|N   - T     '   '   R   -
 4|Y     E       S Q U I D     '    A G H I N P T
 5|L O A T H       A   P I
 6|  W I R E " T E T "   v   "
 7|O L L A   B A G '     O '
 8|=     C R E D O     B R A I N
 9|    R I E L S   '     C '
10|  Z E D   "   M   "   E   "      Turn 23:
11|        -     O     Y E          HastyBot played 7A OLL. for 15 pts from a
12|'     -       V I N O S     '    rack of GILLNOT
13|  X U       ' A '       -
14|  U N J A M   N   K E F   -
15|=     '   O U T F I R E D   =

{"level":"info","time":"2021-09-20T12:08:34-04:00","message":"Spread swing estimate found after 1 plies: 25"}
{"level":"info","time":"2021-09-20T12:08:34-04:00","message":"Best seq so far is [<0xc000438960 action: play word: M2 OWSE score: 35 tp: 4 leave: AE? equity: 0.000 valu: 25.000>]"}
{"level":"info","time":"2021-09-20T12:08:39-04:00","message":"Spread swing estimate found after 2 plies: 37"}
{"level":"info","time":"2021-09-20T12:08:39-04:00","message":"Best seq so far is [<0xc0003c6140 action: play word: H1 WOE. score: 21 tp: 3 leave: AES? equity: 0.000 valu: 12.000> <0xc004162500 action: play word: 9J GA.H score: 27 tp: 3 leave: INPT equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2021-09-20T12:08:49-04:00","message":"Spread swing estimate found after 3 plies: 37"}
{"level":"info","time":"2021-09-20T12:08:49-04:00","message":"Best seq so far is [<0xc0003c6140 action: play word: H1 WOE. score: 21 tp: 3 leave: AES? equity: 0.000 valu: 12.000> <0xc004162500 action: play word: 9J GA.H score: 27 tp: 3 leave: INPT equity: 0.000 valu: -16.000> <0xc01486b900 action: play word: 11B ApSE score: 31 tp: 4 leave:  equity: 0.000 valu: 43.000>]"}
runtime: s.allocCount= 513 s.nelems= 512
fatal error: s.allocCount != s.nelems && freeIndex == s.nelems

goroutine 37 [running]:
runtime.throw({0x147505d, 0x53d55f98})
	runtime/panic.go:1198 +0x71 fp=0xc00012b348 sp=0xc00012b318 pc=0x1034d91
runtime.(*mcache).nextFree(0x1c40108, 0x5)
	runtime/malloc.go:878 +0x1e5 fp=0xc00012b390 sp=0xc00012b348 pc=0x100da05
runtime.mallocgc(0x8, 0x0, 0x0)
	runtime/malloc.go:1046 +0x331 fp=0xc00012b410 sp=0xc00012b390 pc=0x100dd71
runtime.growslice(0xc000126200, {0xc00012b4ce, 0xc22ff4ffe8, 0x2}, 0x8)
	runtime/slice.go:261 +0x4ac fp=0xc00012b478 sp=0xc00012b410 pc=0x104bacc
github.com/domino14/macondo/movegen.(*GordonGenerator).goOn(0xc000126200, 0x0, 0x0, {0xc00012b5ee, 0x1, 0xc0d86c4ab0}, 0x3, 0x130cd0, 0x130cb6)
	github.com/domino14/macondo/movegen/movegen.go:207 +0x19b fp=0xc00012b518 sp=0xc00012b478 pc=0x125fb7b
github.com/domino14/macondo/movegen.(*GordonGenerator).recursiveGen(0xc000126200, 0x0, {0xc00012b5ee, 0x1, 0x1}, 0xc000242a00, 0x130cb6)
	github.com/domino14/macondo/movegen/movegen.go:175 +0x47f fp=0xc00012b598 sp=0xc00012b518 pc=0x125f83f
github.com/domino14/macondo/movegen.(*GordonGenerator).goOn(0xc000126200, 0x1, 0x16, {0xc00012b6f0, 0x0, 0x10000000104b592}, 0x53d569b0, 0x130cb6, 0x0)
	github.com/domino14/macondo/movegen/movegen.go:227 +0x3e5 fp=0xc00012b638 sp=0xc00012b598 pc=0x125fdc5
github.com/domino14/macondo/movegen.(*GordonGenerator).recursiveGen(0xc000126200, 0x1, {0xc00012b6f0, 0x0, 0x0}, 0xc000242a00, 0x0)
	github.com/domino14/macondo/movegen/movegen.go:175 +0x47f fp=0xc00012b6b8 sp=0xc00012b638 pc=0x125f83f
github.com/domino14/macondo/movegen.(*GordonGenerator).genByOrientation(0xc000126200, 0x3, 0x1)
	github.com/domino14/macondo/movegen/movegen.go:140 +0xda fp=0xc00012b720 sp=0xc00012b6b8 pc=0x125f33a
github.com/domino14/macondo/movegen.(*GordonGenerator).GenAll(0xc000126200, 0xc13cdf6400, 0x3c)
	github.com/domino14/macondo/movegen/movegen.go:121 +0x7a fp=0xc00012b760 sp=0xc00012b720 pc=0x125f1da
github.com/domino14/macondo/endgame/alphabeta.(*Solver).generateSTMPlays(0xc00014c0c0, 0x2)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:240 +0x20e fp=0xc00012b950 sp=0xc00012b760 pc=0x137d40e
github.com/domino14/macondo/endgame/alphabeta.(*Solver).childGenerator(0xc00014c0c0, 0xc02931e410, 0x8)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:305 +0xae fp=0xc00012b9c0 sp=0xc00012b950 pc=0x137dd6e
github.com/domino14/macondo/endgame/alphabeta.(*Solver).alphabeta(0xc00014c0c0, 0xc02931e410, 0x1, 0x42140000, 0x4b189680, 0x3)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:502 +0xa5 fp=0xc00012ba68 sp=0xc00012b9c0 pc=0x137efa5
github.com/domino14/macondo/endgame/alphabeta.(*Solver).alphabeta(0xc00014c0c0, 0xc00fba51d0, 0x2, 0x42140000, 0x4b189680, 0x5)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:475 +0x3ab fp=0xc00012bb10 sp=0xc00012ba68 pc=0x137f2ab
github.com/domino14/macondo/endgame/alphabeta.(*Solver).alphabeta(0xc00014c0c0, 0xc0009fb090, 0x3, 0x42140000, 0x4b189680, 0x0)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:509 +0x196 fp=0xc00012bbb8 sp=0xc00012bb10 pc=0x137f096
github.com/domino14/macondo/endgame/alphabeta.(*Solver).alphabeta(0xc00014c0c0, 0xc000396230, 0x4, 0xcb189680, 0x4b189680, 0x0)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:475 +0x3ab fp=0xc00012bc60 sp=0xc00012bbb8 pc=0x137f2ab
github.com/domino14/macondo/endgame/alphabeta.(*Solver).Solve(0xc00014c0c0, 0x4)
	github.com/domino14/macondo/endgame/alphabeta/alphabeta.go:424 +0x7b0 fp=0xc00012bdc0 sp=0xc00012bc60 pc=0x137ec10
github.com/domino14/macondo/shell.(*ShellController).endgame(0xc00023a000, 0xc000382040)
	github.com/domino14/macondo/shell/api.go:231 +0x39e fp=0xc00012be78 sp=0xc00012bdc0 pc=0x139d2fe
github.com/domino14/macondo/shell.(*ShellController).standardModeSwitch(0xc00023a000, {0xc0000fa020, 0x43f}, 0x0)
	github.com/domino14/macondo/shell/shell.go:726 +0x585 fp=0xc00012bf28 sp=0xc00012be78 pc=0x13a2a25
github.com/domino14/macondo/shell.(*ShellController).Loop(0xc00023a000, 0x0)
	github.com/domino14/macondo/shell/shell.go:761 +0x191 fp=0xc00012bfc0 sp=0xc00012bf28 pc=0x13a2e51
main.main·dwrap·1()
	./main.go:61 +0x2a fp=0xc00012bfe0 sp=0xc00012bfc0 pc=0x13a422a
runtime.goexit()
	runtime/asm_amd64.s:1581 +0x1 fp=0xc00012bfe8 sp=0xc00012bfe0 pc=0x1064d21
created by main.main
	./main.go:61 +0x35d

goroutine 1 [chan receive, 5 minutes]:
main.main()
	./main.go:66 +0x3b1

goroutine 6 [chan receive, 5 minutes]:
main.main.func1()
	./main.go:50 +0x7e
created by main.main
	./main.go:48 +0x29d

goroutine 7 [select, 5 minutes]:
github.com/chzyer/readline.(*CancelableStdin).ioloop(0xc00006ede0)
	github.com/chzyer/[email protected]/std.go:93 +0x7a
created by github.com/chzyer/readline.NewCancelableStdin
	github.com/chzyer/[email protected]/std.go:86 +0xfb

goroutine 8 [select, 5 minutes]:
io.(*pipe).Read(0xc00006ee40, {0xc000280000, 0x64, 0x1})
	io/pipe.go:57 +0xb7
io.(*PipeReader).Read(0x0, {0xc000280000, 0x0, 0x0})
	io/pipe.go:134 +0x25
github.com/chzyer/readline.(*FillableStdin).ioloop.func1()
	github.com/chzyer/[email protected]/std.go:161 +0x5b
created by github.com/chzyer/readline.(*FillableStdin).ioloop
	github.com/chzyer/[email protected]/std.go:157 +0x5b

goroutine 9 [select, 5 minutes]:
github.com/chzyer/readline.(*Terminal).ioloop(0xc000078be0)
	github.com/chzyer/[email protected]/terminal.go:135 +0x247
created by github.com/chzyer/readline.NewTerminal
	github.com/chzyer/[email protected]/terminal.go:38 +0x155

goroutine 34 [syscall, 5 minutes]:
os/signal.signal_recv()
	runtime/sigqueue.go:166 +0x28
os/signal.loop()
	os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
	os/signal/signal.go:151 +0x2c

goroutine 35 [chan receive, 5 minutes]:
github.com/chzyer/readline.DefaultOnWidthChanged.func1.1()
	github.com/chzyer/[email protected]/utils_unix.go:75 +0x37
created by github.com/chzyer/readline.DefaultOnWidthChanged.func1
	github.com/chzyer/[email protected]/utils_unix.go:73 +0xa5

goroutine 36 [chan receive, 5 minutes]:
github.com/chzyer/readline.(*Terminal).ReadRune(...)
	github.com/chzyer/[email protected]/terminal.go:100
github.com/chzyer/readline.(*Operation).ioloop(0xc000194690)
	github.com/chzyer/[email protected]/operation.go:111 +0x4b
created by github.com/chzyer/readline.NewOperation
	github.com/chzyer/[email protected]/operation.go:88 +0x310

go1.17.1

shell navigation issues with going back and forth

similar to another issue, but if you make a mistake while annotating, then do p and change the play to something else, then when you get back to a later turn and you do p or n it thinks you're back at the top. Also, changing the play doesn't change it in the exported GCG, but instead adds it as another play right after the mistake.

change randomizer

The tile drawing algorithm uses math/rand and the built-in Fisher-Yates shuffle to generate an initial state for the bag. Then tiles are drawn in order for each player. The bag is re-shuffled if a player exchanges.

The problem with this is that due to the limitations of math/rand only 2^31-1 possible states exist - i.e. there are only around 2 billion possible initial tile shuffles. This is a tiny fraction of the total of approximately 2^384 possible initial bag shuffles for a game of OMGWords. There's a real possibility that at some point we will have two identical shuffles (birthday paradox), and 2 billion may also be small enough to enumerate all possible bags and attack the PRNG that way with knowledge of the first few tiles.

Instead, we should do the following:

  1. Use crypto/rand for tile draws for all actual games. Right now it is only used to seed the PRNG, but this is not sufficient.

  2. Instead of shuffling, pick all tiles one at a time. Shuffling is a bit overkill and the one-at-a-time tile draw algorithm is easy to explain and audit. There is no need to re-shuffle when throwing tiles in.

  3. This should be configurable. crypto/rand is significantly slower than math/rand. For real-life games, it won't make a real difference. But for simulations, it definitely will. Sims should use a faster PRNG. Also, the shuffling algorithm is measurably faster than the "one-at-a-time" picking.

  4. Change from math/rand for the simulation PRNG to lukechampine.com/frand - this is a faster, and more "secure" PRNG with a 256-bit state.

Note -- I haven't yet investigated this deeply, but we can also use rand.Perm as a replacement for shuffling in both cases. https://golang.org/src/math/rand/rand.go?s=11272:11294#L217

If we copy this code and use it for the crypto/rand randomizer, then we can generate an initial permutation of tiles and just use this for drawing. For exchanging, we can model it as a draw, then a swap with a random position in what's left of the permutation for each of the tiles we're throwing in.

add win% or something similar to rank plays by

   A B C D E F G H I J K L M N O     ->             Player_1  EEEIILZ  336
   ------------------------------                   Player_2           298
 1|C     '       =       '     =
 2|O -   T O Y       "       -      Bag + unseen: (16)
 3|m I R A D O R   '       -
 4|F     -   D A B     P U G H '    A A A C E E I I I I L N N Q R S
 5|I       -   G O O E Y       V
 6|T "       X I     M A L T H A
 7|    '       '   '       '   N
 8|=     '     G U M     ' O W N
 9|    '       ' P E W     D O E
10|  "       "       E F   D O R    Turn 20:
11|    K U N A   J   B E V E L S    Player_2 played 4K PUGH for 28 pts from a
12|'     T U R R E T s   - S   '    rack of GHPU
13|    -       ' A '       T
14|  -       "   N   "       -
15|=     '       S       '     =

Macondo plays M1 ELE(G)I(T) here after simulation. On Quackle, ELEGIT also sims higher by equity, but not by win%.

add a network server interface

This could be done relatively easily by refactoring shell.go into functions that always return a value to be displayed rather than displaying it, and have the main loop call ShowMessage or ShowError on the return value. Then a server module could reuse the code but accept incoming commands over the network, call the shell functions and push the retun value back out.

This would make it possible to use macondo as a language-agnostic "library", programs could launch a macondo server subprocess and communicate with it over ipc.

endgame analyzer not ending game!?

regression?

> load woogles SBRtWRzo

> turn 21

> endgame 6

After a few minutes it prints out:

{"level":"info","time":"2021-01-29T18:13:35-05:00","message":"Best seq so far is [<0x140003f5ea0 action: play word: L1 JET. score: 21 tp: 3 leave: ILMS equity: 0.000 valu: 17.000> <0x14000fb2280 action: play word: E9 .AE score: 20 tp: 2 leave: IOORT equity: 0.000 valu: 11.000> <0x140030ea1e0 action: play word: 15L MIL. score: 16 tp: 3 leave: S equity: 0.000 valu: 5.000> <0x14004e46fa0 action: play word: C3 ROTI score: 14 tp: 4 leave: O equity: 0.000 valu: 0.000> <0x1401dc5ac80 action: play word: 11J S.. score: 12 tp: 1 leave: equity: 0.000 valu: 14.000> <0x140af2ada40 action: play word: 13F .O. score: 10 tp: 1 leave: equity: 0.000 valu: 10.000>]"}

game should have been ended with penultimate move (no leave) so why did it generate an extra move?

endgame in shell solves it wrong if we navigate back and forth between positions

EDIT: see comment below, this is only broken on the refactor branch (#148)

with new branch board-refactor (which includes 6-pass fix)

load woogles mQLyde5N
turn 27
endgame 14

Best sequence has a spread difference of 34
Best sequence:
1) 12L ..E.
2) O10 ....S
3) 15A I.
4) (Pass)
5) K2 Ta..
6) J1 DI
7) 2I L..
8) (Pass)
9) 1H PE.

This sequence is wrong. At the very least, turn 9 should be M2 PE(DI) for 4 more points than 1H PE(D), and probably other moves are wrong. The first move should probably block the I spot at 15A (and the second move should take that spot since they have three spots for the S). It looks like minimax breaks down after a bigger number of iterations. Could it be because of the six-zero fix? :(

endgame still passes unnecessarily sometimes!

macondo> endgame 10
plies 10, deepening true, simpleEval false, pruningDisabled false

   A B C D E F G H I J K L M N O                        Carl    HOPS?  344
   ------------------------------    ->                 Noah  AFLNNNT  362
 1|=     '       =       '     =
 2|  -       "       "       -      Bag + unseen: (0)
 3|    - l     '   '       -
 4|'   A I       '       -     '
 5|    N O -           -
 6|  Z I N   "       "       "
 7|U   L I     ' C '       '
 8|R   I S     Q A T S   '     G
 9|E   T E   V I D E O     J   R
10|A B Y     "       Y     A M I    Turn 27:
11|  R   O O T H E C A E   W I N    Carl played M9 JAW for 32 pts from a rack
12|B R O K E   U M     X U   D '    of ?AHJPSW
13|U   -     E G O '     P O D
14|R A I A   W E T   A E     L
15|L   D I G   R E F T   V E E S

{"level":"info","time":"2020-04-12T13:44:36-04:00","message":"Spread swing estimate found after 1 plies: 41"}
{"level":"info","time":"2020-04-12T13:44:36-04:00","message":"Spread swing estimate found after 2 plies: -11"}
{"level":"info","time":"2020-04-12T13:44:36-04:00","message":"Spread swing estimate found after 3 plies: -18"}
{"level":"info","time":"2020-04-12T13:44:36-04:00","message":"Spread swing estimate found after 4 plies: -18"}
{"level":"info","time":"2020-04-12T13:44:42-04:00","message":"Spread swing estimate found after 5 plies: -18"}
{"level":"info","time":"2020-04-12T13:44:45-04:00","message":"Spread swing estimate found after 6 plies: -18"}
{"level":"info","time":"2020-04-12T13:45:12-04:00","message":"Spread swing estimate found after 7 plies: -19"}
{"level":"info","time":"2020-04-12T13:45:22-04:00","message":"Spread swing estimate found after 8 plies: -19"}
{"level":"info","time":"2020-04-12T13:45:36-04:00","message":"Spread swing estimate found after 9 plies: -19"}
{"level":"info","time":"2020-04-12T13:45:57-04:00","message":"Spread swing estimate found after 10 plies: -19"}
{"level":"info","time":"2020-04-12T13:45:57-04:00","message":"Best spread found: -19"}
Best sequence has a spread difference of -19
Best sequence:
1) 5C ..NFAN
2) 6B ...e
3) J13 L..
4) 4G PO
5) 7C ..T
6) G4 ..H
7) (Pass)
8) (Pass)
9) (Pass)
10) 5C ......S

This is from https://www.cross-tables.com/annotated.php?u=31356#28

Shell in Unix: db2 database with data with umlaut not exported to CSV

Hi, I have written a .sh file and in this .sh file I have selected some data which exports a .csv file.
I recognized that all the lines with umlaut (ä,ö,ü etc.) are not considered to be exported, and the rest of the data are okay. So I think it might be a problem of data format in shell code.
I used "UTF-8" to encode the .sh file.
And my db2 code is something like this:
SELECT * FROM table WHERE something IN ('aktiviert', 'gekündigt','beendet')
I suppose there is something wrong with "gekündigt". Could someone help?
Many thanks!

sim crashes

macondo> rack AEFIOST

   A B C D E F G H I J K L M N O                      úrsula           371
   ------------------------------    ->              arcadio  AEFIOST  344
 1|J I N '       =       '     =
 2|  O A R I E S T   "       -      Bag + unseen: (8)
 3|    - E   E U O I       -
 4|'     T     s W O L E S T   '    A D E I N O V Z
 5|      I -           B
 6|  "   N   "       M O     "
 7|    ' A     '   Q I N   G
 8|=     E       V A X   G U L P
 9|    '       '   D       A
10|  T H R A W N   I F     r "      Turn 20:
11|        L O U P   L -   I        arcadio played 1A JIN for 34 pts from a ra
12|N E E M B   T U Y E R E S   '    ck of IJN
13|    -       ' K ' G     H
14|  A C C O R D E D "       -
15|=     '       Y A R R '     =

macondo> gen
     Move                Leave  Score Equity

  1: 1H SOFTIE           A      51    52.09
  2: 1H SOFTA            EI     48    48.93
  3: 1H SOFA             EIT    42    45.35
  4: K10 FA.O            EIST   30    44.28
  5: 1H SEIF             AOT    42    41.89
  6: 1H SAFE             IOT    42    41.26
  7: 1H SOFT             AEI    42    41.18
  8: K10 FO.             AEIST  25    40.13
  9: 1H STOAI            EF     39    38.99
 10: 1H SAFT             EIO    42    38.69
 11: 1H SIFT             AEO    42    38.68
 12: 1H SIF              AEOT   39    38.49
 13: 1H STOAE            FI     39    37.75
 14: C5 OAF              EIST   23    37.28
 15: E4 OF               AEIST  22    37.13
macondo> sim
Simulation started. Please do `sim show` and `sim details` to see more info
macondo> panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0xdc pc=0x13fe5cd]

goroutine 15 [running]:
github.com/domino14/macondo/game.(*Game).PlayMove(0xc000536780, 0xc0003b8f00, 0xc0000fc100, 0x0, 0xc000098570, 0x0)
	/Users/cesar/code/macondo/game/game.go:355 +0x20d
github.com/domino14/macondo/montecarlo.(*Simmer).simSingleIteration(0xc0000be8c0, 0x2, 0x2, 0x1, 0xc00008e900)
	/Users/cesar/code/macondo/montecarlo/montecarlo.go:340 +0x224
github.com/domino14/macondo/montecarlo.(*Simmer).Simulate.func4(0x0, 0x0)
	/Users/cesar/code/macondo/montecarlo/montecarlo.go:264 +0x1aa
golang.org/x/sync/errgroup.(*Group).Go.func1(0xc000137bf0, 0xc000137c80)
	/Users/cesar/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x59
created by golang.org/x/sync/errgroup.(*Group).Go
	/Users/cesar/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:54 +0x66

expire gaddag/dawg caches occasionally

we should maybe expire these every so often, or come up with another way to invalidate the cache for things like the CEL lexicon. (otherwise every time it gets updated we have to restart the backend)

annotating game causes improper endgame behavior at end.


   A B C D E F G H I J K L M N O                     player1           477
   ------------------------------    ->              player2  AACELNY  398
 1|=     '       W R E s T I N G
 2|  -       "   O   "       -      Bag + unseen: (1)
 3|    -       ' N '       -
 4|'     -       D       -     '    R
 5|        -     E     -       F
 6|  " G A R N E R S "       " U
 7|A B O       '   '       '   J
 8|X I   Q   A W A Y     '   L I
 9|  D H U T I ' H E P     ' O
10|  "   A   S T I P E N D   V      Turn 21:
11|K L U G E         T O O   E F    player1 played 1H .REsTING for 86 pts from
12|A     S       V       T O L U     a rack of ?EGINRT
13|Z I T     E R E M I t E - I N
14|O -   C A M   R   "       E D
15|O     '       B       '   S I

macondo> add 4H .ANCEY

   A B C D E F G H I J K L M N O                     player1           477
   ------------------------------                    player2           422
 1|=     '       W R E s T I N G
 2|  -       "   O   "       -      Bag + unseen: (3)
 3|    -       ' N '       -
 4|'     -       D A N C E Y   '    A L R
 5|        -     E     -       F
 6|  " G A R N E R S "       " U
 7|A B O       '   '       '   J
 8|X I   Q   A W A Y     '   L I
 9|  D H U T I ' H E P     ' O
10|  "   A   S T I P E N D   V      Turn 22:
11|K L U G E         T O O   E F    player2 played 4H .ANCEY for 24 pts from a
12|A     S       V       T O L U     rack of AACELNY
13|Z I T     E R E M I t E - I N
14|O -   C A M   R   "       E D
15|O     '       B       '   S I    Game is over.

See above. Game incorrectly ended even though the R was still unseen; the opponent should have had it.

shell crash on `p` play

macondo> s

   A B C D E F G H I J K L M N O     ->               úrsula  NOORTWX  316
   ------------------------------                    arcadio           333
 1|=     '       G       ' C E E
 2|  -       "   R   " V   H -      Bag + unseen: (16)
 3|    -   O   Q I ' P I   I
 4|'   F - U N I O N I S E D   '    A B E E E F G J L L O O S S U ?
 5|    L   T     T   P I
 6|  Z A   V "       I T     "
 7|    M A Y   '   ' E     '
 8|=     B I     T O R C H     =
 9|    ' A n   Y A K       '
10|  "     G "     A R O U S E D    Turn 18:
11|        -           -   A   A    úrsula played L12 MELD for 30 pts from a r
12|'     -       '       M U   W    ack of DELMNTX
13|    -       '   '     E N   N
14|  -       "       "   L A - E
15|=     ' I N T E R R E D     D

macondo> gen
     Move                Leave  Score Equity

  1: C9 WONT             ORX    20    22.41
  2: C9 WORN             OTX    20    22.31
  3: C9 WORT             NOX    20    22.24
  4: C9 WON              ORTX   19    22.11
  5: C9 WOT              NORX   19    19.90
  6: 14D OXO             NRTW   25    19.55
  7: B2 OXO              NRTW   25    19.55
  8: 7L OX               NORTW  22    19.38
  9: 14B WONT            ORX    16    18.41
 10: 14B NOWT            ORX    16    18.41
 11: I4 .OW              NORTX  18    18.36
 12: C9 WO               NORTX  18    18.36
 13: 14B WORT            NOX    16    18.24
 14: N6 WROT.            NOX    16    18.24
 15: 7L OWN              ORTX   15    18.11
macondo> p
{"level":"error","error":"the play-through tile is incorrect (board 20, specified 14)","time":"2020-07-30T22:38:02-04:00"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x140509e]

goroutine 25 [running]:
github.com/domino14/macondo/move.(*Move).Action(...)
	/Users/cesar/code/macondo/move/move.go:122
github.com/domino14/macondo/game.(*Game).playTurn(0xc0001140f0, 0x4, 0x4, 0x3)
	/Users/cesar/code/macondo/game/game.go:683 +0x20e
github.com/domino14/macondo/game.(*Game).PlayToTurn(0xc0001140f0, 0x11, 0x0, 0x0)
	/Users/cesar/code/macondo/game/game.go:602 +0x1bb
github.com/domino14/macondo/shell.(*ShellController).setToTurn(0xc000144000, 0x11, 0xc000020200, 0xc000c88a30)
	/Users/cesar/code/macondo/shell/shell.go:275 +0x46
github.com/domino14/macondo/shell.(*ShellController).prev(0xc000144000, 0xc000076270, 0xc000076270, 0x0, 0x0)
	/Users/cesar/code/macondo/shell/api.go:95 +0x3e
github.com/domino14/macondo/shell.(*ShellController).standardModeSwitch(0xc000144000, 0xc000c88a30, 0x1, 0xc0000b2f00, 0x0, 0x0, 0x0)
	/Users/cesar/code/macondo/shell/shell.go:611 +0x34a
github.com/domino14/macondo/shell.(*ShellController).Loop(0xc000144000, 0xc0000b2f00)
	/Users/cesar/code/macondo/shell/shell.go:672 +0x151
created by main.main
	/Users/cesar/code/macondo/cmd/shell/main.go:55 +0x274

iterative deepening for endgame does not need to store nodes

there is some advantage from storing the old tree while doing iterative deepening. you don't have to regenerate moves when you revisit the tree and try to go one depth down, and we also start searching after sorting by valuation (so more valuable nodes are searched first).

however, this does not scale well for complex endgames. even on my computer with 64GB of ram it dies with a lot of endgames. after reading a lot of the literature on iterative deepening it's worth trying to use it just for its iterative finding capability and not bother re-sorting or storing the entire tree. we can regenerate moves on the fly during recursion, and store them "locally" in the recursion stack. GC should take care of it (we can help it by using sync.Pool maybe) and memory consumption should be drastically lower. the amount of nodes searched apparently scales as (n/(n-1)) where n is the branching factor of the tree; endgames have a large branching factor so adding iterative deepening vs just looking X plies deep is very close.

additionally, we can get a massive speed boost if we do the minimax search in a multi-threaded fashion. it's not clear exactly how to do this yet, but one quick thing i can imagine is a pool of workers who just handle different calls to the recursive alpha-beta function; care has to be taken that they're not writing to common memory.

it's at least worth trying out to see if it is feasible.

using `set lexicon`, then loading a GCG does not keep the set lexicon

I really thought I'd solved the endgame algorithm even if it was slow. This endgame does not solve:

macondo> endgame 10
plies 10, deepening true, simpleEval false, pruningDisabled false

   A B C D E F G H I J K L M N O                    Matthew_           498
   ------------------------------    ->                Nigel      EFL  434
 1|=   G R A N T E D     '     L
 2|  X I     "     A B B O T C Y    Bag + unseen: (1)
 3|    -       F E G     W O   A
 4|'     -     E U O U A E     M    J
 5|        -     O     D
 6|  "       " O I   " V   I "
 7|E A N     M O   '   O   N
 8|C H   Q U I R T   D u D S   =
 9|H I O I     I   '   T   T
10|I "   N A P E     " R   A "      Turn 35:
11|N       U R         Y   R        Matthew_ passed, holding a rack of J
12|G     W A I   Z       -     '
13|    -   S L E E V E E n -
14|  -       L   P   "       -
15|=     '       S T A R K E N S

{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Spread swing estimate found after 1 plies: 47"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Best seq so far is [<0xc05eb2c780 action: play word: N4 ELF score: 31 tp: 3 leave:  equity: 0.000 valu: 47.000>]"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Spread swing estimate found after 2 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Best seq so far is [<0xc05eb2c8c0 action: play word: N6 FE score: 28 tp: 2 leave: L equity: 0.000 valu: 45.750> <0xc05eb2d5e0 action: pass leave: J equity: 0.000 valu: -29.000>]"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Spread swing estimate found after 3 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Best seq so far is [<0xc05eae3900 action: play word: A6 L...... score: 13 tp: 1 leave: EF equity: 0.000 valu: 37.750> <0xc05eb88780 action: pass leave: J equity: 0.000 valu: -44.000> <0xc05f078be0 action: play word: N6 FE score: 28 tp: 2 leave:  equity: 0.000 valu: 44.000>]"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Spread swing estimate found after 4 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Best seq so far is [<0xc05eae3900 action: play word: A6 L...... score: 13 tp: 1 leave: EF equity: 0.000 valu: 37.750> <0xc05eb88780 action: pass leave: J equity: 0.000 valu: -44.000> <0xc05f078be0 action: play word: N6 FE score: 28 tp: 2 leave:  equity: 0.000 valu: 44.000> <0xc05f541a40 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Spread swing estimate found after 5 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:40-04:00","message":"Best seq so far is [<0xc05eae3900 action: play word: A6 L...... score: 13 tp: 1 leave: EF equity: 0.000 valu: 37.750> <0xc05eb88780 action: pass leave: J equity: 0.000 valu: -44.000> <0xc05f078be0 action: play word: N6 FE score: 28 tp: 2 leave:  equity: 0.000 valu: 44.000> <0xc05f541a40 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:41-04:00","message":"Spread swing estimate found after 6 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:41-04:00","message":"Best seq so far is [<0xc05eb2c8c0 action: play word: N6 FE score: 28 tp: 2 leave: L equity: 0.000 valu: 45.750> <0xc05eb2d5e0 action: pass leave: J equity: 0.000 valu: -29.000> <0xc05f0795e0 action: play word: A6 L...... score: 13 tp: 1 leave:  equity: 0.000 valu: 29.000> <0xc05f6e8c80 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:41-04:00","message":"Spread swing estimate found after 7 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:41-04:00","message":"Best seq so far is [<0xc05eae3900 action: play word: A6 L...... score: 13 tp: 1 leave: EF equity: 0.000 valu: 37.750> <0xc05eb88780 action: pass leave: J equity: 0.000 valu: -44.000> <0xc05f078be0 action: play word: N6 FE score: 28 tp: 2 leave:  equity: 0.000 valu: 44.000> <0xc05f541a40 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:42-04:00","message":"Spread swing estimate found after 8 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:42-04:00","message":"Best seq so far is [<0xc05eb2c8c0 action: play word: N6 FE score: 28 tp: 2 leave: L equity: 0.000 valu: 45.750> <0xc05eb2d5e0 action: pass leave: J equity: 0.000 valu: -29.000> <0xc05f0795e0 action: play word: A6 L...... score: 13 tp: 1 leave:  equity: 0.000 valu: 29.000> <0xc05f6e8c80 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:42-04:00","message":"Spread swing estimate found after 9 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:42-04:00","message":"Best seq so far is [<0xc05eae3900 action: play word: A6 L...... score: 13 tp: 1 leave: EF equity: 0.000 valu: 37.750> <0xc05eb88780 action: pass leave: J equity: 0.000 valu: -44.000> <0xc05f078be0 action: play word: N6 FE score: 28 tp: 2 leave:  equity: 0.000 valu: 44.000> <0xc05f541a40 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:43-04:00","message":"Spread swing estimate found after 10 plies: 57"}
{"level":"info","time":"2020-09-12T00:36:43-04:00","message":"Best seq so far is [<0xc05eb2c8c0 action: play word: N6 FE score: 28 tp: 2 leave: L equity: 0.000 valu: 45.750> <0xc05eb2d5e0 action: pass leave: J equity: 0.000 valu: -29.000> <0xc05f0795e0 action: play word: A6 L...... score: 13 tp: 1 leave:  equity: 0.000 valu: 29.000> <0xc05f6e8c80 action: pass leave: J equity: 0.000 valu: -16.000>]"}
{"level":"info","time":"2020-09-12T00:36:43-04:00","message":"Best spread found: 57"}
Best sequence has a spread difference of 57
Best sequence:
1) N6 FE
2) (Pass)
3) A6 L......
4) (Pass)

the correct sequence is EECHING/LEECHING/FLEECHING (with passes in between) for a total spread difference of 65

ARGH!!!!

panic while running autoplay

unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb01dfacedebac1e pc=0x125fb09]


goroutine 25 [running]:
runtime.throw({0x146031b, 0x13d84e0})
        runtime/panic.go:1198 +0x71 fp=0xc0000d38c8 sp=0xc0000d3898 pc=0x1034db1
runtime.sigpanic()
        runtime/signal_unix.go:742 +0x2f6 fp=0xc0000d3918 sp=0xc0000d38c8 pc=0x104a536
github.com/domino14/macondo/strategy.(*OldLeaves).LeaveValue(0xc0000da3f0, {0x4000000000000000, 0xc0000da310, 0x6a})
        github.com/domino14/macondo/strategy/read_olv.go:43 +0x1e9 fp=0xc0000d3958 sp=0xc0000d3918 pc=0x125fb09
github.com/domino14/macondo/strategy.ExhaustiveLeaveStrategy.LeaveValue(...)
        github.com/domino14/macondo/strategy/exhaustive_leave.go:118
github.com/domino14/macondo/strategy.ExhaustiveLeaveStrategy.Equity({{0x14ee140, 0xc0000da3f0}, {0x1812090, 0x0, 0x0}}, 0xc003faa5a0, 0x0, 0xc0000ca050, 0x0)
        github.com/domino14/macondo/strategy/exhaustive_leave.go:102 +0x107 fp=0xc0000d39e0 sp=0xc0000d3958 pc=0x125ed67
github.com/domino14/macondo/strategy.(*ExhaustiveLeaveStrategy).Equity(0xc00007a1e0, 0xf, 0x2000000033, 0x3ffffffffffff, 0x3ffffffffffff)
        <autogenerated>:1 +0x86 fp=0xc0000d3a60 sp=0xc0000d39e0 pc=0x1260466
github.com/domino14/macondo/ai/player.(*RawEquityPlayer).AssignEquity(0xc0000a6228, {0xc00424c000, 0x906, 0xc0000d3b10}, 0x104f489, 0xc0000d3b10, 0x0)
        github.com/domino14/macondo/ai/player/player.go:59 +0x65 fp=0xc0000d3ac0 sp=0xc0000d3a60 pc=0x12625e5
github.com/domino14/macondo/ai/runner.GenerateMoves(0xc0002381e0, {0x14fa8f8, 0xc0000a6228}, {0x14f59d0, 0xc0000da310}, 0x0, 0x1)
        github.com/domino14/macondo/ai/runner/runner.go:107 +0x15d fp=0xc0000d3b78 sp=0xc0000d3ac0 pc=0x126563d
github.com/domino14/macondo/automatic.(*GameRunner).genBestMoveForBot(...)
        github.com/domino14/macondo/automatic/game_runner.go:125
github.com/domino14/macondo/automatic.(*GameRunner).PlayBestStaticTurn(0xc0000d3f28, 0x1)
        github.com/domino14/macondo/automatic/game_runner.go:132 +0x6e fp=0xc0000d3d20 sp=0xc0000d3b78 pc=0x137b94e
github.com/domino14/macondo/automatic.(*GameRunner).playFullStatic(0xc0000d3f28)
        github.com/domino14/macondo/automatic/automatic_utils.go:48 +0xd0 fp=0xc0000d3e50 sp=0xc0000d3d20 pc=0x1379530
github.com/domino14/macondo/automatic.StartCompVCompStaticGames.func1(0x0)
        github.com/domino14/macondo/automatic/automatic_utils.go:117 +0x2a5 fp=0xc0000d3fc8 sp=0xc0000d3e50 pc=0x137af85
github.com/domino14/macondo/automatic.StartCompVCompStaticGames·dwrap·1()
        github.com/domino14/macondo/automatic/automatic_utils.go:121 +0x2d fp=0xc0000d3fe0 sp=0xc0000d3fc8 pc=0x137acad
runtime.goexit()
        runtime/asm_amd64.s:1581 +0x1 fp=0xc0000d3fe8 sp=0xc0000d3fe0 pc=0x1064e01
created by github.com/domino14/macondo/automatic.StartCompVCompStaticGames
        github.com/domino14/macondo/automatic/automatic_utils.go:105 +0x4a5
func (olv *OldLeaves) LeaveValue(leave alphabet.MachineWord) float64 {
	ll := len(leave)
	for i := 1; i < ll; i++ {
		for j := i; j > 0 && leave[j-1] > leave[j]; j-- {             // THIS IS THE CRASH LINE
			leave[j-1], leave[j] = leave[j], leave[j-1]
		}
	}

polish endgame is broken

#character-encoding UTF-8
#player1 1 ptf1559
#player2 2 smut3k
>1: AHIJOUY 8F HUJA +20 20
>1: AHIJOUY --  -20 0
>2: ĆĘIKPST 8G STĘPIĆ +46 46
>1: AHIJOUY 7I HOI +24 24
>2: CFKNWYZ J5 CZ..Y +10 56
>1: AJMSUWY -  +0 24
>2: FKLNŃWW -  +0 56
>1: AJMSUWY -J +0 24
>2: FKLNŃWW -KWŃW +0 56
>1: AAMSUWY 9F AU +14 38
>2: AFLLŁNO 5J .ŁA +12 68
>1: AAMSWYZ 10E SAMY +18 56
>2: CEFLLNO 11C CLE +14 82
>1: AEŁNRWZ 12D AR +13 69
>2: FLNOOOW L3 FL.N +18 100
>1: DEŁNTWZ 13E WENT +10 79
>2: EJNOOOW H13 .EJ +18 118
>1: ?ADIŁZZ 15A ZDZIAŁa. +89 168
>2: IKNOOOW I13 ON +12 130
>1: ABEIMNS 12I SAMBIE +21 189
>2: IIKOOTW M11 K.I +8 138
>1: ?EJNŚWW A14 E. +2 191
>2: AILOOTW C11 .LI +14 152
>1: ?JNNŚWW 12C ...W +7 198
>2: AOOOTWŹ -Ź +0 152
>1: ?AJNNŚW 3L .iŚ +20 218
>2: AGOOOTW K10 GA.O +14 166
>1: AIJNNRW 4L .I +4 222
>2: OORSTWY N12 .WY +10 176
>1: AJNNRRW 11J J. +8 230
>2: ADOORST 12C ....O +8 184
>1: ANNPRRW 9F ..R +10 240
>2: ADORSTY J14 DY +16 200
>1: ACNNPRW 13A CN. +8 248
>2: AOOPRST 10B PO +13 213
>1: ADNPRWZ H8 ...P +7 255
>2: AEKORST 2M KET +25 238
>2: AEKORST --  -25 213
>1: AĄDNRWZ 15J .ARD +7 262
>2: AEKORST 2N ET +18 231
>1: ĄBEGNWZ O1 E. +9 271
>2: AKKORSŹ C10 ....S. +8 239
>1: ĄBGNWZZ 13M ..Ą +7 278
>2: AIKKORŹ M9 RA... +7 246
>1: BGNŃWZZ 10K .N.Ń +26 304
>2: IKKMOŹŻ 9M .OK +12 258
>1: BGHUWZZ N7 ZG.. +12 316
>2: IKMÓŹŻ M1 ŻM.. +13 271
>1: BHUWZ -  +0 316
>2: IKÓŹ B8 KÓ. +9 280
>1: BHUWZ -  +0 316
>2: IŹ 8B .IŹ +21 301
>2:  (BHUWZ) +22 323

set lexicon OSPS44 polish
load /path/to/gcg
turn 49
endgame 10

load the above gcg and do turn 49 and endgame 10 - it generates a bunch of passes in a row. The game should have ended after 2 passes. The more plies are done, the more passes are generated. Wtf?

Best sequence has a spread difference of -1
Best sequence:
1) (Pass)
2) (Pass)
3) (Pass)
4) B8 ZU.
5) 9A K.
6) (Pass)
7) 1L I.
8) (Pass)
9) (Pass)

add stats support to Macondo events

Macondo GameEvent and GameHistory should add a few fields.

GameHistory:

repeated int32 final_scores = 14;

GameEvent:
repeated string words_formed Where the first element is the main word formed, and the remainder are cross-words.

They should have parentheses.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.