NimotsuKun for .NET Core 3.0

C#で書いてみます。

>dotnet run
########
# .. p #
# oo   #
#      #
########
a:left s:right w:up z:down. command?

Program.cs

using System;

class Program
{
    //#壁 _空間 .ゴール oブロック p人
    const string gStageData =
        "########\n" +
        "# .. p #\n" +
        "# oo   #\n" +
        "#      #\n" +
        "########\0";

    const int gStageWidth = 8;
    const int gStageHeight = 5;

    enum OBJ
    {
        OBJ_SPACE,
        OBJ_WALL,
        OBJ_GOAL,
        OBJ_BLOCK,
        OBJ_BLOCK_ON_GOAL,
        OBJ_MAN,
        OBJ_MAN_ON_GOAL,

        OBJ_UNKNOWN,
    };

    static void Main(string[] args)
    {
        string input;
        OBJ[] state = new OBJ[gStageWidth * gStageHeight]; /* 状態配列確保 */

        initialize(state, gStageWidth, gStageData); /* ステージ初期化 */
        /* メインループ */
        while (true)
        {
            /* まず描画 */
            draw(state, gStageWidth, gStageHeight);
            /* クリアチェック */
            if (checkClear(state, gStageWidth, gStageHeight))
            {
                break; /* クリアチェック */
            }
            /* 入力取得 */
            Console.Write("a:left s:right w:up z:down. command?"); /* 操作説明 */
            input=Console.ReadLine();
            Console.Write("\n");
            /* 更新 */
            update(state, input[0], gStageWidth, gStageHeight);
        }
        /* 祝いのメッセージ */
        Console.WriteLine("Congratulation's! you won.");

        return;
    }

    /* ---------------------以下関数定義------------------------------------------ */

    static void initialize(OBJ[] state, int width, string stageData)
    {
        int d = 0; /* 読み込みポインタ */
        int x = 0;
        int y = 0;
        while (stageData[d] != '\0')
        { /* NULL文字でない間 */
            OBJ t; /* 特に意味はないが使う回数が多い変数に私は良くtを使う。temporaryの略。たぶんよくない習慣だが、無駄に長い名前にして読みにくいのも困り物だろう。 */
            switch (stageData[d])
            {
                case '#': t = OBJ.OBJ_WALL; break;
                case ' ': t = OBJ.OBJ_SPACE; break;
                case 'o': t = OBJ.OBJ_BLOCK; break;
                case 'O': t = OBJ.OBJ_BLOCK_ON_GOAL; break;
                case '.': t = OBJ.OBJ_GOAL; break;
                case 'p': t = OBJ.OBJ_MAN; break;
                case 'P': t = OBJ.OBJ_MAN_ON_GOAL; break;
                case '\n': x = 0; ++y; t = OBJ.OBJ_UNKNOWN; break; /* 改行処理 */
                default: t = OBJ.OBJ_UNKNOWN; break;
            }
            ++d;
            if (t != OBJ.OBJ_UNKNOWN)
            { /* 知らない文字なら無視するのでこのif文がある */
                state[y * width + x] = t; /* 書き込み */
                ++x;
            }
        }
    }

    static void draw(OBJ[] state, int width, int height)
    {
        int x, y, t;
        string font = " #.oOpP"; /* Object列挙の順 */
        for (y = 0; y < height; ++y)
        {
            for (x = 0; x < width; ++x)
            {
                OBJ o = state[y * width + x];
                switch (o)
                {
                    case OBJ.OBJ_SPACE: t = 0; break;
                    case OBJ.OBJ_WALL: t = 1; break;
                    case OBJ.OBJ_GOAL: t = 2; break;
                    case OBJ.OBJ_BLOCK: t = 3; break;
                    case OBJ.OBJ_BLOCK_ON_GOAL: t = 4; break;
                    case OBJ.OBJ_MAN_ON_GOAL: t = 5; break;
                    case OBJ.OBJ_MAN: t = 6; break;
                    default: t = 0; break;
                }
                Console.Write(font[t]);
            }
            Console.Write("\n");
        }
    }

    /* 第一引数はほかの関数ではstateとしているが、あまりに頻繁に使うので
    短いsで済ませている。w,hもそれぞれwidth,heightである。 */
    static void update(OBJ[] s, char input, int w, int h)
    {
        /* 移動差分に変換(dはdifferenceでもdeltaでもお好きな方の略だと思って欲しい) */
        int dx = 0;
        int dy = 0;
        int i = -1;
        int x, y, tx, ty, p, tp, tx2, ty2, tp2;
        switch (input)
        {
            case 'a': dx = -1; break;
            case 's': dx = 1; break;
            case 'w': dy = -1; break;
            case 'z': dy = 1; break;
        }
        /* 人座標を検索 */
        for (i = 0; i < w * h; ++i)
        {
            if (s[i] == OBJ.OBJ_MAN || s[i] == OBJ.OBJ_MAN_ON_GOAL)
            {
                break;
            }
        }
        x = i % w; /* xは幅で割ったあまり */
        y = i / w; /* yは幅で割った商 */

        /* 移動後座標(tに意味はない。ごめんなさい) */
        tx = x + dx;
        ty = y + dy;
        /* 座標の最大最小チェック。外れていれば不許可 */
        if (tx < 0 || ty < 0 || tx >= w || ty >= h)
        {
            return;
        }
        /* A.その方向が空白またはゴール。人が移動。 */
        p = y * w + x; /* 人位置 */
        tp = ty * w + tx; /* ターゲット位置(TargetPosition) */
        if (s[tp] == OBJ.OBJ_SPACE || s[tp] == OBJ.OBJ_GOAL)
        {
            s[tp] = (s[tp] == OBJ.OBJ_GOAL) ? OBJ.OBJ_MAN_ON_GOAL : OBJ.OBJ_MAN; /* ゴールならゴール上の人に */
            s[p] = (s[p] == OBJ.OBJ_MAN_ON_GOAL) ? OBJ.OBJ_GOAL : OBJ.OBJ_SPACE; /* もともとゴール上ならゴールに */
                                                                                 /* B.その方向が箱。その方向の次のマスが空白またはゴールであれば移動。 */
        }
        else if (s[tp] == OBJ.OBJ_BLOCK || s[tp] == OBJ.OBJ_BLOCK_ON_GOAL)
        {
            /* 2マス先が範囲内かチェック */
            tx2 = tx + dx;
            ty2 = ty + dy;
            if (tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h)
            { /* 押せない */
                return;
            }

            tp2 = (ty + dy) * w + (tx + dx); /* 2マス先 */
            if (s[tp2] == OBJ.OBJ_SPACE || s[tp2] == OBJ.OBJ_GOAL)
            {
                /* 順次入れ替え */
                s[tp2] = (s[tp2] == OBJ.OBJ_GOAL) ? OBJ.OBJ_BLOCK_ON_GOAL : OBJ.OBJ_BLOCK;
                s[tp] = (s[tp] == OBJ.OBJ_BLOCK_ON_GOAL) ? OBJ.OBJ_MAN_ON_GOAL : OBJ.OBJ_MAN;
                s[p] = (s[p] == OBJ.OBJ_MAN_ON_GOAL) ? OBJ.OBJ_GOAL : OBJ.OBJ_SPACE;
            }
        }
    }

    /* ブロックのみがなければクリアしている。 */
    static bool checkClear(OBJ[] s, int width, int height)
    {
        int i;
        for (i = 0; i < width * height; ++i)
        {
            if (s[i] == OBJ.OBJ_BLOCK)
            {
                return false;
            }
        }
        return true;
    }
}

ファミコン以前で遊ぼう会(非Z80)

ファミコン以前のLSIゲーム、オデッセイ、アタリ、カセットビジョン、インテレビジョン、アルカディア、光速船などの非Z80機種で遊ぶイベントです。

ハッシュタグ(2019/10/20)

TMS99x8で遊ぼう会2(ヨコハマ)

ハッシュタグ(2019/8/18)

NimotsuKun for PV-1000解説

サンプルプログラムの荷物君をz88dkでビルドしましたが仕組みを解説してみます。
>zcc +pv1000 main.c -create-app
上記コマンドでは暗黙的に以下がリンクされています。
main.c
pv1000_crt0.asm
lores.asm
font.asm
16KB ROM(0000-3FFF)のメモリーマップは以下になります。
0000-01FF CRT0
0200-03FF LORES
0400-0FFF FONTS
1000-3FFF MAIN
32KB ROM(0000-7FFF)のメモリーマップは以下になります。
0000-01FF CRT0
0200-03FF LORES
0400-0FFF FONTS
1000-7FFF MAIN
LORES(0a-1f)とFONTS(20-7f)とPCG(e0-ff)は、RGBで合計32バイト使ってキャラクタ定義します。
N/A(8bytes)
R (8bytes)
G (8bytes)
B (8bytes)
VRAMにキャラクタ番号(00-ff)を描くと表示されます。 RAMのメモリーマップは以下になります。
B800-BAFF VRAM
BB00-BBFF RAM
BC00-BFFF PCG
JOYPAD入力やPSG出力はEnri's Home PAGEを参考に、I/Oを叩いたらゲームつくれると思います。 http://www43.tok2.com/home/cmpslv/Pv1000/EnrPV1.htm

NimotsuKun for PV-1000

ゲームプログラマになる前に覚えておきたい技術

http://www.shuwasystem.co.jp/support/7980html/2118.html

f:id:tanam:20190522085507p:plain

サンプルプログラムの荷物君をz88dkでビルドしてPV-1000で動かしてみます。

>zcc +pv1000 main.c -create-app

main.c

/*

  Nimotsu Kun for PV-1000
                               by tanam [2019]

*/

#include "games.h"

typedef int bool;
typedef int Object;
typedef unsigned char u8;
typedef unsigned short u16;
#define true 1
#define false 0
#define KEY_UP1     MOVE_UP
#define KEY_DOWN1   MOVE_DOWN
#define KEY_LEFT1   MOVE_LEFT
#define KEY_RIGHT1  MOVE_RIGHT
#define KEY_A       MOVE_FIRE1
#define KEY_B       MOVE_FIRE2

unsigned char *screen=0xB800;
u16 game[224];

/* #壁 _空間 .ゴール oブロック p人 */
const char* gStageData = 
"\
########\n\
# .. p #\n\
# oo   #\n\
#      #\n\
########\n\
        \n\
        0\
 ###### \n\
#.    # \n\
# o o # \n\
# op# # \n\
#   # # \n\
#.###.# \n\
### ### 0";

#define gStageWidth 8
#define gStageHeight 7

enum OBJ {
	OBJ_SPACE,
	OBJ_WALL,
	OBJ_GOAL,
	OBJ_BLOCK,
	OBJ_BLOCK_ON_GOAL,
	OBJ_MAN,
	OBJ_MAN_ON_GOAL,

	OBJ_UNKNOWN,
};

Object state[56];

void screen_set_char(u8 xts, u8 yts, u8 xte, u8 yte, u16 *map)
{
	int i, j, k;
	k=0;
	for (i = 0; i < 24; i++)
		for (j = 0; j < 32; j++)
			if (j > xts && j < xte && i > yts && i < yte)
				screen[(i+1)*32+j+1]=map[8*(i-1)+j-xts-1];
	return;
}

/* 関数プロトタイプ */
void initialize( Object* state, char* stageData );
void draw( Object* state, int w, int h );
int update( Object* state, u16 input, int w, int h );
bool checkClear( Object* state, int w, int h );

int main(){
	int stage;
 	clg();
	for (stage=0; stage<2; stage++) {
RESET:
		initialize( state, gStageData+stage*63); /* ステージ初期化 */
		/* メインループ */
		while ( true ){
			/* まず描画 */
			draw( state, gStageWidth, gStageHeight );
			/* クリアチェック */
			if ( checkClear(state, gStageWidth, gStageHeight ) ){
				break; /* クリアチェック */
			}
			/* 更新 */
			if (-1 == update( state, joystick(0), gStageWidth, gStageHeight )){
				goto RESET;
			}
		}
	}
	return 0;
}

void initialize( Object* state, char* stageData ){
	const char* d = stageData; /* 読み込みポインタ */
	int x = 0;
	int y = 0;
	while ( *d != '0' ){ /* NULL文字でない間 */
		Object t; /* 特に意味はないが使う回数が多い変数に私は良くtを使う。temporaryの略。たぶんよくない習慣だが、無駄に長い名前にして読みにくいのも困り物だろう。 */
		switch ( *d ){
			case '#': t = OBJ_WALL; break;
			case ' ': t = OBJ_SPACE; break;
			case 'o': t = OBJ_BLOCK; break;
			case 'O': t = OBJ_BLOCK_ON_GOAL; break;
			case '.': t = OBJ_GOAL; break;
			case 'p': t = OBJ_MAN; break;
			case 'P': t = OBJ_MAN_ON_GOAL; break;
			case '\n': x = 0; ++y; t = OBJ_UNKNOWN; break; /* 改行処理 */
			default: t = OBJ_UNKNOWN; break;
		}
		++d;
		if ( t != OBJ_UNKNOWN ){ /* 知らない文字なら無視するのでこのif文がある */
			state[y * gStageWidth + x ] = t; /* 書き込み */
			++x;
		}
	}
	return;
}

void draw(Object* state, int width, int height ){
        int x, y;
 	char font[] = {' ', '#', '.', 'o', 'O', 'p', 'P'};
/*	u16 font[] = {10,11,12,13,14,15,16}; /* Object列挙の順 */
	for ( y = 0; y < height; ++y ){
		for (x=0; x < width; ++x ){
			Object o = state[ y * width + x ];
			game[y * gStageWidth + x] = font[ o ];
		}
	}
	screen_set_char(0, 0, 9, 8, game);
}

/* 第一引数はほかの関数ではstateとしているが、あまりに頻繁に使うので
短いsで済ませている。w,hもそれぞれwidth,heightである。 */
int update( Object* s, u16 input, int w, int h ){
	/* 移動差分に変換(dはdifferenceでもdeltaでもお好きな方の略だと思って欲しい) */
	int dx = 0; 
	int dy = 0;
	int i = -1;
        int x, y, tx, ty, p, tp, tx2, ty2, tp2;
	switch ( input ){
		case KEY_LEFT1: dx = -1; break;
		case KEY_RIGHT1: dx = 1; break;
		case KEY_UP1: dy = -1; break;
		case KEY_DOWN1: dy = 1; break;
		case KEY_A: return -1;
	}
	/* 人座標を検索 */
	for ( i = 0; i < w * h; ++i ){
		if ( s[ i ] == OBJ_MAN || s[ i ] == OBJ_MAN_ON_GOAL ){
			break;
		}
	}
	x = i % w; /* xは幅で割ったあまり */
	y = i / w; /* yは幅で割った商 */

	/* 移動後座標(tに意味はない。ごめんなさい) */
	tx = x + dx;
	ty = y + dy;
	/* 座標の最大最小チェック。外れていれば不許可 */
	if ( tx < 0 || ty < 0 || tx >= w || ty >= h ){
		return;
	}
	/* A.その方向が空白またはゴール。人が移動。 */
	p = y*w + x; /* 人位置 */
	tp = ty*w + tx; /* ターゲット位置(TargetPosition) */
	if ( s[ tp ] == OBJ_SPACE || s[ tp ] == OBJ_GOAL ){
		s[ tp ] = ( s[ tp ] == OBJ_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN; /* ゴールならゴール上の人に */
		s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE; /* もともとゴール上ならゴールに */
	/* B.その方向が箱。その方向の次のマスが空白またはゴールであれば移動。 */
	}else if ( s[ tp ] == OBJ_BLOCK || s[ tp ] == OBJ_BLOCK_ON_GOAL ){
		/* 2マス先が範囲内かチェック */
		tx2 = tx + dx;
		ty2 = ty + dy; 
		if ( tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h ){ /* 押せない */
			return;
		}

		tp2 = ( ty + dy )*w + ( tx + dx ); /* 2マス先 */
		if ( s[ tp2 ] == OBJ_SPACE || s[ tp2 ] == OBJ_GOAL ){
			/* 順次入れ替え */
			s[ tp2 ] = ( s[ tp2 ] == OBJ_GOAL ) ? OBJ_BLOCK_ON_GOAL : OBJ_BLOCK;
			s[ tp ] = ( s[ tp ] == OBJ_BLOCK_ON_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN;
			s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE;
		}
	}
	return 0;
}

/* ブロックのみがなければクリアしている。 */
bool checkClear(Object* s, int width, int height ){
        int i;
	for (i = 0; i < width*height; ++i ){
		if ( s[ i ] == OBJ_BLOCK ){
			return false;
		}
	}
	return true;
}