Compare commits
4 Commits
633a8dedc8
...
0b13d9f027
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b13d9f027 | ||
|
|
e0bff7cb7e | ||
|
|
675e3ecc9d | ||
|
|
ffc60f8a06 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,5 +2,8 @@
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
examples/*
|
||||
!examples/*.cent
|
||||
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
112
cent
112
cent
@@ -1,16 +1,16 @@
|
||||
#! /home/nikolaj/.pyenv/shims/python
|
||||
#! /usr/bin/env python
|
||||
"""
|
||||
Usage:
|
||||
cent (-h|--help)
|
||||
cent -i FILE
|
||||
cent -c [--keep-c] FILE
|
||||
cent (-h|--help)
|
||||
cent -i FILE
|
||||
cent -c [-k|--keep-c] FILE
|
||||
|
||||
Options:
|
||||
-h --help Print this help screen
|
||||
-i Run the interpreter
|
||||
-c Run the compiler
|
||||
--keep-c Keep the generated C file alongside the binary
|
||||
FILE The file to compile/interpret
|
||||
-h --help Print this help screen
|
||||
-i Run the interpreter
|
||||
-c Run the compiler
|
||||
-k --keep-c Keep the generated C file alongside the binary
|
||||
FILE The file to compile/interpret
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
@@ -27,56 +27,56 @@ from centvrion.ast_nodes import Program
|
||||
from centvrion.compiler.emitter import compile_program
|
||||
|
||||
def main():
|
||||
args = docopt(__doc__)
|
||||
file_path = args["FILE"]
|
||||
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
||||
program_text = file_pointer.read() + "\n"
|
||||
args = docopt(__doc__)
|
||||
file_path = args["FILE"]
|
||||
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
||||
program_text = file_pointer.read() + "\n"
|
||||
|
||||
lexer = Lexer().get_lexer()
|
||||
parser = Parser()
|
||||
lexer = Lexer().get_lexer()
|
||||
parser = Parser()
|
||||
|
||||
try:
|
||||
tokens = lexer.lex(program_text)
|
||||
program = parser.parse(tokens)
|
||||
except LexingError as e:
|
||||
pos = e.source_pos
|
||||
char = program_text[pos.idx] if pos.idx < len(program_text) else "?"
|
||||
sys.exit(f"CENTVRION error: Invalid character {char!r} at line {pos.lineno}, column {pos.colno}")
|
||||
try:
|
||||
tokens = lexer.lex(program_text)
|
||||
program = parser.parse(tokens)
|
||||
except LexingError as e:
|
||||
pos = e.source_pos
|
||||
char = program_text[pos.idx] if pos.idx < len(program_text) else "?"
|
||||
sys.exit(f"CENTVRION error: Invalid character {char!r} at line {pos.lineno}, column {pos.colno}")
|
||||
|
||||
if isinstance(program, Program):
|
||||
if args["-i"]:
|
||||
try:
|
||||
program.eval()
|
||||
except CentvrionError as e:
|
||||
sys.exit(f"CENTVRION error: {e}")
|
||||
else:
|
||||
c_source = compile_program(program)
|
||||
runtime_c = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
||||
)
|
||||
out_path = os.path.splitext(file_path)[0]
|
||||
if args["--keep-c"]:
|
||||
tmp_path = out_path + ".c"
|
||||
with open(tmp_path, "w") as f:
|
||||
f.write(c_source)
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
||||
check=True,
|
||||
)
|
||||
else:
|
||||
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp:
|
||||
tmp.write(c_source)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
||||
check=True,
|
||||
)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
if isinstance(program, Program):
|
||||
if args["-i"]:
|
||||
try:
|
||||
program.eval()
|
||||
except CentvrionError as e:
|
||||
sys.exit(f"CENTVRION error: {e}")
|
||||
else:
|
||||
raise Exception("Output not of type 'Program'", type(program))
|
||||
c_source = compile_program(program)
|
||||
runtime_c = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
||||
)
|
||||
out_path = os.path.splitext(file_path)[0]
|
||||
if args["--keep-c"]:
|
||||
tmp_path = out_path + ".c"
|
||||
with open(tmp_path, "w") as f:
|
||||
f.write(c_source)
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
||||
check=True,
|
||||
)
|
||||
else:
|
||||
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp:
|
||||
tmp.write(c_source)
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["gcc", "-O2", tmp_path, runtime_c, "-o", out_path],
|
||||
check=True,
|
||||
)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
else:
|
||||
raise Exception("Output not of type 'Program'", type(program))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -169,7 +169,7 @@ def frac_to_fraction(s, magnvm=False, svbnvlla=False):
|
||||
def fraction_to_frac(f, magnvm=False, svbnvlla=False):
|
||||
if f < 0:
|
||||
if not svbnvlla:
|
||||
raise CentvrionError("Cannot display negative fractions without 'SVBNVLLA' module")
|
||||
raise CentvrionError("Cannot display negative numbers without 'SVBNVLLA' module")
|
||||
return "-" + fraction_to_frac(-f, magnvm, svbnvlla)
|
||||
|
||||
integer_part = int(f)
|
||||
@@ -899,6 +899,9 @@ class BuiltIn(Node):
|
||||
if not isinstance(params[0], ValList):
|
||||
raise CentvrionError("LONGITVDO requires an array")
|
||||
return vtable, ValInt(len(params[0].value()))
|
||||
case "EVERRO":
|
||||
print("\033[2J\033[H", end="", flush=True)
|
||||
return vtable, ValNul()
|
||||
case _:
|
||||
raise NotImplementedError(self.builtin)
|
||||
|
||||
|
||||
@@ -187,6 +187,10 @@ def _emit_builtin(node, ctx):
|
||||
lines.append("break;")
|
||||
lines.append(f"CentValue {tmp} = cent_null();")
|
||||
|
||||
case "EVERRO":
|
||||
lines.append("cent_everro();")
|
||||
lines.append(f"CentValue {tmp} = cent_null();")
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(node.builtin)
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ static int write_val(CentValue v, char *buf, int bufsz) {
|
||||
long num = v.fval.num, den = v.fval.den;
|
||||
if (den < 0) { num = -num; den = -den; }
|
||||
if (num < 0)
|
||||
cent_runtime_error("cannot display negative fraction without SVBNVLLA");
|
||||
cent_runtime_error("cannot display negative numbers without SVBNVLLA");
|
||||
long int_part = num / den;
|
||||
long rem_num = num % den;
|
||||
|
||||
@@ -458,6 +458,11 @@ void cent_dice(CentValue v) {
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
||||
void cent_everro(void) {
|
||||
fputs("\033[2J\033[H", stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
CentValue cent_avdi(void) {
|
||||
char *buf = cent_arena_alloc(cent_arena, 1024);
|
||||
if (!fgets(buf, 1024, stdin)) {
|
||||
|
||||
@@ -174,6 +174,7 @@ CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
||||
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
||||
CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */
|
||||
CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */
|
||||
void cent_everro(void); /* EVERRO */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Array helpers */
|
||||
|
||||
@@ -35,6 +35,7 @@ builtin_tokens = [("BUILTIN", i) for i in [
|
||||
"AVDI_NVMERVS",
|
||||
"AVDI",
|
||||
"DICE",
|
||||
"EVERRO",
|
||||
"FORTIS_NVMERVS",
|
||||
"FORTIS_ELECTIONIS",
|
||||
"LONGITVDO"
|
||||
|
||||
282
examples/connect_iv.cent
Normal file
282
examples/connect_iv.cent
Normal file
@@ -0,0 +1,282 @@
|
||||
// Connect IV — Play against a perfect AI
|
||||
// Minimax with alpha-beta pruning + center-first move ordering
|
||||
// You are X (player I), AI is O (player II)
|
||||
// Enter column as Roman numeral I-VII
|
||||
|
||||
// Returns the bottommost empty row in col, or NVLLVS if full
|
||||
DEFINI find_slot(b, col) VT {
|
||||
DESIGNA ans VT NVLLVS
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
SI b[(r - I) * VII + col] EST NVLLVS TVNC {
|
||||
DESIGNA ans VT r
|
||||
}
|
||||
}
|
||||
REDI(ans)
|
||||
}
|
||||
|
||||
// Returns VERITAS if player has four in a row
|
||||
DEFINI est_victor(b, player) VT {
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
SI b[idx] EST player ET b[idx + I] EST player ET b[idx + II] EST player ET b[idx + III] EST player TVNC {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE VIII FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
SI b[idx] EST player ET b[idx + VII] EST player ET b[idx + XIV] EST player ET b[idx + XXI] EST player TVNC {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
SI b[idx] EST player ET b[idx + VIII] EST player ET b[idx + XVI] EST player ET b[idx + XXIV] EST player TVNC {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT IV VSQVE VIII FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
SI b[idx] EST player ET b[idx + VI] EST player ET b[idx + XII] EST player ET b[idx + XVIII] EST player TVNC {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
REDI(FALSITAS)
|
||||
}
|
||||
|
||||
DEFINI print_board(b) VT {
|
||||
DICE("+---+---+---+---+---+---+---+")
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DESIGNA line VT "| "
|
||||
DONICVM c VT I VSQVE VIII FACE {
|
||||
DESIGNA cell VT b[(r - I) * VII + c]
|
||||
SI cell EST I TVNC {
|
||||
DESIGNA line VT line & "X | "
|
||||
} ALVID SI cell EST II TVNC {
|
||||
DESIGNA line VT line & "O | "
|
||||
} ALVID {
|
||||
DESIGNA line VT line & ". | "
|
||||
}
|
||||
}
|
||||
DICE(line)
|
||||
}
|
||||
DICE("+---+---+---+---+---+---+---+")
|
||||
DICE(" I II III IV V VI VII")
|
||||
REDI(NVLLVS)
|
||||
}
|
||||
|
||||
// Score a 4-cell window: positive favours AI (II), negative favours player (I).
|
||||
// Only scores windows that are not mixed (one side can still win them).
|
||||
DEFINI score_fenestram(a, b, c, d) VT {
|
||||
DESIGNA ai VT NVLLVS
|
||||
DESIGNA pl VT NVLLVS
|
||||
SI a EST II TVNC { DESIGNA ai VT ai + I }
|
||||
SI b EST II TVNC { DESIGNA ai VT ai + I }
|
||||
SI c EST II TVNC { DESIGNA ai VT ai + I }
|
||||
SI d EST II TVNC { DESIGNA ai VT ai + I }
|
||||
SI a EST I TVNC { DESIGNA pl VT pl + I }
|
||||
SI b EST I TVNC { DESIGNA pl VT pl + I }
|
||||
SI c EST I TVNC { DESIGNA pl VT pl + I }
|
||||
SI d EST I TVNC { DESIGNA pl VT pl + I }
|
||||
SI pl EST NVLLVS TVNC {
|
||||
SI ai EST III TVNC { REDI(V) }
|
||||
SI ai EST II TVNC { REDI(II) }
|
||||
}
|
||||
SI ai EST NVLLVS TVNC {
|
||||
SI pl EST III TVNC { REDI(NVLLVS - V) }
|
||||
SI pl EST II TVNC { REDI(NVLLVS - II) }
|
||||
}
|
||||
REDI(NVLLVS)
|
||||
}
|
||||
|
||||
// Static board evaluation: scan all 69 windows of 4 cells.
|
||||
// Returns a score in roughly [-350, 350]; positive = AI advantage.
|
||||
DEFINI aestima(b) VT {
|
||||
DESIGNA score VT NVLLVS
|
||||
// Center column preference: each AI piece in column IV is worth +1
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
SI b[(r - I) * VII + IV] EST II TVNC {
|
||||
DESIGNA score VT score + I
|
||||
}
|
||||
}
|
||||
// Horizontal windows (6 rows x 4 starting columns = 24)
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + I], b[idx + II], b[idx + III])
|
||||
}
|
||||
}
|
||||
// Vertical windows (3 starting rows x 7 columns = 21)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE VIII FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VII], b[idx + XIV], b[idx + XXI])
|
||||
}
|
||||
}
|
||||
// Diagonal up-right windows (3 starting rows x 4 starting columns = 12)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VIII], b[idx + XVI], b[idx + XXIV])
|
||||
}
|
||||
}
|
||||
// Diagonal up-left windows (3 starting rows x 4 starting columns = 12)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT IV VSQVE VIII FACE {
|
||||
DESIGNA idx VT (r - I) * VII + c
|
||||
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VI], b[idx + XII], b[idx + XVIII])
|
||||
}
|
||||
}
|
||||
REDI(score)
|
||||
}
|
||||
|
||||
// Minimax with alpha-beta pruning and depth-limited aestima evaluation.
|
||||
// depth = remaining search plies (counts down from a fixed budget).
|
||||
// Wins score ±(M + depth) so the search always prefers quicker wins and
|
||||
// delays losses; aestima values stay well within ±M so wins dominate.
|
||||
DEFINI minimax(b, depth, alpha, beta, maxi) VT {
|
||||
SI maxi TVNC {
|
||||
SI INVOCA est_victor(b, I) TVNC {
|
||||
REDI(NVLLVS - M - depth)
|
||||
}
|
||||
} ALVID {
|
||||
SI INVOCA est_victor(b, II) TVNC {
|
||||
REDI(M + depth)
|
||||
}
|
||||
}
|
||||
SI depth EST I TVNC {
|
||||
REDI(INVOCA aestima(b))
|
||||
}
|
||||
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
||||
SI maxi TVNC {
|
||||
DESIGNA best VT NVLLVS - M - VII
|
||||
PER c IN col_order FACE {
|
||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||
SI NON (linea EST NVLLVS) TVNC {
|
||||
DESIGNA b[(linea - I) * VII + c] VT II
|
||||
DESIGNA score VT INVOCA minimax(b, depth - I, alpha, beta, FALSITAS)
|
||||
DESIGNA b[(linea - I) * VII + c] VT NVLLVS
|
||||
SI score PLVS best TVNC {
|
||||
DESIGNA best VT score
|
||||
}
|
||||
SI best PLVS alpha TVNC {
|
||||
DESIGNA alpha VT best
|
||||
}
|
||||
SI beta MINVS alpha AVT beta EST alpha TVNC {
|
||||
ERVMPE
|
||||
}
|
||||
}
|
||||
}
|
||||
REDI(best)
|
||||
} ALVID {
|
||||
DESIGNA best VT M + VII
|
||||
PER c IN col_order FACE {
|
||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||
SI NON (linea EST NVLLVS) TVNC {
|
||||
DESIGNA b[(linea - I) * VII + c] VT I
|
||||
DESIGNA score VT INVOCA minimax(b, depth - I, alpha, beta, VERITAS)
|
||||
DESIGNA b[(linea - I) * VII + c] VT NVLLVS
|
||||
SI score MINVS best TVNC {
|
||||
DESIGNA best VT score
|
||||
}
|
||||
SI best MINVS beta TVNC {
|
||||
DESIGNA beta VT best
|
||||
}
|
||||
SI beta MINVS alpha AVT beta EST alpha TVNC {
|
||||
ERVMPE
|
||||
}
|
||||
}
|
||||
}
|
||||
REDI(best)
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the best column for the AI (depth VI = 5 plies of lookahead)
|
||||
DEFINI ai_move(b) VT {
|
||||
DESIGNA col_order VT [IV, III, V, II, VI, I, VII]
|
||||
DESIGNA best_score VT NVLLVS - M - VII
|
||||
DESIGNA best_col VT IV
|
||||
PER c IN col_order FACE {
|
||||
DESIGNA linea VT INVOCA find_slot(b, c)
|
||||
SI NON (linea EST NVLLVS) TVNC {
|
||||
DESIGNA b[(linea - I) * VII + c] VT II
|
||||
DESIGNA score VT INVOCA minimax(b, VI, NVLLVS - M - VII, M + VII, FALSITAS)
|
||||
DESIGNA b[(linea - I) * VII + c] VT NVLLVS
|
||||
SI score PLVS best_score TVNC {
|
||||
DESIGNA best_score VT score
|
||||
DESIGNA best_col VT c
|
||||
}
|
||||
}
|
||||
}
|
||||
REDI(best_col)
|
||||
}
|
||||
|
||||
// --- Board setup ---
|
||||
DESIGNA board VT [I VSQVE XLIII]
|
||||
DONICVM i VT I VSQVE XLIII FACE {
|
||||
DESIGNA board[i] VT NVLLVS
|
||||
}
|
||||
|
||||
DESIGNA moves VT NVLLVS
|
||||
DESIGNA game_over VT FALSITAS
|
||||
|
||||
DICE("=== CONNECT IV ===")
|
||||
DICE("You are X. AI is O.")
|
||||
DICE("Enter column as Roman numeral (I-VII).")
|
||||
DICE("")
|
||||
|
||||
DVM game_over FACE {
|
||||
EVERRO()
|
||||
INVOCA print_board(board)
|
||||
|
||||
DICE("Your move:")
|
||||
DESIGNA col VT AVDI_NVMERVS()
|
||||
SI col PLVS VII TVNC {
|
||||
DICE("Invalid column! Enter I through VII.")
|
||||
CONTINVA
|
||||
}
|
||||
DESIGNA linea VT INVOCA find_slot(board, col)
|
||||
SI linea EST NVLLVS TVNC {
|
||||
DICE("Column full! Try another.")
|
||||
CONTINVA
|
||||
}
|
||||
DESIGNA board[(linea - I) * VII + col] VT I
|
||||
DESIGNA moves VT moves + I
|
||||
SI INVOCA est_victor(board, I) TVNC {
|
||||
INVOCA print_board(board)
|
||||
DICE("You win!")
|
||||
DESIGNA game_over VT VERITAS
|
||||
CONTINVA
|
||||
}
|
||||
SI moves EST XLII TVNC {
|
||||
INVOCA print_board(board)
|
||||
DICE("Draw!")
|
||||
DESIGNA game_over VT VERITAS
|
||||
CONTINVA
|
||||
}
|
||||
|
||||
DICE("AI is thinking...")
|
||||
DESIGNA col VT INVOCA ai_move(board)
|
||||
DESIGNA linea VT INVOCA find_slot(board, col)
|
||||
DICE("AI plays column " & col & ".")
|
||||
DESIGNA board[(linea - I) * VII + col] VT II
|
||||
DESIGNA moves VT moves + I
|
||||
SI INVOCA est_victor(board, II) TVNC {
|
||||
INVOCA print_board(board)
|
||||
DICE("AI wins!")
|
||||
DESIGNA game_over VT VERITAS
|
||||
CONTINVA
|
||||
}
|
||||
SI moves EST XLII TVNC {
|
||||
INVOCA print_board(board)
|
||||
DICE("Draw!")
|
||||
DESIGNA game_over VT VERITAS
|
||||
}
|
||||
}
|
||||
1
tests.py
1
tests.py
@@ -117,6 +117,7 @@ output_tests = [
|
||||
("DICE(\"a\", \"b\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("a"), String("b")]))]), ValStr("a b"), "a b\n"),
|
||||
("DICE(\"line one\")\nDICE(\"line two\")", Program([], [ExpressionStatement(BuiltIn("DICE", [String("line one")])), ExpressionStatement(BuiltIn("DICE", [String("line two")]))]), ValStr("line two"), "line one\nline two\n"),
|
||||
("DICE(DICE(II))", Program([], [ExpressionStatement(BuiltIn("DICE", [BuiltIn("DICE", [Numeral("II")])]))]), ValStr("II"), "II\nII\n"),
|
||||
("EVERRO()", Program([], [ExpressionStatement(BuiltIn("EVERRO", []))]), ValNul(), "\033[2J\033[H"),
|
||||
]
|
||||
|
||||
class TestOutput(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user