X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/3bada052e3dcd53c23e5393165581f880b346e27..HEAD:/devel.but diff --git a/devel.but b/devel.but index 4783b06..970a73a 100644 --- a/devel.but +++ b/devel.but @@ -856,7 +856,7 @@ producing new \c{game_state}s. \S{backend-interpret-move} \cw{interpret_move()} \c char *(*interpret_move)(game_state *state, game_ui *ui, -\c game_drawstate *ds, +\c const game_drawstate *ds, \c int x, int y, int button); This function receives user input and processes it. Its input @@ -868,6 +868,11 @@ indicating an arrow or function key or a mouse event; when coordinates of the mouse pointer relative to the top left of the puzzle's drawing area. +(The pointer to the \c{game_drawstate} is marked \c{const}, because +\c{interpret_move} should not write to it. The normal use of that +pointer will be to read the game's tile size parameter in order to +divide mouse coordinates by it.) + \cw{interpret_move()} may return in three different ways: \b Returning \cw{NULL} indicates that no action whatsoever occurred @@ -1224,6 +1229,34 @@ a mine from the colour it uses when you complete the game. In order to achieve this, its \cw{flash_length()} function has to store a flag in the \c{game_ui} to indicate which flash type is required.) +\S{backend-status} \cw{status()} + +\c int (*status)(game_state *state); + +This function returns a status value indicating whether the current +game is still in play, or has been won, or has been conclusively lost. +The mid-end uses this to implement \cw{midend_status()} +(\k{midend-status}). + +The return value should be +1 if the game has been successfully +solved. If the game has been lost in a situation where further play is +unlikely, the return value should be -1. If neither is true (so play +is still ongoing), return zero. + +Front ends may wish to use a non-zero status as a cue to proactively +offer the option of starting a new game. Therefore, back ends should +not return -1 if the game has been \e{technically} lost but undoing +and continuing is still a realistic possibility. + +(For instance, games with hidden information such as Guess or Mines +might well return a non-zero status whenever they reveal the solution, +whether or not the player guessed it correctly, on the grounds that a +player would be unlikely to hide the solution and continue playing +after the answer was spoiled. On the other hand, games where you can +merely get into a dead end such as Same Game or Inertia might choose +to return 0 in that situation, on the grounds that the player would +quite likely press Undo and carry on playing.) + \S{backend-redraw} \cw{redraw()} \c void (*redraw)(drawing *dr, game_drawstate *ds, @@ -1433,7 +1466,7 @@ them internally. (There are currently no puzzles which have a one-line ASCII representation, so there's no precedent yet for whether that should come with a newline or not.) -\S{backend-wants-statusbar} \cw{wants_statusbar()} +\S{backend-wants-statusbar} \cw{wants_statusbar} \c int wants_statusbar; @@ -1641,6 +1674,43 @@ end does any drawing it informs the front end of which parts of the window it has accessed, and hence which parts need repainting. This is done by calling \cw{draw_update()} (\k{drawing-draw-update}). +Persistence of old drawing is convenient. However, a puzzle should +be very careful about how it updates its drawing area. The problem +is that some front ends do anti-aliased drawing: rather than simply +choosing between leaving each pixel untouched or painting it a +specified colour, an antialiased drawing function will \e{blend} the +original and new colours in pixels at a figure's boundary according +to the proportion of the pixel occupied by the figure (probably +modified by some heuristic fudge factors). All of this produces a +smoother appearance for curves and diagonal lines. + +An unfortunate effect of drawing an anti-aliased figure repeatedly +is that the pixels around the figure's boundary come steadily more +saturated with \q{ink} and the boundary appears to \q{spread out}. +Worse, redrawing a figure in a different colour won't fully paint +over the old boundary pixels, so the end result is a rather ugly +smudge. + +A good strategy to avoid unpleasant anti-aliasing artifacts is to +identify a number of rectangular areas which need to be redrawn, +clear them to the background colour, and then redraw their contents +from scratch, being careful all the while not to stray beyond the +boundaries of the original rectangles. The \cw{clip()} function +(\k{drawing-clip}) comes in very handy here. Games based on a square +grid can often do this fairly easily. Other games may need to be +somewhat more careful. For example, Loopy's redraw function first +identifies portions of the display which need to be updated. Then, +if the changes are fairly well localised, it clears and redraws a +rectangle containing each changed area. Otherwise, it gives up and +redraws the entire grid from scratch. + +It is possible to avoid clearing to background and redrawing from +scratch if one is very careful about which drawing functions one +uses: if a function is documented as not anti-aliasing under some +circumstances, you can rely on each pixel in a drawing either being +left entirely alone or being set to the requested colour, with no +blending being performed. + In the following sections I first discuss the drawing API as seen by the back end, and then the \e{almost} identical function-pointer form seen by the front end. @@ -1715,8 +1785,9 @@ the back end function \cw{colours()} (\k{backend-colours}). Some platforms may perform anti-aliasing on this function. Therefore, do not assume that you can erase a line by drawing the -same line over it in the background colour; anti-aliasing might -lead to perceptible ghost artefacts around the vanished line. +same line over it in the background colour; anti-aliasing might lead +to perceptible ghost artefacts around the vanished line. Horizontal +and vertical lines, however, are pixel-perfect and not anti-aliased. This function may be used for both drawing and printing. @@ -1753,7 +1824,8 @@ same polygon over it in the background colour. Also, be prepared for the polygon to extend a pixel beyond its obvious bounding box as a result of this; if you really need it not to do this to avoid interfering with other delicate graphics, you should probably use -\cw{clip()} (\k{drawing-clip}). +\cw{clip()} (\k{drawing-clip}). You can rely on horizontal and +vertical lines not being anti-aliased. This function may be used for both drawing and printing. @@ -1795,6 +1867,32 @@ interfering with other delicate graphics, you should probably use This function may be used for both drawing and printing. +\S{drawing-draw-thick-line} \cw{draw_thick_line()} + +\c void draw_thick_line(drawing *dr, float thickness, +\c float x1, float y1, float x2, float y2, +\c int colour) + +Draws a line in the puzzle window, giving control over the line's +thickness. + +\c{x1} and \c{y1} give the coordinates of one end of the line. +\c{x2} and \c{y2} give the coordinates of the other end. +\c{thickness} gives the thickness of the line, in pixels. + +Note that the coordinates and thickness are floating-point: the +continuous coordinate system is in effect here. It's important to +be able to address points with better-than-pixel precision in this +case, because one can't otherwise properly express the endpoints of +lines with both odd and even thicknesses. + +Some platforms may perform anti-aliasing on this function. The +precise pixels affected by a thick-line drawing operation may vary +between platforms, and no particular guarantees are provided. +Indeed, even horizontal or vertical lines may be anti-aliased. + +This function may be used for both drawing and printing. + \S{drawing-draw-text} \cw{draw_text()} \c void draw_text(drawing *dr, int x, int y, int fonttype, @@ -1848,6 +1946,54 @@ the back end function \cw{colours()} (\k{backend-colours}). This function may be used for both drawing and printing. +The character set used to encode the text passed to this function is +specified \e{by the drawing object}, although it must be a superset +of ASCII. If a puzzle wants to display text that is not contained in +ASCII, it should use the \cw{text_fallback()} function +(\k{drawing-text-fallback}) to query the drawing object for an +appropriate representation of the characters it wants. + +\S{drawing-text-fallback} \cw{text_fallback()} + +\c char *text_fallback(drawing *dr, const char *const *strings, +\c int nstrings); + +This function is used to request a translation of UTF-8 text into +whatever character encoding is expected by the drawing object's +implementation of \cw{draw_text()}. + +The input is a list of strings encoded in UTF-8: \cw{nstrings} gives +the number of strings in the list, and \cw{strings[0]}, +\cw{strings[1]}, ..., \cw{strings[nstrings-1]} are the strings +themselves. + +The returned string (which is dynamically allocated and must be +freed when finished with) is derived from the first string in the +list that the drawing object expects to be able to display reliably; +it will consist of that string translated into the character set +expected by \cw{draw_text()}. + +Drawing implementations are not required to handle anything outside +ASCII, but are permitted to assume that \e{some} string will be +successfully translated. So every call to this function must include +a string somewhere in the list (presumably the last element) which +consists of nothing but ASCII, to be used by any front end which +cannot handle anything else. + +For example, if a puzzle wished to display a string including a +multiplication sign (U+00D7 in Unicode, represented by the bytes C3 +97 in UTF-8), it might do something like this: + +\c static const char *const times_signs[] = { "\xC3\x97", "x" }; +\c char *times_sign = text_fallback(dr, times_signs, 2); +\c sprintf(buffer, "%d%s%d", width, times_sign, height); +\c draw_text(dr, x, y, font, size, align, colour, buffer); +\c sfree(buffer); + +which would draw a string with a times sign in the middle on +platforms that support it, and fall back to a simple ASCII \cq{x} +where there was no alternative. + \S{drawing-clip} \cw{clip()} \c void clip(drawing *dr, int x, int y, int w, int h); @@ -1863,7 +2009,9 @@ inclusive. (These are exactly the same semantics as After this call, no drawing operation will affect anything outside the specified rectangle. The effect can be reversed by calling -\cw{unclip()} (\k{drawing-unclip}). +\cw{unclip()} (\k{drawing-unclip}). The clipping rectangle is +pixel-perfect: pixels within the rectangle are affected as usual by +drawing functions; pixels outside are completely untouched. Back ends should not assume that a clipping rectangle will be automatically cleared up by the front end if it's left lying around; @@ -2217,6 +2365,20 @@ function; see \k{drawing-draw-polygon}. This function behaves exactly like the back end \cw{draw_circle()} function; see \k{drawing-draw-circle}. +\S{drawingapi-draw-thick-line} \cw{draw_thick_line()} + +\c void draw_thick_line(drawing *dr, float thickness, +\c float x1, float y1, float x2, float y2, +\c int colour) + +This function behaves exactly like the back end +\cw{draw_thick_line()} function; see \k{drawing-draw-thick-line}. + +An implementation of this API which doesn't provide high-quality +rendering of thick lines is permitted to define this function +pointer to be \cw{NULL}. The middleware in \cw{drawing.c} will notice +and provide a low-quality alternative using \cw{draw_polygon()}. + \S{drawingapi-draw-update} \cw{draw_update()} \c void (*draw_update)(void *handle, int x, int y, int w, int h); @@ -2442,6 +2604,19 @@ Implementations of this API which do not provide printing services may define this function pointer to be \cw{NULL}; it will never be called unless printing is attempted. +\S{drawingapi-text-fallback} \cw{text_fallback()} + +\c char *(*text_fallback)(void *handle, const char *const *strings, +\c int nstrings); + +This function behaves exactly like the back end \cw{text_fallback()} +function; see \k{drawing-text-fallback}. + +Implementations of this API which do not support any characters +outside ASCII may define this function pointer to be \cw{NULL}, in +which case the central code in \cw{drawing.c} will provide a default +implementation. + \H{drawingapi-frontend} The drawing API as called by the front end There are a small number of functions provided in \cw{drawing.c} @@ -2544,14 +2719,12 @@ without closing the window...) Frees a mid-end structure and all its associated data. -\H{midend-tilesize} +\H{midend-tilesize} \cw{midend_tilesize()} \c int midend_tilesize(midend *me); Returns the \cq{tilesize} parameter being used to display the -current puzzle. - -\k{backend-preferred-tilesize} +current puzzle (\k{backend-preferred-tilesize}). \H{midend-set-params} \cw{midend_set_params()} @@ -2636,6 +2809,9 @@ to use scroll bars for large puzzles), you can pass dimensions of \cw{INT_MAX} as input to this function. You should probably not do that \e{and} set the \c{user_size} flag, though! +The midend relies on the frontend calling \cw{midend_new_game()} +(\k{midend-new-game}) before calling \cw{midend_size()}. + \H{midend-new-game} \cw{midend_new_game()} \c void midend_new_game(midend *me); @@ -2672,7 +2848,8 @@ undo list (so that an accidental restart can be undone). This function automatically causes a redraw, i.e. the front end can expect its drawing API to be called from \e{within} a call to this -function. +function. Some back ends require that \cw{midend_size()} +(\k{midend-size}) is called before \cw{midend_restart_game()}. \H{midend-force-redraw} \cw{midend_force_redraw()} @@ -2683,7 +2860,8 @@ discarding the current \c{game_drawstate} and creating a new one from scratch before calling the game's \cw{redraw()} function. The front end can expect its drawing API to be called from within a -call to this function. +call to this function. Some back ends require that \cw{midend_size()} +(\k{midend-size}) is called before \cw{midend_force_redraw()}. \H{midend-redraw} \cw{midend_redraw()} @@ -2694,7 +2872,8 @@ calling the game's \cw{redraw()} function. (That is, the only things redrawn will be things that have changed since the last redraw.) The front end can expect its drawing API to be called from within a -call to this function. +call to this function. Some back ends require that \cw{midend_size()} +(\k{midend-size}) is called before \cw{midend_redraw()}. \H{midend-process-key} \cw{midend_process_key()} @@ -2952,7 +3131,40 @@ user. The front end can expect its drawing API and/or \cw{activate_timer()} to be called from within a call to this -function. +function. Some back ends require that \cw{midend_size()} +(\k{midend-size}) is called before \cw{midend_solve()}. + +\H{midend-status} \cw{midend_status()} + +\c int midend_status(midend *me); + +This function returns +1 if the midend is currently displaying a game +in a solved state, -1 if the game is in a permanently lost state, or 0 +otherwise. This function just calls the back end's \cw{status()} +function. Front ends may wish to use this as a cue to proactively +offer the option of starting a new game. + +(See \k{backend-status} for more detail about the back end's +\cw{status()} function and discussion of what should count as which +status code.) + +\H{midend-can-undo} \cw{midend_can_undo()} + +\c int midend_can_undo(midend *me); + +Returns \cw{TRUE} if the midend is currently in a state where the undo +operation is meaningful (i.e. at least one position exists on the undo +chain before the present one). Front ends may wish to use this to +visually activate and deactivate an undo button. + +\H{midend-can-redo} \cw{midend_can_redo()} + +\c int midend_can_redo(midend *me); + +Returns \cw{TRUE} if the midend is currently in a state where the redo +operation is meaningful (i.e. at least one position exists on the redo +chain after the present one). Front ends may wish to use this to +visually activate and deactivate a redo button. \H{midend-serialise} \cw{midend_serialise()}