上传文件至 /
RPG样例
This commit is contained in:
15
config.py
Normal file
15
config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import curses
|
||||
|
||||
difficulty = "Normal"
|
||||
player = {"x": 0, "y": 0}
|
||||
camera = {"x": 0, "y": 0}
|
||||
# objects 从 set 改为 dict:位置 → 物品类型字符串
|
||||
objects = {}
|
||||
|
||||
def init_colors():
|
||||
curses.start_color()
|
||||
curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK) # 玩家
|
||||
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # 绿物品
|
||||
curses.init_pair(5, curses.COLOR_RED, curses.COLOR_BLACK) # 红物品
|
||||
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLACK) # 菜单文字
|
||||
curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK) # 标题/提示
|
||||
80
inventory.py
Normal file
80
inventory.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# inventory.py
|
||||
|
||||
import curses
|
||||
|
||||
class Inventory:
|
||||
def __init__(self, cols=5, rows=4):
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.slots = {i: None for i in range(cols * rows)}
|
||||
for i, ch in enumerate(["S", "H", "P"]):
|
||||
if i < cols * rows:
|
||||
self.slots[i] = ch
|
||||
self.held = None
|
||||
self.held_pos = (0, 0)
|
||||
|
||||
def draw(self, stdscr, scr_w, scr_h):
|
||||
side = int(min(scr_w, scr_h) * 0.6)
|
||||
cell = (side - 2) // max(self.cols, self.rows)
|
||||
win_w = cell * self.cols + 2
|
||||
win_h = cell * self.rows + 2
|
||||
sx = (scr_w - win_w) // 2
|
||||
sy = (scr_h - win_h) // 2
|
||||
|
||||
win = curses.newwin(win_h, win_w, sy, sx)
|
||||
win.bkgd(' ', curses.color_pair(3))
|
||||
win.box()
|
||||
|
||||
for idx in range(self.cols * self.rows):
|
||||
r, c = divmod(idx, self.cols)
|
||||
y0 = 1 + r * cell
|
||||
x0 = 1 + c * cell
|
||||
# 绘格子
|
||||
for x in range(x0, x0+cell):
|
||||
win.addch(y0, x, curses.ACS_HLINE)
|
||||
win.addch(y0+cell-1, x, curses.ACS_HLINE)
|
||||
for y in range(y0, y0+cell):
|
||||
win.addch(y, x0, curses.ACS_VLINE)
|
||||
win.addch(y, x0+cell-1, curses.ACS_VLINE)
|
||||
win.addch(y0, x0, curses.ACS_ULCORNER)
|
||||
win.addch(y0, x0+cell-1, curses.ACS_URCORNER)
|
||||
win.addch(y0+cell-1, x0, curses.ACS_LLCORNER)
|
||||
win.addch(y0+cell-1, x0+cell-1, curses.ACS_LRCORNER)
|
||||
# 画字母
|
||||
ch = self.slots.get(idx)
|
||||
if ch:
|
||||
win.addch(y0 + cell//2, x0 + cell//2, ch, curses.color_pair(2))
|
||||
|
||||
win.refresh()
|
||||
|
||||
if self.held:
|
||||
my, mx = self.held_pos
|
||||
if 0 <= my < scr_h and 0 <= mx < scr_w:
|
||||
stdscr.addch(my, mx, self.held, curses.color_pair(2))
|
||||
stdscr.refresh()
|
||||
|
||||
return (sy, sx, win_h, win_w)
|
||||
|
||||
def click(self, my, mx, area):
|
||||
sy, sx, h, w = area
|
||||
if not (sy <= my < sy+h and sx <= mx < sx+w):
|
||||
return
|
||||
rel_y, rel_x = my-sy-1, mx-sx-1
|
||||
cell_h = (h-2)//self.rows
|
||||
cell_w = (w-2)//self.cols
|
||||
r = rel_y // cell_h
|
||||
c = rel_x // cell_w
|
||||
if 0 <= r < self.rows and 0 <= c < self.cols:
|
||||
idx = r*self.cols + c
|
||||
if self.held:
|
||||
if self.slots.get(idx) is None:
|
||||
self.slots[idx] = self.held
|
||||
self.held = None
|
||||
else:
|
||||
ch = self.slots.get(idx)
|
||||
if ch:
|
||||
self.held = ch
|
||||
self.slots[idx] = None
|
||||
|
||||
def move(self, my, mx):
|
||||
self.held_pos = (my, mx)
|
||||
31
main.py
Normal file
31
main.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# main.py
|
||||
|
||||
import curses
|
||||
from menus import main_menu, start_game_menu, settings_menu, credits_menu
|
||||
from world import game_loop, init_objects
|
||||
from config import init_colors, player, camera
|
||||
|
||||
def main(stdscr):
|
||||
curses.curs_set(0)
|
||||
init_colors()
|
||||
|
||||
h,w=stdscr.getmaxyx()
|
||||
height, width = h-1, w
|
||||
|
||||
player["x"],player["y"]=0,0
|
||||
camera["x"],camera["y"] = player["x"]-width//2, player["y"]-height//2
|
||||
|
||||
init_objects(width, height)
|
||||
|
||||
while True:
|
||||
choice=main_menu(stdscr)
|
||||
if choice==-1: break
|
||||
elif choice==0:
|
||||
mode=start_game_menu(stdscr)
|
||||
if mode in (0,1): game_loop(stdscr, width, height)
|
||||
elif choice==1: settings_menu(stdscr)
|
||||
elif choice==2: credits_menu(stdscr)
|
||||
elif choice==3: break
|
||||
|
||||
if __name__=="__main__":
|
||||
curses.wrapper(main)
|
||||
77
menus.py
Normal file
77
menus.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# menus.py
|
||||
|
||||
import curses
|
||||
from config import difficulty
|
||||
|
||||
def main_menu(stdscr):
|
||||
options = ["Start Game","Settings","Credits","Exit"]
|
||||
sel=0
|
||||
while True:
|
||||
stdscr.clear()
|
||||
h,w=stdscr.getmaxyx()
|
||||
if h<20 or w<50:
|
||||
stdscr.addstr(h//2,w//2-10,"Terminal too small!")
|
||||
stdscr.refresh(); stdscr.getch(); return -1
|
||||
# ASCII 标题
|
||||
draw_title(stdscr,h,w)
|
||||
for i,opt in enumerate(options):
|
||||
x=w//2-len(opt)//2-2; y=h//2+i
|
||||
if i==sel:
|
||||
stdscr.addstr(y,x,f"> {opt} <",curses.color_pair(2))
|
||||
else:
|
||||
stdscr.addstr(y,x,f" {opt} ",curses.color_pair(3))
|
||||
k=stdscr.getch()
|
||||
if k in (ord('w'),curses.KEY_UP): sel=(sel-1)%len(options)
|
||||
elif k in (ord('s'),curses.KEY_DOWN): sel=(sel+1)%len(options)
|
||||
elif k==ord('\n'): return sel
|
||||
|
||||
def draw_title(stdscr,h,w):
|
||||
title=r"""
|
||||
_______ _______ _______
|
||||
( ____ \( ___ )( ____ \
|
||||
| ( \/| ( ) || ( \/
|
||||
| | | | | || (__
|
||||
| | | | | || __)
|
||||
| | | | | || (
|
||||
| (____/\| (___) || (____/\
|
||||
(_______/(_______)(_______/
|
||||
"""
|
||||
for i,line in enumerate(title.splitlines()):
|
||||
stdscr.addstr(h//2-10+i, w//2-len(line)//2, line, curses.color_pair(4))
|
||||
|
||||
def start_game_menu(stdscr):
|
||||
return _generic(stdscr,["Infinite World","Maze","Back"])
|
||||
|
||||
def settings_menu(stdscr):
|
||||
global difficulty
|
||||
opts=["Easy","Normal","Hard"]; idx=opts.index(difficulty)
|
||||
while True:
|
||||
stdscr.clear(); h,w=stdscr.getmaxyx()
|
||||
stdscr.addstr(h//2-2,w//2-7,"Select Difficulty",curses.color_pair(4))
|
||||
for i,d in enumerate(opts):
|
||||
x=w//2-len(d)//2; y=h//2+i
|
||||
if i==idx: stdscr.addstr(y,x,f"> {d} <",curses.color_pair(2))
|
||||
else: stdscr.addstr(y,x,f" {d} ",curses.color_pair(3))
|
||||
k=stdscr.getch()
|
||||
if k in (ord('w'),curses.KEY_UP): idx=(idx-1)%3
|
||||
elif k in (ord('s'),curses.KEY_DOWN): idx=(idx+1)%3
|
||||
elif k==ord('\n'): difficulty=opts[idx]; return
|
||||
|
||||
def credits_menu(stdscr):
|
||||
stdscr.clear(); h,w=stdscr.getmaxyx()
|
||||
msg="Made by 陈坤阳"
|
||||
stdscr.addstr(h//2,w//2-len(msg)//2,msg,curses.color_pair(4))
|
||||
stdscr.refresh(); stdscr.getch()
|
||||
|
||||
def _generic(stdscr,opts):
|
||||
sel=0
|
||||
while True:
|
||||
stdscr.clear(); h,w=stdscr.getmaxyx()
|
||||
for i,o in enumerate(opts):
|
||||
x=w//2-len(o)//2; y=h//2+i
|
||||
if i==sel: stdscr.addstr(y,x,f"> {o} <",curses.color_pair(2))
|
||||
else: stdscr.addstr(y,x,f" {o} ",curses.color_pair(3))
|
||||
k=stdscr.getch()
|
||||
if k in (ord('w'),curses.KEY_UP): sel=(sel-1)%len(opts)
|
||||
elif k in (ord('s'),curses.KEY_DOWN): sel=(sel+1)%len(opts)
|
||||
elif k==ord('\n'): return sel
|
||||
85
world.py
Normal file
85
world.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import random
|
||||
import curses
|
||||
from config import objects, player, camera
|
||||
from inventory import inventory_add # 我们会在 inventory.py 里提供这个函数
|
||||
|
||||
def init_objects(width, height):
|
||||
objects.clear()
|
||||
# 全局范围随机生成
|
||||
for _ in range(99999):
|
||||
x = random.randint(-2000, 2000)
|
||||
y = random.randint(-2000, 2000)
|
||||
# 随机决定生成绿或红:10% 红,90% 绿
|
||||
typ = "red" if random.random() < 0.10 else "green"
|
||||
objects[(x, y)] = typ
|
||||
# 保证初始视野内也有一些
|
||||
camx, camy = camera["x"], camera["y"]
|
||||
for _ in range(200):
|
||||
x = random.randint(camx, camx + width - 1)
|
||||
y = random.randint(camy, camy + height - 1)
|
||||
typ = "red" if random.random() < 0.10 else "green"
|
||||
objects[(x, y)] = typ
|
||||
|
||||
def draw_map(stdscr, width, height):
|
||||
stdscr.clear()
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
wx, wy = camera["x"] + x, camera["y"] + y
|
||||
if (wx, wy) == (player["x"], player["y"]):
|
||||
stdscr.addstr(y, x, "@", curses.color_pair(1))
|
||||
elif (wx, wy) in objects:
|
||||
typ = objects[(wx, wy)]
|
||||
pair = 5 if typ == "red" else 2
|
||||
stdscr.addstr(y, x, "*", curses.color_pair(pair))
|
||||
stdscr.refresh()
|
||||
|
||||
def game_loop(stdscr, width, height):
|
||||
stdscr.nodelay(True)
|
||||
stdscr.timeout(50)
|
||||
|
||||
while True:
|
||||
draw_map(stdscr, width, height)
|
||||
key = stdscr.getch()
|
||||
|
||||
# ESC 退出
|
||||
if key == 27:
|
||||
break
|
||||
# 方向键移动
|
||||
elif key == curses.KEY_UP:
|
||||
player["y"] -= 1
|
||||
elif key == curses.KEY_DOWN:
|
||||
player["y"] += 1
|
||||
elif key == curses.KEY_LEFT:
|
||||
player["x"] -= 1
|
||||
elif key == curses.KEY_RIGHT:
|
||||
player["x"] += 1
|
||||
|
||||
# 摄像机跟随
|
||||
if player["x"] - camera["x"] < 20:
|
||||
camera["x"] -= 1
|
||||
elif player["x"] - camera["x"] > width - 21:
|
||||
camera["x"] += 1
|
||||
if player["y"] - camera["y"] < 7:
|
||||
camera["y"] -= 1
|
||||
elif player["y"] - camera["y"] > height - 8:
|
||||
camera["y"] += 1
|
||||
|
||||
# 随机在玩家附近小范围生成新物品
|
||||
if random.random() < 0.01:
|
||||
ox = random.randint(player["x"] - 20, player["x"] + 20)
|
||||
oy = random.randint(player["y"] - 20, player["y"] + 20)
|
||||
typ = "red" if random.random() < 0.10 else "green"
|
||||
objects[(ox, oy)] = typ
|
||||
|
||||
# 检测拾取
|
||||
pos = (player["x"], player["y"])
|
||||
if pos in objects:
|
||||
typ = objects.pop(pos)
|
||||
# 调用 inventory 系统加入背包
|
||||
inventory_add(typ)
|
||||
# 临时提示
|
||||
stdscr.addstr(height, 0,
|
||||
f"Picked up a {typ} item!",
|
||||
curses.color_pair(4))
|
||||
stdscr.refresh()
|
||||
curses.napms(300)
|
||||
Reference in New Issue
Block a user