diff --git a/examples/connect_iv.cent b/examples/connect_iv.cent new file mode 100644 index 0000000..d7fb03c --- /dev/null +++ b/examples/connect_iv.cent @@ -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 + } +}