Refactor
This commit is contained in:
parent
043378cfbb
commit
31a3ee213e
|
|
@ -29,8 +29,8 @@ void image_draw_point(image_data_t image, int16_t x, int16_t y, image_color_t co
|
||||||
void image_draw_hline(image_data_t image, int16_t x1, int16_t x2, int16_t y, image_color_t color);
|
void image_draw_hline(image_data_t image, int16_t x1, int16_t x2, int16_t y, image_color_t color);
|
||||||
void image_draw_vline(image_data_t image, int16_t x, int16_t y1, int16_t y2, image_color_t color);
|
void image_draw_vline(image_data_t image, int16_t x, int16_t y1, int16_t y2, image_color_t color);
|
||||||
void image_draw_line(image_data_t image, int16_t x1, int16_t y1, int16_t x2, int16_t y2, image_color_t color);
|
void image_draw_line(image_data_t image, int16_t x1, int16_t y1, int16_t x2, int16_t y2, image_color_t color);
|
||||||
void image_draw_rect(image_data_t image, int16_t x, int16_t y, uint16_t w, uint16_t h, image_color_t color);
|
void image_draw_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color);
|
||||||
void image_fill_rect(image_data_t image, int16_t x, int16_t y, uint16_t w, uint16_t h, image_color_t color);
|
void image_fill_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
#include <graphics.h>
|
#include <graphics.h>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
static inline int max(int a, int b) {
|
static inline int32_t max(int32_t a, int32_t b) {
|
||||||
return a > b ? a : b;
|
return a > b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int min(int a, int b) {
|
static inline int32_t min(int32_t a, int32_t b) {
|
||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ void image_free(image_data_t image) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void set_point(image_data_t image, uint16_t x, uint16_t y, image_color_t color) {
|
static inline void set_point(image_data_t image, int16_t x, int16_t y, image_color_t color) {
|
||||||
image.pixels[x + y * image.width] = color;
|
image.pixels[x + y * image.width] = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,15 +45,15 @@ void image_draw_hline(image_data_t image, int16_t x1, int16_t x2, int16_t y, ima
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (x1 > x2) {
|
if (x1 > x2) {
|
||||||
uint16_t temp = x1;
|
int16_t temp = x1;
|
||||||
x1 = x2;
|
x1 = x2;
|
||||||
x2 = temp;
|
x2 = temp;
|
||||||
}
|
}
|
||||||
if (x1 > image.width || x2 < 0) {
|
if (x1 > image.width || x2 < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
x1 = max(0, x1);
|
x1 = (int16_t)max(0, x1);
|
||||||
x2 = min(x2, image.width);
|
x2 = (int16_t)min(x2, image.width);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
set_point(image, x1++, y, color);
|
set_point(image, x1++, y, color);
|
||||||
|
|
@ -64,15 +65,15 @@ void image_draw_vline(image_data_t image, int16_t x, int16_t y1, int16_t y2, ima
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (y1 > y2) {
|
if (y1 > y2) {
|
||||||
uint16_t temp = y1;
|
int16_t temp = y1;
|
||||||
y1 = y2;
|
y1 = y2;
|
||||||
y2 = temp;
|
y2 = temp;
|
||||||
}
|
}
|
||||||
if (y1 > image.width || y2 < 0) {
|
if (y1 > image.width || y2 < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
y1 = max(0, y1);
|
y1 = (int16_t)max(0, y1);
|
||||||
y2 = min(y2, image.height);
|
y2 = (int16_t)min(y2, image.height);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
set_point(image, x, y1++, color);
|
set_point(image, x, y1++, color);
|
||||||
|
|
@ -110,13 +111,13 @@ void image_draw_line(image_data_t image, int16_t x1, int16_t y1, int16_t x2, int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void image_draw_rect(image_data_t image, int16_t x, int16_t y, uint16_t w, uint16_t h, image_color_t color) {
|
void image_draw_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color) {
|
||||||
if (w == 0 || h == 0) {
|
if (w <= 0 || h <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
w = min(image.width - x, w);
|
w = (int16_t)min(image.width - x, w);
|
||||||
h = min(image.height - y, h);
|
h = (int16_t)min(image.height - y, h);
|
||||||
|
|
||||||
image_draw_hline(image, x, x + w - 1, y, color);
|
image_draw_hline(image, x, x + w - 1, y, color);
|
||||||
image_draw_hline(image, x, x + w, y + h - 1, color);
|
image_draw_hline(image, x, x + w, y + h - 1, color);
|
||||||
|
|
@ -125,14 +126,14 @@ void image_draw_rect(image_data_t image, int16_t x, int16_t y, uint16_t w, uint1
|
||||||
image_draw_vline(image, x + w - 1, y, y + h - 1, color);
|
image_draw_vline(image, x + w - 1, y, y + h - 1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void image_fill_rect(image_data_t image, int16_t x, int16_t y, uint16_t w, uint16_t h, image_color_t color) {
|
void image_fill_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color) {
|
||||||
if (w == 0 || h == 0) {
|
if (w <= 0 || h <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
x = max(0, x);
|
x = (int16_t)max(0, x);
|
||||||
y = max(0, y);
|
y = (int16_t)max(0, y);
|
||||||
w = min(image.width - x, w);
|
w = (int16_t)min(image.width - x, w);
|
||||||
h = min(image.height - y, h);
|
h = (int16_t)min(image.height - y, h);
|
||||||
|
|
||||||
while (h--) {
|
while (h--) {
|
||||||
image_draw_hline(image, x, x + w, y++, color);
|
image_draw_hline(image, x, x + w, y++, color);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ await fs.mkdir(outDir, { recursive: true });
|
||||||
const publish = process.env.PUBLISH_LOCATION;
|
const publish = process.env.PUBLISH_LOCATION;
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const itch = args.includes('--itch');
|
const local = args.includes('--local');
|
||||||
let game = args.find(a => !a.startsWith('-')) ?? '';
|
let game = args.find(a => !a.startsWith('-')) ?? '';
|
||||||
|
|
||||||
while (!await isGame(game)) {
|
while (!await isGame(game)) {
|
||||||
|
|
@ -22,7 +22,7 @@ while (!await isGame(game)) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(`Building ${game}...`);
|
console.log(`Building ${game}...`);
|
||||||
const html = await buildHTML(game, { production: true, itch });
|
const html = await buildHTML(game, { production: true, local });
|
||||||
|
|
||||||
if (!html) {
|
if (!html) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
@ -30,7 +30,7 @@ if (!html) {
|
||||||
const filePath = path.resolve(outDir, `${game}.html`);
|
const filePath = path.resolve(outDir, `${game}.html`);
|
||||||
await Bun.write(filePath, html);
|
await Bun.write(filePath, html);
|
||||||
|
|
||||||
if (publish && !itch) {
|
if (publish && !local) {
|
||||||
console.log(`Publishing ${game}...`);
|
console.log(`Publishing ${game}...`);
|
||||||
const result = await $`scp "${filePath}" "${publish}${game}.html"`;
|
const result = await $`scp "${filePath}" "${publish}${game}.html"`;
|
||||||
if (result.exitCode === 0) {
|
if (result.exitCode === 0) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { BunFile } from 'bun';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { minify } from 'html-minifier';
|
import { minify } from 'html-minifier';
|
||||||
import UglifyJS from 'uglify-js';
|
import UglifyJS from 'uglify-js';
|
||||||
|
|
@ -10,17 +11,27 @@ import filePlugin from './filePlugin';
|
||||||
|
|
||||||
import { getGames } from './isGame';
|
import { getGames } from './isGame';
|
||||||
|
|
||||||
|
const b64 = async (file: string | BunFile) => (
|
||||||
|
typeof file === 'string'
|
||||||
|
? Buffer.from(file)
|
||||||
|
: Buffer.from(await file.arrayBuffer())
|
||||||
|
).toString('base64');
|
||||||
|
|
||||||
interface Args {
|
interface Args {
|
||||||
production?: boolean;
|
production?: boolean;
|
||||||
portable?: boolean;
|
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
itch?: boolean;
|
local?: boolean;
|
||||||
}
|
}
|
||||||
export async function buildHTML(game: string, { production = false, portable = false, mobile = false, itch = false }: Args = {}) {
|
export async function buildHTML(game: string, { production = false, mobile = false, local = false }: Args = {}) {
|
||||||
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', itch ? 'index-itch.html' : 'index.html')).text();
|
const assetsDir = path.resolve(import.meta.dir, 'assets');
|
||||||
|
const srcDir = path.resolve(import.meta.dir, '..', 'src');
|
||||||
|
const gameDir = path.resolve(srcDir, 'games', game);
|
||||||
|
const gameAssetsDir = path.resolve(gameDir, 'assets');
|
||||||
|
|
||||||
|
const html = await Bun.file(path.resolve(assetsDir, local ? 'index-local.html' : 'index.html')).text();
|
||||||
const bundle = await Bun.build({
|
const bundle = await Bun.build({
|
||||||
outdir: '/tmp',
|
outdir: '/tmp',
|
||||||
entrypoints: [path.resolve(import.meta.dir, '..', 'src', 'index.ts')],
|
entrypoints: [path.resolve(srcDir, 'index.ts')],
|
||||||
sourcemap: production ? 'none' : 'inline',
|
sourcemap: production ? 'none' : 'inline',
|
||||||
define: {
|
define: {
|
||||||
global: 'window',
|
global: 'window',
|
||||||
|
|
@ -31,7 +42,7 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
imagePlugin,
|
imagePlugin,
|
||||||
audioPlugin,
|
audioPlugin,
|
||||||
fontPlugin,
|
fontPlugin,
|
||||||
wasmPlugin({ production, portable }),
|
wasmPlugin({ production }),
|
||||||
filePlugin,
|
filePlugin,
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -42,24 +53,24 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
console.error('No entry point found:', bundle.outputs);
|
console.error('No entry point found:', bundle.outputs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const iconFile = Bun.file(path.resolve(import.meta.dir, '..', 'src', 'games', game, 'assets', 'favicon.ico'));
|
const iconFile = Bun.file(path.resolve(gameAssetsDir, 'favicon.ico'));
|
||||||
let icon = '';
|
let icon = '';
|
||||||
if (await iconFile.exists()) {
|
if (await iconFile.exists()) {
|
||||||
icon = `<link rel="icon" href="data:;base64,${Buffer.from(await iconFile.arrayBuffer()).toString('base64')}" />`;
|
icon = `<link rel="icon" href="data:;base64,${await b64(iconFile)}" />`;
|
||||||
}
|
}
|
||||||
let style = '';
|
let style = '';
|
||||||
let styleFile = bundle.outputs.find(a => a.kind === 'asset' && a.path.endsWith('.css'));
|
let styleFiles = bundle.outputs.filter(a => a.kind === 'asset' && a.path.endsWith('.css'));
|
||||||
if (styleFile) {
|
for (let styleFile of styleFiles) {
|
||||||
style = await styleFile.text();
|
style += await styleFile.text();
|
||||||
}
|
}
|
||||||
const title = game[0].toUpperCase() + game.slice(1).toLowerCase();
|
const title = game[0].toUpperCase() + game.slice(1).toLowerCase();
|
||||||
let pwaIconFile = Bun.file(path.resolve(import.meta.dir, '..', 'src', 'games', game, 'assets', 'pwa_icon.png'));
|
let pwaIconFile = Bun.file(path.resolve(gameAssetsDir, 'pwa_icon.png'));
|
||||||
if (!await pwaIconFile.exists()) {
|
if (!await pwaIconFile.exists()) {
|
||||||
pwaIconFile = Bun.file(path.resolve(import.meta.dir, 'assets', 'pwa_icon.png'));
|
pwaIconFile = Bun.file(path.resolve(assetsDir, 'pwa_icon.png'));
|
||||||
}
|
}
|
||||||
let manifest = '';
|
let manifest = '';
|
||||||
if (production) {
|
if (production && !local) {
|
||||||
const pwaIcon = `data:;base64,${Buffer.from(await pwaIconFile.arrayBuffer()).toString('base64')}`;
|
const pwaIcon = `data:;base64,${await b64(pwaIconFile)}`;
|
||||||
const publishURL = process.env.PUBLISH_URL ? `${process.env.PUBLISH_URL}${game}` : '.';
|
const publishURL = process.env.PUBLISH_URL ? `${process.env.PUBLISH_URL}${game}` : '.';
|
||||||
const manifestJSON = JSON.stringify({
|
const manifestJSON = JSON.stringify({
|
||||||
name: title,
|
name: title,
|
||||||
|
|
@ -76,7 +87,7 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
type: 'image/png'
|
type: 'image/png'
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
manifest = `<link rel="manifest" href="data:;base64,${Buffer.from(manifestJSON).toString('base64')}" />`;
|
manifest = `<link rel="manifest" href="data:;base64,${await b64(manifestJSON)}" />`;
|
||||||
}
|
}
|
||||||
let script = await scriptFile.text();
|
let script = await scriptFile.text();
|
||||||
const inits = new Set<string>();
|
const inits = new Set<string>();
|
||||||
|
|
@ -106,8 +117,10 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
|
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to avoid $& being replaced
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
|
||||||
const resultHTML = html
|
const resultHTML = html
|
||||||
.replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`) // to avoid $& being replaced
|
.replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`)
|
||||||
.replace('<!--$TITLE$-->', () => title)
|
.replace('<!--$TITLE$-->', () => title)
|
||||||
.replace('<!--$ICON$-->', () => icon)
|
.replace('<!--$ICON$-->', () => icon)
|
||||||
.replace('<!--$MANIFEST$-->', () => manifest)
|
.replace('<!--$MANIFEST$-->', () => manifest)
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ Bun.serve({
|
||||||
game,
|
game,
|
||||||
{
|
{
|
||||||
production: url.searchParams.get('production') === 'true', // to debug production builds
|
production: url.searchParams.get('production') === 'true', // to debug production builds
|
||||||
portable: url.searchParams.get('portable') === 'true', // to skip AssemblyScript compilation,
|
|
||||||
mobile: detectedBrowser.mobile || url.searchParams.get('mobile') === 'true',
|
mobile: detectedBrowser.mobile || url.searchParams.get('mobile') === 'true',
|
||||||
|
local: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (html) {
|
if (html) {
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,6 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
'nontrapping-fptoint',
|
'nontrapping-fptoint',
|
||||||
'reference-types',
|
'reference-types',
|
||||||
'multivalue',
|
'multivalue',
|
||||||
|
|
||||||
].map(f => `-m${f}`);
|
].map(f => `-m${f}`);
|
||||||
|
|
||||||
const flags = [
|
const flags = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
export const formatError = (error: unknown, message: string = ''): string => {
|
||||||
|
const prefix = message ? `${message}: ` : '';
|
||||||
|
const suffix = (error && typeof error === 'object' ) ? (('stack' in error) ? `\n${error.stack}` : '') : '';
|
||||||
|
|
||||||
|
const errorMessage = formatErrorMessage(error).trim();
|
||||||
|
|
||||||
|
return `${prefix}${errorMessage}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatErrorMessage = (error: unknown): string => {
|
||||||
|
if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
return `${error.message}`;
|
||||||
|
} else if (error) {
|
||||||
|
return error.toString();
|
||||||
|
} else {
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { formatError, formatErrorMessage } from "./errors";
|
||||||
import Input from "./input";
|
import Input from "./input";
|
||||||
import { nextFrame } from "./utils";
|
import { nextFrame } from "./utils";
|
||||||
|
|
||||||
type Setup<T extends Record<string, unknown> | void> = () => Promise<T> | T;
|
type Setup<T extends Record<string, unknown> | void> = () => Promise<T> | T;
|
||||||
type Frame<T extends Record<string, unknown> | void> = (dt: number, state: T) => Promise<void> | void;
|
type Frame<T extends Record<string, unknown> | void> = (dt: number, state: T) => Promise<T | void> | T | void;
|
||||||
type GameMain = () => void;
|
type GameMain = () => void;
|
||||||
|
|
||||||
export function gameLoop<T extends Record<string, unknown> | void>(frame: Frame<T>): GameMain;
|
export function gameLoop<T extends Record<string, unknown> | void>(frame: Frame<T>): GameMain;
|
||||||
|
|
@ -10,24 +11,39 @@ export function gameLoop<T extends Record<string, unknown> | void>(setup: Setup<
|
||||||
export function gameLoop<T extends Record<string, unknown> | void>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
|
export function gameLoop<T extends Record<string, unknown> | void>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
|
||||||
return async () => {
|
return async () => {
|
||||||
let state: T;
|
let state: T;
|
||||||
if (frame) {
|
try {
|
||||||
state = await (setupOrFrame as Setup<T>)();
|
if (frame) {
|
||||||
} else {
|
state = await (setupOrFrame as Setup<T>)();
|
||||||
frame = setupOrFrame as Frame<T>;
|
} else {
|
||||||
state = {} as T;
|
frame = setupOrFrame as Frame<T>;
|
||||||
|
state = {} as T;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(formatError(e, 'Error in game setup'));
|
||||||
|
alert(formatErrorMessage(e));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prevFrame = performance.now();
|
try {
|
||||||
while (true) {
|
let prevFrame = performance.now();
|
||||||
await nextFrame();
|
while (true) {
|
||||||
Input.updateKeys();
|
await nextFrame();
|
||||||
|
Input.updateKeys();
|
||||||
|
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const dt = (now - prevFrame) / 1000;
|
const dt = (now - prevFrame) / 1000;
|
||||||
|
|
||||||
await frame(dt, state);
|
const newState = await frame(dt, state);
|
||||||
|
if (newState) {
|
||||||
|
state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
prevFrame = performance.now();
|
prevFrame = performance.now();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(formatError(e, 'Error in game loop'));
|
||||||
|
alert(formatErrorMessage(e));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export const nextFrame = async (): Promise<number> => new Promise((resolve) => requestAnimationFrame(resolve));
|
export const nextFrame = async (): Promise<number> => new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
|
export const delay = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
export const randInt = (min: number, max: number) => Math.round(min + (max - min - 1) * Math.random());
|
export const randInt = (min: number, max: number) => Math.round(min + (max - min - 1) * Math.random());
|
||||||
export const randBool = () => Math.random() < 0.5;
|
export const randBool = () => Math.random() < 0.5;
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 158 B |
|
|
@ -207,7 +207,6 @@ async function loop() {
|
||||||
}
|
}
|
||||||
rowsToClear.clear();
|
rowsToClear.clear();
|
||||||
colsToClear.clear();
|
colsToClear.clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.clear();
|
display.clear();
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { createCanvas } from "@common/display/canvas";
|
||||||
|
import { gameLoop } from "@common/game";
|
||||||
|
|
||||||
|
type State = ReturnType<typeof setup>;
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const canvas = createCanvas(800, 600);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("Failed to get canvas context");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const frame = (dt: number, state: State) => {
|
||||||
|
const { ctx } = state;
|
||||||
|
ctx.fillStyle = "blue";
|
||||||
|
ctx.fillRect(0, 0, 800, 600);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default gameLoop(setup, frame);
|
||||||
Loading…
Reference in New Issue