This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
The_Cat's profile picture

Trying to make a game. (snake)

Started by The_Cat, 14 March 2015 - 03:16 PM
The_Cat #1
Posted 14 March 2015 - 04:16 PM
Hi, so i decided i wanted to try to make a game for the first time, but i have no clue how to so i just had a go and this is as far as i got.
This is quite simple but i didnt want to add anymore such as if it goes out of bounds and such until i get this working.

function setXY(x,y) -- Sets X and Y position
   term.setCursorPos(x,y)
end
function clearScreen() -- Clears Screen
    term.clear()
    term.setCursorPos(1,1)
end
function drawSnake(x, y, length) -- Draws the snake
   for i = 1, length do
      setXY(x,y)
      term.write("O")
   end
end
function keyPress()
   local delay = os.startTimer(.1)
   local event, pull1 = os.pullEvent()

   if event == "key" then
	  snakeDir = pull1
   elseif event == "timer" and pull1 == delay then -- Checks if the event is a timer and if the delay is == to the pull
	  if snakeDir == 17 then -- Up
	     posY = posY - 1
   elseif snakeDir == 31 then -- Down
	     posY = posY + 1
	  elseif snakeDir == 30 then -- Left
	     posX = posX - 1
	  elseif snakeDir == 32 then -- Right
	     posX = posX + 1
	  end
   end

   clearScreen()
end

snakeDir = 32
posX = 5
posY = 10
clearScreen()

while true do
    keyPress()
    drawSnake(posX, posY, 1)
end

So when i run this code without pressing any buttons it move across the screen but when you click a key such a S it will go down or it will stop, it seems to vary randomly.
So basicly the buttons to comtrol the snake do not work.
InDieTasten #2
Posted 14 March 2015 - 04:23 PM
Note: You definitely want to store each snake "segment"/pixel, since you won't be able to check for collisions later.

The problem I see in your code is that you are generating more events than being processed. For every timer you start you pull one event. The keys won't get recognised I would assume. Your mainloop should ask for events all the time, and if a timer was pulled, start a new one, otherwise change the direction. That should fix it. Hope this was kind of helpful
HPWebcamAble #3
Posted 14 March 2015 - 06:59 PM
In case you didn't know, CC comes with a snake program. I think you can run it by typing 'snake' Wow I'm tired its 'worm', sorry
(Make sure the computer doesn't have a user-created worm program)

You might want to look through the code just to get an idea of how it works.
Edited on 14 March 2015 - 06:57 PM
InDieTasten #4
Posted 14 March 2015 - 07:17 PM
In case you didn't know, CC comes with a snake program. I think you can run it by typing 'snake'
(Make sure the computer doesn't have a user-created snake program)

You might want to look through the code just to get an idea of how it works.
It's called worm ;)/>
For beginners this can get confusing though. Reading another-ones code is definitely harder than writing own code, especially when the "infrastructure" is different from what you expect, and as beginner you start to build yourself misconceptions without noticing.
The_Cat #5
Posted 14 March 2015 - 08:47 PM
Note: You definitely want to store each snake "segment"/pixel, since you won't be able to check for collisions later.

The problem I see in your code is that you are generating more events than being processed. For every timer you start you pull one event. The keys won't get recognised I would assume. Your mainloop should ask for events all the time, and if a timer was pulled, start a new one, otherwise change the direction. That should fix it. Hope this was kind of helpful

Thanks, i got it to work perfectly.

But i knew that i would have to make the segments individually and sort follow the head of the snake, now i just need the tail (segments) to follow it when you move down, up etc. Heres what i got, ( also added bounds kinda ;)/> )


function setXY(x,y) --# Sets X and Y position
   term.setCursorPos(x,y)
end
function clearScreen() -- #Clears Screen
	term.clear()
	term.setCursorPos(1,1)
end
pos = {}
function snakeSeg(length, headX, headY)
	for i =1, length do --Loop That
		pos[i] = {}
		pos[i]["x"] =  headX - length + i - 1--#When i change the head pos it will change the segment pos
		pos[i]["y"] = headY
		setXY(pos[i]["x"], pos[i]["y"])
		write("W")
	end
end

function drawSnakeHead(x, y) --# Draws the snake
	setXY(x,y)
	term.write("O")
end

function bounds(xPos, yPos) -- #Bounds Function so if the
	local x,y = term.getSize()
	if xPos < 1 then
		posXH = x
	elseif xPos > x then
		posXH = 1
	elseif yPos < 1 then
		posYH = y
	elseif yPos > y then
		posYH = 1
	end
end

testL = 5 --#Temp test

function keyPress()
	if event == "timer" and pull1 == delay then
		if snakeDir == 17 then -- Up
			posYH = posYH - 1
			test = snakeDir
		elseif snakeDir == 31 then -- Down
			posYH = posYH + 1
			testL = testL + 1 -- #Temp test so when i go down it grows snake (same as eating food to grow)
			test = snakeDir
		elseif	snakeDir == 30 then -- Left
			posXH = posXH - 1
			test = snakeDir
		elseif snakeDir == 32 then -- Right
			posXH = posXH + 1
			test = snakeDir
		else
			snakeDir = test
		end
		bounds(posXH, posYH)
		clearScreen()
		
		snakeSeg(testL, posXH, posYH)
		drawSnakeHead(posXH, posYH)
		delay = os.startTimer(.01) -- Needs to be here for snaek to move with timer
	elseif event == "key" then
		snakeDir = pull1
	end	
end

snakeDir = 32
posXH = 6
posYH = 10

clearScreen()

delay = os.startTimer(1)
while true do
	event, pull1 = os.pullEvent()
	keyPress()
end

I was thinking that each segment before the last needs to be in the postion of the one that was infront , but i'm not sure how to put this down in code.
Edited on 14 March 2015 - 08:33 PM
Bomb Bloke #6
Posted 14 March 2015 - 09:43 PM
I'd make a table containing the x/y co-ords of each segment of the snake. For each movement, I'd insert the new "head" position in at the start of the table:

table.insert(snakeBody, {newX, newY})
term.setCursorPos( snakeBody[1][1], snakeBody[1][2] )
term.write("O")
term.setCursorPos( snakeBody[2][1], snakeBody[2][2] )
term.write("W")

… and if the table has more entries than the maximum allowed length of the snake, I'd "undraw" the last index and remove it:

if #snakeBody > snakeLength then
  term.setBackgroundColour(colours.black)
  term.setCursorPos( snakeBody[#snakeBody][1], snakeBody[#snakeBody][2] )
  term.write(" ")

  table.remove(snakeBody, #snakeBody)
end

Note that this way, instead of redrawing the whole screen with each movement, you're just drawing a few characters. Speeds up execution no end and eliminates flicker.

To test if the snake had collided with itself, I'd either iterate through the snakeBody table after every movement (to make sure the new entry at the start doesn't match any of the other entries), or I'd define a 2D table with dimensions equal to the screen and keep that updated according to the current state of of the snakeBody table. The first method saves on RAM, the second method executes faster.

Lastly, regarding this sort of thing:

function setXY(x,y) --# Sets X and Y position
   term.setCursorPos(x,y)
end

If you really want to shrink function names like that, but don't want to change their behaviour at all, just do:

setXY = term.setCursorPos
The_Cat #7
Posted 15 March 2015 - 12:05 AM
I'd make a table containing the x/y co-ords of each segment of the snake. For each movement, I'd insert the new "head" position in at the start of the table:

table.insert(snakeBody, {newX, newY})
term.setCursorPos( snakeBody[1][1], snakeBody[1][2] )
term.write("O")
term.setCursorPos( snakeBody[2][1], snakeBody[2][2] )
term.write("W")

I dont really understand what this does,
I tried putting it in a loop with the snake length

snakeBody = {}
function snakeParts(snakeLength)
	for i = 1, snakeLength do
		table.insert(snakeBody, {posXH-i, posYH})
		term.setCursorPos( snakeBody[1][1], snakeBody[1][2] )
		term.write("O")
		term.setCursorPos( snakeBody[2][1], snakeBody[2][2] ) --# Having 2 or greater than 1 value on he left [] breaks the code saying "23: attempt to index ? (a nil value) <-- this is line 23 in my code
		term.write("W")
		--#i = i + 1 --Just a test becuase i think it was printing all the letters on top of each other
	end
end

Also, am i not storing the X and Y cords in this function?

function snakeSeg(length, headX, headY)
		for i =1, length do --Loop That
				pos[i] = {}
				pos[i]["x"] =  headX - length + i - 1--#When i change the head pos it will change the segment pos
				pos[i]["y"] = headY
				setXY(pos[i]["x"], pos[i]["y"])
				write("W")
		end
end

Edited on 14 March 2015 - 11:21 PM
Bomb Bloke #8
Posted 15 March 2015 - 12:55 AM
The problem with your snakeSeg function is that it 1) redraws the whole snake and 2) doesn't track the old locations of each of the snake's parts. You need to keep that information, not try to regenerate it for every movement! The code I'm suggesting is a replacement for both your snakeSeg and your drawSnakeHead functions.

Though I did make a bit of a mistake in that the insert call should've been written as table.insert(snakeBody, 1, {posXH-i, posYH}). You still need the tail-chopping code, though!:

posXH, posYH, testL = 6, 10, 2

snakeBody = { {posXH, posYH}, {posXH + 1, posYH} }  -- We need a two-length snake to start off with, as we always draw two segments of the snake.

function snakeParts(length, headX, headY)
	table.insert(snakeBody, 1, {headX, headY})
	term.setCursorPos( snakeBody[1][1], snakeBody[1][2] )  -- This is the head.
	term.write("O")
	term.setCursorPos( snakeBody[2][1], snakeBody[2][2] )  -- This is what WAS the head last time the snake moved.
	term.write("W")
	
	if #snakeBody > length then  -- If the snake's allowed length didn't get longer, then...
		term.setBackgroundColour(colours.black)
		term.setCursorPos( snakeBody[#snakeBody][1], snakeBody[#snakeBody][2] )  -- This was the very last segment of the snake.
		term.write(" ")
		table.remove(snakeBody, #snakeBody)  -- Chopped off!
	end
end

Make sense? You'd then change this bit from your main program loop:

                bounds(posXH, posYH)
                clearScreen()

                snakeSeg(testL, posXH, posYH)
                drawSnakeHead(posXH, posYH)
                delay = os.startTimer(.01) -- Needs to be here for snaek to move with timer

… to just:

                bounds(posXH, posYH)

                snakeParts(testL, posXH, posYH)
                delay = os.startTimer(.01) -- Needs to be here for snaek to move with timer
Edited on 14 March 2015 - 11:55 PM
The_Cat #9
Posted 15 March 2015 - 03:37 PM
posXH, posYH, testL = 6, 10, 2

snakeBody = { {posXH, posYH}, {posXH + 1, posYH} }  -- We need a two-length snake to start off with, as we always draw two segments of the snake.

function snakeParts(length, headX, headY)
	table.insert(snakeBody, 1, {headX, headY})
	term.setCursorPos( snakeBody[1][1], snakeBody[1][2] )  -- This is the head.
	term.write("O")
	term.setCursorPos( snakeBody[2][1], snakeBody[2][2] )  -- This is what WAS the head last time the snake moved.
	term.write("W")
	
	if #snakeBody > length then  -- If the snake's allowed length didn't get longer, then...
		term.setBackgroundColour(colours.black)
		term.setCursorPos( snakeBody[#snakeBody][1], snakeBody[#snakeBody][2] )  -- This was the very last segment of the snake.
		term.write(" ")
		table.remove(snakeBody, #snakeBody)  -- Chopped off!
	end
end
Now i understand :)/>
It works like a charm thanks, just need to add the food and make it look colourful and sexy! :D/>
Edited on 15 March 2015 - 05:08 PM