Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #Needed for key input
- require 'Win32API'
- #Node Class represents a Node in our Graph/Array
- class Node
- def initialize(x, y)
- @x = x
- @y = y
- end
- def x=(val)
- @x = val
- end
- def y=(val)
- @y = val
- end
- def x()
- @x
- end
- def y()
- @y
- end
- def to_s()
- return "x: " + @x.to_s + ", y: " + @y.to_s
- end
- end
- #Maze Class creates a Labyrinth as playfield
- class Maze
- #initialize the class with dimension
- #Only odd numbers are valid in order for the algorithm to work properly
- def initialize(rows, cols)
- @rows = rows
- @cols = cols
- #Makes sure the starting values are odd numbers and within range
- @oddRowNums = ((rows-1)/2).times.map{ |i| i*2+1 }
- @oddColNums = ((cols-1)/2).times.map{ |i| i*2+1 }
- @sY = @oddRowNums[rand(@oddRowNums.length())]
- @sX = @oddColNums[rand(@oddColNums.length())]
- initField()
- end
- def initField()
- #Represents the maze field
- @field = Array.new(rows){ Array.new(cols) }
- #List where we save all frontier nodes
- @frontierList = Array.new()
- #Init field full of walls represented by the value 5
- for i in 0..rows-1
- for j in 0..cols-1
- @field[i][j] = 5
- end
- end
- end
- def generate()
- #Mark the start
- markAdjacent(@sX, @sY)
- while(!(@frontierList.empty?))
- system('cls')
- curNode = @frontierList.delete_at(rand(@frontierList.length()))
- nList = neighbours(curNode.x, curNode.y)
- rndNeighbour = nList[rand(nList.length)]
- carve(rndNeighbour, curNode)
- markAdjacent(curNode.x, curNode.y)
- printField()
- sleep(0.1)
- end
- #Place exit
- makeExit()
- #Place start
- startNode = placeStart()
- system('cls')
- return startNode
- end
- def placeStart()
- #Find a random valid starting position for the player
- while(true)
- x = @oddColNums[rand(@oddColNums.length())]
- y = @oddRowNums[rand(@oddRowNums.length())]
- if(@field[y][x] != 5)
- distanceX = @eX - x
- distanceY = @eY - y
- #Make sure the start isn't too close to the exit
- if(distanceX.abs > ((cols-1)/2).to_i) || (distanceY.abs > ((rows-1)/2).to_i)
- @field[y][x] = 1
- break
- end
- end
- end
- return Node.new(x,y)
- end
- def makeExit()
- #####
- # This is a Diagram depicting the possible sides
- # and values for x,y (active = 1, inactive = 0)
- # for the exit
- #
- # 1(1,0)
- # #########
- # 1(0,1)# # 0(0,1)
- # #########
- # 0(1,0)
- #
- #Choose a random side and choose which coordinate is the active one
- side = rand(2)
- xSet = rand(2)
- ySet = 1-xSet
- @eX = 0
- @eY = 0
- inValid = true
- #Run as long as we have found a proper value where the opposite neighbour
- #is not a wall so we can actually exit
- while(inValid)
- #These will keep track of which neighbour to lookup
- valX = 0
- valY = 0
- #Check for side 1 as shown in diagram
- if(side == 1)
- if(xSet == 1) #1(1,0)
- #x can be random between 1 and cols-2 so that we cant even select
- #the corners of the field as an exit
- @eX = 1+rand(cols-2)
- @eY = 0
- else #1(0,1)
- #this time y is random and x is fixed
- @eX = 0
- @eY = 1+rand(rows-2)
- end
- if(@eX == 0)
- #In case x was fixed, look at the neighbour to the right of it
- valX = 1
- else
- #In case y was fixed, look at the neighbour below it
- valY = 1
- end
- else
- if(xSet == 1) #0(1,0)
- #Again, x can be random wheras y is fixed at maximum value (we are
- #on side 0 now, so fixed values are forced to maximum
- @eX = 1+rand(cols-2)
- @eY = rows-1
- else #0(0,1)
- #y random, x fixed to maximum
- @eX = cols-1
- @eY = 1+rand(rows-2)
- end
- if(@eX == cols-1)
- #If x was fixed, look up the neighbour to its left
- valX = -1
- else
- #If y was fixed, look up the neighbour above it
- valY = -1
- end
- end
- #Actual neighbour lookup, if there's a wall as neighbour we wouldn't be able
- #to exit so try with some new random values
- if(@field[@eY+valY][@eX+valX] != 5)
- inValid = false
- end
- end
- #Sets the exit based on values found
- @field[@eY][@eX] = 0
- end
- #Marks the field at x,y and its frontiers
- def markAdjacent(x, y)
- @field[y][x] = 0
- add_frontier(x-2, y)
- add_frontier(x+2, y)
- add_frontier(x, y-2)
- add_frontier(x, y+2)
- end
- #Adds the frontier if its within valid values and the frontier was not previously traversed
- def add_frontier(x, y)
- if (x > 0 && x < cols-1) && (y > 0 && y < rows-1) && (@field[y][x] == 5)
- #Frontier is represented by the value 6
- @field[y][x] = 6
- @frontierList.push(Node.new(x, y))
- end
- end
- #Gets all neighbours of the node that have been marked as walkable
- def neighbours(x, y)
- list = Array.new()
- if(checkNode(x-2, y))
- list.push(Node.new(x-2, y))
- end
- if(checkNode(x+2, y))
- list.push(Node.new(x+2, y))
- end
- if(checkNode(x, y-2))
- list.push(Node.new(x, y-2))
- end
- if(checkNode(x, y+2))
- list.push(Node.new(x, y+2))
- end
- return list
- end
- #Checks the given position if its walkable
- def checkNode(x, y)
- if (x > 0 && x < cols-1) && (y > 0 && y < rows-1) && (@field[y][x] == 0)
- return true
- end
- return false
- end
- def carve(from, to)
- if (from.y < to.y)
- carveBlock(from.x, from.y+1)
- end
- if (from.y > to.y)
- carveBlock(from.x, from.y-1)
- end
- if (from.x < to.x)
- carveBlock(from.x+1, from.y)
- end
- if (from.x > to.x)
- carveBlock(from.x-1, from.y)
- end
- end
- def carveBlock(x, y)
- if (x > 0 && x < cols-1) && (y > 0 && y < rows-1) && (@field[y][x] == 5)
- @field[y][x] = 0
- end
- end
- #rows getter
- def rows()
- return @rows
- end
- #cols getter
- def cols()
- return @cols
- end
- #gets the field
- def field()
- return @field
- end
- #Prints the field according to the internal representation of our playfield array
- def printField()
- for i in 0..rows-1
- for j in 0..cols-1
- if @field[i][j] == 0
- #Walkable path
- print " "
- elsif @field[i][j] == 6
- #Frontier
- print "F"
- elsif @field[i][j] == 5
- #Border
- print "#"
- end
- end
- puts
- end
- puts "Creating maze..."
- end
- end
- #Game class represents our game. Stores the playing field and handles the character
- #move logic
- class Game
- #Initialize the game:
- #rows and cols define the size of the playing field
- #gChar is the visual representation of our character
- #sX and sY are the starting X and Y coordinates - if they're wrong we just start at 1,1
- def initialize(rows, cols, gChar, powerups, powerupRep)
- @rows = rows
- @cols = cols
- @gChar = gChar
- @running = true
- if(powerups > (rows * cols)/2)
- @powerups = cols-2
- else
- @powerups = powerups
- end
- @powerupRep = powerupRep
- @collected = 0
- @info
- end
- #Sets up our playfield
- def init()
- #Create a maze playfield
- maze = Maze.new(@rows, @cols)
- #Set game character's node
- @gNode = maze.generate()
- #Fetch the field
- @playfield = maze.field()
- #Set starting positions based on what the field gave back as starting pos
- #@gY = startNode.y
- #@gX = startNode.x
- setPowerups()
- end
- #Sets randomly placed powerups into the gamefield
- def setPowerups()
- count = 0
- while (count < @powerups)
- if(setPowerup() == true)
- count += 1
- end
- end
- end
- #sets one powerup unit - returns true if success
- def setPowerup()
- #random x,y position
- x = 1+rand(@cols-1)
- y = 1+rand(@rows-1)
- #only place the powerup if the field is actually free (walkable)
- if(@playfield[y][x] == 0)
- @playfield[y][x] = 9
- return true
- end
- return false
- end
- #Updates the game
- def update(keyinput)
- @input = keyinput
- #Move character
- moveChar(@input.chr)
- end
- def winningScenario()
- #If the player managed to walk the border he obviously hit the exit
- if(((@gNode.x == 0) || (@gNode.y == 0) || (@gNode.x == @cols-1) || (@gNode.y == @rows-1)))
- if(@collected.to_i() == @powerups.to_i())
- #Clear screen and print winning message - stop the game
- system('cls')
- puts
- puts
- puts
- puts " #####################################"
- puts " # Unlike this text, you made it out #"
- puts " # of the crazy maze! #"
- puts " # Congratulations! #"
- puts " #####################################"
- sleep(3)
- system('cls')
- @running = false
- else
- @info = "Yea, you can't exit before collecting all powerups, bro"
- end
- end
- end
- #Renders the game
- def render()
- printField()
- end
- #Checks if we should keep the game loop alive
- def isRunning()
- #ESC = 27 stops the loop
- if @input == 27
- @running = false
- end
- return @running
- end
- #Prints the field according to the internal representation of our playfield array
- def printField()
- for i in 0..@rows-1
- for j in 0..@cols-1
- if @playfield[i][j] == 0
- #Walkable path
- print " "
- elsif @playfield[i][j] == 1
- #Character
- print @gChar
- elsif @playfield[i][j] == 5
- #Border
- print "#"
- elsif @playfield[i][j] == 9
- #Powerup
- print @powerupRep.to_s()
- end
- end
- puts
- end
- puts "P: " + @collected.to_s()
- if(@info != "")
- puts @info
- @info = ""
- end
- end
- #This moves the cahracter around depending on the direction that was supplied W,S,A,D
- def moveChar(dir)
- case dir
- #when "w" then move( 0,-1)
- when "w" then move(Node.new(0, -1))
- #when "s" then move( 0, 1)
- when "s" then move(Node.new(0, 1))
- #when "a" then move(-1, 0)
- when "a" then move(Node.new(-1, 0))
- #when "d" then move( 1, 0)
- when "d" then move(Node.new(1, 0))
- end
- #Check if winning scenario was reached yet
- winningScenario()
- end
- #Moves the character in x and y direction
- def move(node)
- if isValidMove(node)
- clearCurrentPosition()
- updatePosition(node)
- end
- end
- def updateField(node, val)
- @playfield[node.y][node.x] = val
- end
- #Clears the current position back to walkable area
- def clearCurrentPosition()
- updateField(@gNode, 0)
- end
- #This method should never be called without calling isValidMove(x,y) first!
- #It updates the character's position in the playfield
- def updatePosition(node)
- @gNode.x += node.x
- @gNode.y += node.y
- updateField(@gNode, 1)
- end
- #Performs a check to see if the move in x,y direction is valid
- def isValidMove(node)
- #Check if the move in (x,y) direction is still inside the array
- if (@gNode.x+node.x >= 0) && (@gNode.x+node.x <= @cols-1) && (@gNode.y+node.y >= 0) && (@gNode.y+node.y <= @rows-1)
- #Check if the position is walkable.
- if @playfield[@gNode.y+node.y][@gNode.x+node.x] != 5
- #Also check if a powerup is going to be picked up
- if(@playfield[@gNode.y+node.y][@gNode.x+node.x] == 9)
- @collected += 1
- end
- return true
- end
- end
- return false
- end
- end
- # = Main Program =
- #Reads user Input and clears buffer every time (not optimal, but good enough for now)
- def readKey()
- char = Win32API.new('crtdll','_getch', [], 'L').Call
- clearBuffer()
- return char
- end
- def clearBuffer()
- system('cls')
- end
- #Inits the program
- def init()
- #Initialize game
- clearBuffer()
- puts "##########################################################"
- puts "# Welcome to the crazy maze game! #"
- puts "##########################################################"
- puts
- puts "How big should the playing field be?"
- print "Width: "
- width = gets
- print "Height: "
- height = gets
- #Clamp the values so they're within (5 <= h <= 23) and (5 <= w <= 65)
- height = [[5, height.to_i].max, 21].min
- width = [[5, width.to_i].max, 63].min
- #Make sure the values are odd
- if (width%2 == 0)
- width += 1
- end
- if (height%2 == 0)
- height += 1
- end
- #Inits the game - dimensions, playerfigure representation, amount of powerups
- $game = Game.new(height,width, "@", 15, ".")
- $game.init()
- end
- running = true
- init()
- #Render loop
- while running
- $game.render()
- $game.update(readKey())
- running = $game.isRunning()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement