Compare commits

...

4 Commits

Author SHA1 Message Date
Nikolaj
0b13d9f027 🐐 Connect 4 game 2026-04-13 13:04:46 +02:00
Nikolaj
e0bff7cb7e 🐐 Small fix 2026-04-13 11:45:21 +02:00
Nikolaj
675e3ecc9d 🐐 EVERRO 2026-04-13 11:18:46 +02:00
Nikolaj
ffc60f8a06 🐐 Small fixes 2026-04-13 11:07:29 +02:00
9 changed files with 358 additions and 58 deletions

3
.gitignore vendored
View File

@@ -2,5 +2,8 @@
__pycache__/
.pytest_cache/
examples/*
!examples/*.cent
.claude/
CLAUDE.md

6
cent
View File

@@ -1,15 +1,15 @@
#! /home/nikolaj/.pyenv/shims/python
#! /usr/bin/env python
"""
Usage:
cent (-h|--help)
cent -i FILE
cent -c [--keep-c] 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
-k --keep-c Keep the generated C file alongside the binary
FILE The file to compile/interpret
"""
import os

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)) {

View File

@@ -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 */

View File

@@ -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
View 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
}
}

View File

@@ -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):