上传文件至 /
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