forked from hexianglong/o.nmgjg.com.cn
上传文件至 RPG_Sample
一个纯终端的RPG游戏基本样例 包含键盘、鼠标交互等 Signed-off-by: Tommmy <pinetree985@gmail.com>
This commit is contained in:
16
RPG_Sample/config.py
Normal file
16
RPG_Sample/config.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# config.py
|
||||
|
||||
import curses
|
||||
|
||||
difficulty = "Normal"
|
||||
player = {"x": 0, "y": 0}
|
||||
camera = {"x": 0, "y": 0}
|
||||
objects = set()
|
||||
|
||||
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(3, curses.COLOR_WHITE, curses.COLOR_BLACK) # 菜单文字
|
||||
curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK) # 标题/提示
|
||||
80
RPG_Sample/inventory.py
Normal file
80
RPG_Sample/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
RPG_Sample/main.py
Normal file
31
RPG_Sample/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
RPG_Sample/menus.py
Normal file
77
RPG_Sample/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
|
||||
94
RPG_Sample/world.py
Normal file
94
RPG_Sample/world.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# world.py
|
||||
|
||||
import random
|
||||
import curses
|
||||
from config import objects, player, camera
|
||||
from inventory import Inventory
|
||||
|
||||
inventory = Inventory(cols=5, rows=4)
|
||||
show_inv = False
|
||||
inv_area = None
|
||||
|
||||
def init_objects(width, height):
|
||||
objects.clear()
|
||||
for _ in range(99999):
|
||||
x = random.randint(-2000, 2000)
|
||||
y = random.randint(-2000, 2000)
|
||||
objects.add((x, y))
|
||||
camx, camy = camera["x"], camera["y"]
|
||||
for _ in range(200):
|
||||
x = random.randint(camx, camx+width-1)
|
||||
y = random.randint(camy, camy+height-1)
|
||||
objects.add((x, y))
|
||||
|
||||
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:
|
||||
stdscr.addstr(y,x,"*",curses.color_pair(2))
|
||||
stdscr.refresh()
|
||||
|
||||
def game_loop(stdscr, width, height):
|
||||
global show_inv, inv_area
|
||||
|
||||
stdscr.keypad(True)
|
||||
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
|
||||
curses.mouseinterval(0)
|
||||
curses.putp(b"\033[?1003h")
|
||||
|
||||
stdscr.nodelay(True)
|
||||
stdscr.timeout(30)
|
||||
|
||||
try:
|
||||
while True:
|
||||
draw_map(stdscr, width, height)
|
||||
if show_inv:
|
||||
inv_area = inventory.draw(stdscr, width, height)
|
||||
|
||||
key = stdscr.getch()
|
||||
if key == ord('e'):
|
||||
show_inv = not show_inv
|
||||
stdscr.clear()
|
||||
continue
|
||||
|
||||
if show_inv and key == curses.KEY_MOUSE:
|
||||
_, mx, my, _, bstate = curses.getmouse()
|
||||
inventory.move(my, mx)
|
||||
if bstate & curses.BUTTON1_PRESSED:
|
||||
inventory.click(my, mx, inv_area)
|
||||
continue
|
||||
|
||||
# ←↑→↓ 控制
|
||||
if 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
|
||||
elif key == 27:
|
||||
break
|
||||
|
||||
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)
|
||||
objects.add((ox,oy))
|
||||
|
||||
pos = (player["x"],player["y"])
|
||||
if pos in objects:
|
||||
objects.remove(pos)
|
||||
stdscr.addstr(height,0,"You picked up an item!",curses.color_pair(4))
|
||||
stdscr.refresh()
|
||||
curses.napms(300)
|
||||
finally:
|
||||
curses.putp(b"\033[?1003l")
|
||||
Reference in New Issue
Block a user