commit 73212e26056c82ab2f8b0c72abbdc23192f89a55 Author: Tommmy Date: Sun May 18 11:48:19 2025 +0800 上传文件至 / RPG样例 diff --git a/config.py b/config.py new file mode 100644 index 0000000..41e820f --- /dev/null +++ b/config.py @@ -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) # 标题/提示 diff --git a/inventory.py b/inventory.py new file mode 100644 index 0000000..f980020 --- /dev/null +++ b/inventory.py @@ -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) diff --git a/main.py b/main.py new file mode 100644 index 0000000..36af4a3 --- /dev/null +++ b/main.py @@ -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) \ No newline at end of file diff --git a/menus.py b/menus.py new file mode 100644 index 0000000..1356f94 --- /dev/null +++ b/menus.py @@ -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 diff --git a/world.py b/world.py new file mode 100644 index 0000000..3482858 --- /dev/null +++ b/world.py @@ -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)