diff options
| author | Cliff Click <cliffc@acm.org> | 2026-08-10 11:11:18 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-11-14 16:23:39 -0800 |
| commit | 207febef9924c8fa5391185e75d42a7ea480bb38 (patch) | |
| tree | 68ec6225a89fa1178c32fdf659b9b4f2c3327288 /docs/chapter31/examples | |
| parent | 259f60b105eb770bc6f7e3bd36d91f2b217e9315 (diff) | |
| download | Simple-207febef9924c8fa5391185e75d42a7ea480bb38.tar.gz Simple-207febef9924c8fa5391185e75d42a7ea480bb38.zip | |
Chapter 31: Software Drawing, Time, and Graphical Gameslinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: Symious <14933944+symious@users.noreply.github.com>
Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs/chapter31/examples')
| -rw-r--r-- | docs/chapter31/examples/A_helloWorld.smp | 2 | ||||
| -rw-r--r-- | docs/chapter31/examples/Breakout.smp | 364 | ||||
| -rw-r--r-- | docs/chapter31/examples/BubbleSort.smp | 38 | ||||
| -rw-r--r-- | docs/chapter31/examples/Capitalize.smp | 23 | ||||
| -rw-r--r-- | docs/chapter31/examples/Clock.smp | 128 | ||||
| -rw-r--r-- | docs/chapter31/examples/Dijkstra.smp | 71 | ||||
| -rw-r--r-- | docs/chapter31/examples/FileIO.smp | 52 | ||||
| -rw-r--r-- | docs/chapter31/examples/Graphics.smp | 17 | ||||
| -rw-r--r-- | docs/chapter31/examples/README.md | 108 | ||||
| -rw-r--r-- | docs/chapter31/examples/TicTacToe.smp | 132 | ||||
| -rw-r--r-- | docs/chapter31/examples/TicTacToeGraphical.smp | 168 |
11 files changed, 1103 insertions, 0 deletions
diff --git a/docs/chapter31/examples/A_helloWorld.smp b/docs/chapter31/examples/A_helloWorld.smp new file mode 100644 index 00000000..4ed6abdf --- /dev/null +++ b/docs/chapter31/examples/A_helloWorld.smp @@ -0,0 +1,2 @@ +sys.io.p("Hello, World!\n"); +return 0; diff --git a/docs/chapter31/examples/Breakout.smp b/docs/chapter31/examples/Breakout.smp new file mode 100644 index 00000000..b1d0b443 --- /dev/null +++ b/docs/chapter31/examples/Breakout.smp @@ -0,0 +1,364 @@ +// Clear 24 x 8 bricks. Arrows or mouse move; Space/click serves; P pauses; +// R restarts; Q/Escape exits. Any argument runs ten frames as a smoke test. +u32 WIDTH = 1008, HEIGHT = 760, COLS = 24, ROWS = 8; +int LEFT = 24, RIGHT = 984, TOP = 84, BOTTOM = 718; +int BRICK_TOP = 152, BRICK_DX = 40, BRICK_DY = 22, BRICK_W = 36, BRICK_H = 18; +int RADIUS = 5, PADDLE_HALF = 56, PADDLE_Y = 674; +int READY = 0, PLAY = 1, WON = 2, LOST = 3; +int KEY_LEFT = 263, KEY_RIGHT = 262; // MiniFB key codes; gfx.LEFT is a mouse button. +i64 STEP_ms = 8, FRAME_ms = 16; +u32 BACKGROUND = 0x0b1220, WHITE = 0xeef7ff, MUTED = 0x8aabc4, CYAN = 0x4ce0ef; +val COLORS = new u32[~]{0xff5268,0xff8050,0xffb548,0xffdb58, + 0x9bdd58,0x45d8b0,0x49b9f5,0x8d94ff}; +val DIGITS = new u8[~][~]{"0","1","2","3","4","5","6","7","8","9"}; + +struct Game { + u8[] bricks = new u8[COLS*ROWS]{int i -> 1;}; + int score = 0, remaining = COLS*ROWS, lives = 3, depth = 1, phase = READY; + bool boosted = false, backWall = false, paused = false; + f64 x = 504.0, y = 663.0, dx = 0.4, dy = -0.916515138991168; + + // Fixed 8 ms simulation, split further to travel at most two pixels per step. + // Even at maximum boosted speed a ball cannot tunnel through an 18 px brick. + val _tick = { int direction -> + if( paused || phase == WON || phase == LOST ) return 0; + _paddle(paddle+direction*6.4); + if( phase == READY ) { x = paddle; y = PADDLE_Y-RADIUS-6; return 0; } + f64 seconds = 0.008; // 8ms + while( seconds > 0.000000001 && phase == PLAY ) { + f64 dt = 2.0/speed; + if( dt > seconds ) dt = seconds; + seconds -= dt; + f64 oldX = x, oldY = y; + x += dx*speed*dt; + y += dy*speed*dt; + if( x < LEFT+RADIUS && dx < 0.0 ) { + x = 2.0*(LEFT+RADIUS)-x; dx = -dx; _cue(1); + } + if( x > RIGHT-RADIUS && dx > 0.0 ) { + x = 2.0*(RIGHT-RADIUS)-x; dx = -dx; _cue(1); + } + if( y < TOP+RADIUS && dy < 0.0 ) { + y = 2.0*(TOP+RADIUS)-y; dy = -dy; + backWall = true; _cue(1); + } + if( dy > 0.0 && oldY <= PADDLE_Y-RADIUS && y >= PADDLE_Y-RADIUS && + paddle-PADDLE_HALF-RADIUS <= x <= paddle+PADDLE_HALF+RADIUS ) _rebound(); + if( y > BOTTOM+RADIUS ) { + _lostBall(); + break; + } + // Map the center into the grid of radius-expanded brick boxes. + // Their overlap is smaller than one cell: at most 2 x 2 candidates. + if( x <= LEFT+2 -RADIUS || x >= LEFT+2 +(COLS-1)*BRICK_DX+BRICK_W+RADIUS || + y <= BRICK_TOP-RADIUS || y >= BRICK_TOP+(ROWS-1)*BRICK_DY+BRICK_H+RADIUS ) + continue; + int col = (x-(LEFT+2 -RADIUS))/BRICK_DX; + int row = (y-(BRICK_TOP-RADIUS))/BRICK_DY; + bool hit = false; + for( int r = row > 0 ? row-1 : 0; r <= row && r < ROWS && !hit; r++ ) + for( int c = col > 0 ? col-1 : 0; c <= col && c < COLS; c++ ) { + int i = r*COLS+c; + if( bricks[i] == 0 ) continue; + f64 left = LEFT+2 +c*BRICK_DX-RADIUS; + f64 top = BRICK_TOP+r*BRICK_DY-RADIUS; + f64 right = left+BRICK_W+2*RADIUS; + f64 bottom= top +BRICK_H+2*RADIUS; + if( left < x < right && top < y < bottom ) { + bool back = false; + // Resolve the face crossed this step, using the previous center. + // Vertical faces win a simultaneous corner contact. + if( oldY <= top && dy > 0.0 ) { + y = top; dy = -dy; back = true; + } else if( oldY >= bottom && dy < 0.0 ) { + y = bottom; dy = -dy; + } else if( oldX <= left && dx > 0.0 ) { + x = left; dx = -dx; + } else if( oldX >= right && dx < 0.0 ) { + x = right; dx = -dx; + } else continue; // Missed all bricks + _brick(i,back); + hit = true; + break; + } + } + } + return 0; + }; + + + // Several sound things might happen in this event, pick one + int sound = 0; // Increasing priority: wall, paddle, brick, boost, miss, win. + val _cue = { int snd -> if( snd > sound ) sound = snd; }; + + // Ball speed is a fraction of deepest brick hit, plus backwall boost + f64 speed; + val _speed = { -> speed = (320.0+35.0*(depth-1))*(boosted ? 1.65 : 1.0); }; + + // Clamp paddle to wall sides + f64 paddle; + val _paddle = { f64 x -> + paddle = x < LEFT +PADDLE_HALF ? LEFT +PADDLE_HALF : + x > RIGHT-PADDLE_HALF ? RIGHT-PADDLE_HALF : + x; + }; + + // Hit the paddle + val _rebound = { -> + // Where along the paddle did you hit? + f64 offset = (x-paddle)/PADDLE_HALF; + if( offset < -1.0 ) offset = -1.0; + if( offset > 1.0 ) offset = 1.0; + dx = 0.88*offset; + // A near-center hit still drifts sideways instead of repeating forever. + if( -0.12 < dx < 0.12 ) dx = dx < 0.0 ? -0.12 : 0.12; + dy = -_sqrt(1.0-dx*dx); // Bounce upwards speed + y = PADDLE_Y-RADIUS; // Reset ball hit to top of paddle + backWall = false; // Did not hit back wall + _cue(2); // Paddle bounce sound + }; + + // Hit a brick? + int soundDepth; // Which brick depth for the boop + val _brick = { int index, bool back -> + if( bricks[index] == 0 ) return 0; // No brick to hit! + bricks[index] = 0; // Clear brick + int row = ROWS-index/COLS; // Get brick row + score += row; // Score by row + remaining--; // Fewer bricks to iwn + if( row > depth ) depth = row; // Max depth seen + // Brick-hit sound + if( sound <= 3 ) { _cue(3); soundDepth = row; } + if( back && backWall && !boosted ) { _cue(3); boosted = true; } + if( remaining == 0 ) { _cue(6); phase = WON; } + _speed(); + return 0; + }; + + // Missed ball with paddle + val _lostBall = { -> + lives--; + phase = lives == 0 ? LOST : READY; + backWall = false; + depth = 1; + boosted = false; + _speed(); + x = paddle; y = PADDLE_Y-RADIUS-6; + _cue(5); + }; + + // Reset for another round + val _reset = { -> + for( int i = 0; i < bricks#; i++ ) bricks[i] = 1; + score = 0; + remaining = COLS*ROWS; + lives = 3; + depth = 1; + phase = READY; + boosted = false; + backWall = false; + paused = false; + sound = 0; + soundDepth = 1; + paddle = 504.0; + x = paddle; + y = PADDLE_Y-RADIUS-6; + _speed(); + }; + + // -------------------------------- + // Drawing code + val _hud = { !sys.draw.Canvas c -> + c.text(24,22,"BREAKOUT",5,WHITE); + c.text(264,20,"SCORE",2,MUTED); + _number(c,264,40,score,3,4,WHITE); + c.text(424,20,"BRICKS",2,MUTED); + _number(c,424,40,remaining,3,4,WHITE); + c.text(584,20,"BALLS",2,MUTED); + for( int i = 0; i < lives; i++ ) c.fillCircle(590+23*i,50,5,CYAN); + c.text(714,20,"SPEED",2,MUTED); + int ispeed = speed; + _number(c,714,40,ispeed,3,4,boosted ? COLORS[0] : WHITE); + c.text(838,40,boosted ? "BOOST!" : "NORMAL",3,boosted ? COLORS[0] : MUTED); + }; + + + val _bricks = { !sys.draw.Canvas c -> + c.fillRect(LEFT-4,TOP-4,RIGHT-LEFT+8,4,MUTED); + c.fillRect(LEFT-4,TOP,4,BOTTOM-TOP,0x29435a); + c.fillRect(RIGHT,TOP,4,BOTTOM-TOP,0x29435a); + c.text(376,108,boosted ? "BACK WALL BOOST ACTIVE" : "BREAK THROUGH FOR A BOOST",2,boosted ? COLORS[0] : MUTED); + for( int i = 0; i < bricks#; i++ ) { + if( bricks[i] == 0 ) continue; + int row = i/COLS; + i64 x = LEFT+2+(i-row*COLS)*BRICK_DX, y = BRICK_TOP+row*BRICK_DY; + c.fillRect(x,y,BRICK_W,BRICK_H,COLORS[row]); + c.fillRect(x,y,BRICK_W,2,WHITE); + } + }; + + val _overlay = { !sys.draw.Canvas c -> + if( phase != PLAY || paused ) { + c.fillRect(246,422,516,108,0x17263a).rect(246,422,516,108,2,0x29435a); + u8[~] title = paused ? "PAUSED" : phase == WON ? "WALL CLEARED!" : phase == LOST ? "GAME OVER" : "READY?"; + c.text(504-(title#*4-1)*4/2,442,title,4,WHITE); + u8[~] help = paused ? "P TO CONTINUE" : phase == WON || phase == LOST ? "R TO PLAY AGAIN" : "SPACE OR CLICK TO SERVE"; + c.text(504-(help#*4-1)*2/2,490,help,2,CYAN); + } + return 0; + }; + + // With this Canvas, we draw into the blank canvas, then "present". The + // underlying graphics lib copies the bits, and we are then free to clear + // and draw again while the old bits continue to be presented. + val _draw = { !sys.draw.Canvas c -> + c.clear(BACKGROUND); + _hud(c); + _bricks(c); + i64 paddle = paddle, ballX = x, ballY = y; + int glowRadius = RADIUS+3; + c.fillRect(paddle-PADDLE_HALF,PADDLE_Y,2*PADDLE_HALF,12,CYAN); + c.fillRect(paddle-PADDLE_HALF+4,PADDLE_Y+2,2*PADDLE_HALF-8,2,WHITE); + c.fillCircle(ballX,ballY,glowRadius,boosted ? 0x672a42 : 0x224b61); + c.fillCircle(ballX,ballY,RADIUS,boosted ? COLORS[0] : WHITE); + _overlay(c); + c.text(24,738,"ARROWS / MOUSE: MOVE SPACE / CLICK: SERVE P: PAUSE R: RESTART Q / ESC: QUIT",2,MUTED); + }; + + // Served and still holding serve key + bool serveHeld; + // Paused and still holding pause key + bool pauseHeld; + // Restart and still holding restart key + bool restartHeld; + // Last mouse position; track moving mouse + int lastMouse; + + // Read and act on the controls + val _input = { -> + val gfx = sys.gfx; + bool serve = gfx.key(' ') || gfx.button(gfx.LEFT); + bool pause = gfx.key('P'); + bool restart= gfx.key('R'); + + // Keyboard direction and mouse position + int direction = (gfx.key(KEY_RIGHT) ? 1 : 0)-(gfx.key(KEY_LEFT) ? 1 : 0); + int mouse = gfx.mouseX(); + + // Restart requested + restartHeld = debounce( restart, restartHeld, { -> _reset(); sys.audio.stop(); } ); + + // Playing or ready to play game? + if( phase == READY || phase == PLAY ) { + // Flip pause state + pauseHeld = debounce( pause, pauseHeld, { -> paused = !paused; } ); + + // If no key and mouse moved, set paddle to mouse + if( mouse != lastMouse && direction == 0 && !paused ) { + _paddle(mouse*1.0); // Set paddle to mouse + // If READY and not PLAY, ball tracks paddle directly + if( phase == READY ) x = paddle; + } + lastMouse = mouse; + + } + + // Serve requested + serveHeld = debounce( serve, serveHeld, _serve ); + + // Return key direction, so the tick tracking can slide paddle smoothly + return direction; + }; + + val debounce = { bool b, bool bHeld, { int } action -> + if( b && !bHeld ) // Action requested and repeating + action(); // Do action + b; // Dont action again, until 'b' is released + }; + + + // Serve the ball; give it a nominal direction and speed + val _serve = { -> + if( phase == READY && !paused ) { + phase = PLAY; + dx = 0.4; + dy = -_sqrt(1.0-dx*dx); + } + return 0; + }; +}; + +// Fixed-width decimal labels avoid allocating new strings on every frame. +val _number = { !sys.draw.Canvas c, i64 x, i64 y, int value, int digits, int scale, u32 color -> + for( int i = 0; i < digits; i++ ) { + c.text(x+(digits-1-i)*4*scale,y,DIGITS[value-value/10*10],scale,color); + value /= 10; + } +}; + +// Short, fading triangle waves; the array initializer builds PCM once at startup. +val _tone = { int period -> + val samples = new i16[~2400]{int i -> + int phase = i-i/period*period; + int triangle = phase < period/2 ? phase : period-phase; + return (triangle*4-period)*6000/period*(2400-i)/2400; + }; + int handle = sys.audio.load(samples); + return handle < 0 ? sys.audio.BOOP : handle; +}; + + +// Fast low accuracy sqrt +val _sqrt = { f64 n -> + f64 r = 1.0; + for( int i = 0; i < 8; i++ ) r = (r+n/r)*0.5; + return r; +}; + +val main = { u8[~][] args -> + // Shortcuts + val gfx = sys.gfx, audio = sys.audio; + + // Open a window + if( gfx.open("Simple Breakout",WIDTH,HEIGHT) != gfx.OK ) return 1; + + // Audio setup; pre-specify a series of sounds + audio.open(); // Optional: a missing audio backend leaves play silent. + val sounds = new int[~]{0,_tone(120),_tone(80),audio.BOOP,audio.WIN,audio.LOSE,audio.WIN}; + // Bottom to top: roughly 333..800 Hz, below the old 1000 Hz brick tone. + val brickSounds = new int[~ROWS]{int i -> int period = 144-i*12; _tone(period);}; + + // Setup drawing board + val c = new sys.draw.Canvas(WIDTH,HEIGHT); + + // Setup game contents + val g = new Game; + g._reset(); + + // + int frames = 0, status = gfx.OK; + i64 last_ms = sys.time.monotonic(), pending_ms = 0, frame_ms = last_ms; + + while( status == gfx.OK ) { + if( gfx.key('Q') || gfx.key(gfx.ESCAPE) ) break; + int direction = g._input(); + i64 now_ms = sys.time.monotonic(), elapsed_ms = now_ms-last_ms; + last_ms = now_ms; + // Drop long stalls instead of teleporting the ball after a window drag. + pending_ms += elapsed_ms < 0 ? 0 : elapsed_ms > 64 ? 64 : elapsed_ms; + while( pending_ms >= STEP_ms ) { g._tick(direction); pending_ms -= STEP_ms; } + if( g.sound != 0 ) { + audio.play(g.sound == 3 ? brickSounds[g.soundDepth-1] : sounds[g.sound]); + g.sound = 0; + } + if( now_ms >= frame_ms ) { + g._draw(c); + status = gfx.present(c.pixels); + frame_ms = now_ms+FRAME_ms; + if( args# > 1 && ++frames == 10 ) break; + } + if( status == gfx.OK ) status = gfx.wait(STEP_ms-pending_ms); + } + audio.close(); gfx.close(); + return status == gfx.ERROR ? 1 : 0; +}; diff --git a/docs/chapter31/examples/BubbleSort.smp b/docs/chapter31/examples/BubbleSort.smp new file mode 100644 index 00000000..6bdb13a9 --- /dev/null +++ b/docs/chapter31/examples/BubbleSort.smp @@ -0,0 +1,38 @@ + +// An implementation of +// https://sampleprograms.io/projects/bubble-sort/ +// +// Bubble sort, with reading (parsing) and writing (formatting) the data. + +val sort = { !sys.aryi64 nums -> + sys.aryi64?! more = nums; + while( more ) { + more = null; + for( u32 i = 0; i < nums.len-1; i++ ) + if( nums.buf[i] > nums.buf[i+1] ) { + u32 next = i+1; + more = nums.swap(i,next); + } + } +}; + +// Read one process argument of the form "[ 4, 3, 2, 1]" and print it sorted. +// args[0] is the executable name, as with native C argc/argv. +val main = { u8[~][] args -> + val usage = { -> + sys.io.p("Usage: please provide a list of at least two integers to sort in the format \"[1, 2, 3, 4, 5]\"\n"); + return 0; + }; + if( args# != 2 ) return usage(); + + var nums = sys.aryi64.parseAryI64(args[1]); + if( !nums ) return usage(); + if( nums.len < 2 ) return usage(); + + // Bubble sort array of integers + sort(nums); + + // Print the same bracketed array-list format accepted as input. + nums.str().add('\n').write(1); + return 0; +}; diff --git a/docs/chapter31/examples/Capitalize.smp b/docs/chapter31/examples/Capitalize.smp new file mode 100644 index 00000000..00fdd481 --- /dev/null +++ b/docs/chapter31/examples/Capitalize.smp @@ -0,0 +1,23 @@ + +// An implementation of +// https://sampleprograms.io/projects/capitalize/ + +val main = { u8[~][] args -> + if( args# > 2 ) { + sys.io.p("Use quotes around multiple strings.\n"); + return 0; + } + if( args# != 2 || args[1]# == 0 ) { + sys.io.p("Usage: please provide a string\n"); + return 0; + } + + u8[~] input = args[1]; + var output = new u8[input# + 1]; + u32 len = input#; + sys.ary.copy(output,0,input,0,len); + output[0] = sys.char.toupper(input[0]); + output[input#] = '\n'; + sys.io.p(output); + return 0; +}; diff --git a/docs/chapter31/examples/Clock.smp b/docs/chapter31/examples/Clock.smp new file mode 100644 index 00000000..ecf0fae1 --- /dev/null +++ b/docs/chapter31/examples/Clock.smp @@ -0,0 +1,128 @@ +// Local clocks; click the button or press Space to switch. Q/Escape exits. +// Any argument runs ten frames, exercising both faces for smoke tests. +u32 GREEN = 0x62ff70, DIM = 0x123d20, BACKGROUND = 0x020b05; + +val main = { u8[~][] args -> + val gfx = sys.gfx; + val time = sys.time; + // The main Canvas + val screen = new sys.draw.Canvas(720,540); + if( gfx.open("Simple digital and analog clocks",screen.width,screen.height) != gfx.OK ) return 1; + + // Button to flip clock style + val button = new sys.draw.Button(220,454,280,52,"ANALOG CLOCK"); + val timers = new sys.time.Timers(); + u32 frame_id = timers.every(33); + if( frame_id == 0 ) { gfx.close(); return 1; } + + bool analog = false, held = false; + int frames = 0, status = gfx.OK; + + // Main event loop + while( status == gfx.OK ) { + // Did the user press space bar? + bool space = gfx.key(' '); + // Did the left mouse button go down? + bool down = gfx.button(gfx.LEFT); + // ANd did it go down over this button? + bool click = button.update(gfx.mouseX(),gfx.mouseY(),down); + // If clicked, flip the clock mode + if( click || (space && !held) ) analog = !analog; + held = space; + if( gfx.key('Q') ) break; + // Change button label + button.label = analog ? "DIGITAL CLOCK" : "ANALOG CLOCK"; + // If the next timer event is the every-33-msec event + if( timers.poll(time.monotonic()) == frame_id ) { + // Draw the clock screen and button + _draw(screen,time.now(),analog); + button.draw(screen,3,GREEN,BACKGROUND,DIM); + // Presne the canvas on screen + status = gfx.present(screen.pixels); + // Testing only: show 5 frames, flip to analog, show 5 more and exit + if( args# > 1 ) { + frames++; + if( frames == 5 ) analog = true; + if( frames == 10 ) break; + } + } + if( status != gfx.OK ) break; + // Stall until next timer event + status = gfx.wait(time.remaining(timers.nextDeadline(),time.monotonic())); + } + gfx.close(); + return status == gfx.ERROR ? 1 : 0; +}; + +val _draw = { !sys.draw.Canvas screen, i64 wall_ms, i64 analog -> + val d = sys.time.local(wall_ms); + screen.clear(BACKGROUND); + screen.text(266,24,"SIMPLE CLOCK",4,GREEN); + if( analog ) _analog(screen,d.hour,d.minute,d.second,d.millisecond); + else _digital(screen,d.hour,d.minute,d.second); + screen.text(298,412,"%(sys.time.formatLocal(wall_ms,sys.time.DATE))",4,GREEN); + screen.text(249,520,"SPACE SWITCH Q OR ESC QUIT",2,GREEN); +}; + +// a..g bits: top, upper-right, lower-right, bottom, lower-left, upper-left, +// middle. Faint unlit bars retain the old LED figure-eight silhouette. +val _digit = { !sys.draw.Canvas c, int x, int y, i64 digit -> + int bits = digit == 0 ? 0x3f : digit == 1 ? 0x06 : digit == 2 ? 0x5b : + digit == 3 ? 0x4f : digit == 4 ? 0x66 : digit == 5 ? 0x6d : + digit == 6 ? 0x7d : digit == 7 ? 0x07 : digit == 8 ? 0x7f : digit == 9 ? 0x6f : 0; + for( int i = 0; i < 7; i++ ) { + u32 color = (bits&(1<<i)) != 0 ? GREEN : DIM; + if( i == 0 || i == 3 || i == 6 ) { + int dy = i == 0 ? 0 : i == 3 ? 144 : 72; + c.line(x+14,y+dy,x+58,y+dy,10,color); + } else { + bool right = i == 1 || i == 2; + int dx = right ? 68 : 4, dy = i == 1 || i == 5 ? 12 : 84; + c.line(x+dx,y+dy,x+dx,y+dy+48,10,color); + } + } +}; + +val _digital = { !sys.draw.Canvas c, int hour, int minute, int second -> + _digit(c,68,166,hour/10); _digit(c,154,166,hour-hour/10*10); + _digit(c,284,166,minute/10); _digit(c,370,166,minute-minute/10*10); + _digit(c,500,166,second/10); _digit(c,586,166,second-second/10*10); + for( int i = 0; i < 2; i++ ) { + c.fillCircle(254+i*216,214,6,GREEN); + c.fillCircle(254+i*216,262,6,GREEN); + } + c.text(272,352,"LOCAL TIME 24H",3,GREEN); +}; +// Rational sine approximation, accurate enough for pixel clock geometry. +// Arguments are nonnegative turns; no additional native math dependency. +val _sin = { f64 turn -> + i64 whole = turn; turn -= whole; + bool negative = turn > 0.5; + f64 x = (negative ? turn-0.5 : turn)*6.283185307179586; + f64 product = x*(3.141592653589793-x); + f64 numerator = negative ? -16.0 : 16.0; + return numerator*product/(49.34802200544679-4*product); +}; +val _hand = { !sys.draw.Canvas c, f64 turn, int length, int thickness, u32 color -> + int x = 360+_sin(turn)*length, y = 232-_sin(turn+0.25)*length; + c.line(360,232,x,y,thickness,color); +}; +val _analog = { !sys.draw.Canvas c, int hour, int minute, int second, int millisecond -> + c.circle(360,232,154,3,GREEN); + for( int i = 0; i < 60; i++ ) { + f64 turn = i/60.0; + bool major = i-i/5*5 == 0; + int inner = major ? 133 : 143; + int x0 = 360+_sin(turn)*inner, y0 = 232-_sin(turn+0.25)*inner; + int x1 = 360+_sin(turn)*149, y1 = 232-_sin(turn+0.25)*149; + c.line(x0,y0,x1,y1,major ? 3 : 1,GREEN); + } + c.text(349,100,"12",2,GREEN); c.text(484,227,"3",2,GREEN); + c.text(357,354,"6",2,GREEN); c.text(230,227,"9",2,GREEN); + f64 seconds = second+millisecond/1000.0; + f64 minutes = minute+seconds/60.0; + _hand(c,(hour-hour/12*12+minutes/60.0)/12.0,78,8,GREEN); + _hand(c,minutes/60.0,112,5,GREEN); + _hand(c,seconds/60.0,128,2,0xc3ffcb); + c.fillCircle(360,232,6,GREEN); +}; diff --git a/docs/chapter31/examples/Dijkstra.smp b/docs/chapter31/examples/Dijkstra.smp new file mode 100644 index 00000000..6591b310 --- /dev/null +++ b/docs/chapter31/examples/Dijkstra.smp @@ -0,0 +1,71 @@ + +// An implementation of +// https://sampleprograms.io/projects/dijkstra/ + +val shortestPath = { !sys.aryi64 matrix, int nodes, int src, int dst -> + i64 infinity = sys.integer.MAX_I64; + var visited = new sys.adt.bitset(); + // All nodes are infinitely far from each other, except the starting point + var dist = new i64[nodes]{ int i -> i == src ? 0 : infinity; }; + + i64 result = -1; + int count = 0; + while( count < nodes && result < 0 ) { + int nearest = -1; + i64 best = infinity; + for( int node = 0; node < nodes; node++ ) + if( !visited.get(node) && dist[node] < best ) { + nearest = node; + best = dist[node]; + } + if( nearest < 0 ) count = nodes; + else { + if( nearest == dst ) return best; + visited.set(nearest); + for( int node = 0; node < nodes; node++ ) { + u32 index = nearest*nodes + node; + i64 weight = matrix.get(index); + if( weight > 0 && !visited.get(node) && best+weight < dist[node] ) + dist[node] = best+weight; + } + } + count++; + } + return result; +}; + +val usage = { -> + sys.io.p("Usage: please provide three inputs: a serialized matrix, a source node and a destination node\n"); + return 0; +}; + +val main = { u8[~][] args -> + // Exactly 3 args + if( args# != 4 ) return usage(); + // First is a serialized square matrix of positive numbers + var matrix = sys.aryi64.parseAryI64(args[1]); + if( !matrix ) return usage(); + if( !matrix.len ) return usage(); + // All numbers are positive + for( u32 i = 0; i < matrix.len; i++ ) + if( matrix.get(i) < 0 ) return usage(); + // Matrix is square + int nodes = 0; + while( nodes*nodes < matrix.len ) nodes++; + if( nodes*nodes != matrix.len ) + return usage(); // Not a square matrix + + // src,dst are values within the matrix + i64 src = sys.aryu8.atoi(args[2]); + i64 dst = sys.aryu8.atoi(args[3]); + if( src < 0 || src >= nodes || dst < 0 || dst >= nodes ) + return usage(); // Source and destination land within the graph + + int source = src, destination = dst; + i64 cost = shortestPath(matrix,nodes,source,destination); + if( cost >= 0 ) { + new sys.aryu8().num(cost).add('\n').write(1); + return 0; + } + return usage(); +}; diff --git a/docs/chapter31/examples/FileIO.smp b/docs/chapter31/examples/FileIO.smp new file mode 100644 index 00000000..59b3159e --- /dev/null +++ b/docs/chapter31/examples/FileIO.smp @@ -0,0 +1,52 @@ + +// An implementation of +// https://sampleprograms.io/projects/file-input-output/ + +val FILE = "output.txt"; + +val main = { u8[~][] args -> + var str = new sys.aryu8(); + str.addAry("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"); + str.addAry("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n"); + str.addAry("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n"); + str.addAry("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"); + + // Create + i32 fd = sys.io.create(FILE); + if( fd == -1 ) return _error(); + // Write + u32 len = str.len; + int wrote = sys.io.writeOff(fd,str.buf,0,len); + if( wrote == -1 ) return _error(); + if( wrote != str.len ) return _error2(-2); + // Close + if( sys.io.close(fd) != 0 ) return _error(); + + // Open + i32 fd2 = sys.io.open(FILE); + if( fd2 == -1 ) return _error(); + // Read + var got = sys.io.read(fd2); + if( !got ) return _error(); + if( got# != str.len ) return _error2(-3); + // Close + if( sys.io.close(fd2) != 0 ) return _error(); + + // Check contents are equal + if( sys.ary.cmp(got,0,str.buf,0,len) ) + return _error2(-4); + + // Remove + if( sys.io.remove(FILE) != 0 ) return _error(); + + sys.io.p("File I/O succeeded\n"); + return 0; +}; + +val _error2 = { i32 errno -> + sys.io.remove(FILE); + new sys.aryu8().addAry("Failed with error code ").num(errno).add('\n').write(sys.libc.STDERR); + sys.libc._exit(errno); +}; + +val _error = { -> _error2(sys.libc.errno()); }; diff --git a/docs/chapter31/examples/Graphics.smp b/docs/chapter31/examples/Graphics.smp new file mode 100644 index 00000000..8b251fa2 --- /dev/null +++ b/docs/chapter31/examples/Graphics.smp @@ -0,0 +1,17 @@ +// Optional graphics smoke test: pixels are drawn entirely in Simple. +// Pass any argument for a short, self-closing test; otherwise close or press Esc. +val main = { u8[~][] args -> + if( sys.gfx.open("Simple pixels",320,240) != sys.gfx.OK ) return 1; + val pixels = new u32[320*240]; + for( int y = 0; y < 240; y++ ) + for( int x = 0; x < 320; x++ ) + pixels[y*320+x] = (x*255/319 << 16) | (y*255/239 << 8) | 64; + int result = sys.gfx.present(pixels); + int frames = 0; + while( result == sys.gfx.OK ) { + result = sys.gfx.poll(true); + if( args# > 1 && ++frames == 5 ) break; + } + sys.gfx.close(); + return result == sys.gfx.ERROR ? 1 : 0; +}; diff --git a/docs/chapter31/examples/README.md b/docs/chapter31/examples/README.md new file mode 100644 index 00000000..20b644cf --- /dev/null +++ b/docs/chapter31/examples/README.md @@ -0,0 +1,108 @@ +# Runnable examples + +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. +Optional [background audio](../audio.md) supplies soft move and win/loss cues. + +The [digital and analog clock](Clock.smp) demonstrates the [time library](../time.md), +green seven-segment digits, smooth clock hands, and a reusable software button. +Run `make run-clock CTAGS=`; click the button or press Space to switch faces, +and Q/Escape to exit. Both faces show system-local time. +`make clock-example CTAGS=` builds it without starting a window. + +The [Breakout game](Breakout.smp) has 24 columns and 8 rows, with colors and +point values by depth: 1 at the bottom through 8 at the top, 864 points total. +Clear the wall before losing all three balls. Mouse movement or the arrow keys +move the paddle; Space/left click serves, P pauses, R restarts, Q/Escape exits. +Run `make run-breakout CTAGS=` or build only with `make breakout CTAGS=`. +The familiar paddle, serve, and limited-ball rules follow +[Atari's Breakout instructions](https://atari.com/pages/breakout). + +The deepest row reached with the current ball sets the base speed: 320 pixels/second plus 35 for +each additional depth. After touching the back wall, hitting the top of a brick +activates a **one-time 1.65x speed boost**; side/front hits do not. Returning to +the paddle disarms that trip's back-wall opportunity. Losing a ball resets depth +and boost, so each new ball starts at 320 pixels/second and can earn its own +back-wall boost. Edge hits on the paddle give a wider +rebound than center hits. Clear all bricks to win; no new wall replaces them. + +Physics uses fixed 8 ms ticks with movement split into steps of at most two +pixels, including at boosted speed. Rendering runs roughly every 16 ms. The +brick and sound tables demonstrate array initializers; the short bounce tones +use read-only PCM arrays made by index functions. Brick boops rise in pitch with +row depth, from about 333 Hz at the bottom to 800 Hz at the top. Audio is optional. Native +tests in `BreakoutTest` exercise collisions, scoring, lives, pause, paddle angles, +the boost, and clearing the whole wall without a display or audio device. + +From `chapter31`, build the tic-tac-toe executable (without starting it): + +```sh +make lib tictactoe CTAGS= +``` + +Run the Windows build from PowerShell: + +```powershell +.\build\examples\x86_64_v2_win64\TicTacToe.exe +``` + +Or from Cygwin: + +```sh +./build/examples/x86_64_v2_win64/TicTacToe.exe +``` + +The Windows executable uses Cygwin's runtime; its `bin` directory must be on +`PATH` (as it is when building with Cygwin GCC). + +For a native Linux build, run these commands from this same `chapter31` +directory in WSL. Separate Java and native output directories let the Windows +and Linux builds coexist: + +```sh +make lib tictactoe CLZDIR=build/classes-linux CTAGS= +./build/examples/x86_64_v2_SystemV/TicTacToe +``` + +From PowerShell, prefix either command with `wsl -d Ubuntu-24.04 --`. +On a standalone Linux checkout, `CLZDIR` can be omitted. +The compiler's `--link` option produces an executable without running it; +`--norun` continues to produce object files only. + +`TicTacToe.smp` currently prints the welcome, square numbering, and empty board, +then waits for a move. Press **1–9** without Enter to place X, or +**q**, Escape, Ctrl-C, or Ctrl-D to quit. Enter is silently ignored; other +invalid keys and occupied squares prompt again without taking a turn. The board +is printed after each player and CPU move; only accepted move digits are echoed. +Play continues until someone wins or the board is +full. The CPU takes an immediate win, blocks an immediate loss, then prefers +the center, corners, and edges. + +Other examples are Hello World (`A_helloWorld.smp`), `BubbleSort.smp`, +`Capitalize.smp`, `Dijkstra.smp`, and `FileIO.smp`. The latter creates, checks, +and removes `output.txt` in the working directory. + +## Single-keystroke input + +`sys.io.getch()` blocks for one byte without terminal editing or echo. It returns +0–255 for a byte, or -1 for EOF or an error. On -1, `sys.libc.errno()` is +`sys.libc.EOF` (-1) for end-of-input, otherwise the OS error code. +Terminal Ctrl-C and Ctrl-D are bytes 3 +and 4, so the application decides how to handle them. Unicode and function keys +may produce multiple bytes; this is an ASCII-oriented byte API, not a key-event +decoder. + +The small C runtime adapter uses [POSIX terminal modes](https://man7.org/linux/man-pages/man3/termios.3.html), +including Cygwin on Windows. It restores the saved mode before returning, +including after an interrupted read. Files and pipes use an ordinary one-byte +read. `make test CLASS=TerminalInputTest` runs the Java regression tests for +redirected input, errors, prompt output, Enter, and Escape. Check terminal +editing, echo, and mode restoration by running the executable in a terminal. +`make test CLASS=TicTacToeTest` checks complete games, all eight winning lines, +draws, occupied squares, and CPU blocking. diff --git a/docs/chapter31/examples/TicTacToe.smp b/docs/chapter31/examples/TicTacToe.smp new file mode 100644 index 00000000..ee4e3182 --- /dev/null +++ b/docs/chapter31/examples/TicTacToe.smp @@ -0,0 +1,132 @@ +// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom. +val main = { u8[~][] args -> + sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O. You go first.\n"); + sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n"); + sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n"); + val board = new u8[9]{ int i -> ' '; }; // Initialize the board + // Draw empty board + _draw(board); + + while( true ) { + sys.io.p("Your move (1-9): "); + // Valid user move 1-9, or 0 for quitting + int square = _validMove(board) - 1; + if( square < 0 ) return 0; + // Set user choice; print board + board[square] = 'X'; + sys.io.p("\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + // Compute CPU move + int reply = _cpu(board); + // Set CPU choice; print board + board[reply] = 'O'; + sys.io.p("CPU plays %(reply+1).\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + } +}; + +// Return with a valid user move 1-9, or 0 to exit +val _validMove = { u8[~] board -> + while( true ) { + int key = sys.io.getch(); + if( key == -1 && sys.libc.errno() != sys.libc.EOF ) { + sys.io.err("\nUnable to read the keyboard.\n"); + return 0; + } + // Various quitting conditions + if( key == -1 || key == 'q' || key == 'Q' || key == 27 || key == 3 || key == 4 ) { + sys.io.p("\nGoodbye!\n"); + return 0; + } + // Ignore CR/LF + if( key == '\r' || key == '\n' ) continue; + + // Valid choice + if( key < '1' || key > '9' ) { + sys.io.p("\nChoose a square from 1 to 9: "); + continue; + } + if( board[key-'1'] != ' ' ) { + sys.io.p("\nThat square is occupied. Your move (1-9): "); + continue; + } + sys.io.p("%c(key)"); + return key-'0'; + } +}; + +// Return the winning mark, or zero. +val _winner = { 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 board[row]; + if( board[i] != ' ' && board[i] == board[i+3] && board[i] == board[i+6] ) + return board[i]; + } + if( board[4] != ' ' ) { + if( board[0] == board[4] && board[4] == board[8] ) return board[4]; + if( board[2] == board[4] && board[4] == board[6] ) return board[4]; + } + return 0; +}; + +val _finished = { u8[~] board -> + int winner = _winner(board); + if( winner == 'X' ) { sys.io.p("You win!\n"); return true; } + if( winner == 'O' ) { sys.io.p("CPU wins!\n"); return true; } + for( int i = 0; i < board#; i++ ) + if( board[i] == ' ' ) return false; + sys.io.p("It's a draw!\n"); + return true; +}; + +// Try each empty square, restoring it before returning. +val _threat = { u8[] board, u8 mark -> + for( int i = 0; i < board#; 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 < board#; i += 2 ) + if( board[i] == ' ' ) return i; + // Take whatever is available + for( int i = 0; i < board#; i++ ) + if( board[i] == ' ' ) return i; + return -1; +}; + +val _draw = { u8[~] board -> + val out = new sys.aryu8(); + for( int row = 0; row < 3; row++ ) { + for( int col = 0; col < 3; col++ ) { + out.add(' ').add(board[row*3+col]).add(' '); + if( col < 2 ) out.add('|'); + } + out.add('\n'); + if( row < 2 ) out.addAry("---+---+---\n"); + } + out.write(sys.libc.STDOUT); +}; diff --git a/docs/chapter31/examples/TicTacToeGraphical.smp b/docs/chapter31/examples/TicTacToeGraphical.smp new file mode 100644 index 00000000..d3d575a7 --- /dev/null +++ b/docs/chapter31/examples/TicTacToeGraphical.smp @@ -0,0 +1,168 @@ +// 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; + sys.audio.open(); // Audio is optional; failure leaves the game silent. + _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.wait(17); + 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 ) { sys.audio.stop(); _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) ) { + sys.audio.play(_sound(board)); + 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.audio.close(); + sys.gfx.close(); + return result == sys.gfx.ERROR ? 1 : 0; +}; + +// Called only for an accepted move; invalid input never requests a sound. +val _sound = { u8[~] board -> + int winner = _winner(board); + return winner == 'X' ? sys.audio.WIN : winner == 'O' ? sys.audio.LOSE : sys.audio.BOOP; +}; + +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); +}; |
