I have been researching racing AI for a university dissertation and there are many things you need to consider when writing AI for a racing game:
Follow the racing line.
Adjust speed to maintain appropriate velocity for the part of track that the vehicle is on.
Avoid collisions with other vehicles.
Move to an overtaking position.
Move to a blocking position to avoid being overtaken.
Try to stay on track.
Drive back onto the track if the above failed.
The AI must be beatable!
... there may be others but if you can get the above list working then you should at least have a reasonable AI.
The first piece of advice I can offer is this: when dealing with racing AI consider the game world to be 2D. It doesn't matter to the AI if you are writing a 2D top down racer or a full 3D in-car racer. The track is essentially 2D. It may have undulations that cause the cars to speed up or slow down but that is unimportant as you will see when you read on.
Follow the racing line: From what you have said, I am guessing that the system you have at the moment is a series of waypoints that the AI cars drive to and then move on to the next. If those waypoints are on the racing line then you have satisfied the first point. But it will be like watching items on a conveyor belt and not very race-like.
Adjust speed to maintain appropriate velocity for the part of track that the vehicle is on: You say you have a player operated car (POC) working at the moment and that is a good start. Is the POC able to lose traction and slide off the road or can it take corners as fast as the player chooses? If you don't have a maximum lateral traction, then I would suggest that is the first step you want to take.
Once you have that working you can adapt your waypoints to include more information. At the very least they should have a maximum speed at that point but don't asssign that maximum speed to each car but consider it a target and choose a random speed that is close to it (more under than over). That way you will have more realistic cornering because each AI will either take the corner slightly slower than maximum or slightly faster - at which point they will lose traction and slide off the road.
Also, don't consider the waypoints as targets that each AI must get to. Instead consider them as a target that they try to get to but getting close is good enough. A simple distance check would do. The reason for that is: If an AI or the POC is following the racing line (ie. passing directly through each waypoint) and another AI is along side it, the second AI will not reach the waypoint without having to stop or turn around (not desirable!). Which brings is nicely onto ...
Avoid collisions with other vehicles: You will need two collision systems. Firstly a collision check system which checks to see if two cars have collided and performs whatever actions are necessary, and secondly, a collision avoidance system which looks ahead to see if a collision is likely to happen and if so take avoiding action. A collision may happen because a car has taken a corner too fast and slid into another. If that is the case it is unavoidable and can be ignored. However, a collision is most likely to happen when one car is on the racing line and another wants to be in the same place. Here you could say maintain as close a position to the racing line as possible without colliding. That would work well if the two cars were along side each other like at the start of the race but if one car was moving faster and wanted to overtake then it would fail because it would simple stay behind the first car. Which brings us onto ...
Move to an overtaking position: What I would suggest here (and for the case above) is that a second temporary waypoint is assigned for the overtaking car. Look at the vehicle in front and assertain the largest width between it and the edge of the track. Then place a new waypoint in the centre of that area. Once the AI has reached the new waypoint continue as normal ... which means asking "can the AI get onto the racing line?" If yes then steer towards it. If no then repeat this step again.
Of course it is possible that there are two or more cars ahead and there isn't a gap wide enough for the new waypoint. In that case the AI would have to reduce speed until it could overtake.
Move to a blocking position to avoid being overtaken: For a truely accurate AI, this would need to be taken into consideration. However, for a game it would be very annoying to hav every AI car weave infront of you when trying to get past. So I would say that when wrinting racing AI for a game this can (and should) be ignored.
Try to stay on track: You might think that this is already taken care of because of the use of waypoints. However, if an AI takes a corner too fast and loses traction, then it needs to rectify that before leaving the track. Either by braking or using oposite lock.
Drive back onto the track if the above failed: In the event of the above failing or an AI be forced off the road due to a collision then it will need to find its way back to the track. For this I would recommend using flow fields. To use flow field you would divide the game world into an array of small grids. Each grid holds a directional vector. When an AI leaves the track, determine which grid it is in and then you can read the directional vector from the array to asscertain which direction the AI needs to steer in order to get back on the track.
The AI must be beatable: It is far too easy to allow the AI to drive the best possible line at the fastest possible speed. Doing that will of course greatly annoy any player. You will need to include a random fluctuation in the speed at each waypoint (as mentioned ealier). You will also need to adjust the AI to give the player a chance. If it is too hard to beat your users will soon give up. Equally if it is too easy to beat there is no challenge. This isn't easy to get right but is achievable. You will need to adjust the AI's top speed. Reduce it if they are too far ahead and increase it if they are too far behind.
Finally, when creating the waypoints. It would take a lot of trial and error to determine the maximum speed for each waypoint. What I would suggest is to get you POC working correctly and then drive it round the track many (many!) times. Each time you store waypoints along the way including the speed and direction at each one. Then after each lap, check your lap time. If it is your fastest, then these waypoints are the ones to use in the game. If the POC leaves the track at any point then that lap is void. You could improve this system by splitting the lap into several sections and determining the fastest time in each section. I would also recommend allowing friends to try to beat your fastest time and use their waypoints if they are faster than yours because (painful as it sounds) sometimes other people are better at playing your games than you are.
Further reading:
Start your Engines A full book dedicated to writing a racing game.
AI Game Programming Wisdom There are currently four books in the series but volumes 1,2 & 3 have chapters dedicated to racing AI and are well worth a read.
I would also recommend the
Game Programming Gems series of books. There are eight in the series (plus best of) and although none of them have any specific racing AI chapters, they all offer some very useful advice in all areas of game creation.
Good luck!