上传文件至 /

RPG样例
This commit is contained in:
2025-05-18 11:48:19 +08:00
Unverified
commit 73212e2605
5 changed files with 288 additions and 0 deletions

15
config.py Normal file
View 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
View 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
View 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
View 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
View 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)