From 0720fd8ed0aa92248622a9f3c3ebd522b002fd91 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Wed, 11 Nov 2026 15:40:22 -0800 Subject: graphic tictactoe Hex & octal digits --- chapter31/docs/31-grammar.ja.md | 3 +- chapter31/docs/31-grammar.md | 11 +- chapter31/docs/drawing.md | 81 +++++++++++++ chapter31/docs/examples/README.md | 4 + chapter31/docs/examples/TicTacToeGraphical.smp | 156 +++++++++++++++++++++++++ chapter31/docs/graphics.md | 3 + 6 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 chapter31/docs/drawing.md create mode 100644 chapter31/docs/examples/TicTacToeGraphical.smp (limited to 'chapter31/docs') diff --git a/chapter31/docs/31-grammar.ja.md b/chapter31/docs/31-grammar.ja.md index 465bdcf7..f4635f21 100644 --- a/chapter31/docs/31-grammar.ja.md +++ b/chapter31/docs/31-grammar.ja.md @@ -167,7 +167,7 @@ argumentList : assignment (',' assignment)* ','? ; // earlier chapters are replaced by constructor arguments. newExpression : 'new' type ('[' assignment ']' | callSuffix)? ; -INTEGER_LITERAL : '0' | [1-9] DIGIT* ; +INTEGER_LITERAL : [1-9] DIGIT* | '0' [0-7]* | '0' [xX] HEX_DIGIT+ ; FLOAT_LITERAL : DIGIT+ ('.' DIGIT* ('e' DIGIT+)? | 'e' '-'? DIGIT+) ; STRING_LITERAL : '"' (ESCAPE | ~["\\])* '"' ; CHARACTER_LITERAL : '\'' (ESCAPE | ~['\\]) '\'' ; @@ -175,6 +175,7 @@ fragment ESCAPE : '\\' [0ntr\\'"] ; IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; fragment NON_DIGIT : [a-zA-Z_] ; fragment DIGIT : [0-9] ; +fragment HEX_DIGIT : [0-9a-fA-F] ; WHITESPACE : [ \t\r\n]+ -> skip ; LINE_COMMENT : '//' ~[\r\n]* -> skip ; BLOCK_COMMENT : '/*' .*? '*/' -> skip ; diff --git a/chapter31/docs/31-grammar.md b/chapter31/docs/31-grammar.md index 1aa31d42..19f834af 100644 --- a/chapter31/docs/31-grammar.md +++ b/chapter31/docs/31-grammar.md @@ -11,6 +11,14 @@ language; it is not input to a parser generator. `int` is an alias for `i32`; `i64` explicitly selects 64-bit integer storage. This alias does not change literal inference or intermediate arithmetic. +Integer literals use decimal (`42`), leading-zero octal (`052`), or hexadecimal +(`0x2a`, `0X2A`). Octal digits are 0–7; `08` and `09` are errors. Hexadecimal +and octal literals can spell any 64-bit bit pattern: the high bit gives the +usual signed value, so `0xffffffffffffffff` is -1. Decimal literals retain +their signed 64-bit limit. Floating literals remain decimal, including forms +such as `012.5` and `012f`. A radix prefix does not change integer inference +or storage width. + ```antlrv4 grammar SimpleLanguage; @@ -179,7 +187,7 @@ argumentList : assignment (',' assignment)* ','? ; // earlier chapters are replaced by constructor arguments. newExpression : 'new' type ('[' assignment ']' | callSuffix)? ; -INTEGER_LITERAL : '0' | [1-9] DIGIT* ; +INTEGER_LITERAL : [1-9] DIGIT* | '0' [0-7]* | '0' [xX] HEX_DIGIT+ ; // The f suffix also accepts an integer-shaped token, e.g. 12f. FLOAT_LITERAL : DIGIT+ ('.' DIGIT* ('e' DIGIT+)? | 'e' '-'? DIGIT+) 'f'? | DIGIT+ 'f' ; @@ -197,6 +205,7 @@ fragment ESCAPE : '\\' [0ntr\\'"%] ; IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; fragment NON_DIGIT : [a-zA-Z_] ; fragment DIGIT : [0-9] ; +fragment HEX_DIGIT : [0-9a-fA-F] ; WHITESPACE : [ \t\r\n]+ -> skip ; LINE_COMMENT : '//' ~[\r\n]* -> skip ; BLOCK_COMMENT : '/*' .*? '*/' -> skip ; diff --git a/chapter31/docs/drawing.md b/chapter31/docs/drawing.md new file mode 100644 index 00000000..d9d7e624 --- /dev/null +++ b/chapter31/docs/drawing.md @@ -0,0 +1,81 @@ +# Drawing in Simple + +`sys.draw` draws into an ordinary `u32[]`. It has no native dependencies and +does not open a window. `sys.gfx` presents the resulting pixels using the +optional [MiniFB backend](graphics.md). + +```simple +val canvas = new sys.draw.Canvas(600,780); +u32 orange = 0xe47b57; +canvas.clear(sys.draw.WHITE); +canvas.fillRect(20,20,180,80,orange); +canvas.rect(20,20,180,80,3,sys.draw.BLACK); +canvas.circle(300,200,48,8,sys.draw.BLUE); +canvas.fillCircle(420,200,20,sys.draw.GREEN); +canvas.line(40,320,250,440,12,orange); +canvas.text(40,500,"YOUR TURN",4,sys.draw.BLACK); +// After sys.gfx.open succeeds: +sys.gfx.present(canvas.pixels); +``` + +## Small raster API + +Coordinates start at the upper left; x increases rightward and y downward. +Drawing methods return the canvas for chaining. Everything is opaque RGB; +there is no blending, antialiasing, transform stack, or native drawing state. +Coordinates accept `i64` so ordinary widened arithmetic can be passed directly. +Circle radii and circle stroke widths are `int`. Use screen-sized coordinates, +extents, and stroke widths. + +| Operation | Behavior | +| --- | --- | +| `rgb(r,g,b)` | Clamp components to 0–255 and pack RGB. `BLACK`, `WHITE`, `RED`, `GREEN`, and `BLUE` are available too. | +| `new Canvas(width,height)` | Allocate a zero-filled pixel array. Dimensions must be positive and their product must fit a signed 32-bit index; invalid sizes exit with status 1. Allocation can still fail for lack of memory. | +| `clear(color)` | Fill the entire canvas. | +| `pixel(x,y,color)` | Set one pixel; ignore points outside the canvas. | +| `fillRect(x,y,w,h,color)` | Fill a half-open rectangle: x through x+w-1, y through y+h-1. | +| `rect(x,y,w,h,thickness,color)` | Draw an inward outline. A sufficiently wide outline fills the rectangle. | +| `fillCircle(cx,cy,radius,color)` | Fill a circle; radius zero draws its center pixel. | +| `circle(cx,cy,radius,thickness,color)` | Draw an inward circular outline. | +| `line(x0,y0,x1,y1,thickness,color)` | Draw a round-ended segment. A zero-length segment draws a round dot. Thickness is its geometric diameter, sampled at integer pixel centers. | +| `text(x,y,string,scale,color)` | Draw a 3×5 bitmap font, with one column between characters. Upper/lowercase ASCII letters, digits, spaces, and basic punctuation are supported. Newline advances six scaled pixels. Unknown characters use `?`. | + +Shapes clip to the canvas. Nonpositive rectangle extents, stroke widths, or +text scale do nothing; negative circle radii do nothing. Line and circle +coverage scans only the clipped bounding box, which is sufficient for small +turn-based games. This is a starting raster library rather than a fast animated +renderer. The pixel array remains available for direct drawing and inspection. + +Packed colors use hexadecimal literals (`0xRRGGBB`); `rgb(r,g,b)` remains handy +for computed components. The font masks use leading-zero octal, with five +digits after the prefix: each digit is one three-pixel row, top to bottom. +For example, `A` is `025755`: rows `010`, `101`, `111`, `101`, `101`. + +## Graphical tic-tac-toe + +From `chapter31`: + +```sh +make run-graphical-tictactoe CTAGS= +``` + +Click an empty square or press **1–9**, numbered left-to-right, top-to-bottom. +You are X; the CPU is O. It wins when possible, blocks your immediate win, +then prefers the center, corners, and edges. The result stays visible with +the winning line highlighted. Press **R** or **Space** to start a new game, +or **Q**/**Escape** to quit. Restart is also available during a game. + +Held buttons and keys do not take repeated turns. Moving the pointer highlights +empty cells. The canvas is redrawn only after a move, restart, or hover change; +polling still processes window events and waits between polls. + +`make graphical-tictactoe CTAGS=` builds without opening a window or requiring +MiniFB. Its output is `build/examples/x86_64_v2_win64/TicTacToeGraphical.exe` +on Windows or `build/examples/x86_64_v2_SystemV/TicTacToeGraphical` on Linux. +The explicit backend path follows [the pixel-window instructions](graphics.md). +Passing any argument performs a five-poll, self-closing smoke test. + +The original `make tictactoe` still builds the terminal game. +`make test CLASS=DrawTest CTAGS=` checks raster pixels and complete game turns +without a display. `make tests_nographics CTAGS=` runs the ordinary suite with +the backend disabled. diff --git a/chapter31/docs/examples/README.md b/chapter31/docs/examples/README.md index 2f19f09c..db95487e 100644 --- a/chapter31/docs/examples/README.md +++ b/chapter31/docs/examples/README.md @@ -4,6 +4,10 @@ These programs live beside the documentation so readers can find and run them. Their non-graphical native regression tests run as part of `make tests`. The optional [pixel-window example](Graphics.smp) uses MiniFB; see [graphics setup](../graphics.md). `make run-graphics` opens it explicitly. +The [graphical tic-tac-toe](TicTacToeGraphical.smp) uses the Simple +[drawing library](../drawing.md). Run it with +`make run-graphical-tictactoe CTAGS=`; click a cell or press 1–9, R/Space to +replay, and Q/Escape to quit. The terminal game below remains available. From `chapter31`, build the tic-tac-toe executable (without starting it): diff --git a/chapter31/docs/examples/TicTacToeGraphical.smp b/chapter31/docs/examples/TicTacToeGraphical.smp new file mode 100644 index 00000000..7daf6710 --- /dev/null +++ b/chapter31/docs/examples/TicTacToeGraphical.smp @@ -0,0 +1,156 @@ +// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom. +// Graphical tic-tac-toe; the same strategy as TicTacToe.smp. +// Click an empty square or press 1-9. R/Space replays; Q/Escape quits. +// Pass any argument for a five-poll window smoke test. +val main = { u8[~][] args -> + val screen = new sys.draw.Canvas(600,780); + val board = new u8[9]; + _reset(board); + if( sys.gfx.open("Simple tic-tac-toe",screen.width,screen.height) != sys.gfx.OK ) return 1; + _draw(screen,board,-1); + int result = sys.gfx.present(screen.pixels); + bool mouseHeld = false, keyHeld = false, replayHeld = false; + int hover = -1, frames = 0; + while( result==sys.gfx.OK ) { + result = sys.gfx.poll(true); + if( result!=sys.gfx.OK ) break; + if( sys.gfx.key('Q') ) break; + bool down = sys.gfx.button(sys.gfx.LEFT); + bool replay = sys.gfx.key('R') || sys.gfx.key(' '); + int square = _square(sys.gfx.mouseX(),sys.gfx.mouseY()); + bool dirty = square!=hover; + hover = square; + if( replay && !replayHeld ) { _reset(board); dirty=true; } + bool digit = false; + int choice = down && !mouseHeld ? square : -1; + for( int i=0; i<9; i++ ) { + int key = '1'+i; + if( sys.gfx.key(key) ) { + digit = true; + if( !keyHeld ) choice=i; + } + } + if( !(replay && !replayHeld) && _move(board,choice) ) dirty=true; + mouseHeld = down; + keyHeld = digit; + replayHeld = replay; + if( dirty ) { + _draw(screen,board,hover); + result = sys.gfx.present(screen.pixels); + } + if( args#>1 && ++frames==5 ) break; + } + sys.gfx.close(); + return result==sys.gfx.ERROR ? 1 : 0; +}; + +val _reset = { u8[] board -> + for( int i=0; i<9; i++ ) board[i]=' '; +}; +val _square = { int x, int y -> + if( x<48 || x>=552 || y<168 || y>=672 ) return -1; + return (y-168)/168*3+(x-48)/168; +}; + +// One player turn followed by one CPU turn. Invalid/finished moves do nothing. +val _move = { u8[] board, int square -> + if( square<0 || square>=9 || board[square]!=' ' || _finished(board) ) return false; + board[square] = 'X'; + if( !_finished(board) ) { + int reply = _cpu(board); + if( reply>=0 ) board[reply]='O'; + } + return true; +}; + +val _finished = { u8[~] board -> + if( _winner(board) ) return true; + for( int i=0; i<9; i++ ) if( board[i]==' ' ) return false; + return true; +}; + +val _winningLine = { u8[~] board -> + for( int i=0; i<3; i++ ) { + int row = i*3; + if( board[row]!=' ' && board[row]==board[row+1] && board[row]==board[row+2] ) return i; + if( board[i]!=' ' && board[i]==board[i+3] && board[i]==board[i+6] ) return 3+i; + } + if( board[4]!=' ' ) { + if( board[0]==board[4] && board[4]==board[8] ) return 6; + if( board[2]==board[4] && board[4]==board[6] ) return 7; + } + return -1; +}; +// Return the winning mark, or zero. +val _winner = { u8[~] board -> + int line = _winningLine(board); + return line<0 ? 0 : line<3 ? board[line*3] : line<6 ? board[line-3] : board[4]; +}; + +// Try each empty square, restoring it before returning. +val _threat = { u8[] board, u8 mark -> + for( int i=0; i<9; i++ ) + if( board[i]==' ' ) { + board[i] = mark; + bool wins = _winner(board)==mark; + board[i] = ' '; // Restore board + if( wins ) return i; + } + return -1; +}; +// Win if possible, otherwise block, then prefer center, corners, and edges. +// Called only after _finished has ruled out a full board. +val _cpu = { u8[] board -> + // If O can win, do so + int win = _threat(board,'O'); + if( win>=0 ) return win; + // If X will win next move, block + int block = _threat(board,'X'); + if( block>=0 ) return block; + // If center is open, take it + if( board[4]==' ' ) return 4; + // If a corner is open, take it + for( int i=0; i<9; i+=2 ) if( board[i]==' ' ) return i; + // Take whatever is available + for( int i=0; i<9; i++ ) if( board[i]==' ' ) return i; + return -1; +}; + +val _draw = { !sys.draw.Canvas screen, u8[~] board, int hover -> + u32 background = 0x142e36, paper = 0xf2eddf, ink = 0x35535a; + u32 cross = 0xe47b57, ring = 0x248b89, accent = 0xf0c778; + int winner = _winner(board); + bool finished = _finished(board); + screen.clear(background); + screen.text(48,34,"TIC TAC TOE",6,paper); + screen.fillRect(48,90,504,3,ink); + screen.text(48,116,finished ? (winner=='X' ? "YOU WIN!" : winner=='O' ? "CPU WINS" : "DRAW") : "YOUR TURN",4,accent); + screen.line(428,112,450,134,5,cross).line(450,112,428,134,5,cross); + screen.circle(496,123,13,5,0x70c9bf); + screen.text(428,145,"YOU",2,0xa2b9b7); + screen.text(484,145,"CPU",2,0xa2b9b7); + screen.fillRect(48,168,504,504,paper); + if( !finished && hover>=0 && hover<9 && board[hover]==' ' ) + screen.fillRect(48+(hover-hover/3*3)*168,168+hover/3*168,168,168,0xd9e8de); + for( int i=1; i<3; i++ ) { + screen.fillRect(48+i*168-3,168,6,504,ink); + screen.fillRect(48,168+i*168-3,504,6,ink); + } + for( int i=0; i<9; i++ ) { + int cx = 132+(i-i/3*3)*168, cy = 252+i/3*168; + if( board[i]=='X' ) + screen.line(cx-38,cy-38,cx+38,cy+38,12,cross).line(cx+38,cy-38,cx-38,cy+38,12,cross); + else if( board[i]=='O' ) screen.circle(cx,cy,48,11,ring); + else screen.text(cx-6,cy-10,i==0 ? "1" : i==1 ? "2" : i==2 ? "3" : + i==3 ? "4" : i==4 ? "5" : i==5 ? "6" : i==6 ? "7" : i==7 ? "8" : "9",4,0x95a59e); + } + int line = _winningLine(board); + if( line>=0 ) { + int first = line<3 ? line*3 : line<6 ? line-3 : line==6 ? 0 : 2; + int last = line<3 ? first+2 : line<6 ? first+6 : line==6 ? 8 : 6; + screen.line(132+(first-first/3*3)*168,252+first/3*168,132+(last-last/3*3)*168,252+last/3*168,5,0xeab64e); + } + screen.text(48,704,"CLICK A SQUARE OR PRESS 1-9",3,paper); + screen.text(48,738,"R OR SPACE REPLAY Q OR ESC QUIT",3,0xa2b9b7); + screen.rect(48,168,504,504,3,ink); +}; diff --git a/chapter31/docs/graphics.md b/chapter31/docs/graphics.md index 7e1636f9..fb3972ee 100644 --- a/chapter31/docs/graphics.md +++ b/chapter31/docs/graphics.md @@ -4,6 +4,9 @@ Simple owns the pixel array and drawing code. [MiniFB](https://github.com/emoon/ provides a window, input, and pixel presentation. The first interface is `sys.gfx`: one fixed-size window, operated on the main thread. The [Graphics example](examples/Graphics.smp) draws a color gradient in Simple. +The [drawing library and graphical tic-tac-toe](drawing.md) build on this pixel +interface. `make run-graphical-tictactoe CTAGS=` builds the optional backend and +opens the game; `make graphical-tictactoe CTAGS=` only builds its executable. ## Default: no graphics dependency -- cgit v1.2.3