ComputerCraft Archive

[Beta] Dodj - Dodgeball in CC!

computer utility unknown forum

Description

DodjDave-ee's Dodgeball for CC!Look! It's dodgeball!Yes it is! And it's completely working in every way (once you get catching-the-ball down pat).The game has support for an unlimited amount of player...

Installation

Copy one of these commands into your ComputerCraft terminal:

Pastebin:pastebin get zsijDWCe [beta]_dodj_-_dodgeball_in_cc!
wget:wget https://pastebin.com/raw/zsijDWCe [beta]_dodj_-_dodgeball_in_cc!
Archive:wget https://cc.shobie.xyz/cc/get/pb-zsijDWCe [beta]_dodj_-_dodgeball_in_cc!
Quick Install: wget https://cc.shobie.xyz/cc/get/pb-zsijDWCe [Beta] Dodj - Dodgeball in CC!

Usage

Run the program after downloading

Tags

forumgames

Source

View Original Source

Code Preview

--[[

	Dodj
	By Dave-ee Jones

	Dodgeball in CC!

--]]

local t_TERM = {} -- Terminal dimensions and positioning
t_TERM.W, t_TERM.H = term.getSize()

local n_VERSION = "BETA"

--[[ CONFIGURATION ]]--

-- Ball update interval
local n_BALL_UPDATE_INTERVAL = 1 / 30 -- seconds / frames (smaller ratio = faster balls)
-- Player update interval
local n_PLAYER_UPDATE_INTERVAL = 1 / 4 -- seconds / frames (smaller ratio = faster movement)

-- Player settings (keybindings, sprites, etc.)
--- This is where you can add more players. Follow the same pattern as 1.
local t_PLAYER_SETTINGS = {
	[1] = {
		LEFT = keys.a, -- Left key (press to move left)
		UP = keys.w, -- Up key (press to move up)
		RIGHT = keys.d, -- Right key (press to move right)
		DOWN = keys.s, -- Down key (press to move down)
		ACTION = keys.leftShift, -- Action key (press to pickup/throw/drop)
		STOP = keys.z, -- Stop key (press while pressing a move key to not move)
		SPRITE = "\1", -- Sprite (1 character)
		TEAM = colors.cyan, -- Team, colour based. Defaults are blue and red.
		X = 3, -- X position at the start of the game and when players are reset
		Y = 3 -- Y position at the start of the game and when players are reset
	},
	[2] = {
		LEFT = keys.left,
		UP = keys.up,
		RIGHT = keys.right,
		DOWN = keys.down,
		ACTION = keys.o,
		STOP = keys.p,
		SPRITE = "\2",
		TEAM = colors.red,
		X = t_TERM.W - 3,
		Y = t_TERM.H - 3
	}
}

-- Default sprites for directions
local t_SPRITES = {
	LEFT = "\17",
	UP = "\30",
	RIGHT = "\16",
	DOWN = "\31",
	BALL = "\7"
}

local c_MAIN_BG = colors.black -- Background colour
local c_MAIN_FG = colors.white -- Foreground colour
local c_BOARD_BG = colors.gray -- Board's background colour

--[[ END CONFIGURATION ]]--

-- Metatables
local t_PLAYER = {}
local t_BALL = {}
t_PLAYER.__index = t_PLAYER
t_BALL.__index = t_BALL

-- Tables
local t_PLAYERS = {} -- Not to be confused with the metatable
local t_BALLS = {} -- Also not to be confused with the metatable
local t_BOARD = { -- t_BOARD dimensions and positioning, along with scoreboard
	X = 1,
	Y = 1,
	W = t_TERM.W - 1,
	H = t_TERM.H - 1,
	XMID = t_TERM.W / 2 + 1,
	TEAMS = {}
}

-- Help
local function f_HELP()
	printError("----------")
	printError("Dodj "..n_VERSION)
	printError("----------")
	printError("Setting up a player:")
	printError("- Edit Dodj")
	printError("- Scroll down to the 't_PLAYER_SETTINGS' table")
	printError("- Make an entry for each player missing")
	error("",0)
end

-- Draw all the players
local function f_PLAYERS_DRAW()
	for i=1,#t_PLAYERS do
		t_PLAYERS[i]:draw()
	end
end

-- Draw all the balls
local function f_BALLS_DRAW()
	for i=1,#t_BALLS do
		t_BALLS[i]:draw()
	end
end

-- Create the teams
local function f_TEAMS_CREATE()
	-- Create a team based on the players' colours
	local n_TEAMS = 0
	for i=1,#t_PLAYERS do
		if not t_BOARD.TEAMS[t_PLAYERS[i].TEAM] then
			-- Team doesn't exist, so let's create it
			t_BOARD.TEAMS[t_PLAYERS[i].TEAM] = {
				SCORE = 0,
				X = 0
			}
			n_TEAMS = n_TEAMS + 1
		end
	end
	local n_INDEX = 1
	if n_TEAMS == 2 then
		-- If there's only 2 teams, draw the text on either side of the board
		for k,v in pairs(t_BOARD.TEAMS) do
			if n_INDEX == 1 then
				t_BOARD.TEAMS[k].X = 2
			else
				t_BOARD.TEAMS[k].X = t_TERM.W - 2
			end
			n_INDEX = n_INDEX + 1
		end
	else
		-- Calculate distance between written text to calculate where we end up drawing them
		local n_XSTART = 2
		local n_DISTANCE_BETWEEN_TEAMS = (t_TERM.W - (n_XSTART*2))/(n_TEAMS-1)
		for k,v in pairs(t_BOARD.TEAMS) do
			t_BOARD.TEAMS[k].X = n_XSTART+n_DISTANCE_BETWEEN_TEAMS*(n_INDEX-1)
			n_INDEX = n_INDEX + 1
		end
	end
end

-- Add one to a team's score
local function f_TEAMS_SCORE(c_TEAM)
	t_BOARD.TEAMS[c_TEAM].SCORE = t_BOARD.TEAMS[c_TEAM].SCORE + 1
end

-- Draw the teams' score
local function f_TEAMS_DRAW()
	term.setBackgroundColor(c_BOARD_BG)
	for k,v in pairs(t_BOARD.TEAMS) do
		term.setTextColor(k)
		term.setCursorPos(t_BOARD.TEAMS[k].X,1)
		write(t_BOARD.TEAMS[k].SCORE)
	end
end

-- Draw the board
local function f_BOARD_DRAW()
	paintutils.drawBox(t_BOARD.X,t_BOARD.Y,t_BOARD.X+t_BOARD.W,t_BOARD.Y+t_BOARD.H,c_BOARD_BG)
	f_TEAMS_DRAW()
end

-- Reset the board
local function f_BOARD_RESET()
	f_BOARD_DRAW()
	-- Reset the players
	for i=1,#t_PLAYERS do
		t_PLAYERS[i]:clear()
		t_PLAYERS[i]:reset()
	end
	-- Reset the balls
	for i=1,#t_BALLS do
		t_BALLS[i]:clear()
		t_BALLS[i]:reset()
	end
	f_BALLS_DRAW()
	f_PLAYERS_DRAW()
	tm_BALL_UPDATE = os.startTimer(1)
end

-- Create a new player
function t_PLAYER:new(n_NUMBER)
	if not t_PLAYER_SETTINGS[n_NUMBER] then
		-- No settings for the player found
		printError("[ERROR] No settings for player "..n_NUMBER)
		printError("Type '"..shell.getRunningProgram().." help' for help.")
		error("",0)
	end
	local self = {
		NUMBER = n_NUMBER,
		DIRECTION = t_SPRITES.DOWN,
		ITEM = nil,
		ACTION_CD = false,
		CATCHING = false,
		STOPPED = false,
		KEY_STATES = {
			LEFT = false,
			UP = false,
			RIGHT = false,
			DOWN = false,
			ACTION = false,
			STOP = false
		}
	}
	-- This is where the t_PLAYER_SETTINGS table comes in handy!
	self.LEFT = t_PLAYER_SETTINGS[n_NUMBER].LEFT
	self.UP = t_PLAYER_SETTINGS[n_NUMBER].UP
	self.RIGHT = t_PLAYER_SETTINGS[n_NUMBER].RIGHT
	self.DOWN = t_PLAYER_SETTINGS[n_NUMBER].DOWN
	self.ACTION = t_PLAYER_SETTINGS[n_NUMBER].ACTION
	self.STOP = t_PLAYER_SETTINGS[n_NUMBER].STOP
	self.X = t_PLAYER_SETTINGS[n_NUMBER].X
	self.Y = t_PLAYER_SETTINGS[n_NUMBER].Y
	self.TEAM = t_PLAYER_SETTINGS[n_NUMBER].TEAM
	self.SPRITE = t_PLAYER_SETTINGS[n_NUMBER].SPRITE
	self.XSTART = self.X
	self.YSTART = self.Y
	setmetatable(self,t_PLAYER)
	return self
end

-- Move a player left
function t_PLAYER:moveLeft()
	self.DIRECTION = t_SPRITES.LEFT
	if not self.KEY_STATES.STOP and self.X > (t_BOARD.X + 1) then
		self:clear()
		self.X = self.X - 1
		if self.ITEM then
			self.ITEM:clear()
			self.ITEM.X = self.X
			self.ITEM.Y = self.Y + 1
		end
		f_BALLS_DRAW()
		f_PLAYERS_DRAW()
	end
	self:draw()
end

-- Move a player up
function t_PLAYER:moveUp()
	self.DIRECTION = t_SPRITES.UP
	if not self.KEY_STATES.STOP and self.Y > (t_BOARD.Y + 1) then
		self:clear()
		self.Y = self.Y - 1
		if self.ITEM then
			self.ITEM:clear()
			self.ITEM.X = self.X
			self.ITEM.Y = self.Y + 1
		end
		f_BALLS_DRAW()
		f_PLAYERS_DRAW()
	end
	self:draw()
end

-- Move a player right
function t_PLAYER:moveRight()
	self.DIRECTION = t_SPRITES.RIGHT
	if not self.KEY_STATES.STOP and self.X < (t_BOARD.X + t_BOARD.W - 1) then
		self:clear()
		self.X = self.X + 1
		if self.ITEM then
			self.ITEM:clear()
			self.ITEM.X = self.X
			self.ITEM.Y = self.Y + 1
		end
		f_BALLS_DRAW()
		f_PLAYERS_DRAW()
	end
	self:draw()
end

-- Move a player down
function t_PLAYER:moveDown()
	self.DIRECTION = t_SPRITES.DOWN
	if not self.KEY_STATES.STOP and self.Y < (t_BOARD.Y + t_BOARD.H - 2) then
		self:clear()
		self.Y = self.Y + 1
		if self.ITEM then
			self.ITEM:clear()
			self.ITEM.X = self.X
			self.ITEM.Y = self.Y + 1
		end
		f_BALLS_DRAW()
		f_PLAYERS_DRAW()
	end
	self:draw()
end

-- Reset a player
function t_PLAYER:reset()
	self.X = self.XSTART
	self.Y = self.YSTART
	self.STOPPED = false
	self.ACTION_CD = false
	self.ITEM = nil
	self.DIRECTION = t_SPRITES.DOWN
	self.CATCHING = false
end

-- Clear a player's drawn sprite
function t_PLAYER:clear()
	paintutils.drawPixel(self.X,self.Y,c_MAIN_BG)
	paintutils.drawPixel(self.X,self.Y+1,c_MAIN_BG)
end

-- Pickup/Throw/Drop something that a player holds
function t_PLAYER:action()
	if self.ITEM then
		-- Player is holding an item and wants to be rid of it
		if self.ITEM.SPRITE == t_SPRITES.BALL then
			-- It's a ball! THROW IT!
			self.ITEM.DIRECTION = self.DIRECTION
			self.ITEM.IS_THROWN = true
			self.ITEM.IS_CAUGHT = false
			self.ITEM = nil
		elseif self.ITEM.SPRITE == t_SPRITES.BLOCK then
			-- It's a block! (of some kind..) DROP IT!
			self.ITEM.IS_HELD = false
		end
	else
		self.CATCHING = true
	end
end

-- Draw a player
function t_PLAYER:draw()
	-- Draw player's sprite
	term.setTextColor(c_MAIN_FG)
	term.setBackgroundColor(self.TEAM)
	term.setCursorPos(self.X,self.Y)
	write(self.SPRITE)
	-- Draw player's direction/item
	term.setTextColor(c_MAIN_FG)
	term.setBackgroundColor(self.TEAM)
	term.setCursorPos(self.X,self.Y+1)
	if not self.ITEM then
		write(self.DIRECTION)
	else
		write(self.ITEM.SPRITE)
	end
end

-- Create a new ball
function t_BALL:new(n_NUMBER)
	local self = {
		NUMBER = n_NUMBER,
		DIRECTION = t_SPRITES.DOWN,
		IS_THROWN = false,
		IS_CAUGHT = false,
		TEAM = c_MAIN_FG,
		SPRITE = t_SPRITES.BALL
	}
	local _n_X = 0
	local _n_Y = 0
	local b_LOOKING = true
	while b_LOOKING do
		-- Looking for a spot to put the ball..
		_n_X = math.random(2,t_TERM.W-1)
		_n_Y = math.random(2,t_TERM.H-1)
		local _collision = false
		for i=1,#t_PLAYERS do
			if t_PLAYERS[i].XSTART == _n_X and t_PLAYERS[i].YSTART == _n_Y then
				-- Ah, can't use this position because it's the same as this player's starting position
				_collision = true
			end
		end
		for i=1,#t_BALLS do
			if t_BALLS[i].XSTART == _n_X and t_BALLS[i].YSTART == _n_Y then
				-- Ah, already a ball there
				_collision = true
			end
		end
		if not _collision then
			-- Okay, we found our spot
			b_LOOKING = false
		end
	end
	self.X = _n_X
	self.Y = _n_Y
	self.XSTART = _n_X
	self.YSTART = _n_Y
	setmetatable(self,t_BALL)
	return self
end

-- Calculate if the ball is touching a player
function t_BALL:collidedWithPlayer()
	for i=1,#t_PLAYERS do
		-- Okay, Player i, are you touching the ball?
		if self.X == t_PLAYERS[i].X and self.Y >= t_PLAYERS[i].Y and self.Y <= (t_PLAYERS[i].Y + 1) then
			-- Oh boi, you are!
			return t_PLAYERS[i]
		end
	end
	-- No one touching the ball!
	return false
end

-- Move a ball
function t_BALL:move()
	if self.IS_THROWN then
		-- Ahh, the ball is supposed to be moving
		if self.DIRECTION == t_SPRITES.LEFT then
			-- Moving left, eh?
			self:clear()
			if self.X > (t_BOARD.X + 1) then
				-- Okay, no wall, so let's keep going!
				self.X = self.X - 1
			else
				-- Oops, hit a wall, so let's rebound!
				self.DIRECTION = t_SPRITES.RIGHT
			end
		elseif self.DIRECTION == t_SPRITES.UP then
			-- Moving up in the world
			self:clear()
			if self.Y > (t_BOARD.Y + 1) then
				-- Okay, no ceiling, so let's keep moving up
				self.Y = self.Y - 1
			else
				-- Okay, hit the ceiling, let's fall
				self.DIRECTION = t_SPRITES.DOWN
			end
		elseif self.DIRECTION == t_SPRITES.RIGHT then
			-- Moving along the right path
			self:clear()
			if self.X < (t_BOARD.X + t_BOARD.W - 1) then
				-- Okay, it really is the right path
				self.X = self.X + 1
			else
				-- It's all gone left (including this ball)
				self.DIRECTION = t_SPRITES.LEFT
			end
		elseif self.DIRECTION == t_SPRITES.DOWN then
			-- Falling
			self:clear()
			if self.Y < (t_BOARD.Y + t_BOARD.H - 1) then
				-- We haven't hit the ground
				self.Y = self.Y + 1
			else
				-- Balls are bouncy, so they rebound off the ground
				self.DIRECTION = t_SPRITES.UP
			end
		end
		f_BALLS_DRAW()
		f_PLAYERS_DRAW()
	end
	if not self.IS_CAUGHT then
		local _other = self:collidedWithPlayer()
		if _other then
			-- Ball is sitting on a player
			if self.TEAM == c_MAIN_FG and _other.CATCHING then
				-- Player is eligible to catch the ball, so they do
				_other.ITEM = self
				self.TEAM = _other.TEAM
				self.IS_CAUGHT = true
				self.IS_THROWN = false
				_other:draw()
			elseif _other.CATCHING then
				-- Ball isn't white, but the player wants to catch the ball
				if (self.DIRECTION == t_SPRITES.DOWN and _other.DIRECTION == t_SPRITES.UP) or (self.DIRECTION == t_SPRITES.UP and _other.DIRECTION == t_SPRITES.DOWN) then
					-- Player is eligible to catch the ball, so they do
					_other.ITEM = self
					self.TEAM = _other.TEAM
					self.IS_CAUGHT = true
					self.IS_THROWN = false
					_other:draw()
				elseif (self.DIRECTION == t_SPRITES.LEFT and _other.DIRECTION == t_SPRITES.RIGHT) or (self.DIRECTION == t_SPRITES.RIGHT and _other.DIRECTION == t_SPRITES.LEFT) then
					-- Player is eligible to catch the ball, so they do
					_other.ITEM = self
					self.TEAM = _other.TEAM
					self.IS_CAUGHT = true
					self.IS_THROWN = false
					_other:draw()
				else
					-- Player isn't in any position to catch the ball, even though he tried..
					if self.TEAM ~= _other.TEAM and self.TEAM ~= c_MAIN_FG then
						-- Player just got hit!
						f_TEAMS_SCORE(self.TEAM)
						f_BOARD_RESET()
					end
				end
			else
				-- Player isn't trying to catch it and the ball isn't white
				if self.TEAM ~= _other.TEAM and self.TEAM ~= c_MAIN_FG then
					-- AND the ball isn't on the same team as the player, NOT GOOD!
					f_TEAMS_SCORE(self.TEAM)
					f_BOARD_RESET()
				end
			end
		end
	end
end

-- Reset a ball
function t_BALL:reset()
	self.X = self.XSTART
	self.Y = self.YSTART
	self.IS_THROWN = false
	self.IS_CAUGHT = false
	self.DIRECTION = t_SPRITES.DOWN
	self.TEAM = c_MAIN_FG
end

-- Clear a ball's drawn sprite
function t_BALL:clear()
	paintutils.drawPixel(self.X,self.Y,c_MAIN_BG)
end

-- Draw a ball
function t_BALL:draw()
	term.setTextColor(self.TEAM)
	term.setBackgroundColor(c_MAIN_BG)
	term.setCursorPos(self.X,self.Y)
	write(t_SPRITES.BALL)
end

-- Creating players based on arguments
local tArgs = { ... }
if tArgs[1] then
	if not tonumber(tArgs[1]) then
		-- Argument isn't a number, therefore we check for keywords
		if tArgs[1] == "help" then
			-- Argument was "help"
			f_HELP()
		else
			-- Unknown argument, error out
			error("[ERROR] Invalid argument: "..tArgs[1],0)
		end
	end
	-- Arguments found, let's create some players
	for i=1,tArgs[1] do
		t_PLAYERS[i] = t_PLAYER:new(i)
	end
	-- Lets create twice the amount of balls (max 10)
	for i=1,(tArgs[1]*2) do
		if #t_BALLS ~= 10 then
			t_BALLS[i] = t_BALL:new(i)
		else
			break
		end
	end
else
	-- No arguments found, default to 2 players
	for i=1,2 do
		t_PLAYERS[i] = t_PLAYER:new(i)
	end
	-- 2 players = 4 balls
	for i=1,4 do
		t_BALLS[i] = t_BALL:new(i)
	end
end

-- Setup the teams
f_TEAMS_CREATE()

-- Back to the drawing board..
local b_RUNNING = true
term.setBackgroundColor(c_MAIN_BG)
term.clear()
f_BOARD_DRAW()

-- Draw all the things before we start looping
f_BALLS_DRAW()
f_PLAYERS_DRAW()

-- Starts the timers
local tm_BALL_UPDATE = os.startTimer(1)
local tm_PLAYER_UPDATE = os.startTimer(1)

-- The Frightening Loop
while b_RUNNING do
	local e_EVENT, e_BUTTON, e_X, e_Y = os.pullEvent()
	if e_EVENT == "key" then
		-- WHICH PLAYER PRESSED A BUTTON, HUH?!
		for i=1,#t_PLAYERS do
			-- WAS IT YOU, i?!
			if e_BUTTON == t_PLAYERS[i].LEFT then
				-- AHA, NOW YOU CAN MOVE LEFT FOR WHAT YOU DID!
				t_PLAYERS[i]:moveLeft()
				t_PLAYERS[i].KEY_STATES.LEFT = true
			elseif e_BUTTON == t_PLAYERS[i].UP then
				-- AHA, NOW YOU CAN MOVE UP FOR WHAT YOU DID!
				t_PLAYERS[i]:moveUp()
				t_PLAYERS[i].KEY_STATES.UP = true
			elseif e_BUTTON == t_PLAYERS[i].RIGHT then
				-- AHA, NOW YOU CAN MOVE RIGHT FOR WHAT YOU DID!
				t_PLAYERS[i]:moveRight()
				t_PLAYERS[i].KEY_STATES.RIGHT = true
			elseif e_BUTTON == t_PLAYERS[i].DOWN then
				-- AHA, NOW YOU CAN MOVE DOWN FOR WHAT YOU DID!
				t_PLAYERS[i]:moveDown()
				t_PLAYERS[i].KEY_STATES.DOWN = true	
			elseif e_BUTTON == t_PLAYERS[i].STOP then
				-- AHH, YOU WANT TO STOP DO YOU? FINE.
				t_PLAYERS[i].KEY_STATES.STOP = true
			elseif e_BUTTON == t_PLAYERS[i].ACTION then
				-- WHAT? YOU WANT TO DO SOMETHING? GO AHEAD.
				t_PLAYERS[i]:action()
				t_PLAYERS[i].KEY_STATES.ACTION = true
			end
		end
	elseif e_EVENT == "key_up" then
		-- WHICH PLAYER PRESSED A BUTTON, HUH?!
		for i=1,#t_PLAYERS do
			-- WAS IT YOU, i?!
			if e_BUTTON == t_PLAYERS[i].LEFT then
				-- AHA, NOW YOU CAN STAY RIGHT THERE!
				t_PLAYERS[i].KEY_STATES.LEFT = false
			elseif e_BUTTON == t_PLAYERS[i].UP then
				-- STOP UPPING THEN!
				t_PLAYERS[i].KEY_STATES.UP = false
			elseif e_BUTTON == t_PLAYERS[i].RIGHT then
				-- AHA, THE RIGHT CHOICE!
				t_PLAYERS[i].KEY_STATES.RIGHT = false
			elseif e_BUTTON == t_PLAYERS[i].DOWN then
				-- AHA, NOW YOU CAN STAY RIGHT THERE!
				t_PLAYERS[i].KEY_STATES.DOWN = false
			elseif e_BUTTON == t_PLAYERS[i].STOP then
				-- AHH, YOU DON'T WANT TO STOP DO YOU? FINE.
				t_PLAYERS[i].KEY_STATES.STOP = false
			elseif e_BUTTON == t_PLAYERS[i].ACTION then
				-- WHAT? YOU DON'T WANT TO DO SOMETHING?
				t_PLAYERS[i].CATCHING = false
				t_PLAYERS[i].KEY_STATES.ACTION = false
			end
		end
	elseif e_EVENT == "timer" then
		if e_BUTTON == tm_BALL_UPDATE then
			-- BALLS ARE BEING MOVED, GET READY..
			for i=1,#t_BALLS do
				-- WANNA MOVE, i? THIS IS YOUR CHANCE.
				t_BALLS[i]:move()
			end
			tm_BALL_UPDATE = os.startTimer(n_BALL_UPDATE_INTERVAL)
		elseif e_BUTTON == tm_PLAYER_UPDATE then
			-- PLAYERS ARE BEING MOVED, GET READY..
			for i=1,#t_PLAYERS do
				if t_PLAYERS[i].KEY_STATES.LEFT then
					-- WHEN YOU GONNA STOP HEADING LEFT?
					t_PLAYERS[i]:moveLeft()
				elseif t_PLAYERS[i].KEY_STATES.UP then
					-- MOVIN' ON UP!
					t_PLAYERS[i]:moveUp()
				elseif t_PLAYERS[i].KEY_STATES.RIGHT then
					-- RIGHT, RIGHT!
					t_PLAYERS[i]:moveRight()
				elseif t_PLAYERS[i].KEY_STATES.DOWN then
					-- DOWN WE GO!
					t_PLAYERS[i]:moveDown()
				end
				tm_PLAYER_UPDATE = os.startTimer(n_PLAYER_UPDATE_INTERVAL)
			end
		end
	end
end