PV16SOG BASIC

Langage BASIC du PV16SOG

Licence

Ce document fait partie du projet PV16SOG et est fourni sous licence CC-NC-SA-BY V3.0

auteur: Jacques Deschênes
révision 1.0
Copyright: 2015,2016, Jacques Deschênes

Présentation

L'ordinateur PV16SOG possède un interpréteur BASIC inspiré de QBASIC. Le code source BASIC est d'abord compilé en bytecode pour être exécuté sur une machine virtuelle à piles. Ce document décris ce langage BASIC et son utilisation.

Caractéristques du langage.


Référence des commandes BASIC

PV16SOG BASIC est insensible à casse tous les mots réservés et indentificateurs de variables sont convertis en majuscules par le compilateur.

conventions typographiques

les éléments de syntaxe indiqués entre '[' et ']' sont des éléments optionnels. si le caractère '+' suit le caractère ']' l'élément peut-être répété un nombre abritraire de fois.

le caractère '|' sépare les alternatives

Dans les examples le > au début d'une ligne représente l'invite de la ligne de commande.

expr signifit une expression arithmétique.
expr_list liste d'expression numérique. Les éléments sont séparés par la virgule ','.
cond signifit une condition logique résultant de la comparaison de 2 expr.
log_expr signigit un ensemble de cond reliées par les opérateurs logiques AND et OR.
arg_list est la liste des arguments passés à une sous-routine ou fonction. La virgule ',' est le séparateur de liste.
val_list est une liste de valeur numérique ou chaîne servant à initialiser un tableau lors de sa déclaration.
var représente un nom de variable.
num représente un nombre entier.
const représente une constante numérique.
block groupe d'instructions BASIC.

Opérateurs par ordre de précédence

Cette table montre les opérateurs par ordre de précédence. À précédence égale les opérateurs sont traités de gauche à droite.

opérateurdescription
()Parenthèse de groupement d'expression. Le groupement entre parenthèses est réservé aux expression arithmétiques. Les cond et les log_expr ne peuvent pas être regroupées.
func()Les fonctions ont la plus haute priorité dans les expression arithmétiques.
* / %multiplication, division entière, modulo
+ -addition soustraction
= >= < <= <> >< les opérateurs de comparaison utilisés dans les cond
NOT négation logique appliquée au résultat d'une cond
ANDconjonction logique appliqué entre les cond
ORalternative logique appliqué entre les cond

nom de variables

Les noms de variables sont insensibles à la casse. Tous les noms sont convertis en majuscules par le compilateur. Un nom peut avoir un maximum de 31 caractères, doit commencer par une lettre mais peut contenir des chiffres et le caractère '_'. Si le nom de la variable se termine par le caractère '$' il s'agit d'une variable chaîne. Si le nom se termine par le caractère '#' il s'agit d'une variable octet {0-255}. Autrement il s'agit d'une variable de type entier 16 bits dans l'interval de valeurs {-32768 - 32767}. Les tableaux peuvent-être de ces 3 types de données et ne peuvent avoir qu'une seule dimension.

Les variables chaîne sont en fait des constantes, une fois une valeur assignée à une telle variable celle-ci ne peut-être changée sauf en utilisant un truc de programmation avancé.

valeurs litérales

Les valeurs litérales numérique peuvent-être saisie sous 3 formes:

  1. décimal 23, -267 , -32560, 255
  2. hexadécimal $23f2, $fff3
  3. binaire #101, #100011

Les chaînes de caractères sont saisies entre guillemets '"'. Un caractère ASCII est saisie en le précédent de la barre oblique '\'

 example: 
>print "Hello world"
Hello world
>putc \a
a
>|

fonctions et sous-routines

Les paramètres des fonctions et sous-routines sont passées par valeur sauf si le nom de la variable est précédé du caractère '@'. Dans ce cas le paramètre doit-être le nom d'une variable qui est passée par référence.

example:
    sprite(10,10,8,8,@lem#,@rest#) ' lem# est le nom d'une variable taleau qui contient un sprite.
                                   ' ce tableau doit-être passé par référence.
                                   ' rest# sauvegarde le fond d'écran couvert par le sprite.
    

La valeur de retour d'une fonction peut-être ignorée. Si la fonction est appellée comme une commande la valeur de retour est simplement jetée.

commandes BASIC

index


Examples de programmes

Le jeux de la vie

Ce premier example est une implémentation du jeux de la vie du mathématicien John H. Conway. En dépit de son nom il ne s'agit pas d'un jeux mais d'une simulation d'automate cellulaire à 2 dimensions. Au démarrage un curseur en forme de croix apparaît au centre de l'écran. Il s'agit de créer une configuraton de départ. On déplace le curseur à l'aide des flèches du clavier. La touche C ajoute une cellule à la position du curseur et la touche ESPACE supprime une cellule existante. Pour démarrer la simulation on utilise la touche ENTER. On regarde évoluer cet univers d'automates cellulaires d'une génération à l'autre. Le nombre de générations est affiché dans le coin inférieur gauche. Même si ce BASIC ne permet que la définition de tableaux à 1 seule dimension ce programme simule un tableau d'octets à 3 dimensions g#(1368) qui est la grille univers dans lequel évoluent les automates cellulaires.

Vous pouvez vous représenter cette grille comme une feuille de papier quadrillé dont certains carrés seraient noircis. Ces carrés noircis représenteraient les cellules vivantes. Pour chaque carré de la feuille on compte le nombre de cellules vivantes qui l'entoure et on applique les règles suivantes:

  1. S'il y a moins de 2 cellules vivantes dans le voisinage ce carré sera vide à la prochaine génération. Si le carré est vivant il meurt.
  2. S'il y a 2 cellules vivantes dans le voisinage ce carré demeurera dans le même état à la prochaine génération. C'est à dire vide s'il est vide ou plein s'il est plein.
  3. S'il y a 3 cellules vivantes dans le voisinage et que le carré est vide il sera plein à la prochaine génération. C'est une naissance.
  4. S'il y a plus 3 cellules vivantes dans le voisinage et que le carré est plein il sera vide à la prochaine génération. Mort par surpopulation.

Ces règles sont très simples et pourtant elles donnent lieu à une évolution qui peut-être très complexe dépendant de la configuration de départ.


	rem John H. Conway's game of life
	rem simulation automate cellulaire 2D
	dim rest#(24) ' pour restaurer le fond d'ecran
	dim cursor#(24)=(
		$00,$70,$00,
		$00,$70,$00,
		$00,$70,$00,
		$77,$77,$70,
		$00,$70,$00,
		$00,$70,$00,
		$00,$70,$00,
		$00,$00,$00)

	const width=38 ' dimension horizontale de grille
	const height=18 ' dimension verticale de la grille
	const cell=127  ' caractère utilisé pour représenter les cellules
	const empty=32  ' caractère position grille vide
	const k_up=141    ' valeur touche flèche vers le haut
	const k_down=142  ' valeur touche flèche vers le bas
	const k_left=143  ' valeur touche flèche vers la gauche
	const k_right=144 ' valeur touche flèche vers la droite
	const odd=684     ' déplacement grille génération impaire
	dim g#(1368)  'grille univers
	gen=0  'compteur de génération
	src=0  ' déplacement grille source
	des=odd 'déplacement grille destination

    ' sous-routine d'effacement de la grille
	sub clear_grid()
	local i
	  for i=1 to ubound(g#)
		g#(i)=empty
	  next i
	end sub

    ' sous-routine d'affichage de la grille
	sub display_grid()
	local x,y
	 for x=1 to 38
	  for y=1 to 18
	   locate(y,x)
	   putc g#((y-1)*width+x+src)
	  next y
	 next x
	 locate(20,0)
	 print gen ;
	end sub 

    ' initialisation de la grille
    ' avec le modèle de départ.
	sub set_grid()
	local k,x,y
	 x=width/2+1
	 y=height/2+1
	 cls
	 locate(21,0)
	 ? "<SPACE>,<C>,<ENTER>";
	 gen=0
	 src=0
	 des=odd
	 k=0 
	 clear_grid()
	 while k<>13
	  locate(0,0)
	  ? "x:",x," y:",y,"    ";
	  locate(y,x)
	  putc g#((y-1)*width+x)
	  sprite(x*6,y*8,6,8,@cursor#,@rest#)
	  while not k : k= key() : wend
	  remspr(x*6,y*8,6,8,@rest#)
	  select case k
	  case k_up
		if y>1 then y=y-1 end if
	  case k_down
		if y<height then y=y+1 end if
	  case k_left
		if x>1 then x=x-1 end if
	  case k_right
		if x<38 then x=x+1 end if
	  case 32
		g#(width*(y-1)+x)=empty
	  case 67,99
		g#((y-1)*width+x)=cell
	  end select
	  if k<>13 then k=0 end if
	 wend
	 cls
	 ? "<ESC> quit, 'r' new grid";
	end sub

    ' compte le voisinage d'une cellule
	func countn(x,y)
	local n,x0,y0,ofs
	 n=0
	 for x0=max(x-1,1) to min(38,x+1)
	  for y0=max(y-1,1) to min(18,y+1)
	  if x0<>x or y0<>y then
	   ofs=(y0-1)*width+x0+src
	   if g#(ofs)=cell then
		n=n+1
	   end if
	  end if
	  next y0
	 next x0
	 return n 
	end func

    ' calcule les valeurs de la prochaine
    ' génération.
	sub next_gen()
	local n,x,y,ofs
	  for x=1 to 38
	   for y=1 to 18
		n=countn(x,y)
		ofs=(y-1)*width+x
		select case n
		case 2
		 g#(ofs+des)=g#(ofs+src)
		case 3
		 g#(ofs+des)=cell
		case else ' 0,1,4,5,6,7,8
		 g#(ofs+des)=empty
		end select
	   next y
	  next x
	  gen=gen+1
	  src=odd-src
	  des=odd-des
	end sub

    ' boucle principal
    ' du programme
	set_grid()
	r=1
	while r
	  display_grid()
	  next_gen()        
	  select case key()
	  case 27
	   r=0
	  case \r,\R
	   set_grid()
	  end select
	wend
	cls
début

spred.bas

spred.bas, (sprite editor) est un éditeur de figurine pour les jeux vidéo. On peut définir des sprites directement en BASIC comme montré dans l'example précédent avec le cursor mais ce n'est pas très commode. Cet éditeur simple permet de les créer et de les sauvegarder sous forme de fichier binaire.

Au démarrage le programme demande le nom du fichier sous lequel le sprite doit-être sauvegarder ou le nom d'un fichier existant qui peut-être chargé pour modification. Ensuite les dimensions du sprite sont demandées. width est la largeur en pixels et height la hauteur. La grandeur maximale est de 32x32 pixels. Une fois ces informations saisies un rectangle apparaît au centre de l'écran avec un x qui représente le curseur. Ce rectangle est la zone d'édition zoommée 4X. En haut à gauche apparait les coordonnées du curseur et sous les coordonnées apparaît la représentation taille réelle du sprite.
Les commandes sont entièrement au clavier.

Il s'agit donc d'un programme très simple. On ne peut éditer qu'un seul sprite par session. Cette limitation est du au fait que ce BASIC considère les chaînes de caractères comme des constantes donc la valeur de name$ ne peut-être modifiée une fois initialisée par la commande INPUT en début de programme. Pour éditer un autre sprite il faut sortir du programme et le relancer avec la commande run spred.bas. Cette limitation peut-être contournée, je vous laisse ce problème à titre d'exercice pratique.


	REM editeur de sprite

	' nom, largeur, hauteur, couleur pixel
	dim name$, w, h, c=0
	' tableau contenant le sprite
	dim spr#(512) ' contient le sprite à dessiner
	dim rest#(8) ' données de restauration fond ecran pour curseur
	' sprite curseur
	dim c#(8)=($f0,$0f,   
		$0f,$f0,
		$0f,$f0,
		$f0,$0f)
	' x offset, y offset, x, y
	dim xo,yo,x,y
	const psize=4  'dimension pixel zoomé
	const xres=240  'résolution écran horizontale
	const yres=170  ' résolution écran verticale
	cls
	' saisie des paramètres
	input "sprite name", name$
	input "sprite width", w
	input "sprite height", h

    'dessine le rectangle d'édition
	sub draw_bounds()
	 cls
	 xo=(xres-psize*w)/2
	 yo=(yres-psize*h)/2
	 rect(xo-1,yo-1,psize*w+2,psize*h+2,7)
	 x=w/2
	 y=h/2
	end sub

    'affiche les coordonnées {x,y} du curseur
	sub prtxy()
	  locate(0,0)
	  print "x:",x," y:",y ;
	end sub

    'sauvegarde du sprite 
    'dans un fichier binaire
	sub save_sprite()
	  srwrite(0,@w,2)
	  srwrite(2,@h,2)
	  srwrite(4,@spr#,w*h/2)
	  srsave name$,w*h/2+4
	  locate(19,0)
	  ? name$ ,"saved"
	  ? w,"x",h,"pixels";
	end sub

    'dessine le sprite
    'dans le rectangle d'édition
	sub fill_canevas()
	local i,x,y,c
	  for x=0 to w-1
	   for y=0 to h-1
		i=(y*w+x)/2+1
		if btest(x,0) then
		  c=spr#(i)%16
		else
		  c=spr#(i)/16
		end if
		box(xo+x*psize,yo+y*psize,psize,
		psize,c)
		setpixel(10+x,10+y,c)
	   next y
	  next x
	end sub

    'charge un fichier binaire
    'sprite
	sub load_sprite()
	local size
	 size=srload name$
	 if size then
	   srread(0,@w,2)
	   srread(2,@h,2)
	   srread(4,@spr#,size-4)
	   draw_bounds()
	   fill_canevas()
	   locate(19,0)
	   ? name$, "loaded"
	   ? "width",w,"height",h ;
	 else
	   locate(19,0)
	   ? "load failed";
	 end if
	end sub

    'assigne la couleur du pixel
    '{x,y} dans le tableau
	sub let_pixel(x,y,c)
	local idx
	  idx=(y*w+x)/2+1
	  if btest(x,0) then 'impair
	   spr#(idx)=spr#(idx)/16*16+c
	  else ' pair
	   spr#(idx)=spr#(idx)%16+c*16
	  end if
	end sub


	draw_bounds()

    ' boucle principale du programme
	while 1
	 prtxy()
	 sprite(xo+x*psize,yo+y*psize,psize,
	 psize,@c#,@rest#)
	 pause(20)
	 remspr(xo+x*psize,yo+y*psize,psize,
	 psize,@rest#)
	 k=key()
	 if k>=\a then k=k-32 end if
	 select case k
	 ' sélection couleur 0-9
	 case \0,\1,\2,\3,\4,\5,\6,\7,\8,\9
	  c=k-\0
	  ' sélection couleur A-F
	 case \A,\B,\C,\D,\E,\F
	  c=k-\A+10
	 case 32 ' ESPACE  = colore le pixel
	  box(xo+x*psize,yo+y*psize,psize,
	  psize,c)
	  setpixel(10+x,10+y,c)
	  let_pixel(x,y,c)
	 case 141 ' déplacement vers le haut
	  if y>0 then y=y-1 end if
	 case 142 ' déplacement vers le bas
	  if y<h-1 then y=y+1 end if
	 case 143 ' déplacement vers la gauche
	  if x>0 then x=x-1 end if
	 case 144 ' déplacement vers la droite
	  if x<h-1 then x=x+1 end if
	 case 27 ' ESC = quit
	  cls
	  bye
	 case \S  ' sauvegarde sprite
	  save_sprite()
	 case \L  ' charge sprite
	  load_sprite()
	 end select
	wend

début

jstick.bas

Ce démo montre comment utiliser le joystick pour déplacer un sprite à l'écran. Il utilise la directive USE pour inclure 2 fichiers. joystick.inc contient la définitions des constantes pour faciliter l'utilisation du joystick. lem.spr contient le code basic pour un sprite appelé LEM (Lunar Expedition Module).


rem fichier: joystick.inc
rem constantes pour le joystick
const button=1
const up=16
const down=8
const right=2
const left=4
const upleft=20
const upright=18
const downleft=12
const downright=10


rem fichier: lem.spr
rem définition d'un sprite appellé LEM
rem Lunar Expedition Module
' sprite LEM
dim lem#(128)=(
$00,$00,$00,$ff,$ff,$00,$00,$00,
$00,$00,$0f,$f0,$0f,$f0,$00,$00,
$00,$0f,$ff,$ff,$ff,$ff,$f0,$00,
$00,$ff,$ff,$ff,$ff,$ff,$ff,$00,
$00,$f0,$00,$00,$00,$00,$0f,$00,
$f0,$f0,$ff,$ff,$ff,$ff,$0f,$0f,
$0f,$f0,$ff,$00,$00,$ff,$0f,$f0,
$00,$f0,$ff,$00,$00,$ff,$0f,$00,
$0f,$f0,$ff,$ff,$ff,$ff,$0f,$f0,
$f0,$f0,$00,$00,$00,$00,$0f,$0f,
$00,$ff,$ff,$ff,$ff,$ff,$ff,$00,
$00,$ff,$ff,$ff,$ff,$ff,$ff,$00,
$00,$0f,$00,$f0,$f0,$00,$f0,$00,
$00,$f0,$0f,$00,$0f,$00,$0f,$00,
$0f,$f0,$00,$00,$00,$00,$0f,$f0,
$ff,$00,$00,$00,$00,$00,$00,$ff
)


REM fichier: jstick.bas
REM ce démo montre comment
REM utiliser le joystick pour 
rem déplacer un sprite à l'écran
use "lem.spr"
use "joystick.inc"

cls
dim rest#(128) ' pour restauration fond écran.
dim x=112,y=77 'position du sprite

'déplacement vers la gauche
sub move_left()
 if x>0 then x=x-1 end if
end sub

'déplacement vers la droite
sub move_right()
 if x<224 then x=x+1 end if
end sub

'déplacement vers le haut
sub move_up()
  if y>0 then y=y-1 end if
end sub

'déplacement vers le bas
sub move_down()
  if y&tl;154 then y=y+1 end if
end sub

'boucle principale du programme
while 1
 sprite(x,y,16,16,@lem#,@rest#)
 pause(20)
 remspr(x,y,16,16,@rest#)
 select case jstick()
 case UP
  move_up()
 case DOWN
  move_down()
 case RIGHT
  move_right()
 case LEFT
  move_left()
 case UPRIGHT
  move_up()
  move_right()
 case UPLEFT
  move_up()
  move_left()
 case DOWNRIGHT
  move_down()
  move_right()
 case DOWNLEFT
  move_down()
  move_left()
 case BUTTON  'termine démo
  cls
  bye
 end select
wend
début