rubyjourney.py

Created by azerlap652

Created on January 18, 2025

23.8 KB

Ruby Journey version Numworks !!! (devlogs sur le vrai jeu: youtube.com/@aizeir)


from math import *
from random import *
from kandinsky import *
from ion import *
from time import sleep, monotonic

## ENGINE
W,H = 320,222
SIZE, WIN = (W,H), (0,0,W,H)
FONTW, FONTH = 10,18

# Colors
GRAY3 = (51, 51, 51)  # 333333
BLACK = (25, 31, 34)  # 191f22
GRAY2 = (89, 82, 70)  # 595246
GRAY1 = (128, 118, 101)  # 807665
GRAY0 = (187, 176, 148)  # bbb094
GREEN4 = (47, 68, 67)  # 2f4443
GREEN3 = (59, 94, 88)  # 3b5e58
GREEN2 = (90, 140, 108)  # 5a8c6c
GREEN1 = (139, 180, 141)  # 8bb48d
GREEN0 = (192, 208, 165)  # c0d0a5
WHITE = (247, 239, 199)  # f7efc7
BLUE1 = (161, 205, 176)
BLUE2 = (112, 147, 149)  # 709395
BLUE3 = (74, 120, 123)  # 4a787b
PINK3 = (56, 49, 64)  # 383140
PINK2 = (115, 77, 92)  # 734d5c
PINK1 = (167, 103, 114)  # a76772
PINK0 = (204, 134, 125)  # cc867d
ORANGE0 = (224, 186, 139)  # e0ba8b
ORANGE1 = (195, 130, 82)  # c38252
ORANGE2 = (161, 86, 60)  # a1563c
ORANGE3 = (111, 52, 45)  # 6f342d
ORANGE4 = (68, 39, 31)  # 44271f

COLORS = [GRAY3, BLACK, GRAY2, GRAY1, GRAY0, GREEN4, GREEN3, GREEN2, GREEN1, GREEN0, WHITE, BLUE1, BLUE2, BLUE3, PINK3, PINK2, PINK1, PINK0, ORANGE0, ORANGE1, ORANGE2, ORANGE3, ORANGE4]

# Utils
def clamp(m,x,M): return max(m,min(x,M))
def proba(n): return randint(1,n)==1
def ints(list): return tuple(int(x) for x in list)
def center_pos(rect): return rect[0]+rect[2]//2, rect[1]+rect[3]//2

# - collision
def collide_x(r1,r2): return r1[0] < r2[0]+r2[2] and r2[0] < r1[0]+r1[2]
def collide_y(r1,r2): return r1[1] < r2[1]+r2[3] and r2[1] < r1[1]+r1[3]
def collide  (r1,r2): return collide_x(r1,r2) and collide_y(r1,r2)

# - rect
def bottom(rect): return rect[1]+rect[3]
def right(rect):  return rect[0]+rect[2]

def add(r1,r2):
    res = []
    for i in range(max(len(r1),len(r2))):
        x = r1[i] if i < len(r1) else 0
        y = r2[i] if i < len(r2) else 0
        res.append(x+y)
    return res

# - drawing
def draw_rect(rect, color):
    fill_rect(int(rect[0]),int(rect[1]),int(rect[2]),int(rect[3]),color)

def draw_rect_mult(x,y,w,h, color):
    fill_rect(int(x),int(y),int(w),int(h),color)

def fill(color=WHITE):
    draw_rect(WIN, color)

def draw_rects(img,color=BLACK, X=0,Y=0):
    """( (x,y,w,h[,color]), ... )"""
    for r in img:
        draw_rect_mult(X+r[0],Y+r[1], r[2], r[3], (r[4] if len(r) > 4 else color))
    
def draw_text(text, x, y, side="topleft", color=BLACK, background=WHITE):
    if "right" in side: x -= len(text)*FONTW
    elif not "left" in side: x -= len(text)*FONTW/2
    if "bottom" in side: y -= FONTH
    elif not "top" in side: y -= FONTH/2
    draw_string(text, int(round(x)),int(round(y)), color, background)

# - time (time: ne pas utiliser en dehors d'engine)
_time,timers = 0,{}
def every(dt):
    if dt not in timers: timers[dt] = _time
    return (_time - timers[dt]) >= dt
    
def timer(t, dura):
    return (monotonic() - t) >= dura

# - update
just_pressed,_pressed = {},{}

def update(keys=[KEY_OK]):
    """ keys: touches inscrites dans just_pressed. ATTENTION: ne JAMAIS utiliser just_pressed dans un "every" """
    global _time
    for dt in timers:
        if _time-timers[dt] >= dt:
            timers[dt] = _time
    _time = monotonic()

    for key in keys:
        kd = keydown(key)
        just_pressed[key] = kd and not _pressed.get(key)
        _pressed[key] = kd
    return True


## IMAGES
map="""
-1,5,-1,-1,9,-1,-1,-1,-1,-1,-1,11,9,-1,-1,8,9,-1,9,-1,-1,11,-1,9,-1,-1,-1,-1,-1,9
-1,-1,-1,11,-1,-1,11,-1,4,-1,-1,4,-1,-1,-1,-1,-1,4,11,-1,-1,-1,9,-1,14,-1,-1,11,5,-1
9,-1,-1,-1,-1,5,-1,-1,-1,-1,-1,-1,-1,13,-1,9,-1,-1,11,-1,-1,5,-1,-1,-1,-1,-1,4,-1,9
-1,-1,9,-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,4,-1,-1,-1,-1,-1,-1,8,-1,-1,-1,9,-1
4,-1,-1,-1,13,-1,-1,8,10,-1,-1,-1,5,10,-1,-1,-1,-1,-1,-1,-1,-1,4,-1,-1,-1,-1,8,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,4,-1,-1,4,-1,-1,-1,5,-1,-1,-1,-1,-1,6,-1,-1,10,11,-1,-1,4,-1,-1,-1,-1,-1,14,-1,8
4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,12,-1,-1,-1,-1,-1,-1,-1,13,-1,-1,-1,-1,-1,5
-1,-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,11,-1,-1,-1,-1,-1,-1,-1,11,-1,-1,9,9
-1,9,10,-1,-1,-1,9,4,-1,-1,-1,5,-1,-1,-1,9,-1,-1,-1,-1,-1,5,-1,-1,-1,-1,-1,10,9,9
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-1,4,-1,-1,-1,-1,-1,-1,14,-1,-1,-1,-1,5,-1,-1,-1,-1,4,-1,-1,4,-1,10,-1,4,-1,-1,-1,-1
11,-1,-1,8,9,-1,5,-1,-1,-1,-1,-1,-1,9,-1,7,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,13,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1,4,-1,-1,-1,10,-1,-1,-1,10,8,-1,-1,-1,-1,-1,9,-1,-1,-1,-1,9,-1
-1,4,-1,-1,13,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,8,-1,11,11
-1,-1,9,-1,-1,9,-1,4,-1,-1,-1,-1,4,-1,9,-1,-1,9,11,-1,-1,-1,5,-1,-1,9,-1,-1,11,11
"""
imgs = ['4"5"6"7"8"9":";"<"="D"E$F$G#H#I#J#K#L#M"T"U$V#W#X#Y#Z#[#\\#]"d"e#f#g4h4i4j4k#l"m"t"u#v4!K"9#K$K%9&9.9/K0K1K293K4K5969>9?K@KAKBKCKDKEKF9N9OKPKQKRKSKTKUKV9^9_9`9a9b9c9d9e9f9o9pMqMrMsMtMu9)P*c+c,c-c.c/P9P:P;P<P=P>P?PIPJ^K^LPMPN^OPYPZP[P]P^P_P', '4"5"6"7"8"9":";"<"="D"E#F#G#H#I#J#K$L$M"T"U#V#W#X#Y#Z#[#\\$]"d"e"f#g4h4i4j4k#l#m"u"v"!K"K#9$K%K&:\'9/9091K2K394K5K6K79?9@KAKBKCKDKEKFKG9O9PKQKRKSKTKUKVKW9_9`9a9b9c9d9e9f9g9p9qMrMsMtMuMv9*P+c,c-c.c/c0P:P;P<P=P>P?P@PJPK^LPMPN^O^PPZP[P\\P^P_P`P', '5"6"7"8"9":";"D"E#F#G#H#I#J#K#L"T"U#V#W#X#Y#Z#[#\\"d"e#f4g4h4i4j4k#l"t"u4v"!K"K#K$9%K&9.9/K091K2K3K495K69>9?K@KAKBKCKDKEKF9N9OKPKQKRKSKTKUKV9^9_9`9a9b9c9d9e9f9o9pMqMrMsMtMu9)P*c+c,c-c.c/P9P:P;P<P=P>P?PIPJ^K^LPM^N^OPYPZP[P]P^P_P', '6"7"8"9":"E"F#G#H#I#J#K"T"U#V#W#X#Y#Z#[#\\"d"e#f#g#h#i#j#k#l"t"u!v!!8"8#8$8%8&9.9/K0K1K2K3K4K5K69>9?K@KAKBKCKDKEKF9N9OKPKQKRKSKTKUKV9^9_9`9a9b9c9d9e9f9o9pMqMrMsMtMu9)P*c+c,c-c.c/P9P:P;P<P=P>P?PIPJ^K^LPM^N^OPYPZP[P]P^P_P', '5"6"7"8"9":";"<"D"E&F&G&H&I&J&K&L&M"S"T&U\'V\'W\'X\'Y\'Z\'[\'\\\']&^"b"c&d\'e(f(g(h(i(j(k(l(m\'n&o"q"r&s\'t(u(v(!?"?#?$?%?&?\'?(>)=*9+9,=->.?/?0?1?2?3?4?5?6?7?8>9=:9;9<==>>>??@?A?B?C?D?E?F?G>H>I=J9K9L=M=N>O>P>Q>R>S>T>U>V>W>X=Y=Z9[9\\9]=^=_=`>a>b>c>d>e>f=g=h=i9j9l9m9n=o=p=q=r=s=t=u=v=!T"P#P\'P(P)P*P+P,P-P.P/P0P1P2P:P;e<d=d>e?PIPJPKeLdMdNeOPPPYPZe[d\\d]d^d_e`PjPkPlPmPnPoP', '5"6"7"8"9":";"<"D"E&F&G&H&I&J&K&L&M"S"T&U\'V\'W\'X\'Y\'Z\'[\'\\\']&^"b"c&d\'e(f(g(h(i(j(k(l(m\'n&o"q"r&s\'t(u(v)!@"@#@$@%@&?\'?(>)=*9+9,=->.>/?0?1@2@3@4@5?6?7>8>9=:9;9<==>>>?>@>A?B?C?D?E>F>G>H>I=J9K9L=M=N=O>P>Q>R>S>T>U>V>W=X=Y=Z9\\9]9^=_=`=a=b=c=d=e=f=g=h9i9m9n9o=p=q=r=s=t=u=v=!P"P(P)P*P+P,P-P.P/P0P1P:P;e<d=d>e?PIPJPKeLdMdNeOPPPYPZe[d\\d]d^d_e`PjPkPlPmPnPoP', '%"&"\'"(")"*"+"4"5,6,7,8,9,:,;,<"D"E(F,G,H,I,J,K(L"T"U*V(W(X(Y(Z([*\\"d"e"f*g*h*i*j*k"l"t"u4v"!9"9#9$9%K&9.9/J091K2K3K495J69>9?J@9AJBJCJD9EJF9N9OJPJQJRJSJTJUJV9^9_9`9a9b9c9d9e9f9o9p?q@r?s?t@u9)P*W+V,V-W.V/P9P:P;P<P=P>P?PIPJUKULPMUNUOPYPZP[P]P^P_P', '4"5"6"7"8"9":";"<"D"E$F$G$H$I$J$K$L"T"U$V$W4X4Y4Z$[$\\"d"e$f$g4h4i4j$k$l"t"u#v5!L"L#L$L%:&9.9/L091L2L3L495L69>9?L@9ALBLCLD9ELF9N9OLPLQLRLSLTLULV9^9_9`9a9b9c9d9e9f9o9pHqGrHsGtHu9)P*^+^,^-^.^/P9P:P;P<P=P>P?PIPJ]K]LPM]N]OPYPZP[P]P^P_P', 'G"H"I"J"K"U"V"W&X\'Y\'Z&[&\\"d"e&f\'g\'h(i(j\'k&l"m"s"t"u\'v(!?"?#?$?%>&=\'9-9.=/>0?1?2?3?4?5?6>7989=9>>??@?A?B?C?D?E>F=G=H9M9N=O>P?Q?R?S?T?U?V>W=X9]9^=_>`>a>b?c?d>e>f=g9h9m9n9o=p=q>r>s>t=u=v=!P"P(P)P*P+T,T-T.P/T0P1P9P:P;P<P=P>P?P@PJPKPLPMPNPOP', '6"7"8"9":";"E"F#G#H#I#J#K#L"T"U#V#W#X#Y#Z#[#\\#]"d"e#f#g#h#i#j#k#l#m"s"t"u!v#!:":#:$:%:&8\'9(9,9-8.9/9081:2:3:4:5869798899<9=:>8?9@8A8B8C8D8E8F9G8H:I9L9M:N:O:P8Q8R8S8T8U8V:W:X:Y9\\9]8^:_:`:a:b:c:d:e:f:g:h8i9l9m9n8o8p:q:r:s:t:u:v8!O"P#P&P\'P(P)O*O+O,O-O.O/O0O1P2P3P7P8P9P:O;O<O=O>O?O@PAPBPHPIPJPKPLPMPNPOPPPQPZP[P\\P]P^P_P', '87G7H6I7W7X6Y7f7g6h5i6j7v7!L"K#L$N0N1K2J3K4NA9BMC9Q9RNS9a9bNc9q9rNs9+P,e-P;P<e=PKPLeMP', '\'&(&)&*"6&7(8(9(:\';"E&F(G(H(I(J\'K6L"U&V(W(X\'Y\'Z6[6\\"d&e&f(g\'h6i6j6k6l"m"s&t\'u&v\'!?"M#M$M%M&9\'N(9,=-?.>/=0?1?2>3>4M5M697N8M99<==?>>?=@?A>B=C=D9EMF9GNHMI9L=M?N>O=P>Q>R=S=TNU9V9WNXMY9\\=]?^>_=`>a=bNcNdNeNf9gNhMi9l=m?n>o=p=qNrNsNtNu9v9!e"d#P&T\'V(T)U*U+P,P-P.P/e0e1P2d3P6T7T8U9U:U;U<P=P>e?e@eAeBPCPFTGUHUIUJUKULPMPNeOePeQeReSPWTXUYUZU[T]P^P_e`eaebPhTiTjTnPoPpPqP', 'E"F"H"I5K"L"T"U6V6W"X5Y5Z"[6\\6]"c"d7e6f6g5h5i5j5k6l6m7n"s"t7u6v6!L"K#K$L%M&M\'N(9-9.8/M0L1L2K3K4L5L6M7889=9>N?M@LAKBKCKDKELFMGNH9M9NNOMPLQKRJSJTKULVMWNX9]9^8_M`MaKbJcJdKeMfMg8h9m9nNoMpMqNrMsMtNuMvM!e"P\'P(e)d*d+e,d-d.e/d0d1e2P7P8O9d:d;O<d=d>O?d@dAOBPHPIeJeKPLeMeNPOePeQPYPZP\\P]P_P`P', 'I"J"M"X"Y5Z5["\\"]5^"h"i6j6k5l5m6n"q"r"s"t""9#L$L%M&M\'M(9)9+9,J-J.K/909293L4M5B6M7M8B9M:9;9<J=K>L?K@9A9BMCLDLEMFMGMHMILJ9K9LKMLNKOMP9QLRLSMTLULVLWLXLY9\\9]K^M_N`LaLbMcNdMeMfMgMh9m9n9oMpLqMrMsMtNuNv9!P(P)d*d+e,e-d.d/P0P8P9d:P;P<P=d>P?e@PHPIPKPLPMPNPOP', 'D"G"H"S"T5U"V"W5X5Y"c"d6e5f5g6h6i"r"s"t6u6v6!L"L#9\'9(9)9*9+9,M-B.M/M0B1M2L3959697K8J9J:9;9<L=M>M?M@MALBLCMD9E9FKGLHKIJJ9L9MLNLOLPLQLRMSLTLU9VMWKXLYKZ9]9^M_M`MaMbNcMdLeLfNgMhKi9n9o9pNqNrMsMtMuLvM!P"P)P*P+d,d-e.e/d0d1P9P:e;P<d=P>P?P@dAPJPKPLPMPNPPPQP', "T'X'e'g'u'!>?UPU`U", "M'U5^'d5e3f5n't'u5v'/>k>!c&U)U0c1a2c6U8U@UAcBUHUQU", "e't'.>e@fAgAt@u@vA.W/W0U1U?U@U", "<)=*>*K)L)M*[)\\)]'^'d'l'm't'r@sAtA&X'X(W+W,W-X7X8W9W;W<W=U>UFUGUHWIWLUMUWUXU"]
items = ['Y7Z7[7\\7e7f7g7h7i3j3k+l+m7n7r7s7t7u3v3!B"B#B$B%B&B\'B(B)N,N-J.B/B0B1B2B3B4B5B6B7K8K9N<N=J>J?J@BABBBCBDBEKFKGKHKINLNMJNJOJPJQJRBSKTKUKVKWKXKYN\\N]J^J_J`JaJbBcKdKeKfKgKhKiNlNmJnJoJpJqJrBsKtKuKvK!b"e#e\'e(e)a*a+a,Y-b.b/b0e1e9e:e;a<Y=b>e?eKeLeMe', 'U/V/W/X/Y/Z/[/\\/d/e1f1g/h2i2j/k1l1m/s/t1u1v/!I"I#I$I%F&H\'H(F,F-H.H/F0I1I2I3I4I5I6F7H8H9F<F=F>F?F@FAFBFCFDFEFFFGFHFIFMFNGOGPFQHRHSHTHUFVGWGXF^F_G`FaFbHcHdFeGfGgFoFpGqFrHsHtFuGvF*]+^,]-].^/];]<]=]>]L]M]', 'W"X"Y"Z"f"g%h%i%j%k"l"u"v%!;";#;$;%:&:\'9.9/<0;1;2;3;4;5:6:79>9?<@;A;B;C;D:E:F:G8H9M9N<O;P;Q;R;S:T:U:V8W8X9]9^<_;`;a:b:c:d:e8f8g8h9m9n;o:p:q:r:s:t8u8v8!O"P\'P(R)Q*Q+Q,O-O.O/O0O1P8P9O:O;O<O=O>O?P@PIPJPKPLPMPNP', 'V!W!X"e!f+g%h$i"j"k"u!v%!;":#M$N%N&9/80<1;2:3M4N5N69?8@<A;B:CMDNENF9O8P<Q;R:SMTNUNV9_8`Ba<b;c9dNe9p8q8r9s9tMu9-P.c/P=P>c?PMPNdOP]P^e_PmPnPoP', 'X!g!h6i"u!v!!B"<#;$9%9.8/B0B1<2;3;4:5:69=8>B?<@9A;B:C:D9E:F:G9L8M<N9O9P9Q9RNS9T9U9V9W:X9\\9]9a9bMc9g9h9q9rLs9+P,c-P;P<c=PKPLdMP[P\\e]PkPlPmP', 'X!Y!Z"g!h+i%j"v!!B"<#;$9081<2;39@8A<B;C9P8Q;RNS9`9a9bMc9q9rLs9+P,d-P;P<e=PJPKQLRMQNPZP[P\\Q]P^PkPlPmP', 'V/W/X/Y/Z/[/f/g2h2i1j1k/v/!I"I#H$H%F0F1I2I3H4H5F@FAIBHCHDHEFPFQIRHSHTGUF`FaIbHcHdGeFpFqHrHsHtGuF*]+_,_-^.^/]:];_<^=^>^?]J]K_L^M^N^O]Z][]\\]]]^]_]', 'Z#[#\\#g#h#i#j%k%l%m#n#t#u#v#!<"<#<$<%<&<\'<(<):-:.</<0<1<2<3<4<5<6<7:8:98=:>;?;@<A<B<C<D<E:F:G:H:I8M:N;O;P;Q;R<S:T:U:V:W:X:Y8]:^;_;`;a;b<c:d:e:f:g:h8n:o;p;q;r<s:t:u:v8!O)Q*Q+R,S-Q.O/O;Q<Q=O']
itemnames = ["gold","ruby","stone","axe","pickaxe","lance","wood","iron"]

pnjs = {6:"explorer",7:"blacksmith"}
talks = {
    6: ["hello how are you ?","YOU: i'm good.","you still think of that ruby ?","YOU: yes, or i wouldn't stay.", "good determination.", "but i think you'll not get it.","YOU: shut up."],
    7: ["hello how is it ?","YOU: i'm fine.","can i tell you a joke ?","how to make a firefly happy?","YOU: what?!","cut its tail,it'll be delighted", "YOU: ...", "YOU: (dies of cringe)"],
}
trades = {
    6: [(0,1,6,40),(6,4,2,1),(7,3,5,1),(0,2,1,1)],
    7: [(2,4,7,1),(7,2,3,1),(7,2,4,1),(7,4,0,1)],
}
quests = {
    6: [["10 wood to lit campfire", (6,10), "thank you ! (+2 stone)", (2,2)], ["give 1 iron, get a surprise", (7,1), "haha scammed you ! (+1 stone)", (2,1)]],
    7: [["need 4 more rocks...", (2,4), "nice ! (+16 wood)", (6,16)], ["help need 2 iron now !", (7,2), "didn't used ur rocks (+4 rocks)", (2,4)]],
}
TALK_PNJ = "1:trade | 2:quest | 3:talk"
QUEST_NO_NEED = "i don't need anything."
FRIENDSHIP = "new trade unlocked !"
tools = {4:(3,6,2),5:(3,6,2),8:(3,6,1),9:(4,2,1),11:(3,6,3),13:(5,None,0),14:(5,None,0)}
tools_items = (3,4,5)

## JEU
TS = 32
scale = TS//16
DSP_W, DSP_H = W//TS, H//TS
ECART = H-DSP_H*TS

PLR_SPAWN = 5, 1
MOVE_COOLDOWN = 0.1
FLOOR = GREEN2

FLOORED = [15,16,17,18]

# Map
map = map.strip().split("\n")
MAPW, MAPH = len(map[0].split(",")), len(map)
in_bounds = lambda x,y: 0 <= x < MAPW and 0 <= y < MAPH

def create_world():
    world = [0 for _ in range(MAPW*MAPH)]
    plr_pos = None
    for y in range(MAPH):
        row = map[y].split(",")
        for x in range(MAPW):
            tile = int(row[x])
            if tile == 0:
                plr_pos = (x,y)
                tile = -1
            world[y*MAPW+x] = tile if tile != -1 else choice(FLOORED)
    return world, plr_pos[0], plr_pos[1]

# Dessiner monde
def draw_tile(tile_x, tile_y, offset, color):
    fill_rect((tile_x-offset[0])*TS, (tile_y-offset[1])*TS, TS, TS, color)

def draw_object(tile_x, tile_y, offset, type):
    if type == -1: return
    for i in range(0, len(imgs[type]), 2):
        r, c = ord(imgs[type][i])-33, ord(imgs[type][i+1])-33
        col,q = c%23, c//23
        pos = q*86+r
        x,y = pos%16, pos//16
        color = COLORS[col]
        fill_rect((tile_x-offset[0])*TS+x*scale, (tile_y-offset[1])*TS+y*scale, scale, scale, color)

def draw_item(X, Y, type):
    for i in range(0, len(items[type]), 2):
        r, c = ord(items[type][i])-33, ord(items[type][i+1])-33
        col,q = c%23, c//23
        pos = q*86+r
        x,y = pos%16, pos//16
        color = COLORS[col]
        fill_rect(X+x*2, Y+y*2, 2, 2, color)

def draw_world(world, plr_x, plr_y, offset, anim):
    fill_rect(0,0,W,DSP_H*TS,GREEN2)
    for dsp_y in range(DSP_H):
        for dsp_x in range(DSP_W):
            x,y = dsp_x+offset[0], dsp_y+offset[1]
            tile = world[y*MAPW+x]
            draw_object(x, y, offset, tile)
    draw_object(plr_x, plr_y, offset, anim)

def draw_last_row(world, plr_x, plr_y, offset, anim):
    fill_rect(0,(DSP_H-1)*TS,W,TS,GREEN2)
    dsp_y = DSP_H-1
    for dsp_x in range(DSP_W):
        x,y = dsp_x+offset[0], dsp_y+offset[1]
        tile = world[y*MAPW+x]
        draw_object(x, y, offset, tile)
    if plr_y == dsp_y+offset[1]:
        draw_object(plr_x, plr_y, offset, anim)

def draw_inv_item(i, item, inv, fill=False):
    x = 4 + (32+4)*i
    y = H-ECART-4
    if fill: fill_rect(x,H-ECART,TS,ECART,GRAY2)
    if item not in inv: return
    draw_item(x,y,item)
    if item not in tools_items:
        draw_text(str(inv[item]), x+32, y+32, "bottomright", WHITE, BLACK)

def draw_inventory(inv):
    for i, item in enumerate(inv):
        draw_inv_item(i, item, inv)

def draw_band(facing, inv, talking, quest, quest_idx):
    fill_rect(0,H-ECART,W,ECART,GRAY2)
    if talking == "trade":
        draw_inventory(inv)
        fill_rect(0,H-ECART-32,W,32,GRAY1)
        x,y = 4, H-ECART-4-32
        for i in range(4):
            i1,a1,i2,a2 = trades[facing][i]
            if (i2 in tools_items and i2 in inv):
                draw_text("X", x+32+4, y+20, "center", WHITE, GRAY1)
            elif (i-1 <= quest_idx[facing]):
                draw_item(x,y,i1)
                draw_item(x+32+8,y,i2)
                text = str(a1)+">"+str(a2)
                draw_text(text, x+32+4, y+32, "midbottom", WHITE, BLACK)
            else:
                draw_text("?", x+32+4, y+20, "center", WHITE, GRAY1)
            x += 32+8+32+6
    elif talking == "quest":
        fill_rect(0,H-ECART-32,W,32,GRAY1)
        text = quest[0]
        draw_text(text, W/2, (DSP_H-1)*TS+TS//2, "center", WHITE, GRAY1)
        draw_inventory(inv)
    elif talking == "questgive":
        draw_text("give your "+str(quest[1][1])+" "+itemnames[quest[1][0]]+"? 1:yes 2:no", W/2, (DSP_H*TS+H)/2, "center", WHITE, GRAY2)
    elif talking == "questthank":
        draw_text(quest[2], W/2, (DSP_H*TS+H)/2, "center", WHITE, GRAY2)
    elif talking:
        text = talking
        if isinstance(talking, int):
            text = talks[facing][talking-1]
        draw_text(text, W/2, (DSP_H*TS+H)/2, "center", WHITE, GRAY2)
    elif facing in pnjs:
        text = "(OK - "+pnjs[facing]+")"
        draw_text(text, W/2, (DSP_H*TS+H)/2, "center", WHITE, GRAY2)
    else:
        draw_inventory(inv)

def game():
    # Setup
    world, plr_x, plr_y = create_world()
    move_timer = 0
    offset = [plr_x-plr_x%DSP_W,plr_y-plr_y%DSP_H]
    anim = 0
    inv = {0:1}
    facing_pos = plr_x+1, plr_y
    facing = world[facing_pos[1]*MAPW+facing_pos[0]]
    talking = None
    quest_idx = {i:0 for i in pnjs}
    quest = None

    def trade(i):
        i1, a1, i2, a2 = trades[facing][i-1]
        if i2 in tools_items and i2 in inv: return
        if inv.get(i1,0) >= a1:
            inv[i1] -= a1
            if inv[i1]==0: del inv[i1]
            if i2 not in inv: inv[i2] = 0
            inv[i2] += a2
            fill_rect(0,H-ECART,W,ECART,GRAY2)
            draw_inventory(inv)

    def do_quest():
        quest_idx[facing] += 1
        i1, a1 = quest[1]
        i2, a2 = quest[3]
        inv[i1] -= a1
        if inv[i1]==0: del inv[i1]
        if i2 not in inv: inv[i2] = 0
        inv[i2] += a2

    # Dessiner au début
    fill_rect(0,H-ECART,W,ECART,GRAY2)
    draw_world(world, plr_x, plr_y, offset, anim)
    draw_band(facing, inv, talking, quest, quest_idx)
    # Update
    while update(keys=[KEY_LEFT, KEY_RIGHT,KEY_UP,KEY_DOWN,KEY_OK, KEY_ONE, KEY_TWO, KEY_THREE, KEY_PLUS, KEY_FOUR]):

        if just_pressed[KEY_OK]:
            if talking:
                # Dialog
                if isinstance(talking, int):
                    # Continue dialog
                    if talking < len(talks[facing]):
                        talking += 1
                    # Close dialog
                    elif talking == len(talks[facing]):
                        talking = None
                    draw_band(facing, inv, talking, quest, quest_idx)
                # Trade
                elif talking == "trade":
                    talking = None
                    draw_band(facing, inv, talking, quest, quest_idx)
                    draw_last_row(world, plr_x, plr_y, offset, anim)
                    if 1 in inv:
                        end()
                        draw_band(facing, inv, talking, quest, quest_idx)
                        draw_world(world, plr_x, plr_y, offset, anim)
                        continue
                # Quest
                elif talking == "quest":
                    its = quest[1]
                    if its[0] in inv and inv[its[0]] >= its[1]:
                        talking = "questgive"
                    else:
                        talking = None
                    draw_band(facing, inv, talking, quest, quest_idx)
                    draw_last_row(world, plr_x, plr_y, offset, anim)
                elif talking == "questthank":
                    talking = FRIENDSHIP
                    draw_band(facing, inv, talking, quest, quest_idx)
                # PNJ, ... les trucs qui quitte au OK.
                elif talking in (TALK_PNJ,FRIENDSHIP, QUEST_NO_NEED):
                    talking = None
                    draw_band(facing, inv, talking, quest, quest_idx)
            # Open dialog
            elif facing in pnjs:
                talking = TALK_PNJ
                draw_band(facing, inv, talking, quest, quest_idx)
            # Break prop
            elif facing in tools and tools[facing][0] in inv:
                # Erase prop
                coord = facing_pos[1]*MAPW+facing_pos[0]
                draw_tile(facing_pos[0],facing_pos[1], offset, FLOOR)

                # Add item
                if tools[facing][1] != None:
                    item = tools[facing][1]
                    if item not in inv: inv[item]=0
                    inv[item] += tools[facing][2]
                    draw_band(facing, inv, talking, quest, quest_idx)

                # replace tile and facing.
                world[coord] = facing = -1


        # Boutons 1 2 3 +
        # choisir entre trade quest talk
        if talking == TALK_PNJ:
            if just_pressed[KEY_ONE]:
                talking = "trade"
                draw_band(facing, inv, talking, quest, quest_idx)
            elif just_pressed[KEY_TWO]:
                if quest_idx[facing] == 2:
                    talking = QUEST_NO_NEED
                else:
                    quest = quests[facing][quest_idx[facing]]
                    talking = "quest"
                draw_band(facing, inv, talking, quest, quest_idx)
            elif just_pressed[KEY_THREE]:
                talking = 1
                draw_band(facing, inv, talking, quest, quest_idx)
        # choisir le trade
        elif talking == "trade":
            if just_pressed[KEY_ONE]:     trade(1)
            elif just_pressed[KEY_TWO]:   trade(2)
            elif just_pressed[KEY_THREE]: trade(3)
            elif just_pressed[KEY_PLUS] or just_pressed[KEY_FOUR]:  trade(4)
        # choisir entre give ou pas
        elif talking == "questgive":
            if just_pressed[KEY_ONE]:
                do_quest()
                talking = "questthank"
                draw_band(facing, inv, talking, quest, quest_idx)
            elif just_pressed[KEY_TWO]:
                talking = None
                draw_band(facing, inv, talking, quest, quest_idx)
            
        # Direction
        dx, dy = 0, 0
        old_x, old_y = plr_x, plr_y
        if timer(move_timer, MOVE_COOLDOWN) and not talking:
            dx -= (just_pressed[KEY_LEFT] and plr_x > 0)
            dx += (just_pressed[KEY_RIGHT] and plr_x < MAPW - 1)
            dy -= (just_pressed[KEY_UP] and plr_y > 0 and dx==0)
            dy += (just_pressed[KEY_DOWN] and plr_y < MAPH - 1 and dx==0)
        has_moved = dx != 0 or dy != 0

        # Animation
        old_anim = anim
        if dx < 0: anim = 1
        if dx > 0: anim = 0
        if dy < 0: anim = 3
        if dy > 0: anim = 2

        # Collision
        t = world[(plr_y+dy)*MAPW+(plr_x+dx)]
        if has_moved and (t not in FLOORED and t!=-1):
            has_moved = anim != old_anim
        # Move
        elif has_moved:
            plr_x += dx; plr_y += dy
            move_timer = monotonic()
        # Facing
        redraw_band = False
        if dx != 0 or dy != 0:
            facing_pos = plr_x+dx, plr_y+dy
            
            new_facing = world[facing_pos[1]*MAPW+facing_pos[0]] if in_bounds(facing_pos[0],facing_pos[1]) else -1
            redraw_band = (facing in pnjs) != (new_facing in pnjs)
            facing = new_facing

        # Draw
        # Changement de zone
        if not 0<= plr_x-offset[0] <DSP_W:
            offset[0] += DSP_W * (-1,1)[plr_x-offset[0] >= DSP_W]
            draw_world(world, plr_x, plr_y, offset, anim)
        if not 0<= plr_y-offset[1] <DSP_H:
            offset[1] += DSP_H * (-1,1)[plr_y-offset[1] >= DSP_H]
            draw_world(world, plr_x, plr_y, offset, anim)
        # Joueur a bougé
        elif has_moved:
            draw_tile(old_x, old_y, offset, FLOOR)  # Restaurer l'ancienne case
            draw_object(old_x, old_y, offset, world[old_y*MAPW+old_x])
            draw_object(plr_x, plr_y, offset, anim)
        # redessiner la bande quand le joueur rencontre/quitte un pnj
        if redraw_band:
            draw_band(facing, inv, talking, quest, quest_idx)

def tutorial():
    tuto = [
    [
        '"i want that ruby',
        ' but i only took 1 gold..."',
        "Grind and trade with PNJs !",
        "Do quests to unlock trades !",
        "(OK: continue)",
    ],
    [
        'Arrows: move',
        'OK: interact, next dialogs',
        "OK: quit trade and pnj menu",
        '1/2/3/4: select trade',
        "(OK: start)",
    ],
    ]
    idx = 0
    def draw_tuto():
        fill(BLACK)
        for i, text in enumerate(tuto[idx]):
            draw_text(text,W/2,50+i*32,"center",WHITE,BLACK)
    
    draw_tuto()
    while update(keys=[KEY_OK]):
        if just_pressed[KEY_OK]:
            if idx == len(tuto)-1:
                game()
            else:
                idx += 1
                draw_tuto()

def end():
    poem = [
    [
        'you made it. you got the ruby.',
        'thanks for playing.',
        'see more of the actual game',
        "  at youtube.com/@aizeir",
        "(OK: continue)",
    ],
    [
        'Ruby Journey Numworks',
        'by Aizeir',
        "(OK: end)",
    ],
    ]
    idx = 0
    def draw_end():
        fill(BLACK)
        for i, text in enumerate(poem[idx]):
            draw_text(text,W/2,50+i*32,"center",WHITE,BLACK)
    
    draw_end()
    while update(keys=[KEY_OK]):
        if just_pressed[KEY_OK]:
            if idx == len(poem)-1:
                return
            else:
                idx += 1
                draw_end()

# Start
tutorial()

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.