cake
/
pixeltd
Archived
1
0
Fork 0

some more edits

This commit is contained in:
Thomas Touhey 2020-12-26 12:18:19 +01:00
parent 1884df3f1e
commit cc60012f85
10 changed files with 1682 additions and 11 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
build-fx
*.g1a
__pycache__

View File

@ -3,6 +3,8 @@
# the [fxsdk] program.
#---
PATH := .:$(PATH)
#
# Configuration
#

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,17 +1,138 @@
#!/usr/bin/env python3
import fxconv
from itertools import product
def convert(input, output, params, target):
if params["type"] != "map":
raise fxconv.FxconvError(f"unknown conversion type {params["type"]}")
raise fxconv.FxconvError(f"unknown conversion type {params['type']}")
# Convert the map into binary.
# coordinate system start from (0, 0), positive to right and bottom
# okay for now we just hardcode 18x10 (16x8 + borders).
#
# Format of the map:
#
# - width (1 byte)
# - height (1 byte)
# - camera_x (1 byte)
# - camera_y (1 byte)
# - spawn_x (1 byte), where to spawn enemies
# - spawn_y (1 byte), where to spawn enemies
# - number of enemy path elements (1 byte)
# - unused (9 bytes)
#
# - offset of grid data from file start (2 bytes, big endian)
# - offset of enemy path data from file start (2 bytes, big endian)
# - unused (12 bytes)
# - grid data (variable)
# - enemy path (variable)
#
# every element of grid data is:
# - type (1 byte), amongst:
# 0x00: basic placement tile
# 0x01: basic enemy path
# 0x02: castle
#
# every element of enemy path is:
# - delta (1 byte):
# 0x00: up
# 0x01: right
# 0x02: down
# 0x03: left
data = open(input, "r").read()
print(data)
lines = open(input, "r").read().splitlines()
w, h = 18, 10
spawn_x, spawn_y = None, None
arr_x, arr_y = None, None
grid_data = b''
if len(lines) != h:
raise fxconv.FxconvError(f"should have 10 lines, has {len(lines)}")
for line_no, line in enumerate(lines):
line = line.strip()
if len(line) != w:
raise fxconv.FxconvError(f"line no. {line_no + 1} "
f"should have 18 columns, has {len(line)}")
for car_no, c in enumerate(line):
if c == '#':
grid_data += b'\0'
elif c == ' ':
grid_data += b'\1'
elif c == '>':
if spawn_x is not None:
raise fxconv.FxconvError("multiple spawn points, please "
"choose, you dumbass")
spawn_x, spawn_y = car_no, line_no
grid_data += b'\1'
elif c == 'x':
if arr_x is not None:
raise fxconv.FxconvError("multiple castles, please "
"choose, you dumbass")
arr_x, arr_y = car_no, line_no
grid_data += b'\2'
else:
raise fxconv.FxconvError(f"invalid character at "
f"line {line_no + 1}, column {car_no + 1}")
if spawn_x is None:
raise fxconv.FxconvError("no spawn point, is that some kind of "
"sick joke")
if arr_x is None:
raise fxconv.FxconvError("no castle, are you kidding me")
def find_path(previous):
for delta_x, delta_y in ((0, -1), (1, 0), (0, 1), (-1, 0)):
x, y = previous[-1]
x += delta_x
y += delta_y
if (x, y) == (arr_x, arr_y):
return previous + ((x, y),)
elif grid_data[y * w + x] != 1:
pass
elif (x, y) not in previous:
result = find_path(previous + ((x, y),))
if result is not None:
return result
return None
path_to_arr = find_path(((spawn_x, spawn_y),))
if path_to_arr is None:
raise fxconv.FxconvError(f"no path from spawn to castle")
path_data = b''
for (ax, ay), (bx, by) in zip(path_to_arr, path_to_arr[1:]):
path_data += int({
(0, -1): 0,
(1, 0): 1,
(0, 1): 2,
(-1, 0): 3
}[(bx - ax, by - ay)]).to_bytes(1, 'big')
def round4len(x):
x = len(x)
result = (x // 4) * 4 + (x % 4 != 0) * 4
return result
def round4data(x):
return x + b'\0' * (round4len(x) - len(x))
data = int(w).to_bytes(1, 'big') + int(h).to_bytes(1, 'big')
data += int(1).to_bytes(1, 'big') + int(1).to_bytes(1, 'big')
data += int(spawn_x).to_bytes(1, 'big')
data += int(spawn_y).to_bytes(1, 'big')
data += int(len(path_data)).to_bytes(1, 'big')
data += b'\0' * 9
data += int(32).to_bytes(2, 'big')
data += int(32 + round4len(grid_data)).to_bytes(2, 'big')
data += b'\0' * 12
data += round4data(grid_data)
data += round4data(path_data)
data = b'placeholder'
fxconv.elf(data, output, "_" + params["name"], **target)
# End of file.

177
fxconv Executable file
View File

@ -0,0 +1,177 @@
#! /usr/bin/env python3
import getopt
import sys
import os
import fxconv
import subprocess
# Note: this line is edited at compile time to insert the install folder
PREFIX="""\
/usr
""".strip()
help_string = f"""
usage: fxconv [-s] <python script> [files...]
fxconv -b <bin file> -o <object file> [parameters...]
fxconv -i <png file> -o <object file> (--fx|--cg) [parameters...]
fxconv -f <png file> -o <object file> [parameters...]
fxconv converts data files such as images and fonts into gint formats
optimized for fast execution, or into object files.
Operating modes:
-s, --script Expose the fxconv module and run this Python script
-b, --binary Turn data into an object file, no conversion
-i, --image Convert to gint's bopti image format
-f, --font Convert to gint's topti font format
--libimg-image Convert to the libimg image format
When using -s, additional arguments are stored in the [fxconv.args] variable of
the module. This is intended to be a restricted list of file names specified by
a Makefile, used to convert only a subset of the files in the script.
The operating mode options are shortcuts to convert single files without a
script. They accept parameters with a "category.key:value" syntax, for example:
fxconv -f myfont.png -o myfont.o charset:ascii grid.padding:1 height:7
When converting images, use --fx (black-and-white calculators) or --cg (16-bit
color calculators) to specify the target machine.
Install PREFIX is set to '{PREFIX}'.
""".strip()
# Simple error-warnings system
def err(msg):
print("\x1b[31;1merror:\x1b[0m", msg, file=sys.stderr)
def warn(msg):
print("\x1b[33;1mwarning:\x1b[0m", msg, file=sys.stderr)
# "converters" module from the user project
try:
import converters
except ImportError:
converters = None
def main():
# Default execution mode is to run a Python script for conversion
modes = "script binary image font bopti-image libimg-image"
mode = "s"
output = None
model = None
target = { 'toolchain': None, 'arch': None, 'section': None }
use_custom = False
# Parse command-line arguments
if len(sys.argv) == 1:
print(help_string, file=sys.stderr)
sys.exit(1)
try:
longs = "help output= fx cg toolchain= arch= section= custom " + modes
opts, args = getopt.gnu_getopt(sys.argv[1:], "hsbifo:", longs.split())
except getopt.GetoptError as error:
err(error)
sys.exit(1)
for name, value in opts:
# Print usage
if name == "--help":
err(help_string, file=sys.stderr)
sys.exit(0)
# TODO: fxconv: verbose mode
elif name == "--verbose":
pass
elif name in [ "-o", "--output" ]:
output = value
elif name in [ "--fx", "--cg" ]:
model = name[2:]
elif name == "--toolchain":
target['toolchain'] = value
elif name == "--arch":
target['arch'] = value
elif name == "--section":
target['section'] = value
elif name == "--custom":
use_custom = True
mode = "custom"
# Other names are modes
else:
mode = name[1] if len(name)==2 else name[2:]
# Remaining arguments
if args == []:
err(f"execution mode -{mode} expects an input file")
sys.exit(1)
input = args.pop(0)
# In --script mode, run the Python script with an augmented PYTHONPATH
if mode == "s":
if output is not None:
warn("option --output is ignored in script mode")
if PREFIX == "":
err("unknown or invalid install path x_x")
sys.exit(1)
env = os.environ.copy()
if "PYTHONPATH" in env:
env["PYTHONPATH"] += f":{PREFIX}/bin"
else:
env["PYTHONPATH"] = f"{PREFIX}/bin"
p = subprocess.run([ sys.executable, input ], env=env)
if p.returncode != 0:
sys.exit(1)
# In shortcut conversion modes, read parameters from the command-line
else:
def check(arg):
if ':' not in arg:
warn(f"argument {arg} is not a valid parameter (ignored)")
return ':' in arg
def insert(params, path, value):
if len(path) == 1:
params[path[0]] = value
return
if not path[0] in params:
params[path[0]] = {}
insert(params[path[0]], path[1:], value)
args = [ arg.split(':', 1) for arg in args if check(arg) ]
params = {}
for (name, value) in args:
insert(params, name.split("."), value)
if "type" in params:
pass
elif(len(mode) == 1):
params["type"] = { "b": "binary", "i": "image", "f": "font" }[mode]
else:
params["type"] = mode
# Will be deprecated in the future
if params["type"] == "image":
warn("type 'image' is deprecated, use 'bopti-image' instead")
params["type"] = "bopti-image"
# Use the custom module
custom = None
if use_custom:
if converters is None:
err("--custom specified but no [converters] module in wd")
sys.exit(1)
custom = converters.convert
try:
fxconv.convert(input, params, target, output, model, custom)
except fxconv.FxconvError as e:
err(e)
sys.exit(1)
if __name__ == "__main__":
main()

1082
fxconv.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,313 @@
#include "main.h"
#include <gint/display.h>
#include <gint/keyboard.h>
#define NENEMIES 64
/* Map definition.
* This defines how we'll naviguate through the map. */
/* State definition, as used throughout the main game.
*
* For each enemy, the data is:
* - alive: if the enemy is alive or dead.
*
* - nstepf: number of frames for one step, defines the speed of the
* enemy.
*
* - x, y: calculated only for display and collision.
* - step, substep: where the AI is at. `step` is the index starting at zero
* of the path where is at, `substep` is the substep from 0 to 7, looping
* when 8 (augmenting step, except when at the castle).
* - nfwait: number of frames to be waited.
* - pdr: the enemy that spawned previously or is the next further on the
* path. Simple chain list that could evolve later if some enemies are
* slowed down or have various speeds, allowing some enemies to overtake
* others. */
void dmap(void *map)
struct enemy {
int alive;
int nstepf;
int x, y;
int step, substep;
int nfwait;
struct enemy **ndrp; /* pointer on the pointer to this enemy structure */
struct enemy *pdr; /* previous enemy to be drawn */
};
struct state {
/* Map information:
*
* - w, h: dimensions
* - sp_x, sp_y: enemy spawn coordinates
* - nep: number of enemy path */
int w, h;
int sp_x, sp_y;
int nep; /* number of enemies path. */
uint8_t *grid;
uint8_t *ep;
/* AI information:
*
* - tbsp: time between enemy spawns.
* - tnsp: time to next spawn (as a number of ticks).
* - nae: number of currently alive enemies.
* - first_enemy: pointer to the last enemy to have spawned,
* to draw first */
int tbsp, tnsp;
int nae;
struct enemy *first_enemy;
/* Camera, cursor and display information:
*
* - cam_x, cam_y: camera coordinates, always displays 16x8 tiles
* - cur_x, cur_y: map coordinate for the cursor.
* - drf: draw frame. */
int cam_x, cam_y;
int cur_x, cur_y;
int drf; /* draw frame, varies from 0 to 255 and looping */
/* Raw data */
struct enemy enemies[NENEMIES];
};
static struct state state;
/* init(raw): initialize the state with the given raw map data. */
static void init(uint8_t *raw)
{
int i;
/* Initialize map information */
state.w = raw[0];
state.h = raw[1];
state.sp_x = raw[4];
state.sp_y = raw[5];
state.nep = raw[6];
state.grid = &raw[(raw[16] << 8) | raw[17]];
state.ep = &raw[(raw[18] << 8) | raw[19]];
/* Initialize AI information */
state.tbsp = 50;
state.tnsp = state.tbsp;
state.nae = 0;
state.first_enemy = NULL;
for (i = 0; i < NENEMIES; i++)
state.enemies[i].alive = 0;
/* Initialize display information */
state.cam_x = raw[2];
state.cam_y = raw[3];
state.cur_x = 1;
state.cur_y = 1;
state.drf = 0;
}
/* tick(): tick the game engine. */
static void tick()
{
struct enemy *ep;
int i;
for (ep = state.first_enemy; ep; ep = ep->pdr) {
if (!--ep->nfwait) {
ep->nfwait = ep->nstepf;
switch (state.ep[ep->step]) {
case 0:
ep->y--;
break;
case 1:
ep->x++;
break;
case 2:
ep->y++;
break;
case 3:
ep->x--;
break;
}
ep->substep++;
if (ep->substep == 8) {
ep->step++;
ep->substep = 0;
if (ep->step == state.nep) {
ep->alive = 0;
if (ep->ndrp)
(*ep->ndrp) = ep->pdr;
state.nae--;
/* TODO: remove one life to the castle */
}
}
}
}
/* Spawn enemies if required */
if (!--state.tnsp) {
state.tnsp = state.tbsp;
if (state.nae < NENEMIES) {
for (i = 0, ep = &state.enemies[0]; i < NENEMIES; i++, ep++);
ep->pdr = state.first_enemy;
ep->pdr->ndrp = &ep->pdr;
state.first_enemy = ep;
ep->ndrp = &state.first_enemy;
ep->alive = 1;
ep->x = state.sp_x << 3;
ep->y = state.sp_y << 3;
ep->step = 0;
ep->substep = 0;
/* TODO: use a given type of enemy? */
ep->nstepf = 10;
ep->nfwait = ep->nstepf;
state.nae++;
}
}
}
/* tile_cam(x, y): return the type of the tile at coordinates (x, y) relative
* to the current camera position.
*
* If out of bounds, a basic placement tile (id. 0) will be returned */
static inline int tile_cam(int x, int y)
{
x += state.cam_x;
y += state.cam_y;
if (x < 0 || x >= state.w || y < 0 || y >= state.h)
return 0;
return state.grid[y * state.w + x];
}
/* draw(): basically the drawing function for everything. */
static void draw()
{
int x, y;
dclear(C_WHITE);
/* draw the grid */
for (y = 0; y < 8; y++) {
for (x = 0; x < 16; x++) {
int tile = tile_cam(x, y);
int ox = x * 8, oy = y * 8;
if (tile == 1 || tile == 2) {
/* above, below, left, right */
if (tile_cam(x, y - 1) == 0)
dline(ox, oy, ox + 7, oy, C_BLACK);
if (tile_cam(x, y + 1) == 0)
dline(ox, oy + 7, ox + 7, oy + 7, C_BLACK);
if (tile_cam(x - 1, y) == 0)
dline(ox, oy, ox, oy + 7, C_BLACK);
if (tile_cam(x + 1, y) == 0)
dline(ox + 7, oy, ox + 7, oy + 7, C_BLACK);
/* top left, top right, bottom left, bottom right */
if (tile_cam(x - 1, y - 1) == 0)
dpixel(ox, oy, C_BLACK);
if (tile_cam(x + 1, y - 1) == 0)
dpixel(ox + 7, oy, C_BLACK);
if (tile_cam(x - 1, y + 1) == 0)
dpixel(ox, oy + 7, C_BLACK);
if (tile_cam(x + 1, y + 1) == 0)
dpixel(ox + 7, oy + 7, C_BLACK);
}
}
}
/* Draw the enemies. */
{
struct enemy *ep;
extern bopti_image_t img_enemy;
for (ep = state.first_enemy; ep; ep = ep->pdr)
dimage(ep->x, ep->y, &img_enemy);
}
/* Draw the cursor */
{
int d = state.drf & 128 ? 2 : 0;
int ox = state.cur_x - state.cam_x + 1;
int oy = state.cur_y - state.cam_y + 1;
dline(ox + d, oy, ox + d + 1, oy, C_BLACK);
dline(ox + d + 4, oy, ox + d + 5, oy, C_BLACK);
dline(ox, oy + d, ox, oy + d + 1, C_BLACK);
dline(ox, oy + d + 4, ox, oy + d + 5, C_BLACK);
dline(ox + d, oy + 7, ox + d + 1, oy + 7, C_BLACK);
dline(ox + d + 4, oy + 7, ox + d + 5, oy + 7, C_BLACK);
dline(ox + 7, oy + d, ox + 7, oy + d + 1, C_BLACK);
dline(ox + 7, oy + d + 4, ox + 7, oy + d + 5, C_BLACK);
}
/* okay we can update now */
dprint(16, 96, C_BLACK, "state.tnsp = 0x%08X", state.tnsp);
dupdate();
}
/* Main game function. */
menu_t *game(menu_t *last_menu)
{
int timeout = 1 /* a non-zero value just in case */;
extern uint8_t map_basic[];
(void)last_menu;
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
dupdate();
getkey();
init(map_basic);
while (1) {
int should_draw = 0;
key_event_t event = getkey_opt(GETKEY_MOD_SHIFT | GETKEY_BACKLIGHT,
&timeout);
/* TODO: find a way to creatively deduce the time it takes to
* go from here to the `if (should_draw)`, in order to keep
* an as-constant-as-possible draw time */
switch (event.type) {
case KEYEV_NONE:
/* timeout event */
should_draw = 1;
break;
case KEYEV_DOWN:
if (event.key == KEY_EXE)
return no_menu;
}
if (timeout < 0 || should_draw) {
timeout = 65;
state.drf = (state.drf + 1) & 255;
/* Here, we should draw. */
tick(); /* TODO: perhaps a better placement somewhere */
draw();
}
}
return no_menu;
}