Skip to content

Instantly share code, notes, and snippets.

@BrandonDyer64
Created August 12, 2019 22:48
Show Gist options
  • Save BrandonDyer64/5f3754a86c9e40eda2c4abaa48454153 to your computer and use it in GitHub Desktop.
Save BrandonDyer64/5f3754a86c9e40eda2c4abaa48454153 to your computer and use it in GitHub Desktop.
/*
Tic-Tac-Toe
━━━━━━━━━━━
Kevin Language Example program
By: Brandon Dyer <github.com/BrandonDyer64>
*/
import Console from "console"
import Math from "math"
import { CheckWin, CheckDraw } from "./CheckBoard"
// Constant 8-bit integers
// These are automatically cast by the compiler
let i8 width = 3;
let i8 height = 3;
// The Main function is where our program starts
fn Main([String] args) {
// Multiline strings start and end with """
// Indentation is handled automatically
Console::Print("""
Tic-Tac-Toe
━━━━━━━━━━━
""");
// This loop will keep calling PlayGame
// until PlayGame returns true
loop => PlayGame();
Console::Print("""
━━━━━━━━━━━
Thanks for playing!
""");
}
fn bool PlayGame() {
// Our board is an array of 2-bit integers (values 0-3)
// We use the `build` keyword to fill the array with zeros
let mut board = build (0, width * height, i) => 0i2;
// Keep looping until PlayRound returns a winner
// board.PlayRound() is the same as PlayRound(board)
let winner = loop => board.PlayRound();
// Output who the winner is
let result = switch winner {
:player => "You win!";
:ai => "You lose.";
default => "Draw.";
};
result.Console::Print();
// We can ask questions in the terminal with Console::Ask
return Console::Ask("Would you like to play again? [y/N]: ").LowerCase() != "y"
}
fn symbol PlayRound(mut [i2] board) {
// Function pointers allow us to use inversion of control
if board.Move(2, &MoveAI) return :ai;
if board.Move(1, &MovePlayer) return :player;
// Check for draw
if board.CheckDraw() return :draw;
// The `:none` symbol is treated as false by if statements
return :none;
}
fn bool Move(mut [i2] board, i2 num, fn i8([i2]) fun) {
// We call our fun variable as a lambda
// With lambdas, we can't use `board.fun()` as fun may
// be a field
// We could instead use `board |> fun()`
let move = fun(board);
// Modify the board value
// This is why the paramater type is `mut [i2]`
// `mut` lets us modify the contents of the array
board[move] = num;
return board.CheckWin();
}
fn i8 MoveAI([i2] board) {
// The standard library gives us some neat functions
// such as `Math::Random`
let i8 move = Math::RandomInt(1, 10);
if !board.CheckMove(move) return MoveAI(board);
return move;
}
fn i8 MovePlayer([i2] board) {
// We can insert values into strings
// like this `${value}`
Console::Print("""
━━━━━━━━━━━
${board.PrettyState()}
\0[BLU]
1 ┃ 2 ┃ 3
━━━╋━━━╋━━━
4 ┃ 5 ┃ 6
━━━╋━━━╋━━━
7 ┃ 8 ┃ 9
\0[DEF]
""");
let i8 move = Console::AskInteger("Your move [1-${width * height}]: ") - 1;
// Recursion is only safe when it is the last statement
// It is allowed here because it is the only call in the
// return expression
// If we had other calls, we'd need to tell the compiler
// to allow unsafe recursion with `#[allow(recursion)]`
if !board.CheckMove(move) {
Console::Print("Invalid move.");
return MovePlayer(board);
}
return move;
}
// Functions that immediately return don't need curly braces
// We can just use `=>` followed by an expression
fn bool CheckMove([i2] board, i8 move) =>
// This expression starts with an `and` operator, so
// `and` can be the only operator chained to this expression
// We could do this with either `and` or `&&`
and 1 <= move <= width * height
and board[move] == 0;
fn String PrettyState([i2] board) =>
board map (el, i) =>
// Likewise, this expression starts with a `+` operator
+ switch el {
1 => " \0[GRN]o\0[DEF] ";
2 => " \0[RED]x\0[DEF] ";
default => " ";
};
+ i % width != 0
? "\0[BLU]┃\0[DEF]"
: "\n"
+ i % width == 0 and i < width * height - 1
? "\0[BLU]━━━╋━━━╋━━━\0[DEF]\n"
: "";
// We export whatever function we want to use as a starting
// point for our program
export Main;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment