Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
mibi88 | 3c8f8b69ef | |
mibi88 | fb88a41da6 | |
mibi88 | 2702a61839 | |
mibi88 | 12ed26362c | |
mibi88 | e275effb3a | |
mibi88 | 70b115a737 | |
mibi88 | ec509b1967 | |
mibi88 | 692db46f7a | |
mibi88 | 0a0f87f543 | |
mibi88 | e1ebed2497 | |
mibi88 | 4e766bf06b | |
mibi88 | 96d6177674 | |
mibi88 | 59b69f8cc2 |
|
@ -0,0 +1,196 @@
|
|||
# libSCII
|
||||
|
||||
Pour utiliser libSCII, il faut créer une nouvelle variable de la classe Scii :
|
||||
|
||||
## La classe `Scii`
|
||||
|
||||
```python
|
||||
scii = Scii(world, player, npc_collision)
|
||||
```
|
||||
|
||||
Voici les arguments qu'on peut donner à Scii :
|
||||
|
||||
```python
|
||||
world, player, on_npc_collision, scii_keys = {"left": '4', "right": '6', "up": '8', "down": '2', "quit": 'q', "old_messages": '0'}, get_input_text = None, no_collision = " ^", collision_checker = None, message_history_max = 20, screen_width = 21, screen_height = 7, portrait_width = 7
|
||||
```
|
||||
|
||||
* `world` va contenir les données du monde. Des explication plus précises sont plus bas.
|
||||
* `player` va conten les données du joueur. Des explication plus précises sont plus bas.
|
||||
* `on_npc_collision` doit être une fonction avec les arguments `self, npc` qui est exécutée lorsque le joueur va vers le pnj.
|
||||
* `self` contient la classe `Scii` et permet d'y appeler des fonctions.
|
||||
* `npc` contient les données du pnj, comme elles sont écrites dans `world` (voir plus bas).
|
||||
* `scii_keys` contient les touches que le joueur utilise pour faire certaines action. Quand `scii_keys` est égal à `None`, les touches utilisés sont : `{"left": '4', "right": '6', "up": '8', "down": '2', "quit": 'q', "old_messages": '0'}`. `"old_messages"` permet de voir les anciennes conversations.
|
||||
* `get_input_text` est une fonction qui doit retourner une chaine de caractères qui sera affichée dans l'input pour récupérer les touches.
|
||||
* `no_collision` est une chaine de caractères qui contient les caractères que le joueur peut traverser. Par défaut, lorsque `no_collision` est égal à `None`, la chaine de caractères est `" ^"`.
|
||||
* `collision_checker` est une fonction qui prend les paramètres `self, world, player, x, y` et qui retourne 0 si le joueur peut passer à (x, y), 1 si il ne peut pas passer à (x, y) et 2 si il y a un pnj à (x, y). `self` permet d'appeler des fonctions de la classe `Scii` ou à en récupérer des données et `world` et `player` contiennent les mêmes données que `world` et `player` qui sont des arguments de `Scii`.
|
||||
* `message_history_max` contient le nombre de messages maximals qui seront conservés dans l'historique des conversations.
|
||||
* `screen_width` est un `int` qui contient la largeur de l'écran en nombre de caractères.
|
||||
* `screen_height` est un `int` qui contient la hauteur de l'écran en nombre de caractères.
|
||||
* `portrait_width` la largeur des portraits qui peuvent s'afficher à côté du texte.
|
||||
|
||||
## `world`, le monde du jeu
|
||||
|
||||
`world` est un dictionnaire qui contiendra tout les éléments du monde du jeu.
|
||||
|
||||
### Un exemple de `world`
|
||||
|
||||
```python
|
||||
|
||||
map0 = """
|
||||
########################################################
|
||||
# ________ \|/ #
|
||||
# | ) | _|_ #
|
||||
# |_)____| \_/ ______ #
|
||||
# ^ [==-==-] _ #
|
||||
# <=> [-===-=] // #
|
||||
# / \ [==-==-] <=// #
|
||||
# | || #
|
||||
########################################################
|
||||
"""
|
||||
|
||||
animations = {
|
||||
"replace_animations": {
|
||||
'-': {
|
||||
"frames": ['=', '-'],
|
||||
"animation_frame": 0
|
||||
},
|
||||
'=': {
|
||||
"frames": ['-', '='],
|
||||
"animation_frame": 0
|
||||
}
|
||||
},
|
||||
"coords_animations": {
|
||||
"4, 4": {
|
||||
"frames": ['^', ' '],
|
||||
"animation_frame": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world = {
|
||||
"dmode": STICKY,
|
||||
"map_num": 0,
|
||||
"maps": [
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"data": map0,
|
||||
"transp_char": None,
|
||||
"animations": animations
|
||||
}
|
||||
],
|
||||
"jumps": [
|
||||
{
|
||||
"x": 4,
|
||||
"y": 4,
|
||||
"to_x": 1,
|
||||
"to_y": 1,
|
||||
"to_map": 0,
|
||||
"isactive": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"npc": [
|
||||
{
|
||||
"x": 35,
|
||||
"y": 1,
|
||||
"isvisible": 1,
|
||||
"collision_check": 1,
|
||||
"layer": 0,
|
||||
"map": 0,
|
||||
"char": '&',
|
||||
"name": "massena"
|
||||
},
|
||||
{
|
||||
"x": 35,
|
||||
"y": 6,
|
||||
"isvisible": 1,
|
||||
"collision_check": 1,
|
||||
"layer": 0,
|
||||
"map": 0,
|
||||
"char": '&',
|
||||
"name": "lephenixnoir"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Description
|
||||
|
||||
* `dmode` : La méthode d'affichage de la carte :
|
||||
* `STICKY` : Le joueur est au milieu à part si il est trop proche du bord de la carte, il va donc se décentrer.
|
||||
* `CENTERED` : Le joueur est toujours au centre (comme dans Asci).
|
||||
* `BLOCKS` : La carte s'affiche par portions (un peu comme c'est parfois le cas dans Megaman sur la NES).
|
||||
* `STICKYBLOCKS` : La carte s'affiche comme dans `BLOCKS`, mais elle remplit toujours l'écran.
|
||||
* `map_num` : Le numéro de la carte sur laquelle on va démarrer le jeu.
|
||||
* `maps` : Une liste de plusieurs cartes, chacune constituée de :
|
||||
* `layers` : les différentes couches qui constituent les maps. Ces layers sont dessinés du premier au dernier.
|
||||
* `data` : Une string qui contient le layer (`map0` dans l'exemple).
|
||||
* `transp_char` : Le caractère qui ne sera pas affiché : permet de voir le layer d'en dessous. Si il est à `None`, aucun caractère ne sera enlevé.
|
||||
* `animations` (**Optionnel**) : Dictionnaire qui permet d'ajouter des animations à la map constitué de :
|
||||
* `replace_animations` : Dictionnaire pour les animations où un caractère est remplacé par un autre sur toute la map. Pour chaque caractère qui doit être animé, il faut y ajouter un autre dictionnaire où le nom est le caractère qui doit être animé. Ces dictionnaires sont constitués de :
|
||||
* `frames` : Liste qui contient les différent caractères qui constituent l'animation.
|
||||
* `animation_frame` : Quel caractère de l'animation va être affiché. C'est une position dans `frames`.
|
||||
* `coords_animations` : Dictionnaire pour les animations où un caractère est remplacé par un autre uniquement à une position bien précise. Pour chaque position qui doit être animée, il faut y ajouter un autre dictionnaire où le nom est `"x, y"` où x doit être remplacé par l'abscisse et y par l'ordonnée du caractère qui doit être animé. Ces dictionnaires sont constitués de :
|
||||
* `frames` : Liste qui contient les différent caractères qui constituent l'animation.
|
||||
* `animation_frame` : Quel caractère de l'animation va être affiché. C'est une position dans `frames`.
|
||||
* `jumps` : Liste des endroits où le joueur peut sauter d'une map à l'autre et/ou d'une position à l'autre. Cette liste est constituée de dictionnaires constituées de :
|
||||
* `x` et `y` : La position de départ.
|
||||
* `to_x` et `to_y` : La position de d'arrivée.
|
||||
* `to_map` : La map de l'arrivée.
|
||||
* `isactive` : Booléen : est ce que ce jump peut téléporter le joueur ?
|
||||
* `npc` : Liste des pnj qui sont des dictionnaires constituée de :
|
||||
* `x` et `y` : Leur position sur la map.
|
||||
* `isvisible` : est ce qu'ils sont visibles.
|
||||
* `collision_check` : Booléen : est ce qu'on peut entrer en collision avec eux.
|
||||
* `layer` : Au dessus de quel layer ils s'affichent.
|
||||
* `map` : Sur quelle map ils sont.
|
||||
* `char` : Quel caractère est utilisé pour les afficher.
|
||||
* `name` : Le nom du pnj, ce qui permet de l'identifier.
|
||||
|
||||
## `player`, le joueur
|
||||
|
||||
### Un exemple de `player`
|
||||
|
||||
```python
|
||||
player = {
|
||||
"x": 3,
|
||||
"y": 4,
|
||||
"isvisible": 1,
|
||||
"collision_check": 1,
|
||||
"layer": 0,
|
||||
"playerc": '@'
|
||||
}
|
||||
```
|
||||
|
||||
### Description
|
||||
|
||||
* `x` et `y` : Sa position sur la map.
|
||||
* `isvisible` : est ce qu'il est visible.
|
||||
* `collision_check` : Booléen : est ce qu'il est stoppé par les objets durs et les pnj.
|
||||
* `layer` : Au dessus de quel layer ils s'affiche.
|
||||
|
||||
La map sur laquelle il est est déjà définie dans `map_num` de `world`.
|
||||
|
||||
## Les fonctions de `Scii`
|
||||
|
||||
`self` est la classe `Scii`.
|
||||
|
||||
* `mainloop(self)` : Lance la boucle du jeu.
|
||||
* Déjà utilisés dans mainloop :
|
||||
* `get_map_width(self, map_data)` : Retourne la largeur de la map.
|
||||
* `map_data` : dictionnaire de la map comme il est dans `world["maps"]`
|
||||
* `get_map_height(self, map_data)` : Retourne la hauteur de la map.
|
||||
* `map_data` : dictionnaire de la map comme il est dans `world["maps"]`
|
||||
* `draw_map(self, mode, show_player)`
|
||||
* `mode` : Mode de dessin de la map (`STICKY`, `CENTERED`, `BLOCKS` ou `STICKYBLOCKS`)
|
||||
* `show_player` : Booléen : est ce que le joueur doit être affiché.
|
||||
* `def show_text(self, text)` : Affiche du texte. L'historique du texte affiché est disponible en appuyant sur `scii_keys["old_messages"]` ou en appelant `show_old_messages(self)`.
|
||||
* `text` : Texte à afficher.
|
||||
* `portrait` (**Optionnel**) : Affiche un portrait à côté du texte.
|
||||
* `show_old_messages(self)` : Affiche les anciennes conversations.
|
||||
* `ask_choice(self, text, choices)` : Demande à l'utilisateur de faire un choix parmi les choix proposés dans `choices`. Retourne le choix fait. Le premier choix est le numéro 1, et les prochains sont à chaque fois de 1 plus grands : dans `["Oui", "Non", "Je ne sais pas"]` "Oui" est le choix numéro 1, "Non" est le choix numéro 2 et "Je ne sais pas" est le choix numéro 3.
|
||||
* `text` : Texte à afficher avant de demander à l'utilisateur quel proposition il fait. Les choix seront affichés en dessous.
|
||||
* `choices` : Liste de chaînes de caractères des choix que l'utilisateur.
|
||||
* `portrait` (**Optionnel**) : Affiche un portrait à côté du texte.
|
596
LIBSCII.py
596
LIBSCII.py
|
@ -1,276 +1,320 @@
|
|||
"""
|
||||
SCII - make ASCII RPG with multiple layered maps.
|
||||
Copyright (C) 2023 Mibi88
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
"""
|
||||
|
||||
# e-mail : mbcontact50@gmail.com
|
||||
|
||||
#--- scii - make ASCII RPG with multiple layered maps ---
|
||||
|
||||
# Map drawing modes
|
||||
|
||||
STICKY = 0
|
||||
CENTERED = 1
|
||||
BLOCKS = 2
|
||||
STICKYBLOCKS = 3
|
||||
|
||||
class Scii:
|
||||
def __init__(self, world, player, on_npc_collision, scii_keys = None, get_input_text = None, no_collision = None, collision_checker = None,
|
||||
message_history_max = None, screen_width = 21, screen_height = 7):
|
||||
self.world = world
|
||||
self.player = player
|
||||
self.map_num = world["map_num"]
|
||||
self.screen_width = screen_width
|
||||
self.screen_height = screen_height-1
|
||||
self.screen_x_middle = self.screen_width//2
|
||||
self.screen_y_middle = self.screen_height//2
|
||||
self.screen_size = self.screen_width*self.screen_height
|
||||
self._vram = [' ' for i in range(self.screen_size)]
|
||||
self.on_char_handlers = {}
|
||||
if scii_keys: self.scii_keys = scii_keys
|
||||
else: self.scii_keys = {"left": '4', "right": '6', "up": '8', "down": '2', "quit": 'q', "old_messages": '0'}
|
||||
if get_input_text: self.get_input_text = get_input_text
|
||||
else: self.get_input_text = self._default_intext
|
||||
if collision_checker: self.collision_checker = collision_checker
|
||||
else: self.collision_checker = self._default_collision_check
|
||||
self.last_key = ""
|
||||
if not message_history_max: self.message_history_max = 20
|
||||
else: self.message_history_max = message_history_max
|
||||
self.old_messages = []
|
||||
self.on_npc_collision = on_npc_collision
|
||||
if no_collision: self.no_collision = no_collision
|
||||
else: self.no_collision = " ^"
|
||||
self.collision_npc = None
|
||||
def _default_collision_check(self, world, player, x, y):
|
||||
collision = 0
|
||||
for i in self.world["maps"][self.map_num]["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
try:
|
||||
c = data[y][x]
|
||||
if c != i["transp_char"] and not (c in self.no_collision):
|
||||
return 1
|
||||
for npc in world["npc"]:
|
||||
if npc["map"] == self.map_num and npc["x"] == x and npc["y"] == y and npc["collision_check"]:
|
||||
self.collision_npc = npc
|
||||
return 2
|
||||
except:
|
||||
if x >= 0 and x < self.get_map_width() and y >= 0 and y < self.get_map_height(): return 0
|
||||
else: return 1
|
||||
return 0
|
||||
def _default_intext(self):
|
||||
return "> "
|
||||
def _input(self):
|
||||
i = input(self.get_input_text())
|
||||
if len(i) > 0:
|
||||
self.last_key = i[0]
|
||||
return self.last_key
|
||||
return self.last_key
|
||||
def _mainloop(self):
|
||||
key = ""
|
||||
while key != self.scii_keys["quit"]:
|
||||
self.draw_map(self.world["dmode"], self.player["isvisible"])
|
||||
key = self._input()
|
||||
nx = self.player["x"]
|
||||
ny = self.player["y"]
|
||||
if key == self.scii_keys["left"]: nx = self.player["x"]-1
|
||||
elif key == self.scii_keys["right"]: nx = self.player["x"]+1
|
||||
elif key == self.scii_keys["up"]: ny = self.player["y"]-1
|
||||
elif key == self.scii_keys["down"]: ny = self.player["y"]+1
|
||||
collision = self.collision_checker(self.world, self.player, nx, ny)
|
||||
if self.player["collision_check"] and not collision:
|
||||
self.player["x"] = nx
|
||||
self.player["y"] = ny
|
||||
elif collision == 2 and self.collision_npc != None:
|
||||
self.on_npc_collision(self, self.collision_npc)
|
||||
for jump in self.world["maps"][self.map_num]["jumps"]:
|
||||
if jump["isactive"] and self.player["x"] == jump["x"] and self.player["y"] == jump["y"]:
|
||||
self.player["x"] = jump["to_x"]
|
||||
self.player["y"] = jump["to_y"]
|
||||
self.map_num = jump["to_map"]
|
||||
self.world["map_num"] = self.map_num
|
||||
if key == self.scii_keys["old_messages"]: self.show_old_messages()
|
||||
def mainloop(self):
|
||||
try:
|
||||
self._mainloop()
|
||||
except Exception as e: print("Ow there was an error in SCII : {}".format(e))
|
||||
def _setc(self, x, y, c):
|
||||
if len(c) == 1 and x>=0 and x<self.screen_width and y>=0 and y<self.screen_height:
|
||||
self._vram[y*self.screen_width+x] = c
|
||||
def _dvram(self):
|
||||
for y in range(self.screen_height):
|
||||
for x in range(self.screen_width):
|
||||
print(self._vram[y*self.screen_width+x], end="")
|
||||
print()
|
||||
def _cvram(self):
|
||||
self._vram = [' ' for i in range(self.screen_size)]
|
||||
def get_map_width(self, map_data):
|
||||
max_len = 0
|
||||
for i in map_data["layers"]:
|
||||
for line in i["data"].split('\n'):
|
||||
if len(line)>max_len: max_len = len(line)
|
||||
return max_len
|
||||
def get_map_height(self, map_data):
|
||||
max_len = 0
|
||||
for i in map_data["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
if len(data) > max_len:
|
||||
max_len = len(data)
|
||||
return max_len
|
||||
def draw_map(self, mode, show_player):
|
||||
map_data = self.world["maps"][self.map_num]
|
||||
osx = self.player["x"]
|
||||
osy = self.player["y"]
|
||||
sx = osx
|
||||
sy = osy
|
||||
if mode == STICKY:
|
||||
sx -= self.screen_x_middle
|
||||
sy -= self.screen_y_middle
|
||||
# Fix x
|
||||
if sx < 0:
|
||||
sx = 0
|
||||
elif sx + self.screen_width > self.get_map_width(map_data):
|
||||
sx = self.get_map_width(map_data) - self.screen_width
|
||||
px = osx - sx
|
||||
# Fix y
|
||||
if sy < 0:
|
||||
sy = 0
|
||||
elif sy + self.screen_height > self.get_map_height(map_data):
|
||||
sy = self.get_map_height(map_data) - self.screen_height
|
||||
py = osy - sy
|
||||
elif mode == CENTERED:
|
||||
px = self.screen_x_middle
|
||||
py = self.screen_y_middle
|
||||
sx -= self.screen_x_middle
|
||||
sy -= self.screen_y_middle
|
||||
elif mode == BLOCKS:
|
||||
sx = sx // self.screen_width * self.screen_width
|
||||
sy = sy // self.screen_height * self.screen_height
|
||||
px = osx-sx
|
||||
py = osy-sy
|
||||
elif mode == STICKYBLOCKS:
|
||||
sx = sx // self.screen_width * self.screen_width
|
||||
sy = sy // self.screen_height * self.screen_height
|
||||
if sx > self.get_map_width(map_data) - self.screen_width:
|
||||
sx = self.get_map_width(map_data) - self.screen_width
|
||||
if sy > self.get_map_height(map_data) - self.screen_height:
|
||||
sy = self.get_map_height(map_data) - self.screen_height
|
||||
px = osx-sx
|
||||
py = osy-sy
|
||||
self._cvram()
|
||||
layerc = 0
|
||||
for i in map_data["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
map_width = self.get_map_width(map_data)
|
||||
map_height = self.get_map_height(map_data)
|
||||
for y in range(self.screen_height):
|
||||
for x in range(self.screen_width):
|
||||
try:
|
||||
if sx+x >= 0 and sx+x < map_width and sy+y >= 0 and sy+y < map_height:
|
||||
c = data[sy+y][sx+x]
|
||||
if c != i["transp_char"]:
|
||||
self._setc(x, y, c)
|
||||
except: pass
|
||||
for npc in self.world["npc"]:
|
||||
if npc["layer"] == layerc and npc["map"] == self.map_num and npc["isvisible"]:
|
||||
self._setc(px + (npc["x"] - osx), py + (npc["y"] - osy), npc["char"])
|
||||
if layerc == self.player["layer"]: self._setc(px, py, self.player["playerc"])
|
||||
layerc += 1
|
||||
self._dvram()
|
||||
def show_text(self, text):
|
||||
dwidth = self.screen_width - 2
|
||||
self._cvram()
|
||||
dtext = ""
|
||||
sz = 0
|
||||
y = 0
|
||||
for i in text.split(" "):
|
||||
if len(i) > dwidth:
|
||||
count = sz
|
||||
for c in i:
|
||||
dtext += c
|
||||
count += 1
|
||||
if count >= dwidth - 1:
|
||||
dtext += "-\n"
|
||||
count = 0
|
||||
y += 1
|
||||
dtext += ' '
|
||||
sz = count + 1
|
||||
elif sz + len(i) + 1 < dwidth:
|
||||
dtext += i + ' '
|
||||
sz += len(i) + 1
|
||||
y += i.count('\n')
|
||||
else:
|
||||
dtext += '\n' + i + ' '
|
||||
sz = len(i) + 1
|
||||
y += i.count('\n') + 1
|
||||
x = 0
|
||||
y = 0
|
||||
msg = ""
|
||||
for i in dtext:
|
||||
if i == '\n':
|
||||
y += 1
|
||||
x = 0
|
||||
if y >= self.screen_height:
|
||||
self._setc(self.screen_width - 1, self.screen_y_middle, '>')
|
||||
self._dvram()
|
||||
intxt = input("Continue > ")
|
||||
if len(intxt) > 0:
|
||||
if intxt[0] == self.scii_keys["quit"]: return
|
||||
y = 0
|
||||
x = 0
|
||||
self._add_message(msg)
|
||||
msg = ""
|
||||
self._cvram()
|
||||
else:
|
||||
self._setc(x, y, i)
|
||||
x += 1
|
||||
msg += i
|
||||
self._dvram()
|
||||
if msg != "": self._add_message(msg)
|
||||
input("End of the text > ")
|
||||
def _add_message(self, message):
|
||||
self.old_messages.append(message)
|
||||
while(len(self.old_messages) > self.message_history_max): del self.old_messages[0]
|
||||
def show_old_messages(self):
|
||||
if len(self.old_messages) < 1:
|
||||
self._cvram()
|
||||
x = 1
|
||||
for i in "No messages.":
|
||||
self._setc(x, self.screen_y_middle, i)
|
||||
x += 1
|
||||
self._dvram()
|
||||
input("Go back > ")
|
||||
c = len(self.old_messages)
|
||||
for i in [i for i in self.old_messages[::-1]]:
|
||||
self.show_text(i)
|
||||
c -= 1
|
||||
if c < 1: input("Go back > ")
|
||||
else: intxt = input("Next message > ")
|
||||
if len(intxt) > 0:
|
||||
if intxt[0] == 'q': return
|
||||
def ask_choice(self, text, choices):
|
||||
choice = 0
|
||||
while choice < 1 or choice > len(choices):
|
||||
newtext = text + "\n\n"
|
||||
c = 1
|
||||
for i in choices:
|
||||
newtext += "{} - {}\n".format(c, i)
|
||||
c += 1
|
||||
self.show_text(newtext)
|
||||
try:
|
||||
choice = int(input("Choice > "))
|
||||
except: choice = -1
|
||||
return choice
|
||||
#---
|
||||
"""
|
||||
SCII - make ASCII RPG with multiple layered maps.
|
||||
Copyright (C) 2023 Mibi88
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
"""
|
||||
|
||||
# e-mail : mbcontact50@gmail.com
|
||||
|
||||
#--- scii - make ASCII RPG with multiple layered maps ---
|
||||
|
||||
# Map drawing modes
|
||||
|
||||
STICKY = 0
|
||||
CENTERED = 1
|
||||
BLOCKS = 2
|
||||
STICKYBLOCKS = 3
|
||||
|
||||
class Scii:
|
||||
def __init__(self, world, player, on_npc_collision, scii_keys = {"left": '4', "right": '6', "up": '8', "down": '2', "quit": 'q', "old_messages": '0'}, get_input_text = None,
|
||||
no_collision = " ^", collision_checker = None, message_history_max = 20, screen_width = 21, screen_height = 7, portrait_width = 7):
|
||||
self.world = world
|
||||
self.player = player
|
||||
self.map_num = world["map_num"]
|
||||
self.screen_width = screen_width
|
||||
self.screen_height = screen_height-1
|
||||
self.screen_x_middle = self.screen_width//2
|
||||
self.screen_y_middle = self.screen_height//2
|
||||
self.screen_size = self.screen_width*self.screen_height
|
||||
self._vram = [' ' for i in range(self.screen_size)]
|
||||
self.on_char_handlers = {}
|
||||
self.scii_keys = scii_keys
|
||||
if get_input_text: self.get_input_text = get_input_text
|
||||
else: self.get_input_text = self._default_intext
|
||||
if collision_checker: self.collision_checker = collision_checker
|
||||
else: self.collision_checker = self._default_collision_check
|
||||
self.last_key = ""
|
||||
self.message_history_max = message_history_max
|
||||
self.old_messages = []
|
||||
self.on_npc_collision = on_npc_collision
|
||||
self.no_collision = no_collision
|
||||
self.collision_npc = None
|
||||
self.portrait_width = portrait_width
|
||||
def _default_collision_check(self, world, player, x, y):
|
||||
collision = 0
|
||||
for i in world["maps"][self.map_num]["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
try:
|
||||
c = data[y][x]
|
||||
if c != i["transp_char"] and not (c in self.no_collision):
|
||||
return 1
|
||||
for npc in world["npc"]:
|
||||
if npc["map"] == self.map_num and npc["x"] == x and npc["y"] == y and npc["collision_check"]:
|
||||
self.collision_npc = npc
|
||||
return 2
|
||||
except:
|
||||
if x >= 0 and x < self.get_map_width(world["maps"][self.map_num]) and y >= 0 and y < self.get_map_height(world["maps"][self.map_num]): return 0
|
||||
else: return 1
|
||||
return 0
|
||||
def _default_intext(self):
|
||||
return "> "
|
||||
def _input(self):
|
||||
try: i = input(self.get_input_text())
|
||||
except: i = input(self.get_input_text(self))
|
||||
if len(i) > 0:
|
||||
self.last_key = i[0]
|
||||
return self.last_key
|
||||
return self.last_key
|
||||
def _mainloop(self):
|
||||
key = ""
|
||||
while key != self.scii_keys["quit"]:
|
||||
self.draw_map(self.world["dmode"], self.player["isvisible"])
|
||||
key = self._input()
|
||||
nx = self.player["x"]
|
||||
ny = self.player["y"]
|
||||
if key == self.scii_keys["left"]: nx = self.player["x"]-1
|
||||
elif key == self.scii_keys["right"]: nx = self.player["x"]+1
|
||||
elif key == self.scii_keys["up"]: ny = self.player["y"]-1
|
||||
elif key == self.scii_keys["down"]: ny = self.player["y"]+1
|
||||
collision = self.collision_checker(self.world, self.player, nx, ny)
|
||||
if self.player["collision_check"] and not collision:
|
||||
self.player["x"] = nx
|
||||
self.player["y"] = ny
|
||||
elif collision == 2 and self.collision_npc != None:
|
||||
self.on_npc_collision(self, self.collision_npc)
|
||||
for jump in self.world["maps"][self.map_num]["jumps"]:
|
||||
if jump["isactive"] and self.player["x"] == jump["x"] and self.player["y"] == jump["y"]:
|
||||
self.player["x"] = jump["to_x"]
|
||||
self.player["y"] = jump["to_y"]
|
||||
self.map_num = jump["to_map"]
|
||||
self.world["map_num"] = self.map_num
|
||||
if key == self.scii_keys["old_messages"]: self.show_old_messages()
|
||||
def mainloop(self):
|
||||
try:
|
||||
self._mainloop()
|
||||
except Exception as e: print("Ow there was an error in SCII : {}".format(e))
|
||||
def _setc(self, x, y, c):
|
||||
if len(c) == 1 and x>=0 and x<self.screen_width and y>=0 and y<self.screen_height:
|
||||
self._vram[y*self.screen_width+x] = c
|
||||
def _dvram(self):
|
||||
for y in range(self.screen_height):
|
||||
for x in range(self.screen_width):
|
||||
print(self._vram[y*self.screen_width+x], end="")
|
||||
print()
|
||||
def _cvram(self):
|
||||
self._vram = [' ' for i in range(self.screen_size)]
|
||||
def get_map_width(self, map_data):
|
||||
max_len = 0
|
||||
for i in map_data["layers"]:
|
||||
for line in i["data"].split('\n'):
|
||||
if len(line)>max_len: max_len = len(line)
|
||||
return max_len
|
||||
def get_map_height(self, map_data):
|
||||
max_len = 0
|
||||
for i in map_data["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
if len(data) > max_len:
|
||||
max_len = len(data)
|
||||
return max_len
|
||||
def draw_map(self, mode, show_player):
|
||||
map_data = self.world["maps"][self.map_num]
|
||||
osx = self.player["x"]
|
||||
osy = self.player["y"]
|
||||
sx = osx
|
||||
sy = osy
|
||||
if mode == STICKY:
|
||||
sx -= self.screen_x_middle
|
||||
sy -= self.screen_y_middle
|
||||
# Fix x
|
||||
if sx < 0:
|
||||
sx = 0
|
||||
elif sx + self.screen_width > self.get_map_width(map_data):
|
||||
sx = self.get_map_width(map_data) - self.screen_width
|
||||
px = osx - sx
|
||||
# Fix y
|
||||
if sy < 0:
|
||||
sy = 0
|
||||
elif sy + self.screen_height > self.get_map_height(map_data):
|
||||
sy = self.get_map_height(map_data) - self.screen_height
|
||||
py = osy - sy
|
||||
elif mode == CENTERED:
|
||||
px = self.screen_x_middle
|
||||
py = self.screen_y_middle
|
||||
sx -= self.screen_x_middle
|
||||
sy -= self.screen_y_middle
|
||||
elif mode == BLOCKS:
|
||||
sx = sx // self.screen_width * self.screen_width
|
||||
sy = sy // self.screen_height * self.screen_height
|
||||
px = osx-sx
|
||||
py = osy-sy
|
||||
elif mode == STICKYBLOCKS:
|
||||
sx = sx // self.screen_width * self.screen_width
|
||||
sy = sy // self.screen_height * self.screen_height
|
||||
if sx > self.get_map_width(map_data) - self.screen_width:
|
||||
sx = self.get_map_width(map_data) - self.screen_width
|
||||
if sy > self.get_map_height(map_data) - self.screen_height:
|
||||
sy = self.get_map_height(map_data) - self.screen_height
|
||||
px = osx-sx
|
||||
py = osy-sy
|
||||
self._cvram()
|
||||
layerc = 0
|
||||
for i in map_data["layers"]:
|
||||
data = i["data"].split("\n")
|
||||
while("" in data): data.remove("")
|
||||
map_width = self.get_map_width(map_data)
|
||||
map_height = self.get_map_height(map_data)
|
||||
if "animations" in i:
|
||||
animations = 1
|
||||
replace_animations = i["animations"]["replace_animations"]
|
||||
coords_animations = i["animations"]["coords_animations"]
|
||||
else:
|
||||
animations = 0
|
||||
for y in range(self.screen_height):
|
||||
for x in range(self.screen_width):
|
||||
try:
|
||||
if sx+x >= 0 and sx+x < map_width and sy+y >= 0 and sy+y < map_height:
|
||||
c = data[sy+y][sx+x]
|
||||
if animations:
|
||||
if c in replace_animations:
|
||||
c = replace_animations[c]["frames"][replace_animations[c]["animation_frame"]]
|
||||
if "{}, {}".format(sx+x, sy+y) in coords_animations:
|
||||
c = coords_animations["{}, {}".format(sx+x, sy+y)]["frames"][coords_animations["{}, {}".format(sx+x, sy+y)]["animation_frame"]]
|
||||
if c != i["transp_char"]:
|
||||
self._setc(x, y, c)
|
||||
except: pass
|
||||
for npc in self.world["npc"]:
|
||||
if npc["layer"] == layerc and npc["map"] == self.map_num and npc["isvisible"]:
|
||||
self._setc(px + (npc["x"] - osx), py + (npc["y"] - osy), npc["char"])
|
||||
if layerc == self.player["layer"] and show_player: self._setc(px, py, self.player["playerc"])
|
||||
if animations:
|
||||
for key in i["animations"]["replace_animations"]:
|
||||
i["animations"]["replace_animations"][key]["animation_frame"] += 1
|
||||
if i["animations"]["replace_animations"][key]["animation_frame"] >= len(i["animations"]["replace_animations"][key]["frames"]):
|
||||
i["animations"]["replace_animations"][key]["animation_frame"] = 0
|
||||
for key in i["animations"]["coords_animations"]:
|
||||
i["animations"]["coords_animations"][key]["animation_frame"] += 1
|
||||
if i["animations"]["coords_animations"][key]["animation_frame"] >= len(i["animations"]["coords_animations"][key]["frames"]):
|
||||
i["animations"]["coords_animations"][key]["animation_frame"] = 0
|
||||
layerc += 1
|
||||
self._dvram()
|
||||
def _show_text(self, text, portrait = None):
|
||||
if portrait: dwidth = self.screen_width - (2 + self.portrait_width)
|
||||
else: dwidth = self.screen_width - 2
|
||||
self._cvram()
|
||||
dtext = ""
|
||||
sz = 0
|
||||
y = 0
|
||||
for i in text.split(" "):
|
||||
if len(i) > dwidth:
|
||||
count = sz
|
||||
for c in i:
|
||||
dtext += c
|
||||
count += 1
|
||||
if count >= dwidth - 1:
|
||||
dtext += "-\n"
|
||||
count = 0
|
||||
y += 1
|
||||
dtext += ' '
|
||||
sz = count + 1
|
||||
elif sz + len(i) + 1 < dwidth:
|
||||
dtext += i + ' '
|
||||
sz += len(i) + 1
|
||||
y += i.count('\n')
|
||||
else:
|
||||
dtext += '\n' + i + ' '
|
||||
sz = len(i) + 1
|
||||
y += i.count('\n') + 1
|
||||
x = 0
|
||||
y = 0
|
||||
msg = ""
|
||||
for i in dtext:
|
||||
if i == '\n':
|
||||
y += 1
|
||||
x = 0
|
||||
if y >= self.screen_height:
|
||||
if portrait: self._draw_portrait(portrait)
|
||||
self._setc(self.screen_width - 1, self.screen_y_middle, '>')
|
||||
self._dvram()
|
||||
intxt = input("Continue > ")
|
||||
if len(intxt) > 0:
|
||||
if intxt[0] == self.scii_keys["quit"]: return
|
||||
y = 0
|
||||
x = 0
|
||||
self._add_message(msg)
|
||||
msg = ""
|
||||
self._cvram()
|
||||
else:
|
||||
if portrait: self._setc(8 + x, y, i)
|
||||
else: self._setc(x, y, i)
|
||||
x += 1
|
||||
msg += i
|
||||
if portrait: self._draw_portrait(portrait)
|
||||
self._dvram()
|
||||
if msg != "": self._add_message(msg)
|
||||
def show_text(self, text, portrait = None):
|
||||
self._show_text(text, portrait)
|
||||
input("End of the text > ")
|
||||
def _draw_portrait(self, portrait):
|
||||
for i in range(self.screen_height):
|
||||
self._setc(1, i, '|')
|
||||
self._setc(self.portrait_width, i, '|')
|
||||
for i in range(self.portrait_width):
|
||||
self._setc(1 + i, self.screen_height - 1, '-')
|
||||
self._setc(1 + i, 0, '-')
|
||||
self._setc(1, self.screen_height - 1, '+')
|
||||
self._setc(self.portrait_width, self.screen_height - 1, '+')
|
||||
self._setc(1, 0, '+')
|
||||
self._setc(self.portrait_width, 0, '+')
|
||||
portrait_data = portrait.split('\n')
|
||||
while("" in portrait_data): portrait_data.remove("")
|
||||
for y in range(1, self.screen_height - 1):
|
||||
for x in range(2, self.portrait_width):
|
||||
try: self._setc(x, y, portrait_data[y - 1][x - 2])
|
||||
except: pass
|
||||
def _add_message(self, message):
|
||||
self.old_messages.append(message)
|
||||
while(len(self.old_messages) > self.message_history_max): del self.old_messages[0]
|
||||
def show_old_messages(self):
|
||||
if len(self.old_messages) < 1:
|
||||
self._cvram()
|
||||
x = 1
|
||||
for i in "No messages.":
|
||||
self._setc(x, self.screen_y_middle, i)
|
||||
x += 1
|
||||
self._dvram()
|
||||
input("Go back > ")
|
||||
c = len(self.old_messages)
|
||||
for i in [i for i in self.old_messages[::-1]]:
|
||||
self._show_text(i)
|
||||
c -= 1
|
||||
if c < 1: input("Go back > ")
|
||||
else: intxt = input("Next message > ")
|
||||
if len(intxt) > 0:
|
||||
if intxt[0] == 'q': return
|
||||
def ask_choice(self, text, choices, portrait = None):
|
||||
choice = 0
|
||||
while choice < 1 or choice > len(choices):
|
||||
newtext = text + "\n"
|
||||
c = 1
|
||||
for i in choices:
|
||||
if c < len(choices): newtext += "{}:{}\n".format(c, i)
|
||||
else: newtext += "{}:{}".format(c, i)
|
||||
c += 1
|
||||
self._show_text(newtext, portrait)
|
||||
try:
|
||||
choice = int(input("Choice > "))
|
||||
except: choice = -1
|
||||
self._add_message("You selected choice {}:{}".format(choice, choices[choice - 1]))
|
||||
return choice
|
||||
#---
|
||||
|
|
38
SCIITEST.py
38
SCIITEST.py
|
@ -12,16 +12,43 @@ map0 = """
|
|||
########################################################
|
||||
"""
|
||||
|
||||
portrait = """
|
||||
___
|
||||
/\\/\\\\
|
||||
\\. ./
|
||||
/ \\
|
||||
"""
|
||||
|
||||
animations = {
|
||||
"replace_animations": {
|
||||
'-': {
|
||||
"frames": ['=', '-'],
|
||||
"animation_frame": 0
|
||||
},
|
||||
'=': {
|
||||
"frames": ['-', '='],
|
||||
"animation_frame": 0
|
||||
}
|
||||
},
|
||||
"coords_animations": {
|
||||
"4, 4": {
|
||||
"frames": ['^', ' '],
|
||||
"animation_frame": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def npc_collision(self, npc):
|
||||
global portrait
|
||||
if npc["name"] == "lephenixnoir":
|
||||
self.show_text("""C'est moi, Lephe',
|
||||
Je suis aussi dans le jeu !""")
|
||||
Je suis aussi dans le jeu !""", portrait)
|
||||
if npc["name"] == "massena":
|
||||
txt = self.ask_choice("""T'as besoin d'aide pour des pixel arts ?""", ["Oui", "Non"])
|
||||
txt = self.ask_choice("T'as besoin d'aide pour des pixel arts ?", ["Oui", "Non"])
|
||||
if txt == 1:
|
||||
self.show_text("""Merci !""")
|
||||
self.show_text("Merci !")
|
||||
else:
|
||||
self.show_text("""Dommage""")
|
||||
self.show_text("Dommage")
|
||||
|
||||
world = {
|
||||
"dmode": STICKY,
|
||||
|
@ -31,7 +58,8 @@ world = {
|
|||
"layers": [
|
||||
{
|
||||
"data": map0,
|
||||
"transp_char": None
|
||||
"transp_char": None,
|
||||
"animations": animations
|
||||
}
|
||||
],
|
||||
"jumps": [
|
||||
|
|
Loading…
Reference in New Issue