Here is a program that results in the open effect error that we talked about.
I've tried stepping through with gdb / lldb, and it seems as if the wrong handler operation is being called (one with fewer arguments).
Not sure if it is actually a problem with open, or a problem with the ordering of operations within a handler, or something else entirely. All I know is that the call stack ends with a segfault, because the address of the _ctx parameter changes (and therefore the allocator is using bad addresses). Putting the _ctx parameter last is quite ingenious in retrospect, since it causes calls to closures with the wrong number of arguments to always fail with a segfault almost immediately.
Issue #357 is related (open calls), but is due to open calls not even being inserted / inferred when adding in a default case that throws exceptions. (Also, I believe that there should be a warning emitted from the compiler for inexhaustive cases to help prevent that).
This is most directly related to #282. (And definitely due to the tail recursive nature which that issue highlights - changing all the operations to ctl clauses fixes this program as well).
I'll try to simplify the program to a more minimum reproducible example, but here is the current example.
Here is a program that results in the open effect error that we talked about.
I've tried stepping through with gdb / lldb, and it seems as if the wrong handler operation is being called (one with fewer arguments). Not sure if it is actually a problem with open, or a problem with the ordering of operations within a handler, or something else entirely. All I know is that the call stack ends with a segfault, because the address of the
_ctx
parameter changes (and therefore the allocator is using bad addresses). Putting the_ctx
parameter last is quite ingenious in retrospect, since it causes calls to closures with the wrong number of arguments to always fail with a segfault almost immediately.Issue #357 is related (open calls), but is due to open calls not even being inserted / inferred when adding in a default case that throws exceptions. (Also, I believe that there should be a warning emitted from the compiler for inexhaustive cases to help prevent that).
This is most directly related to #282. (And definitely due to the tail recursive nature which that issue highlights - changing all the operations to ctl clauses fixes this program as well).
I'll try to simplify the program to a more minimum reproducible example, but here is the current example.
Code Example Dropdown
```koka import std/os/readline fun member(x: a, xs: list, compare: (a, a) -> bool) : bool match xs Nil -> False Cons(y, ys) -> x.compare(y) || member(x, ys,compare) fun member(xs: list, x: a, compare: (a, a) -> bool) : bool x.member(xs, compare) value struct coord x: int y: int fun show(c: coord) : string { "(" ++ c.x.show() ++ ", " ++ c.y.show() ++ ")" } fun (==)(c1: coord, c2: coord) : bool c1.x == c2.x && c1.y == c2.y effect ctl eject(): a fun parse(str : string, moves : list) : coord
match str.list()
Cons('0', Cons(' ', Cons('0', Nil))) -> Coord(0,0)
Cons('0', Cons(' ', Cons('1', Nil))) -> Coord(0,1)
Cons('0', Cons(' ', Cons('2', Nil))) -> Coord(0,2)
Cons('1', Cons(' ', Cons('0', Nil))) -> Coord(1,0)
Cons('1', Cons(' ', Cons('1', Nil))) -> Coord(1,1)
Cons('1', Cons(' ', Cons('2', Nil))) -> Coord(1,2)
Cons('2', Cons(' ', Cons('0', Nil))) -> Coord(2,0)
Cons('2', Cons(' ', Cons('1', Nil))) -> Coord(2,1)
Cons('2', Cons(' ', Cons('2', Nil))) -> Coord(2,2)
Cons('0', Cons(',', Cons('0', Nil))) -> Coord(0,0)
Cons('0', Cons(',', Cons('1', Nil))) -> Coord(0,1)
Cons('0', Cons(',', Cons('2', Nil))) -> Coord(0,2)
Cons('1', Cons(',', Cons('0', Nil))) -> Coord(1,0)
Cons('1', Cons(',', Cons('1', Nil))) -> Coord(1,1)
Cons('1', Cons(',', Cons('2', Nil))) -> Coord(1,2)
Cons('2', Cons(',', Cons('0', Nil))) -> Coord(2,0)
Cons('2', Cons(',', Cons('1', Nil))) -> Coord(2,1)
Cons('2', Cons(',', Cons('2', Nil))) -> Coord(2,2)
Cons('q', Nil) -> eject()
_ ->
println("Invalid move, please try again")
gen_move(moves)
fun gen_move(moves : list) : coord
val move : coord = parse(readline(), moves)
if moves.any() fn (c : coord) { c == move } then {
println("Invalid move, please try again")
gen_move(moves)
} else {
move
}
fun create-board() : list
> { [['.','.','.'],['.','.','.'],['.','.','.']] } fun show(grid: list
>) : string var line := 0 grid.foldl(" 0 1 2\n") fn(acc, row: list)
val out = row.foldl(acc ++ line.show() ++ " ") fn(acc, col: char)
acc ++ col.string() ++ " "
++ "\n"
line := line + 1
out
fun get_board_position(board : list
>, coord : coord) : maybe {
match board[coord.y] {
Nothing -> Nothing
Just(row) -> row[coord.x]
}
}
fun mark_board(board: list
>,coord: coord, mark: char): maybe
>> val new_row: maybe
> = match board[coord.y] Nothing -> Nothing Just(row) -> Just(row.take(coord.x) ++ mark.Cons(row.drop(coord.x + 1))) match new_row Nothing -> Nothing Just(row) -> Just(board.take(coord.y) ++ row.Cons(board.drop(coord.y + 1))) effect ctl not_full() : () fun check_full(board: list
>) : bool fun helper() var full := True board.foreach() fn(row) if '.'.member(row) fn (a, b) a == b then { not_full() } full with ctl not_full() False helper() fun check_win(board: list
>, mark: char) :bool
var win := False
var i := 0
while {i < 3} {
if board.get_board_position(Coord(i,0)).unwrap() == mark && board.get_board_position(Coord(i,1)).unwrap() == mark && board.get_board_position(Coord(i,2)).unwrap() == mark then {
win := True
}
if board.get_board_position(Coord(0,i)).unwrap() == mark && board.get_board_position(Coord(1,i)).unwrap() == mark && board.get_board_position(Coord(2,i)).unwrap() == mark then {
win := True
}
i := i + 1
}
if board.get_board_position(Coord(0,0)).unwrap() == mark && board.get_board_position(Coord(1,1)).unwrap() == mark && board.get_board_position(Coord(2,2)).unwrap() == mark then {
win := True
}
if board.get_board_position(Coord(0,2)).unwrap() == mark && board.get_board_position(Coord(1,1)).unwrap() == mark && board.get_board_position(Coord(2,0)).unwrap() == mark then {
win := True
}
win
fun human_logic(board: list
- © Githubissues.
- Githubissues is a development platform for aggregating issues.
>, moves: list, mark: char, other_mark: char) : coord
gen_move(moves)
fun ai_logic(board: list
>, moves: list, mark: char, other_mark: char)
board.gen_ai_move(moves,mark, other_mark)
value struct move
move : coord
score: int
fun (==)(m1 : move, m2 : move) : bool {
m1.move == m2.move && m1.score == m2.score
}
fun eval_board(board : list
>, mark: char, other-mark : char) { if board.check_win(mark) then { 10 } elif board.check_win(other-mark) then { -10 } else { 0 } } fun maximum-move(a : move, b : move) : move { if a.score > b.score then { a } else { b } } fun gen_ai_move(board : list
>, moves : list, mark : char, other-mark : char) {
val best_move : move = Move(Coord(-1,-1), -1000)
[0,1,2].foldl(best_move) fn (bstMove : move, i : int) {
[0,1,2].foldl(bstMove) fn (bstMve : move, j : int) {
if Coord(i,j).member(moves) fn (a,b) {a == b} then {
bstMve
}
else {
val new_board : list
> = board.mark_board(Coord(i,j), mark).unwrap() val new-max = maximum-move(bstMve, Move(Coord(i,j), new_board.minimax(moves ++ [Coord(i,j)], 0, False, mark, other-mark))) new-max } } }.move } fun unwrap(x: maybe): a
match x
Just(a) -> a
Nothing -> throw("value was Nothing")
// A basic implementation of Minimax
// This uses brace style to show that it is possible
fun minimax(board : list
>, moves: list, depth : int, isMaximizingPlayer : bool, mark : char, other-mark : char) : int {
val score : int = board.eval_board(mark, other-mark)
if score == 10 then {
score
}
elif score == -10 then {
score
}
elif board.check_full() then {
0
}
else {
if isMaximizingPlayer then {
val bestVal: int = -1000
[0,1,2].foldl(bestVal) fn (bstVal : int, i : int) {
[0,1,2].foldl(bstVal) fn (bstVl : int, j : int) {
if Coord(i,j).member(moves) fn(a, b) {a == b} then {
bstVl
}
else {
val new_board : list
> = board.mark_board(Coord(i,j), mark).unwrap() val value : int = new_board.minimax(moves ++ [Coord(i,j)], depth + 1, !isMaximizingPlayer, mark, other-mark) max(bstVl, value) } } } } else { val bestVal: int = 1000 [0,1,2].foldl(bestVal) fn (bstVal : int, i : int) { [0,1,2].foldl(bstVal) fn (bstVl : int, j : int) { if Coord(i,j).member(moves) fn(a,b) {a == b} then { bstVl } else { val new_board : list
> = board.mark_board(Coord(i,j), other-mark).unwrap() val value : int = new_board.minimax(moves ++ [Coord(i,j)], depth + 1, !isMaximizingPlayer, mark, other-mark) min(bstVl, value) } } } } } } // The main business logic of the entire game // This function checks if there is a draw or a win fun play_game() val board = get_board() if board.check_full() then println("Draw!") println("Final board:") board.show().println else "Next Turn:".println board.show().println val current_mark = get_current_mark() val other_mark = get_other_mark() play_turn(current_mark, other_mark) val new_board = get_board() if new_board.check_win(current_mark) then println("Player " ++ current_mark.show() ++ " wins!") println("Final board:") new_board.show().println else flip() play_game() effect human_turn fun play_human_turn(board: list
>, moves: list, mark: char, othermark: char) : coord
effect ai_turn
fun play_ai_turn(board: list
>, moves: list, mark: char, othermark: char) : coord
effect player1
fun player1_turn(board: list
>, moves: list, mark: char, othermark: char) : coord
fun get_player1_mark() : char
effect player2
fun player2_turn(board: list
>, moves: list, mark: char, othermark: char) : coord
fun get_player2_mark() : char
effect turn
// Changes the current player to a different one
fun flip() : ()
// Calls the appropriate code for the current player
fun play_turn(mark: char, other_mark: char) : ()
// Gets the symbol of the active player
fun get_current_mark(): char
// Gets the symbol of the inactive player
fun get_other_mark(): char
// This allows us to access the board
fun get_board() : list
> // This allows us to access the moves that have been made fun get_moves(): list
type player
Human
AI
type players
One
Two
// This function encapsulates the state of the entire game
// Think of it as calling a method on an object but with pure functions
fun initialize_and_start_game(game_type: int)
var current_player := One
var current_board := create-board()
var all_moves := []
var player1 := Human
var player2 := AI
match game_type
1 -> {
player1 := Human
player2 := Human
}
2 -> {
player1 := Human
player2 := AI
}
3 -> {
player1 := AI
player2 := AI
}
_ -> throw("invalid game type")
with handler
ctl eject()
println("Have a nice day.")
with fun play_human_turn(board: list
>, moves: list, mark: char, othermark: char)
human_logic(board, moves, mark, othermark)
with fun play_ai_turn(board: list
>, moves: list, mark: char, othermark: char)
ai_logic(board, moves, mark, othermark)
with handler
fun player1_turn(board: list
>, moves: list, mark: char, othermark: char)
match player1
Human -> play_human_turn(board, moves, mark, othermark)
AI -> play_ai_turn(board, moves, mark, othermark)
fun get_player1_mark()
'X'
with handler
fun player2_turn(board: list
>, moves: list, mark: char, othermark: char)
match player2
Human -> play_human_turn(board, moves, mark, othermark)
AI -> play_ai_turn(board, moves, mark, othermark)
fun get_player2_mark()
'O'
with handler
return(x) ()
fun flip()
match current_player
One -> current_player := Two
Two -> current_player := One
fun play_turn(mark: char, other_mark: char)
match current_player
One -> {
val coord = player1_turn(current_board, all_moves, mark, other_mark)
current_board := mark_board(current_board,coord,get_player1_mark()).unwrap
all_moves := Cons(coord, all_moves)
()
}
Two -> {
val coord = player2_turn(current_board, all_moves, mark, other_mark)
current_board := mark_board(current_board,coord,get_player2_mark()).unwrap
all_moves := Cons(coord, all_moves)
()
}
fun get_current_mark() match current_player
One -> get_player1_mark()
Two -> get_player2_mark()
fun get_other_mark() match current_player
Two -> get_player1_mark()
One -> get_player2_mark()
fun get_board() current_board
fun get_moves() all_moves
play_game()
fun prompt_game_type()
match readline().list()
Cons('1', Nil) -> 1
Cons('2', Nil) -> 2
Cons('3', Nil) -> 3
_ ->
println("Invalid game mode, please try again")
prompt_game_type()
fun main ()
println("Welcome to Tic Tac Toe!")
println("Please select a game mode:")
println("1. Two player")
println("2. One player")
println("3. Zero player")
println("Enter the number of the game mode you want to play")
val game_type = 1 // prompt_game_type()
"Enter your input as 'x y' or 'x,y' when selecting a spot".println
initialize_and_start_game(game_type)
```