From 719673166672c782370a859242f97fe4ebf75c8e Mon Sep 17 00:00:00 2001 From: Christopher Seven Phiri Date: Fri, 7 Jun 2024 00:44:55 +0200 Subject: [PATCH] Create simple Tic tac toe game (#7) * init * Update tictactoe.ny * Add string to number conversion * can play simple tic tac toe --- .vscode/launch.json | 5 +++ examples/games/tictactoe.ny | 69 +++++++++++++++++++++++++++++ src/evaluator/assignment.go | 3 +- src/evaluator/boolean.go | 9 ++-- src/evaluator/evaluator.go | 13 ++---- src/evaluator/evaluator_test.go | 7 +-- src/evaluator/for_loop.go | 3 +- src/evaluator/if.go | 3 +- src/evaluator/index.go | 5 ++- src/evaluator/prefix.go | 19 ++++---- src/evaluator/string.go | 2 + src/library/functions/converters.go | 23 ++++++++++ src/library/library.go | 1 + src/library/modules/console.go | 39 ++++++++++++++++ src/object/environment.go | 6 +++ src/values/value.go | 9 ++++ 16 files changed, 188 insertions(+), 28 deletions(-) create mode 100644 examples/games/tictactoe.ny create mode 100644 src/library/functions/converters.go create mode 100644 src/values/value.go diff --git a/.vscode/launch.json b/.vscode/launch.json index 89ec9c7..8a865c8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,12 @@ "request": "launch", "mode": "auto", "program": "${workspaceRoot}/src/main.go", + "console": "integratedTerminal", "cwd": "${workspaceFolder}", + "args": [ + "-f", + "./examples/games/tictactoe.ny" + ] } ] } \ No newline at end of file diff --git a/examples/games/tictactoe.ny b/examples/games/tictactoe.ny new file mode 100644 index 0000000..b5b871a --- /dev/null +++ b/examples/games/tictactoe.ny @@ -0,0 +1,69 @@ +mawu[] board = ["","","","","","","","",""]; +mawu currentPlayer = "X"; +nambala isRunning = zoona; + +ndondomeko printBoard() { + lembanzr("---------"); + lembanzr(board[0] + " | " + board[1] + " | " + board[2]); + lembanzr("---------"); + lembanzr(board[3] + " | " + board[4] + " | " + board[5]); + lembanzr("---------"); + lembanzr(board[6] + " | " + board[7] + " | " + board[8]); + lembanzr("---------"); +} + +ndondomeko checkWin() { + mawu[] winConditions = [ + [0, 1, 2], [3, 4, 5], [6, 7, 8], + [0, 3, 6], [1, 4, 7], [2, 5, 8], + [0, 4, 8], [2, 4, 6] + ]; + + za (x = 0; x < winConditions.length(); x++) { + mawu condition = winConditions[x]; + ngati (board[condition[0]] != "" && board[condition[0]] == board[condition[1]] && board[condition[0]] == board[condition[2]]) { + bweza zoona; + } + } + bweza bodza; +} + +ndondomeko move(player, position) { + ngati (board[position] != "") { + bweza bodza; + } + + board[position] = player; + ngati (player == "X") { + currentPlayer = "O"; + } kapena { + currentPlayer = "X"; + } + bweza zoona; +} + +ndondomeko playGame() { + pamene(isRunning) { + printBoard(); + lembanzr("Player " + currentPlayer + " turn: "); + nambala playerMove = kuNambala(console.landira()); + mawu moved = move(currentPlayer, playerMove - 1); + ngati(moved == bodza) { + lembanzr("Invalid move"); + lembanzr("Try again"); + lembanzr("---------"); + // TODO: Implement a way to continue loop + } kapena { + mawu hasWon = checkWin(); + ngati (hasWon == zoona) { + lembanzr("Player " + player + " has won!"); + lembanzr("Game Over"); + isRunning = bodza; + } kapena { + console.fufuta(); + } + } + } +} + +playGame(); \ No newline at end of file diff --git a/src/evaluator/assignment.go b/src/evaluator/assignment.go index 37b30b2..0e3b6f1 100644 --- a/src/evaluator/assignment.go +++ b/src/evaluator/assignment.go @@ -3,6 +3,7 @@ package evaluator import ( "github.com/sevenreup/chewa/src/ast" "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" ) func evaluateAssigment(node *ast.AssigmentStatement, env *object.Environment) object.Object { @@ -42,7 +43,7 @@ func evaluateIndexAssignment(node *ast.IndexExpression, val object.Object, env * if idx >= len(elements) { for i := len(elements); i <= idx; i++ { - elements = append(elements, NULL) + elements = append(elements, values.NULL) } left.Elements = elements diff --git a/src/evaluator/boolean.go b/src/evaluator/boolean.go index bd1be46..4208b53 100644 --- a/src/evaluator/boolean.go +++ b/src/evaluator/boolean.go @@ -1,10 +1,13 @@ package evaluator -import "github.com/sevenreup/chewa/src/object" +import ( + "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" +) func nativeBoolToBooleanObject(input bool) *object.Boolean { if input { - return TRUE + return values.TRUE } - return FALSE + return values.FALSE } diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index 29ec34b..e099683 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -5,12 +5,7 @@ import ( "github.com/sevenreup/chewa/src/ast" "github.com/sevenreup/chewa/src/object" -) - -var ( - NULL = &object.Null{} - TRUE = &object.Boolean{Value: true} - FALSE = &object.Boolean{Value: false} + "github.com/sevenreup/chewa/src/values" ) func Eval(node ast.Node, env *object.Environment) object.Object { @@ -84,11 +79,11 @@ func Eval(node ast.Node, env *object.Environment) object.Object { func isTruthy(obj object.Object) bool { switch obj { - case NULL: + case values.NULL: return false - case TRUE: + case values.TRUE: return true - case FALSE: + case values.FALSE: return false default: return true diff --git a/src/evaluator/evaluator_test.go b/src/evaluator/evaluator_test.go index 535802e..cb4d90a 100644 --- a/src/evaluator/evaluator_test.go +++ b/src/evaluator/evaluator_test.go @@ -4,6 +4,7 @@ import ( "github.com/sevenreup/chewa/src/lexer" "github.com/sevenreup/chewa/src/object" "github.com/sevenreup/chewa/src/parser" + "github.com/sevenreup/chewa/src/values" "github.com/shopspring/decimal" "testing" ) @@ -78,7 +79,7 @@ func testLiteralExpression( } func testNullObject(t *testing.T, obj object.Object) bool { - if obj != NULL { + if obj != values.NULL { t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) return false } @@ -618,8 +619,8 @@ func TestHashLiterals(t *testing.T) { (&object.String{Value: "two"}).MapKey(): 2, (&object.String{Value: "three"}).MapKey(): 3, (&object.Integer{Value: decimal.NewFromInt(4)}).MapKey(): 4, - TRUE.MapKey(): 5, - FALSE.MapKey(): 6, + values.TRUE.MapKey(): 5, + values.FALSE.MapKey(): 6, } if len(result.Pairs) != len(expected) { t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) diff --git a/src/evaluator/for_loop.go b/src/evaluator/for_loop.go index 481b34a..a655cf1 100644 --- a/src/evaluator/for_loop.go +++ b/src/evaluator/for_loop.go @@ -3,6 +3,7 @@ package evaluator import ( "github.com/sevenreup/chewa/src/ast" "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" ) func evalForLoop(node *ast.ForExpression, env *object.Environment) object.Object { @@ -59,5 +60,5 @@ func evalForLoop(node *ast.ForExpression, env *object.Environment) object.Object loop = false } - return NULL + return values.NULL } diff --git a/src/evaluator/if.go b/src/evaluator/if.go index 6a0d2d1..f5f6d22 100644 --- a/src/evaluator/if.go +++ b/src/evaluator/if.go @@ -3,6 +3,7 @@ package evaluator import ( "github.com/sevenreup/chewa/src/ast" "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" ) func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { @@ -15,6 +16,6 @@ func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Obje } else if ie.Alternative != nil { return Eval(ie.Alternative, env) } else { - return NULL + return values.NULL } } diff --git a/src/evaluator/index.go b/src/evaluator/index.go index 49b48c6..4260aee 100644 --- a/src/evaluator/index.go +++ b/src/evaluator/index.go @@ -3,6 +3,7 @@ package evaluator import ( "github.com/sevenreup/chewa/src/ast" "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" ) func evalIndexExpression(node *ast.IndexExpression, env *object.Environment) object.Object { @@ -31,7 +32,7 @@ func evalArrayIndexExpression(array, index object.Object) object.Object { maxValue := int64(len(arrayObject.Elements) - 1) if idx < 0 || idx > maxValue { - return NULL + return values.NULL } return arrayObject.Elements[idx] @@ -49,7 +50,7 @@ func evaluateMapIndex(node *ast.IndexExpression, left, index object.Object) obje pair, ok := mapObject.Pairs[key.MapKey()] if !ok { - return NULL + return values.NULL } return pair.Value diff --git a/src/evaluator/prefix.go b/src/evaluator/prefix.go index b163fef..e8a667b 100644 --- a/src/evaluator/prefix.go +++ b/src/evaluator/prefix.go @@ -1,6 +1,9 @@ package evaluator -import "github.com/sevenreup/chewa/src/object" +import ( + "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/values" +) func evalPrefixExpression(operator string, right object.Object) object.Object { switch operator { @@ -15,14 +18,14 @@ func evalPrefixExpression(operator string, right object.Object) object.Object { func evalBangOperatorExpression(right object.Object) object.Object { switch right { - case TRUE: - return FALSE - case FALSE: - return TRUE - case NULL: - return TRUE + case values.TRUE: + return values.FALSE + case values.FALSE: + return values.TRUE + case values.NULL: + return values.TRUE default: - return FALSE + return values.FALSE } } diff --git a/src/evaluator/string.go b/src/evaluator/string.go index 0002be7..d55ccf9 100644 --- a/src/evaluator/string.go +++ b/src/evaluator/string.go @@ -13,6 +13,8 @@ func evalStringInfixExpression( return &object.String{Value: leftVal + rightVal} case "==": return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) default: return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) diff --git a/src/library/functions/converters.go b/src/library/functions/converters.go new file mode 100644 index 0000000..19fa1bf --- /dev/null +++ b/src/library/functions/converters.go @@ -0,0 +1,23 @@ +package functions + +import ( + "github.com/sevenreup/chewa/src/object" + "github.com/sevenreup/chewa/src/token" + "github.com/shopspring/decimal" +) + +func ParseStringToNumber(env *object.Environment, tok token.Token, args ...object.Object) object.Object { + if len(args) != 1 { + // TODO: Return error dont panic + panic("parse requires exactly one argument") + } + + if args[0].Type() != object.STRING_OBJ { + return nil + } + + str := args[0].(*object.String).Value + number, _ := decimal.NewFromString(str) + + return &object.Integer{Value: number} +} diff --git a/src/library/library.go b/src/library/library.go index b120faf..831b4fb 100644 --- a/src/library/library.go +++ b/src/library/library.go @@ -15,6 +15,7 @@ func init() { RegisterFunction("lemba", functions.Print) RegisterFunction("lembanzr", functions.PrintLine) + RegisterFunction("kuNambala", functions.ParseStringToNumber) } func RegisterModule(name string, methods map[string]*object.LibraryFunction) { diff --git a/src/library/modules/console.go b/src/library/modules/console.go index 2c5e325..b71b339 100644 --- a/src/library/modules/console.go +++ b/src/library/modules/console.go @@ -1,10 +1,15 @@ package modules import ( + "bufio" "fmt" + "os" + "os/exec" + "runtime" "strconv" "strings" + "github.com/sevenreup/chewa/src/values" "github.com/sevenreup/chewa/src/object" "github.com/sevenreup/chewa/src/token" ) @@ -13,6 +18,8 @@ var ConsoleMethods = map[string]*object.LibraryFunction{} func init() { RegisterMethod(ConsoleMethods, "lemba", consolePrint) + RegisterMethod(ConsoleMethods, "fufuta", consoleClear) + RegisterMethod(ConsoleMethods, "landira", consoleRead) } func consolePrint(env *object.Environment, tok token.Token, args ...object.Object) object.Object { @@ -27,6 +34,38 @@ func consolePrint(env *object.Environment, tok token.Token, args ...object.Objec return nil } +func consoleRead(scope *object.Environment, tok token.Token, args ...object.Object) object.Object { + scanner := bufio.NewScanner(os.Stdin) + + if len(args) == 1 { + prompt := args[0].(*object.String).Value + + fmt.Print(prompt) + } + + val := scanner.Scan() + + if !val { + return values.NULL + } + + return &object.String{Value: scanner.Text()} +} + +func consoleClear(scope *object.Environment, tok token.Token, args ...object.Object) object.Object { + if runtime.GOOS == "windows" { + cmd := exec.Command("cmd", "/c", "cls") + cmd.Stdout = os.Stdout + cmd.Run() + } else { + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + cmd.Run() + } + + return nil +} + func libPrint(values []string) { if len(values) > 0 { str := make([]string, 0) diff --git a/src/object/environment.go b/src/object/environment.go index d8a41f6..d30be88 100644 --- a/src/object/environment.go +++ b/src/object/environment.go @@ -23,6 +23,12 @@ func (e *Environment) Get(name string) (Object, bool) { return obj, ok } func (e *Environment) Set(name string, val Object) Object { + // TODO: Make sure we dont accidentally mutate data that is not in the current scope + _, ok := e.store[name] + if !ok && e.outer != nil { + e.outer.Set(name, val) + return val + } e.store[name] = val return val } diff --git a/src/values/value.go b/src/values/value.go new file mode 100644 index 0000000..fff5df9 --- /dev/null +++ b/src/values/value.go @@ -0,0 +1,9 @@ +package values + +import "github.com/sevenreup/chewa/src/object" + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +)