123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See Tui_snake.mli *)(*****************************************************************************)(* The model *)(*****************************************************************************)(* the board, inside its frame *)letheight=20letwidth=40(* seconds between two moves *)letperiod=0.1typemodel={body:(int*int)list;(* head first *)dir:int*int;(* rows, columns a move *)turn:int*int;(* the direction asked for, taken at the next move *)food:int*int;seed:Lehmer.t;clock:float;(* seconds since the last move *)score:int;crashed:bool;quit:bool;}(* a cell the snake isn't on, from the generator *)letrecplace(seed:Lehmer.t)(body:(int*int)list):(int*int)*Lehmer.t=letseed=Lehmer.nextseedinleti=int_of_float(Lehmer.to_unitseed*.float_of_int(height*width))inletcell=(i/width,imodwidth)inifList.memcellbodythenplaceseedbodyelse(cell,seed)letstart(seed:Lehmer.t):model=letbody=[(10,12);(10,11);(10,10)]inletfood,seed=placeseedbodyin{body;dir=(0,1);turn=(0,1);food;seed;clock=0.;score=0;crashed=false;quit=false}letinit=start(Lehmer.of_int42)(*****************************************************************************)(* Update *)(*****************************************************************************)(* a move: the head a cell further; the tail follows unless it ate *)letmove(m:model):model=(* no turning back into oneself *)letdir=iffstm.turn=-fstm.dir&&sndm.turn=-sndm.dirthenm.direlsem.turninletr,c=List.hdm.bodyinlethead=(r+fstdir,c+snddir)inletate=head=m.foodinletbody=head::(ifatethenm.bodyelseList.filteri(funi_->i<List.lengthm.body-1)m.body)inletoff=fsthead<0||fsthead>=height||sndhead<0||sndhead>=widthinifoff||List.memhead(List.tlbody)then{mwithcrashed=true}elseifatethenletfood,seed=placem.seedbodyin{mwithbody;dir;food;seed;score=m.score+1}else{mwithbody;dir}letupdate(e:Tui.event)(m:model):model=matchewith|Key"q"->{mwithquit=true}|Key"r"whenm.crashed->{(startm.seed)withscore=0}|Key("\x1b[A"|"k")->{mwithturn=(-1,0)}|Key("\x1b[B"|"j")->{mwithturn=(1,0)}|Key("\x1b[C"|"l")->{mwithturn=(0,1)}|Key("\x1b[D"|"h")->{mwithturn=(0,-1)}|Key_->m|Tick_whenm.crashed->m|Tickdt->(* as many moves as periods passed, a slow frame catching up; with
a hair of tolerance, since 0.3 - 0.1 - 0.1 is 0.0999...98 in
floating point, and three periods must be three moves *)letrecsteps(m:model)=ifm.clock>=period-.1e-9&¬m.crashedthensteps(move{mwithclock=m.clock-.period})elseminsteps{mwithclock=m.clock+.dt}(*****************************************************************************)(* View *)(*****************************************************************************)letgreen={Vt.plainwithfg=Vt.Green;bold=true}letred={Vt.plainwithfg=Vt.Red;bold=true}letview(m:model):Curses.t=letcell(r,c)=(r+2,c+1)inletscreen=Curses.create~rows:24~cols:80|>Curses.put00"SNAKE"|>Curses.box10(height+2)(width+2)|>Curses.put346"arrows, or h j k l: turn"|>Curses.put446"q: quit"|>Curses.put646(Printf.sprintf"score: %d"m.score)inletfr,fc=cellm.foodinletscreen=Curses.put~attrs:redfrfc"*"screeninletscreen=List.fold_left(funs(i,rc)->letr,c=cellrcinCurses.put~attrs:greenrc(ifi=0then"@"else"o")s)screen(List.mapi(funirc->(i,rc))m.body)inifm.crashedthenCurses.put~attrs:{Vt.plainwithreverse=true}846" CRASHED -- r: again "screenelsescreenletprogram:modelTui.program={init;update;view;over=(funm->m.quit)}letbody(m:model)=m.bodyletcrashed(m:model)=m.crashedletscore(m:model)=m.score