Page 1 of 1

YAPong source

Posted: Tue Jan 23, 2018 10:22 am
by lazi
Hi!

Here is the source of a silly Pong remake in Hollywood.
It features:
- needs no external files
- hardware double buffer
- a self containing scroller object
- a self containing starfield object
- on the fly generated menu

Hope someone can find something useful in it. I am open to discuss questions, opinions, suggestions.
Not well commented, but feel free to ask while I am remember :)

Code: Select all

;pong by Lazi 2017
@REQUIRE "glgalore.hwp"
@DISPLAY {mode="windowed", title="YaP", borderless=True, width=640, height=400, nomodeswitch=True, scalemode=#SCALEMODE_AUTO}

;@SAMPLE 1,"pong0.wav"
;@SAMPLE 2,"pong1.wav"
;@SAMPLE 3,"pong2.wav"

Const #color_left = $211549
Const #color_right= $0D3A48
Const #color_ball = $FFFF9D
Const #color_middle = $E4D0C2
Const #color_border = $E4D0C2
Const #color_menutext = $AAF9FF
Const #color_score = $D7D7D7

res={w=640,h=400} ; host resolution to adapt for
display=1
sound=False
sample={1,2}

If Exists("pong0.wav") And Exists("pong1.wav") And Exists("pong2.wav")
	LoadSample(1,"pong0.wav")
	SetVolume(1,10)
	LoadSample(2,"pong1.wav")
	SetVolume(2,10)
	LoadSample(3,"pong2.wav")
	SetVolume(3,10)
	sound=True
EndIf

demo_scroller={}
Function demo_scroller:setup(w,y)
	self.fontinfo=0
	self.text="Nolan Bushnell in 1972 started his own company, Atari. He assigned Allan Alcorn to create a cheap ping pong arcade game as a training exercise, though he did not tell Alcorn that it was for training nor that the idea was based on the Odyssey Table Tennis game. Alcorn soon developed Pong, which Bushnell recognized as a potential hit, and it became the company's first game. The next major succes of that company was the Atari 2600 based on the works of chip designer Jay Miner, but that is another story...                                                   "
	self.text=UpperStr(self.text)
	self.width=20
	self.window=0
	self.length=w
	self.pos=0
	self.y=y
	self.br={}

	self.ascii2path = Function()
							font_state={name=GetAttribute(#DISPLAY,0,#ATTRFONTNAME),
										size=GetAttribute(#DISPLAY,0,#ATTRFONTSIZE)}


							  SetFillStyle(#FILLGRADIENT,#LINEAR,#WHITE,#GRAY)
							  SetFormStyle(#ANTIALIAS)
							  SetFont(#MONOSPACE,20)

							  Local f={}
							  Local tbr
							  ; 21 - 7E
							  For Local i=$21 To $ff
								  StartPath(i)
								  MoveTo(i,0,0)
								  AddTextToPath(i,Chr(i))
								  ClosePath(i)
								  ss=TextExtent(Chr(i))
								  InsertItem(f,TextExtent(Chr(i)),i)


								  tbr=CreateBrush(Nil,40,80,#WHITE,{alphachannel=True,clear=True})
								  SelectBrush(tbr)
								  DrawPath(i,#CENTER,ss.height)

								  SelectAlphaChannel(tbr)
								  SetAlphaIntensity(#VANILLACOPY)
                                  DrawPath(i,#CENTER,ss.height)

								  EndSelect
								  self.br[i]=CopyBrush(tbr,Nil,{hardware=True})
								  FreeBrush(tbr)
								Next
								SetFont(font_state.name,font_state.size)
								SetFillStyle(#FILLNONE)
								SetFormStyle(#NORMAL)

							  Return(f)
						EndFunction

	self.fontinfo = self.ascii2path()

	self.sintable = {}

	For Local i=0 To 179 self.sintable[i]=Sin(Rad(i))+.5 Next

	self.draw = Function ()
					Local chars, charpos, c, x, y
					chars=Min(self.pos\self.width+1,self.length\self.width+1)
					charpos=self.pos\self.width

					For Local i=charpos-chars To charpos

					If i>=0
						c=Asc(MidStr(self.text,i,1))
						x=self.length-self.pos+i*self.width

						If c<>32 Then DisplayBrush(self.br[c],x,self.y)

					EndIf

					Next
					self.pos=Wrap(self.pos+2,0,self.width*(StrLen(self.text)-1))


				EndFunction
EndFunction
demo_scroller:setup(640,368)


/*startfield*/
Const #hw=True

self={}
self.scale=0.1
starfield=True
Function p_init(num_stars, max_depth)

    self.num_stars=num_stars
    self.max_depth=max_depth

    ;create the starfield
    self.stars={}
    For Local i=0 To self.num_stars
        ;A star is represented as a list with this format: [X,Y,Z]
        star={ x=Rnd(50)-25, y=Rnd(50)-25 , z=Rnd(self.max_depth) }
        InsertItem(self.stars,star)
    Next
    self.k={}
    self.size={}
	self.shade={}
    For z=1 To 320
        self.k[z]={}
        self.size[z]=Int(Max(1,(1-z/self.max_depth)*5))
        self.shade[z]=Int((1-z/self.max_depth)*255)
        For x=-25 To 25
         self.k[z][x]=(2000/z)*x
        Next
    Next

	origin_x=res.w/2
	origin_y=res.h/2

EndFunction

Function p_move_and_draw_stars()

    Local i, x,y,size,shade

    For Local i=0 To self.num_stars

		self.stars[i].z=self.stars[i].z-1

        If self.stars[i].z <=0
            self.stars[i].x=Rnd(50)-25
            self.stars[i].y=Rnd(50)-25
            self.stars[i].z=self.max_depth
        EndIf

        x=self.k[self.stars[i].z][self.stars[i].x]+origin_x
        y=self.k[self.stars[i].z][self.stars[i].y]+origin_y

		If 0 <= x < res.w And 0 <= y < res.h
            size=self.size[self.stars[i].z]
            shade=self.shade[self.stars[i].z]
			Box(x,y,size,size,RGB(shade,shade,shade))
        EndIf
    Next
EndFunction
p_init(100,320)
/*endof starfield*/


ball={}
Function ball:setup()

	self.x=res.w/2-16/2
	self.y=res.h/2-16/2
	self.dir={-4,4}
	self.dx=self.dir[Rnd(2)]
	self.dy=self.dir[Rnd(2)]
	self.br=CreateBrush(Nil,8,8,#color_ball,{hardware=True})
	self.lastpong=-1

	Function self.draw()
				DisplayBrush(self.br,self.x,self.y)
	EndFunction

	Function self.move()
				Local i, count, pong
				pong=-1
				count=ListItems(field.border)
				self.x=self.x+self.dx
				For i=0 To count-1
					If Collision(#BRUSH_VS_BOX,self.br,self.x,self.y, field.border[i][0], field.border[i][1], field.border[i][2], field.border[i][3])
						If i <> self.lastpong
							self.dx=self.dx*-1
							self.x=self.x+self.dx
							If HaveItem(field.border[i],5) Then	field.border[i][5]()

                                If HaveItem(field.border[i],4)
									Local dy=Abs( (self.y-field.border[i][1]) \ (field.border[i][3]\5)-2)
									self.dy=IIf(dy=0,self.dy\2,Limit(self.dy*dy,-6,6))
									If self.dy=0 Then self.dy=Rnd(4)-2
								EndIf

						EndIf
						pong = i
						If sound Then PlaySample(sample[0])
						sample[0],sample[1]=sample[1],sample[0]
						Break
					EndIf
				Next

				self.y=self.y+self.dy
				For i=0 To count-1
					If Collision(#BRUSH_VS_BOX,self.br,self.x,self.y, field.border[i][0], field.border[i][1], field.border[i][2], field.border[i][3])
                        If i <> self.lastpong
							self.dy=self.dy*-1
							self.y=self.y+self.dy
							If HaveItem(field.border[i],5) Then	field.border[i][5]()

						EndIf
						pong=i
						If sound Then PlaySample(sample[0])
						sample[0],sample[1]=sample[1],sample[0]
						Break
					EndIf
				Next
				self.lastpong=pong
	EndFunction

EndFunction

field={}
Function field:setup()
	self.displaylist={}

					;x,y,w,h,action
	self.border={
					{0,0,res.w,8,Nil},         ;upper
					{0,res.h-8,res.w,8,Nil},     ;bottom
					
					{0-8,0,8,res.h,Nil},         ;left
					{res.w-8+8,0,8,res.h,Nil}      ;right
				}

	self.draw=Function()
				SetFillStyle(#FILLCOLOR)
				For Local y=4 To res.h Step 24 Box(res.w/2-8/2,y,8,8,#color_middle) Next ;middle line
				ForEach(self.border,Function(a,b) Box(b[0],b[1],b[2],b[3],#color_border) EndFunction) ;borders
				ForEach(self.displaylist,Function(a,b) b() EndFunction)
			  EndFunction

EndFunction


Function p_keys()
	If IsKeyDown("UP") Then Return("UP",1)
	If IsKeyDown("DOWN") Then Return("DOWN",1)
	Return("",0)
	;return values - string that contains UP or DOWN and the speed factor 1 is 100% speed
EndFunction


Function p_keys2()
	If IsKeyDown("Q") Then Return("UP",1)
	If IsKeyDown("A") Then Return("DOWN",1)
	Return("",0)
EndFunction

Function p_mouse(y)
	Local my=MouseY()-y
	If Abs(my)>4
	If my<0
		 Return("UP",1)
	Else
		Return("DOWN",1)
	EndIf
	EndIf
	Return("",0)
EndFunction



Function p_droid(aim)
	If aim > ball.y Then Return("UP",1)
	If aim < ball.y Then Return("DOWN",1)
	Return("",0)
EndFunction

Function info() EndFunction

player={}
Function player:setup( x, c, sx, goalb )
		self.active=True
		self.points = 0
		self.speed = 4
		self.size = 40
		self.sx=sx
		self.x = x
		self.y = 200-self.size/2
		self.input = c
		self.displayscore = Function()
								 TextOut(self.sx,16,self.points)
							EndFunction
        SetFont(#SANS,60)
		SetFontStyle(#ANTIALIAS)
		InsertItem(field.displaylist,self.displayscore)

		self.goal = Function()
						If sound Then PlaySample(3)
						self.points = self.points+1
						ball:setup()
						If self.points=10
							p1=player1.points
							p2=player2.points
							p_demo()
							player1.points=p1
							player2.points=p2
							menu.on=True
						EndIf



					EndFunction
		If Not IsNil(goalb) Then field.border[goalb][5] = self.goal

		self.control = Function()
							Switch self.input(self.y+self.size/2)
							Case "UP":
								self.up()
							Case "DOWN":
								self.down()
							EndSwitch
							EndFunction
		self.pos = ListItems(field.border)
		InsertItem(field.border,{self.x,self.y,8,self.size,self.y,Nil},self.pos) ;adds the paddle as a border

		self.update = Function()
								field.border[self.pos]={self.x,self.y,8,self.size,field.border[self.pos][1],Nil} ;new x, new y, width, height, last y to calculate speed, goal function
					  EndFunction

		self.up = Function() If self.y > 8 Then self.y=self.y-self.speed self.update EndFunction
		self.down = Function() If self.y < res.h-8-self.size Then self.y=self.y+self.speed self.update EndFunction

EndFunction



menu={}
Function menu:setup()
	self.on = True
	self.highlight=0
	self.submenu=0
	self.bgbrush=0
	self.brushstack={}

	self.items={{{sel=0,br={0,0,0},brsel={0,0,0},txt={"Yet Another Pong","(c) 2017 by Lazi","www.kezdobetu.hu/software"},action={Function() self.nextstep()  EndFunction,Function() self.nextstep() EndFunction,Function() self.nextstep() EndFunction}},
				{sel=0,br={0,0,0},brsel={0,0,0},txt={"Play"},action={Function()
																menu.on=False
																field:setup()
                                                                SetVolume(1,10)
																SetVolume(2,10)
																SetVolume(3,10)

																Local p1c=0
																Local p2c=0

																Switch self.items[2][1].sel
																Case 0: ;keys
																	p1c=p_keys
																Case 1: ;mouse
																	p1c=p_mouse
																Case 2: ;keyboard
																	p1c=p_keys2
																Case 3: ;touch
																	p1c=p_touch
															    EndSwitch

																Switch self.items[2][2].sel
																Case 0: ;keys
																	p2c=p_keys
																Case 1: ;mouse
																	p2c=p_mouse
																Case 2: ;keyboard
																	p2c=p_keys2
																Case 3: ;touch
																	p2c=p_touch
															    EndSwitch



																Switch self.items[0][2].txt[self.items[0][2].sel]
																Case "Game: 1 Player":
                                                                    field.border={
																			  {0,0,res.w,8,Nil},         ;upper
																			  {0,res.h-8,res.w,8,Nil},     ;bottom

																			  {0-8,0,8,res.h,Nil},         ;left
																			  {res.w-8+8,0,8,res.h,Nil}      ;right
																				}
																	player1:setup(32,p1c,#CENTER-40,3)            ;player controlled by keyboard
																	player1.speed=4
																	player2:setup(res.w-32-8,p_droid,#CENTER+40,2)     ;NPC
																	player2.speed=3                           ;NPC speed is less than the ball, so it can miss

																Case "Game: 1 Player with wall":
                                                                    field.border={
																			  {0,0,res.w,8,Nil},         ;upper
																			  {0,res.h-8,res.w,8,Nil},     ;bottom

																			  {0-8,0,8,res.h,Nil},         ;left
																			  {res.w-8,0,8,res.h,Nil}      ;right
																				}
																	player1:setup(32,p1c,#CENTER-40,2)            ;player controlled by keyboard
																	player1.speed=4
																	player2:setup(res.w,p_droid,#CENTER+40,2)     ;NPC
																	player2.active=False

																Case "Game: 2 Players":
                                                                    field.border={
																			  {0,0,res.w,8,Nil},         ;upper
																			  {0,res.h-8,res.w,8,Nil},     ;bottom

																			  {0-8,0,8,res.h,Nil},         ;left
																			  {res.w-8+8,0,8,res.h,Nil}      ;right
																				}

																	player1:setup(32,p1c,#CENTER-40,3)            ;player controlled by keyboard
																	player1.speed=4
																	player2:setup(res.w-32-8,p2c,#CENTER+40,2)
																	player2.speed=4
	                                                                
																EndSwitch
 
                                                                ball:setup()
 
																EndFunction}},

				{sel=0,br={0,0,0},brsel={0,0,0},txt={"Game: 1 Player", "Game: 1 Player with wall", "Game: 2 Players" },
																action={
																Function() self.nextstep() EndFunction,
																Function() self.nextstep() EndFunction,
																Function() self.nextstep() EndFunction}},

				{sel=0,br={0},brsel={0},txt={"Controls"},action={Function() self.submenu=2 self.highlight=0 EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Settings"},action={Function() self.submenu=1 self.highlight=0 EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Exit"},action={Function() End EndFunction}}
																
																},
				{{sel=0,br={0},brsel={0},txt={"Settings"},action={Function() EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Starfield on/off"},action={Function() starfield=Not starfield EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Sound on/off"},action={Function() sound=Not sound EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Display mode switch"},action={Function()
																EndDoubleBuffer()
																Local old_display=display
																display=CreateDisplay(Nil, {mode= IIf(GetAttribute(#DISPLAY,old_display,#ATTRMODE)=#DISPMODE_WINDOWED, "fullscreen","windowed"), title="YaP", borderless=True, width=res.w, Height=res.h, nomodeswitch=True} )
																CloseDisplay(old_display)
																OpenDisplay(display)
																SelectDisplay(display)
																FreeDisplay(old_display)
																demo_scroller:setup(640,368)
																p_init()
																p_demo()

															  EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Back"},action={Function() self.submenu=0 self.highlight=0 EndFunction}}
												},
				{{sel=0,br={0},brsel={0},txt={"Controls"},action={Function() EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Player 1 - cursor keys", "Player 1 - mouse", "Player 1 - keyboard (Q,A)", "Player 1 - touch"},action={Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction}},
				{sel=1,br={0},brsel={0},txt={"Player 2 - cursor keys", "Player 2 - mouse", "Player 2 - keyboard (Q,A)", "Player 2 - touch"},action={Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction,Function() self.nextstep() self.cs() EndFunction}},
				{sel=0,br={0},brsel={0},txt={"Back"},action={Function() self.submenu=0 self.highlight=0 EndFunction}},

				}
			   }

	self.nextstep=Function()
					Local n=ListItems(self.items[self.submenu][self.highlight].txt)
					self.items[self.submenu][self.highlight].sel=Wrap(self.items[self.submenu][self.highlight].sel+1,0,n)
				  EndFunction

	self.cs = Function()
					If self.items[2][1].sel=self.items[2][2].sel Then self.nextstep()
  			  EndFunction
	
	self.create=Function()
						  Local t,tb
						  /*
						  ;create half opaque canvas behind the menu
						  tb=CreateBrush (Nil,GetAttribute(#DISPLAY,0,#ATTRWIDTH),GetAttribute(#DISPLAY,0,#ATTRHEIGHT),$300060,{alphachannel=True,hardware=False})
						  SelectAlphaChannel(tb)
						  SetAlphaIntensity(100)
						  Cls
						  EndSelect
						  self.bgbrush=CopyBrush(tb,Nil,{hardware=True})
						  FreeBrush(tb)
						  InsertItem(self.brushstack,self.bgbrush)
						  */

						  SetFont(#SANS,30)
						  SetFontColor(#color_menutext)
						  SetFontStyle(#ANTIALIAS)
						  For Local s=0 To ListItems(self.items)-1
						  For Local i=0 To ListItems(self.items[s])-1
						  For Local j=0 To ListItems(self.items[s][i].txt)-1
							  SetFontStyle(#EDGE,#BLACK,5)
							  t=CreateTextObject(Nil,self.items[s][i].txt[j],{color=#color_menutext})
							  tb=ConvertToBrush(#TEXTOBJECT,t,Nil)
					          FreeTextObject(t)
							  t=CopyBrush(tb,Nil,{hardware=True})
							  self.items[s][i].br[j]=t
							  InsertItem(self.brushstack,t)
							  FreeBrush(tb)


							  SetFontStyle(#EDGE,#WHITE,5)
							  t=CreateTextObject(Nil,self.items[s][i].txt[j],{color=#BLACK})
							  tb=ConvertToBrush(#TEXTOBJECT,t,Nil)
					          FreeTextObject(t)
							  t=CopyBrush(tb,Nil,{hardware=True})
							  self.items[s][i].brsel[j]=t
					          InsertItem(self.brushstack,t)
							  FreeBrush(tb)

						  Next
						  Next
						  Next
					EndFunction

	self.display=Function()
					   ;DisplayBrush(self.bgbrush,0,0)
					   Local n
					   For Local i=0 To ListItems(self.items[self.submenu])-1
						  n=self.items[self.submenu][i].sel
						  DisplayBrush( IIf (self.highlight=i, self.items[self.submenu][i].brsel[n], self.items[self.submenu][i].br[n]) ,#CENTER,100+i*30)
					   Next
			 	 EndFunction

	self.create()
EndFunction

Function p_mainloop()
	If starfield Then p_move_and_draw_stars
	player1.control()
	If player2.active Then player2.control()
	field.draw()
	ball.move()
	ball.draw()
EndFunction

Function p_events(msg)
	Switch msg.action
	Case "OnKeyDown":
		Switch msg.key
		Case "ESC":
			Switch menu.on
			Case False:
				menu.on=True
				p_demo()
			Case True:
				menu.on=False
			EndSwitch
		Case "RIGHT":
			If menu.on
				Local n=ListItems(menu.items[menu.submenu][menu.highlight].txt)
				menu.items[menu.submenu][menu.highlight].sel=Wrap(menu.items[menu.submenu][menu.highlight].sel+1,0,n)
				If menu.submenu=2 And (menu.highlight=1 Or menu.highlight=2) Then menu.cs()
			EndIf
		Case "LEFT":
			If menu.on
				Local n=ListItems(menu.items[menu.submenu][menu.highlight].txt)
				menu.items[menu.submenu][menu.highlight].sel=Wrap(menu.items[menu.submenu][menu.highlight].sel-1,0,n)
				If menu.submenu=2 And (menu.highlight=1 Or menu.highlight=2) Then menu.cs()
			EndIf

		Case "DOWN":
			If menu.on Then	menu.highlight=Wrap(menu.highlight+1,0,ListItems(menu.items[menu.submenu]))
		Case "UP":
			If menu.on Then	menu.highlight=Wrap(menu.highlight-1,0,ListItems(menu.items[menu.submenu]))
		Case "\n":
			If menu.on Then menu.items[menu.submenu][menu.highlight].action[menu.items[menu.submenu][menu.highlight].sel]()
		EndSwitch
	EndSwitch
EndFunction

 
Function p_demo()
	field.border={
					{0,0,res.w,8,Nil},         ;upper
					{0,res.h-8,res.w,8,Nil},     ;bottom

					{0,0,8,res.h,Nil},         ;left
					{res.w-8,0,8,res.h,Nil}      ;right
				}
	player1:setup(32,p_droid,#CENTER-40)            ;player controlled by keyboard
	player1.speed=2
	player2:setup(res.w-32-8,p_droid,#CENTER+40)     ;NPC
	player2.speed=3
	SetVolume(1,1)
	SetVolume(2,1)
	SetVolume(3,1)
EndFunction

Function p_init()
	HidePointer()
	BeginDoubleBuffer(True)
	Cls
	Flip
	Cls
	ball:setup()
	field:setup()
	menu:setup()
	InstallEventHandler ({OnKeyDown=p_events})
EndFunction

p_init()

    player1=CopyTable(player)
	player2=CopyTable(player)

p_demo()

Repeat
		 CheckEvent
		 DisableLineHook()
         Box (0,0,320,400,#color_left)
	 	 Box (320,0,320,400,#color_right)
		 If menu.on Then demo_scroller.draw()
		 p_mainloop()
		 info()
		 If menu.on Then menu.display()
		 EnableLineHook()
		 Flip()
Forever